签名插件增加了对构建文件和 Artifact 进行数字签名的能力。这些数字签名可用于证明签署了 Artifact 的作者身份,以及其他信息,例如签名生成的时间。
签名插件目前仅支持生成 OpenPGP 签名(这是发布到 Maven Central 仓库所需的签名格式)。
用法
要使用签名插件,请在构建脚本中包含以下内容
plugins {
signing
}
plugins {
id 'signing'
}
签名凭据
为了创建 OpenPGP 签名,您需要一对密钥(使用GnuPG 工具创建密钥对的说明可以在GnuPG HOWTO中找到)。您需要向签名插件提供您的密钥信息,这意味着需要以下三项内容
-
公钥 ID(keyId 的最后 8 个字符。您可以使用
gpg -K
获取)。 -
包含您的私钥的秘密密钥环文件的绝对路径。(自 gpg 2.1 起,您需要使用命令
gpg --keyring secring.gpg --export-secret-keys > ~/.gnupg/secring.gpg
导出密钥)。 -
用于保护您的私钥的密码。
这些项必须分别作为 signing.keyId
、signing.secretKeyRingFile
和 signing.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-armored 密钥
在某些设置中,使用环境变量传递用于签名的密钥和密码更为方便。例如,在使用 CI 服务器签署 Artifact 时,安全地提供密钥环文件通常很麻烦。另一方面,大多数 CI 服务器都提供了安全存储环境变量并将其提供给构建的方法。使用以下设置,您可以使用 ORG_GRADLE_PROJECT_signingKey
和 ORG_GRADLE_PROJECT_signingPassword
环境变量分别传递密钥(ascii-armored 格式)和密码
signing {
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKey, signingPassword)
sign(tasks["stuffZip"])
}
signing {
def signingKey = findProperty("signingKey")
def signingPassword = findProperty("signingPassword")
useInMemoryPgpKeys(signingKey, signingPassword)
sign stuffZip
}
使用内存中的 ascii-armored OpenPGP 子密钥
为了防止主密钥共享并确保其安全,也可以使用内存中的 ascii-armored 子密钥。使用内存中的 ascii-armored 密钥和子密钥的主要区别在于,还需要指定密钥标识符。使用以下设置,您可以使用 ORG_GRADLE_PROJECT_signingKeyId
、ORG_GRADLE_PROJECT_signingKey
和 ORG_GRADLE_PROJECT_signingPassword
环境变量分别传递密钥标识符、密钥(ascii-armored 格式)和密码
signing {
val signingKeyId: String? by project
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
sign(tasks["stuffZip"])
}
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 子密钥。只需在 signing.keyId
属性中指定子密钥 ID 作为值即可。
使用 gpg-agent
默认情况下,签名插件使用基于 Java 的 PGP 实现进行签名。但此实现无法使用 gpg-agent 程序管理私钥。如果您想使用 gpg-agent,可以更改签名插件使用的签名器实现
signing {
useGpgCmd()
sign(configurations.runtimeElements.get())
}
signing {
useGpgCmd()
sign configurations.runtimeElements
}
这告诉签名插件使用 GnupgSignatory
而不是默认的 PgpSignatory。GnupgSignatory
依赖于 gpg2 程序来签署 Artifact。当然,这要求已安装 GnuPG。
无需任何进一步配置,将使用 PATH
中找到的 gpg
(Windows 上为 gpg.exe
) 可执行文件。密码由 gpg-agent
提供,签名使用默认密钥。
Gnupg 签名器配置
GnupgSignatory
支持多种配置选项,用于控制如何调用 gpg。这些选项通常在 gradle.properties 中设置
示例:配置 GnupgSignatory
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 version 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,允许您指定应该签名的任务和/或配置。
签署 Publication
发布 Artifact 时,您通常希望对其进行签名,以便 Artifact 的使用者可以验证其签名。例如,Java 插件定义了一个组件,您可以使用它来定义一个发布到 Maven (或 Ivy) 仓库的 Publication,通过 Maven 发布插件(或Ivy 发布插件)实现。使用签名 DSL,您可以指定应签署此 Publication 的所有 Artifact。
signing {
sign(publishing.publications["mavenJava"])
}
signing {
sign publishing.publications.mavenJava
}
这将在您的项目中创建一个名为 signMavenJavaPublication
的任务(类型为 Sign),该任务将构建属于此 publication 的所有 Artifact(如果需要),然后为其生成签名。签名文件将与被签名的 Artifact 放在一起。
示例:签署 publication 输出
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
多个以逗号分隔的 publication。或者,您可以使用 publishing.publications
签署所有 publication,或使用 publishing.publications.matching { … }
签署符合指定条件的 publication。
签署 Configuration
通常需要签署 configuration 的 Artifact。例如,Java 插件配置了一个 jar 构建,并将此 jar Artifact 添加到 runtimeElements
configuration 中。使用签名 DSL,您可以指定应签署此 configuration 的所有 Artifact。
signing {
sign(configurations.runtimeElements.get())
}
signing {
sign configurations.runtimeElements
}
这将在您的项目中创建一个名为 signRuntimeElements
的任务(类型为 Sign),该任务将构建所有 runtimeElements
Artifact(如果需要),然后为其生成签名。签名文件将与被签名的 Artifact 放在一起。
示例:签署 configuration 输出
gradle signRuntimeElements
的输出> gradle signRuntimeElements > Task :compileJava > Task :processResources > Task :classes > Task :jar > Task :signRuntimeElements BUILD SUCCESSFUL in 0s 4 actionable tasks: 4 executed
签署任务输出
在某些情况下,您需要签署的 Artifact 可能不属于某个 configuration。在这种情况下,您可以直接签署生成该 Artifact 的任务。
tasks.register<Zip>("stuffZip") {
archiveBaseName = "stuff"
from("src/stuff")
}
signing {
sign(tasks["stuffZip"])
}
tasks.register('stuffZip', Zip) {
archiveBaseName = 'stuff'
from 'src/stuff'
}
signing {
sign stuffZip
}
这将在您的项目中创建一个名为 signStuffZip
的任务(类型为 Sign),该任务将构建输入任务的存档文件(如果需要),然后对其进行签名。签名文件将与被签名的 Artifact 放在一起。
示例:签署任务输出
gradle signStuffZip
的输出> gradle signStuffZip > Task :stuffZip > Task :signStuffZip BUILD SUCCESSFUL in 0s 2 actionable tasks: 2 executed
条件签名
一种常见的用法模式是仅在某些条件下要求对构建 Artifact 进行签名。例如,对于非发布版本,您可能不需要签署 Artifact。为此,您可以将条件指定为 required()
方法的参数。
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"])
}
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
条件不成立,则仅在配置了签名凭据的情况下才会签署 Artifact。或者,无论签名凭据是否可用,您可能都想完全跳过签名。如果是这样,您可以配置 Sign 任务跳过执行,例如通过使用以下示例中所示的 onlyIf()
方法附加一个谓词。
tasks.withType<Sign>().configureEach {
onlyIf("isReleaseVersion is set") { project.extra["isReleaseVersion"] as Boolean }
}
tasks.withType(Sign) {
onlyIf("isReleaseVersion is set") { isReleaseVersion }
}
发布签名
签署 publication 时,生成的签名 Artifact 会自动添加到相应的 publication 中。因此,发布到仓库时(例如执行 publish
任务),您的签名将与其他人 Artifact 一起分发,无需任何额外配置。
签署 configuration 和 任务时,生成的签名 Artifact 会自动添加到 signatures
依赖配置中。