签名插件增加了对已构建文件和制品进行数字签名的功能。这些数字签名可以用于证明构建该制品的身份,以及其他信息,例如签名生成的时间。

签名插件目前仅支持生成 OpenPGP 签名(这是 发布到 Maven Central Repository 所需的签名格式)。

用法

要使用签名插件,请在您的构建脚本中包含以下内容:

build.gradle.kts
plugins {
    signing
}
build.gradle
plugins {
    id 'signing'
}

签名者凭证

为了创建 OpenPGP 签名,您需要一对密钥(有关使用 GnuPG 工具创建密钥对的说明,请参阅 GnuPG HOWTOs)。您需要向签名插件提供您的密钥信息,这意味着三件事:

  • 公钥 ID(keyId 的最后 8 个符号。您可以使用 gpg -K 获取)。

  • 包含您的私钥的密钥环文件的绝对路径。(自 gpg 2.1 起,您需要使用命令 gpg --keyring secring.gpg --export-secret-keys > ~/.gnupg/secring.gpg 导出密钥)。

  • 用于保护您的私钥的密码。

这些项必须分别作为 signing.keyIdsigning.secretKeyRingFilesigning.password 属性的值提供。

鉴于这些值的个人和私密性质,一种好的做法是将它们存储在用户 Gradle 主目录中的 gradle.properties 文件中(如系统属性中所述),而不是项目目录本身。
signing.keyId=24875D73
signing.password=secret
signing.secretKeyRingFile=/Users/me/.gnupg/secring.gpg

如果在用户 gradle.properties 文件中指定此信息(尤其是 signing.password)对您的环境不可行,您可以通过命令行提供信息:

> gradle sign -Psigning.secretKeyRingFile=/Users/me/.gnupg/secring.gpg -Psigning.password=secret -Psigning.keyId=24875D73

使用内存中的 ASCII 装甲密钥

在某些设置中,使用环境变量传递用于签名的秘密密钥和密码会更容易。例如,当使用 CI 服务器对制品进行签名时,安全地提供密钥环文件通常很麻烦。另一方面,大多数 CI 服务器提供安全存储环境变量并将其提供给构建的方法。使用以下设置,您可以分别使用 ORG_GRADLE_PROJECT_signingKeyORG_GRADLE_PROJECT_signingPassword 环境变量传递秘密密钥(以 ASCII 装甲格式)和密码:

build.gradle.kts
signing {
    val signingKey: String? by project
    val signingPassword: String? by project
    useInMemoryPgpKeys(signingKey, signingPassword)
    sign(tasks["stuffZip"])
}
build.gradle
signing {
    def signingKey = findProperty("signingKey")
    def signingPassword = findProperty("signingPassword")
    useInMemoryPgpKeys(signingKey, signingPassword)
    sign stuffZip
}

使用内存中的 ASCII 装甲 OpenPGP 子密钥

为了防止主密钥共享并确保其安全,也可以使用内存中的 ASCII 装甲子密钥。使用内存中的 ASCII 装甲密钥和子密钥的主要区别在于,还需要指定密钥标识符。使用以下设置,您可以分别使用 ORG_GRADLE_PROJECT_signingKeyIdORG_GRADLE_PROJECT_signingKeyORG_GRADLE_PROJECT_signingPassword 环境变量传递密钥标识符、秘密密钥(以 ASCII 装甲格式)和密码:

build.gradle.kts
signing {
    val signingKeyId: String? by project
    val signingKey: String? by project
    val signingPassword: String? by project
    useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
    sign(tasks["stuffZip"])
}
build.gradle
signing {
    def signingKeyId = findProperty("signingKeyId")
    def signingKey = findProperty("signingKey")
    def signingPassword = findProperty("signingPassword")
    useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
    sign stuffZip
}

使用 OpenPGP 子密钥

OpenPGP 支持子密钥,它们与普通密钥类似,但它们绑定到主密钥对。OpenPGP 子密钥的一个特点是它们可以独立于主密钥撤销,这使得密钥管理更容易。关于子密钥如何在软件开发中发挥作用的实际案例研究可以在 Debian wiki 上阅读。

签名插件开箱即用地支持 OpenPGP 子密钥。只需将子密钥 ID 指定为 signing.keyId 属性的值即可。

使用 gpg-agent

默认情况下,签名插件使用基于 Java 的 PGP 实现进行签名。然而,此实现无法使用 gpg-agent 程序来管理私钥。如果您想使用 gpg-agent,可以更改签名插件使用的签名者实现:

build.gradle.kts
signing {
    useGpgCmd()
    sign(configurations.runtimeElements.get())
}
build.gradle
signing {
    useGpgCmd()
    sign configurations.runtimeElements
}

这告诉签名插件使用 GnupgSignatory 而不是默认的 PgpSignatoryGnupgSignatory 依赖 gpg2 程序对制品进行签名。当然,这要求已安装 GnuPG。

在没有任何进一步配置的情况下,将使用 PATH 中找到的 gpg(在 Windows 上:gpg.exe)可执行文件。密码由 gpg-agent 提供,并使用默认密钥进行签名。

Gnupg 签名者配置

GnupgSignatory 支持许多配置选项来控制 gpg 的调用方式。这些通常在 gradle.properties 中设置:

示例:配置 GnupgSignatory

gradle.properties
signing.gnupg.executable=gpg
signing.gnupg.useLegacyGpg=true
signing.gnupg.homeDir=gnupg-home
signing.gnupg.optionsFile=gnupg-home/gpg.conf
signing.gnupg.keyName=24875D73
signing.gnupg.passphrase=gradle
signing.gnupg.executable

用于签名的 gpg 可执行文件。此属性的默认值取决于 useLegacyGpg。如果它为 true,则可执行文件的默认值为 "gpg",否则为 "gpg2"。

signing.gnupg.useLegacyGpg

如果使用 GnuPG 版本 1,则必须为 true,否则为 false。此属性的默认值为 false

signing.gnupg.homeDir

设置 GnuPG 的主目录。如果未给定,则使用 GnuPG 的默认主目录。

signing.gnupg.optionsFile

为 GnuPG 设置自定义选项文件。如果未给定,则使用 GnuPG 的默认配置文件。

signing.gnupg.keyName

用于签名的密钥 ID。如果未给定,则使用 GnuPG 中配置的默认密钥。

signing.gnupg.passphrase

解锁秘密密钥的密码。如果未给定,则使用 gpg-agent 程序获取密码。

所有配置属性都是可选的。

指定要签名的内容

除了配置如何签名(即签名者配置)之外,您还必须指定要签名的内容。签名插件提供了一个 DSL,允许您指定应签名的任务和/或配置。

签名出版物

发布制品时,您通常希望对其进行签名,以便制品的使用者可以验证其签名。例如,Java 插件定义了一个组件,您可以使用它通过 Maven Publish 插件(或 Ivy Publish 插件)定义到 Maven(或 Ivy)存储库的出版物。使用签名 DSL,您可以指定此出版物的所有制品都应签名。

示例 3. 签名出版物
build.gradle.kts
signing {
    sign(publishing.publications["mavenJava"])
}
build.gradle
signing {
    sign publishing.publications.mavenJava
}

这将在您的项目中创建一个任务(类型为 Sign),名为 signMavenJavaPublication,它将构建作为出版物一部分的所有制品(如果需要),然后为它们生成签名。签名文件将放置在被签名制品旁边。

示例:签名出版物输出

gradle signMavenJavaPublication 的输出
> gradle signMavenJavaPublication
> Task :compileJava
> Task :processResources
> Task :classes
> Task :jar
> Task :javadoc
> Task :javadocJar
> Task :sourcesJar
> Task :generateMetadataFileForMavenJavaPublication
> Task :generatePomFileForMavenJavaPublication
> Task :signMavenJavaPublication

BUILD SUCCESSFUL in 0s
9 actionable tasks: 9 executed

此外,上述 DSL 允许 sign 多个逗号分隔的出版物。或者,您可以指定 publishing.publications 以签名所有出版物,或使用 publishing.publications.matching { … } 以签名所有符合指定谓词的出版物。

签名配置

通常需要对配置的制品进行签名。例如,Java 插件配置要构建的 jar,并且此 jar 制品被添加到 runtimeElements 配置中。使用签名 DSL,您可以指定此配置的所有制品都应签名。

示例 4. 签名配置
build.gradle.kts
signing {
    sign(configurations.runtimeElements.get())
}
build.gradle
signing {
    sign configurations.runtimeElements
}

这将在您的项目中创建一个任务(类型为 Sign),名为 signRuntimeElements,它将构建任何 runtimeElements 制品(如果需要),然后为它们生成签名。签名文件将放置在被签名制品旁边。

示例:签名配置输出

gradle signRuntimeElements 的输出
> gradle signRuntimeElements
> Task :compileJava
> Task :processResources
> Task :classes
> Task :jar
> Task :signRuntimeElements

BUILD SUCCESSFUL in 0s
4 actionable tasks: 4 executed

签名任务输出

在某些情况下,您需要签名的制品可能不属于配置。在这种情况下,您可以直接签名生成要签名制品的任务。

build.gradle.kts
tasks.register<Zip>("stuffZip") {
    archiveBaseName = "stuff"
    from("src/stuff")
}

signing {
    sign(tasks["stuffZip"])
}
build.gradle
tasks.register('stuffZip', Zip) {
    archiveBaseName = 'stuff'
    from 'src/stuff'
}

signing {
    sign stuffZip
}

这将在您的项目中创建一个任务(类型为 Sign),名为 signStuffZip,它将构建输入任务的档案(如果需要),然后对其进行签名。签名文件将放置在被签名制品旁边。

示例:签名任务输出

gradle signStuffZip 的输出
> gradle signStuffZip
> Task :stuffZip
> Task :signStuffZip

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

要使任务可“签名”,它必须生成某种类型的档案,即它必须扩展 AbstractArchiveTask。执行此操作的任务是 TarZipJarWarEar 任务。

条件签名

一种常见的用法模式是仅在某些条件下才要求对构建制品进行签名。例如,您可能不需要对非发布版本进行签名。为了实现这一点,您可以将条件指定为 required() 方法的参数。

build.gradle.kts
version = "1.0-SNAPSHOT"
extra["isReleaseVersion"] = !version.toString().endsWith("SNAPSHOT")

signing {
    setRequired({
        (project.extra["isReleaseVersion"] as Boolean) && gradle.taskGraph.hasTask("publish")
    })
    sign(publishing.publications["main"])
}
build.gradle
version = '1.0-SNAPSHOT'
ext.isReleaseVersion = !version.endsWith("SNAPSHOT")

signing {
    required = { isReleaseVersion && gradle.taskGraph.hasTask("publish") }
    sign publishing.publications.main
}

在此示例中,我们只希望在构建发布版本并进行发布时才要求签名。因为我们正在检查任务图以确定是否要发布,所以我们必须将 signing.required 属性设置为一个闭包以推迟评估。有关更多信息,请参阅 SigningExtension.setRequired(java.lang.Object)

如果 required 条件不成立,则只有在配置了签名者凭证时才会对制品进行签名。或者,您可能希望完全跳过签名,无论签名者凭证是否可用。如果是这样,您可以配置跳过 Sign 任务,例如通过使用以下示例中所示的 onlyIf() 方法附加谓词:

build.gradle.kts
tasks.withType<Sign>().configureEach {
    onlyIf("isReleaseVersion is set") { project.extra["isReleaseVersion"] as Boolean }
}
build.gradle
tasks.withType(Sign) {
    onlyIf("isReleaseVersion is set") { isReleaseVersion }
}

发布签名

签名出版物时,生成的签名制品会自动添加到相应的出版物中。因此,当发布到存储库时,例如通过执行 publish 任务,您的签名将与其他制品一起分发,无需任何额外配置。

签名配置任务时,生成的签名制品会自动添加到 signatures 依赖配置中。