JAR 文件规范详解
介绍
JAR 文件是基于 ZIP 文件格式的一种文件格式,用来将许多文件整合成一个文件。一个 JAR 文件本质上是包含可选目录 META-INF 的 zip 文件,可以通过命令行 jar 工具或者在 Java 平台上使用 java.util.jar 中的 API 来创建。JAR 文件的命名没有严格的要求,可以是特定平台上的任意合法文件名称。
在很多场景中,JAR 文件不仅仅用来对 java class 文件和资源文件进行归档,还被用来作应用程序及其扩展的构建块。如果包含 META-INF 目录,则是用来存储包信息和扩展配置数据,配置数据包括安全信息,版本控制信息,扩展信息和服务信息等。
META-INF 目录
在 Java 2 平台会识别和解释 META-INF 目录下面的文件和目录,以配置应用程序、扩展、类加载器和服务。
MANIFEST.MF 是用来定义扩展和包相关数据的清单文件。
INDEX.LIST 通过 jar 工具的-i 选项来生成,它包含应用程序及其扩展程序中的包的位置信息,同时它是 JarIndex 实现的一部分,类加载器使用它来提高类的加载速度。
**x.SF **是 JAR 文件的签名文件,x 表示基本文件名称。
**x.DSA **是于 x.SF 关联的签名块文件,它们有相同的基本文件名。此文件存储相应签名文件的数字签名。
services/ 该目录存储所有服务提供程序配置文件。
名称-值对和节
在我们深入每个配置文件的细节之前,需要定义一些格式约定。
在大部分场景中,包含在清单文件和签名文件中的信息表示为受 RFC822 标准启发的所谓的“名称:值”对。“名称:值”对也被称为头和属性。名称-值对组称为“节”,每一“节”通过空行分离。
任何形式的二进制数据都表示为 base64。行的长度超过 72 字节的二进制数据需要延续。摘要和签名就是二进制数据的例子。
实现应该最多支持 65535 字节的头值。
本文档中的所有规范使用相同的语法,其中终端符号以固定宽度字体显示,非终端符号以斜体字体显示。
JAR Manifest
01
概述
JAR 文件的清单由主节和拥有多个私有 JAR 文件条目的单独节列表,每行通过换行符分隔。主节和单独节都遵循上面指定的节语法。它们都有自己特定的限制和规则。
主节包括包括 JAR 文件自身的安全和配置信息,以及此 JAR 文件所属应用程序及扩展。清单文件同样定义了每个单独清单条目的主属性。每节中没有属性可以使用“Name”作为名称。每一节通过空行结束。
单独节定义了 JAR 文件中包和文件的各种属性。不是所有在 JAR 文件中的文件都需要被作为条目列在清单中,但是所有被签名的文件必须被列出。清单文件自身不需要被列出。每节必须以名称“Name”的属性作为开始,并且值必须是该文件的的相对路径或者是应用存档外部数据的绝对 URL。
如果相同条目有多个单独节,则这些单独节中的属性会被合并。如果不同节中的相同属性有不同的值,则识别最后一个。
不理解的属性会被忽略。这些属性可能包含应用程序使用的实现特定信息。
02
清单文件规范
03
主属性
主属性是清单中出现在主节中的属性。他们可以分为以下几类:
04
每个条目的属性
每个条目的属性分为以下几组:
① 定义文件内容的属性
Content-Type:此属性可用于指定 JAR 文件中特定文件条目的 MIME 类型和数据子类型。值应该是 type/subtype 形式的字符串。
例如,“image/bmp”是一个带有 bmp(表示位图)子类型的图像类型。这将把文件条目指示为图像,并将数据存储为位图。RFC 1521 和 1522 讨论并定义 MIME 类型。
② 定义版本和封装信息的属性
这些属性与上面定义的用于定义扩展包版本控制和封装信息的主属性相同。当作为每个条目属性使用时,这些属性将覆盖主属性,但仅应用于清单条目指定的单个文件。
③ 定义 bean 对象的属性
Java-Bean: 定义特定的 jar 文件条目是否是 Java bean 对象。值应该是“true”或“false”,大小写被忽略。
④ 定义签名的属性
这些属性用于签名和验证目的。更多细节在这里。
**x-Digest-y:**此属性的名称指定用于计算对应 jar 文件条目的摘要值的摘要算法的名称。此属性的值存储实际的摘要值。前缀’x’指定算法名称,可选后缀’y’表示应该根据哪种语言验证摘要值。
**Magic: **这是一个可选属性,应用程序可以使用它来指示 verifier 应该如何计算清单项中包含的摘要值。这个属性的值是一组逗号分隔的上下文特定字符串。
签名的 JAR 文件
01
概述
可以使用 java.security API 对 JAR 文件的子集进行签名。签名 JAR 文件与原始 JAR 文件完全相同,除了它的清单被更新和两个附加文件被添加到 META-INF 目录:一个签名文件和一个签名块文件。如果不使用 jarsigner,签名程序必须同时构造签名文件和签名块文件。
对于签名 JAR 文件中的每个文件条目,会在清单文件中为它创建一个单独的清单条目。每个清单条目列出一个或多个摘要属性和一个可选的 Magic 属性。
02
签名文件
2.1
签名验证
如果签名是有效的,并且在签名生成之后,JAR 文件中的任何文件都没有被更改,那么就会发生成功的 JAR 文件验证。JAR 文件验证包括以下步骤:
① 在第一次解析清单时,验证在签名文件上的签名。为了提高效率,这种验证应该被记忆。注意,此验证仅验证签名说明本身,而不是实际的归档文件。
② 如果签名文件中存在 x-Digest-Manifest 属性,则根据根据整个清单计算的摘要验证该值。如果签名文件中存在多个 x-Digest-Manifest 属性,验证其中至少有一个与计算的摘要值匹配。
③ 如果签名文件中不存在 x-Digest-Manifest 属性,或者在前面的步骤中计算的摘要值都不匹配,那么将执行优化较少的验证:
Ⅰ.如果签名文件中存在 x-Digest-Manifest-Main-Attributes 条目,则根据根据清单文件中的主要属性计算的摘要验证该值。如果计算失败,则 JAR 文件验证失败。这项决定可以因其效率而被记忆。如果签名文件中不存在 x-Digest-Manifest-Main-Attributes 条目,那么它的不存在不会影响 JAR 文件验证,并且清单的主属性也不会被验证。
Ⅱ.根据根据清单文件中相应条目计算的摘要值,验证签名文件中每个源文件信息部分中的摘要值。如果任何摘要值不匹配,则 JAR 文件验证失败。
④ 对于清单中的每个条目,根据根据在“Name:”属性中引用的实际数据计算的摘要验证清单文件中的摘要值,该属性指定一个相对文件路径或 URL。如果任何摘要值不匹配,则 JAR 文件验证失败。
2.2
Magic 属性
验证给定清单条目上签名的另一个要求是验证者理解该条目的清单条目中 Magic 键对值的值。Magic 属性是可选的,但如果解析器正在验证条目的签名,则必须理解该条目的 Magic key 的值。
Magic 属性的值是一组逗号分隔的特定于上下文的字符串。逗号之前和之后的空格将被忽略。大小写被忽略,magic 属性的确切含义是依赖于具体的应用程序。这些值指示如何计算清单条目中包含的散列值,因此对于签名的正确验证至关重要。关键字可以用于动态或嵌入式内容,可以用于多语言文档的多个散列,等等。
在第一个示例中,这些 Magic 的值可能表明 http 查询的结果是嵌入到文档的脚本中(而不是文档本身),而且脚本是动态生成的。这两个信息说明了如何计算哈希值来比较清单的摘要值,从而比较有效签名。
03
数字签名
数字签名是. sf 签名文件的已签名版本。这些是二进制文件,人类无法解释。
包括上面没有列出的签名算法的数字签名文件必须在 META-INF 目录和前缀“SIG-”。相应的签名文件(.sf 文件)也必须具有相同的前缀。
对于那些不支持外部签名数据的格式,该文件应由. sf 文件的签名副本组成。因此,有些数据可能是重复的,验证者应该比较两个文件。
支持外部数据的格式要么引用. sf 文件,要么使用隐式引用对其执行计算。
每个. sf 文件可以有多个数字签名,但是这些签名应该由同一法律实体生成。
文件扩展名可以是 1 到 3 个字母数字字符。未识别的扩展将被忽略。
Manifest 和签名文件的说明
① 解析前:如果文件的最后一个字符是 EOF 字符(代码 26),则 EOF 被视为空格。附加了两个新行(一个用于编辑器,编辑器不会在最后一行的末尾放一个新行,另一个用于语法不必对最后一个条目进行特殊处理,因为它后面可能没有空行)。
② 属性:在所有情况下,对于所有部分,不理解的属性将被忽略;属性名称不区分大小写,然而,生成清单和签名文件的程序应该使用本规范中所示的情况;属性名不能在节中重复。
③ 版本:Manifest-Version 和 Signature-Version 必须是第一个,除此之外,主节中属性的顺序并不重要。
④ 顺序:单个清单条目的顺序不重要;单个签名条目的顺序并不重要,除非被签名的摘要按此顺序。
⑤ 行长度:以 utf8 编码的形式,行长度不能超过 72 字节(不是字符)。如果一个值使初始行比这个长,那么它应该在额外的行上继续(每个行以一个空格开始)。
⑥ 错误:如果不能根据此规范解析文件,则应该输出一个警告,并且所有签名都不可信。
⑦ 限制:因为头名称不能换行,头名称的最大长度是 70 字节(名称后面必须有冒号和空格);NUL、CR 和 LF 不能嵌入 header 值中,NUL、CR、LF 和":"不能嵌入 header 名称中;实现应该支持 65535 字节(不是字符)的头值,以及每个文件 65535 个头,它们可能会耗尽内存,但不应该在这些值以下硬编码限制。
⑧ 签名者:不同的实体可以使用不同的签名算法来共享单个签名文件,这在技术上是可能的。但是这违反了标准,额外的签名可能会被忽略。
⑨ 算法:本标准不强制或限制摘要或签名算法;必须支持至少一种摘要算法;如果摘要算法、签名算法或密钥大小受到 jdk.jar.disabledAlgorithms 安全属性的限制,JAR 将被视为未签名的。注意,jdk.jar.disabledAlgorithms 安全属性被 JDK 引用实现,它不能保证被其他实现检查和使用。
JAR 索引
01
概述
从 1.3 开始,引入 JarIndex 来优化网络应用程序,特别是 applet 的类加载器的类搜索过程。最初,applet 类加载器使用一个简单的线性搜索算法来搜索其内部搜索路径上的每个元素,内部搜索路径是由“ARCHIVE”标签或“class - path”主属性构造的。类加载器加载并在其搜索路径中打开每个元素,直到找到类或资源为止。如果类加载器试图找到一个不存在的资源,那么必须下载应用程序或 applet 中的所有 jar 文件。对于大型网络应用程序和 applet,这可能导致启动缓慢、响应迟缓和浪费网络带宽。JarIndex 机制收集 applet 中定义的所有 jar 文件的内容,并将信息存储在索引文件中,该索引文件位于 applet 类路径的第一个 jar 文件中。加载第一个 jar 文件后,applet 类加载器将使用收集到的内容信息来高效地加载 jar 文件。
现有的 jar 工具被增强,使其能够检查 jar 文件列表并生成关于哪些类和资源驻留在哪些 jar 文件中的目录信息。这个目录信息存储在根 jar 文件的 META-INF 目录中的一个名为 INDEX.LIST 的简单文本文件中。当类加载器加载根 jar 文件时,它读取 INDEX.LIST 文件,并使用它构造哈希表,哈希表是从文件和包名称到 jar 文件名列表的映射。为了找到类或资源,类装入器查询散列表以查找适当的 jar 文件,然后在必要时加载它。
02
索引文件规范
INDEX.LIST 文件包含一个或多个节,每节由一个空行分隔。每节定义一个特定 jar 文件的内容,头文件定义 jar 文件路径名,后面是一个包或文件名列表,每行一个。所有 jar 文件路径都相对于根 jar 文件的代码基。这些路径名的解析方式与当前扩展机制对绑定扩展的解析方式相同。
UTF-8 编码用于支持索引文件中的文件或包名称中的非 ASCII 字符。
03
向后兼容
新的类加载方案完全向后兼容在当前扩展机制之上开发的应用程序。当类加载器加载第一个 jar 文件,并在 META-INF 目录中找到 INDEX.LIST 文件,它将构造索引哈希表并为扩展使用新的加载方案,否则,类加载器将只使用原始的线性搜索算法。
服务提供者
01
概述
服务由抽象类表示。给定服务的提供程序包含一个或多个具体类,这些类使用数据和具体的代码逻辑扩展此服务类。这个 provider 类通常不是整个 provider 本身,而是一个代理,它包含足够的信息来决定 provider 是否能够满足特定的请求,以及可以根据需要创建实际 provider 的代码。提供者类的细节往往是高度订制服务的;没有一个类或接口可以统一它们,因此没有定义这样的类。这里强制执行的唯一要求是,提供程序类必须有一个零参数的构造函数,以便在查找期间可以实例化它们。
02
提供者配置文件
服务提供者通过在资源目录 META-INF/services 中放置一个提供者配置文件来标识自己。文件的名称应该由抽象服务类的完全限定名组成。该文件应该包含一个以换行符分隔的惟一具体提供程序类名称列表。空格和制表符以及空白行都将被忽略。注释字符是’#’ (0x23);在每行中,第一个注释字符之后的所有字符都将被忽略。该文件必须用 UTF-8 编码。
03
例子
每个方法都返回一个适当的对象,如果它不能转换给定的编码,则返回 null。典型的 CharCodec 提供程序将支持多种编码。如果 sun.io.StandardCodec 是 CharCodec 服务的提供者,那么它的 jar 文件将包含文件 META-INF/services/java.io.spi.CharCodec。该文件将包含一行:
Class-Path 属性
应用程序的清单可以指定一个或多个相对 url,引用它需要的其他库的 JAR 文件和目录。这些相对 url 被当作相对于加载应用程序的代码基进行处理。
每个相对 URL 都根据加载包含应用程序或库的代码库解析。如果结果 URL 无效或引用了无法找到的资源,则将忽略它。
生成的 URL 用于扩展应用程序、applet 或 servlet 的类路径,方法是在类路径中紧跟着包含 JAR 文件的 URL 插入 URL。省略任何重复的 url。例如,给定以下类路径:
a.jar b.jar
假设 b.jar 包含以下类路径清单属性:
Class-Path: x.jar a.jar
结果是应用程序类路径如下:
a.jar b.jar x.jar
如果 x.jar 有自己的依赖项,那么将根据后续每个 URL 的相同规则添加这些依赖项。在实际的实现中,JAR 文件依赖项被延迟处理,因此 JAR 文件直到需要时才打开。
包封装
这指定了 javax.servlet.internal 包是密封的,并且包中的所有类都必须从同一个 JAR 文件加载。
这指定此归档中的所有包都是密封的,除非为清单项中具有 sealed 属性的特定包显式重写。
如果缺少这个属性,为了向后兼容,假设 JAR 文件不是密封的。然后,系统默认检查包头的密封信息。
包密封对于安全性也很重要,因为它将对受包保护的成员的访问限制为仅对来自同一 JAR 文件的包中定义的类的访问。
未命名的包是不可密封的,因此要密封的类必须放在它们自己的包中。
更多学习资料戳下方!!!
评论