An sbt plugin to sign artifacts using the GNU Privacy Guard (GnuPG).
GnuPG Version | Tasks |
---|---|
2.0 | Does not support the gpgAddKey task |
2.1 | Does not support the gpgAddKey task |
2.2 | All |
This plugin requires sbt 1.0.0+.
To use the plugin you must first download and install GnuPG.
Add this plugin to your project/plugins.sbt
file:
addSbtPlugin("nz.co.bottech" % "sbt-gpg" % "1.0.0")
The common scenario for using this plugin is:
- Generate a primary key (unless you already have one).
- Add a new subkey for your project.
- Export the subkey.
- Encrypt the subkey and commit it to the project repository.
- During the automated build, decrypt and then import the subkey.
- Build the project artifacts and sign them.
For detailed instructions see Travis CI.
- gpgGenerateKey - Generates a new key pair.
- gpgListKeys - List the existing keys.
- gpgAddKey - Adds a subkey to an existing key.
- gpgExportKey - Exports a key with the primary secret key.
- gpgExportSubkey - Exports a subkey without the primary secret key.
- gpgImportKey - Imports a key to the keyring.
- gpgChangeKeyPassphrase - Changes the passphrase of a key.
- gpgChangeSubkeyPassphrase - Changes the passphrase of a subkey.
- gpgSign - Sign a message.
- gpgSignedArtifacts - Packages all artifacts for publishing, signs them, and then maps the Artifact definition to the generated file.
The referenced settings from all the main tasks have been scoped to that task to make it easier to override just the ones that you care about without impacting other tasks. For example:
gpgExportSubkey / gpgHomeDir := file("~/.gnupg")
gpgImportKey / gpgHomeDir := target.value / ".gnupg"
Use inspect
to see what the scope of various settings are. See sbt - Inspecting the Build for more details.
All tasks can use a specific home directory for GnuPG by setting gpgHomeDir
.
If this is not set (or set to None
) then GnuPG will use the default home directory ~/.gnupg
.
The following will be used to determine the passphrase:
gpgPassphrase
setting.credentials
task.- Pinentry.
If you are just running the tasks from an interactive sbt session then it is best to not specify the passphrase in sbt or a credentials file and instead let GnuPG prompt you for it using pinentry.
If you need to run a task from a non-interactive session, such as during an automated build, then it is best to use the
credentials
task. See sbt - Credentials for more details. The host
must be gpg
. The realm
and user
are not
used. Make sure to secure any credentials file appropriately.
gpgGenerateKey
- Generates a new key pair.
Setting | Description | Required | Default |
---|---|---|---|
gpgNameReal |
The name to use in the key. | ✔ | |
gpgNameEmail |
The email to to use in the key. | ✔ | |
gpgKeyLength |
The length of the generated key in bits. | ✘ | 4096 |
gpgKeyType |
The OpenPGP algorithm number or name to use for the key. | ✘ | RSA |
gpgKeyUsage |
The list of key usages. | ✘ | Set() (will use GnuPG default) |
gpgSubkeyLength |
The length of the generated subkey in bits. | ✘ | 4096 |
gpgSubkeyType |
The OpenPGP algorithm number or name to use for the subkey. | ✘ | RSA |
gpgSubkeyUsage |
The list of subkey usages. | ✘ | sign |
gpgExpireDate |
The expiration date for the key (and the subkey). | ✘ | 0 (does not expire) |
Comments are not encouraged and are therefore not provided as an option.
WARNING: If you set gpgGenerateKey / gpgSelectPassphrase
then this will end up in the generated gpgParametersFile
.
You should take care to secure this file and delete it when it is no longer needed (e.g. clean
).
gpgListKeys
- List the existing keys.
gpgAddKey
- Adds a subkey to an existing key.
Setting | Description | Required | Default |
---|---|---|---|
gpgKeyFingerprint |
The SHA-1 fingerprint of the primary key. | ✘ | default |
gpgSubkeyLength |
The length of the generated subkey in bits. | ✘ | 4096 |
gpgSubkeyType |
The OpenPGP algorithm number or name to use for the subkey. | ✘ | RSA |
gpgSubkeyUsage |
The list of subkey usages. | ✘ | sign |
Use gpgListKeys
to find the key fingerprint.
The best practice is to not keep your primary private key on the key ring of the machine that you use but to instead use subkeys. You can read a good introduction to this on Debian Wiki - Subkeys.
gpgExportKey
- Exports a key with the secret key.
Setting | Description | Required | Default |
---|---|---|---|
gpgArmor |
Create ASCII armored output. | ✘ | true |
gpgKeyFingerprint |
The SHA-1 fingerprint of the key. | ✔ | |
gpgKeyFile |
The output key file. | ✔ |
gpgExportSubkey
- Exports a subkey without the primary secret key.
Setting | Description | Required | Default |
---|---|---|---|
gpgArmor |
Create ASCII armored output. | ✘ | true |
gpgKeyFingerprint |
The SHA-1 fingerprint of the subkey. | ✔ | |
gpgKeyFile |
The output key file. | ✔ |
gpgImportKey
- Imports a key to the keyring.
Setting | Description | Required | Default |
---|---|---|---|
gpgKeyFile |
The input key file. | ✔ |
gpgChangeKeyPassphrase
- Changes the passphrase of a key.
Setting | Description | Required | Default |
---|---|---|---|
gpgKeyFile |
The key file. | ✔ | |
gpgKeyFingerprint |
The SHA-1 fingerprint of the key. | ✔ |
This cannot be used with a passphrase, it can only be used with Pinentry.
WARNING: this will overwrite the key file with the new key with the new passphrase.
gpgChangeSubkeyPassphrase
- Changes the passphrase of a subkey.
Setting | Description | Required | Default |
---|---|---|---|
gpgKeyFile |
The key file. | ✔ | |
gpgKeyFingerprint |
The SHA-1 fingerprint of the key. | ✔ |
This cannot be used with a passphrase, it can only be used with Pinentry.
WARNING: this will overwrite the key file with the new key with the new passphrase.
gpgSign
- Sign a message.
Setting | Description | Required | Default |
---|---|---|---|
gpgArmor |
Create ASCII armored output. | ✘ | true |
gpgKeyFingerprint |
The SHA-1 fingerprint of the key with signing capabilities. | ✔ | |
gpgMessage |
The message to sign. | ✔ | |
gpgSignatureFile |
The output signature file. | ✔ |
gpgSignedArtifacts
- Packages all artifacts for publishing, signs them, and then maps the Artifact definition to the
generated file.
Setting | Description | Required | Default |
---|---|---|---|
gpgArmor |
Create ASCII armored output. | ✘ | true |
gpgKeyFingerprint |
The SHA-1 fingerprint of the key with signing capabilities. | ✔ | |
gpgSignArtifacts |
Whether to sign artifacts. | ✘ | true |
Note that these should be scoped to the gpgSigner
task.
There are a bunch of examples in the sbt tests.
If you already have a primary key and you are following the best practices then you should mount the device that contains
the key now, then set gpgHomeDir
to the GnuPG home directory on that device.
If you do not already have a primary key then you need to generate one.
set gpgNameReal := "Your (Organization) Name"
set gpgNameEmail := "[email protected]"
gpgGenerateKey
Pinentry should appear and ask you to enter passphrase.
You should see the fingerprint of your new primary key.
[info] Generated your new primary key: 84C263516A75C26F1ADD723FC148D2D9D807D63F
By default it will have also generated a new subkey with signing capabilities. You should only use this key in your build and never use your primary key.
You may want to use another subkey if you have already used the default one and want to minimise the impact of a compromised subkey.
set gpgAddKey/gpgKeyFingerprint := "84C263516A75C26F1ADD723FC148D2D9D807D63F"
gpgAddKey
Where gpgKeyFingerprint
is the fingerprint of your primary key.
If you do not know the fingerprint then read Find the Fingerprint.
Look for the row starting with pub
which matches your primary key.
Underneath that is a row starting with fpr
.
The fingerprint is the hexadecimal value in that row.
To find the fingerprint of a key then you need to find it in the key listings:
gpgListKeys
Which will output something similar to:
[info] pub:u:4096:1:C148D2D9D807D63F:1538989071:::u:::escaESCA:::+:::23::0:
[info] fpr:::::::::84C263516A75C26F1ADD723FC148D2D9D807D63F:
[info] grp:::::::::0DEC20F89D3657E3DD606B9EAEA3A96EE9ED162C:
[info] uid:u::::1538989071::A05C2A6F40589222BAA65BEF1C1507E9261D2AF0::BotTech <[email protected]>::::::::::0:
[info] sub:u:4096:1:674FFAE89237F93F:1538989071::::::s:::+:::23:
[info] fpr:::::::::8BD27F291CB15ABD0DEFA583674FFAE89237F93F:
[info] grp:::::::::F243E10E8ACABDF33113B24A2D420FFAC7C71125:
The fingerprint of a key is the hexadecimal value in the row starting with fpr
.
In this example there are two fingerprints, one for the primary key 84C263516A75C26F1ADD723FC148D2D9D807D63F
and one
for the subkey 8BD27F291CB15ABD0DEFA583674FFAE89237F93F
.
Now that you have the fingerprint of primary key you can export it.
We have to export the primary key because we need the primary secret key in order to change the passphrase of the subkey in the next step.
set gpgExportKey/gpgKeyFingerprint := "84C263516A75C26F1ADD723FC148D2D9D807D63F"
show gpgExportKey
Pinentry will ask you for the passphrase.
This will show you the location of the key file. If you forgot to use the show
command then you can get it afterwards
by using:
show gpgKeyFile
The subkey that was exported in the previous step will have the same passphrase as the primary key. This is not ideal because we need to commit this passphrase (encrypted of course) to the build and so that increases the chances that it may get compromised. We need to change the passphrase so that in the worst case, only this subkey is compromised.
Unfortunately GnuPG does not have a good way of doing this. We must import the key into a temporary home directory, then change the passphrase and then export it back out again.
set gpgChangeSubkeyPassphrase/gpgKeyFingerprint := "8BD27F291CB15ABD0DEFA583674FFAE89237F93F"
gpgChangeSubkeyPassphrase
Pinentry should appear initially for the current passphrase and then again for the new passphrase.
Remember to use the subkey fingerprint here and not the primary key.
Since we will commit the subkey to source code repository it is a good idea to also encrypt it just in case something went wrong and the key was exported without a passphrase or included the primary secret key or perhaps your passphrase was weak.
We will use the Travis CLI to encrypt all the secrets to be used in the build.
Go to GitHub and create a Personal access token with the following scopes:
user:email
read:org
repo_deployment
repo:status
write:repo_hook
See Travis CI for open source projects on what these scopes are used for.
Save the token somewhere safe as you will need it to login to the Travis CLI and if you forget it you will need to generate a new one.
Next encrypt the GPG secret key using the instructions on encrypting files.
Install the Travis CLI:
gem install travis
Login using the GitHub Token:
travis login -g YOUR_GITHUB_TOKEN
Encrypt the secret key:
travis encrypt-file target/.gnupg/key.asc
Add the output to the before_deploy
section of the .travis.yml
file. For example:
before_deploy:
- openssl aes-256-cbc -K $encrypted_12345abcdef -iv $encrypted_12345abcdef -in travis/key.asc.enc -out travis/key.asc -d
Move the encrypted secret key:
mv key.asc.enc travis/
Delete the unencrypted secret key:
rm target/.gnupg/key.asc
Now encrypt the GPG passphrase using the instructions on encryption keys.
travis encrypt
PGP_PASS=YOUR_PGP_PASSPHRASE
NOTE: If on macOS you will need to press Enter
before pressing Ctrl+D
. The line feed character will not be included.
Add the output to the env.global
section of the .travis.yml
file.
The last few steps are to configure the key and add the signed artifacts so that they are published.
First add the GPG passphrase from the environment variable set in the previous step:
gpgPassphrase := Option(System.getenv("PGP_PASS"))
Then add the path to the secret key (the output of the openssl
command above):
gpgKeyFile := Some(file("travis") / "key.asc")
And lastly add the fingerprint of the subkey:
gpgKeyFingerprint := "8BD27F291CB15ABD0DEFA583674FFAE89237F93F!"
NOTE: The fingerprint must end with a !
in order to get GnuPG to use the subkey.
Now when you run publish
the artifacts will be published along with their signatures.
This plugin was generated from the [BotTech/sbt-autoplugin.g8] Giter8 template.
Credit to these Open Source projects:
- dwijnand/sbt-dynver
- marcuslonnberg/sbt-docker
- ohnosequences/sbt-github-release
- sbt/sbt-bintray
- sbt/sbt-dirty-money
Special thanks to:
- Docker for providing the containerization.
- GitHub for hosting the git repository.
- GnuPG for providing a free implementation of the OpenPGP standard.
- JFrog for distributing the releases on Bintray.
- Lightbend for Scala, sbt and distributing the plugin in the community sbt repository.
- scalacenter for Scala and indexing this project in the Scaladex.
- Travis CI for running the build.
- All the other contributors who made this project possible.