Encrypt a build-result – automaticaly

So in my last blogpost I showed how to automatically create a PHAR-file using Travis-CI. It didn’t take long for a challenge to show up: Digitally sign the resulting PHAR-file.

I already did some work with encrypting and decrypting stuff for an individual automated deployment to a server a few months back. So signing stuff shouldn’t be that complicated I thought. Therefore: Challenge accepted!

Setting the boundaries

So I want to sign the resulting PHAR-file to allow people to verify it’s integrity using GPG. For that I will use a new key and not my usual default one as I need to add that whole key to the repository somehow. It will be encrypted and password-protected but it will be open for attack. So I don’t want that to be my default one but a separate one that I can easily revoke should need arise.

This key will then be encrypted and added to the repository. And we also need to add the decryption-password as well as the key-password as environment-variables to the CI-Server.

Why GPG? There are other ways of signing software but using GPG is the easiest as you do not need a certification authority to approve a key for you and also it is readily available on Travis-CI so I don’t need to install any additional software.

Create a new GPG-key

So the first step is to create a new GPG-key. You can either do that via a GUI or via the CLI like this:

gpg --gen-key

That will ask some questions on screen: I went for RSA only for signing, 4096 bit key length, 10 years validity and added Name, email-address and a comment. I then choose a passphrase which I stored in my Password-Manager. I’m going to refer to that passhprase later on as “Signing-Pasphrase”.

At the end you will have something along these lines in your CLI:

pub   4096R/0FD194D3 2017-01-19 [expires: 2027-01-17]
  Fingerprint = F36F 0D67 2CBB 788B 7721  E13E 9959 31D9 0FD1 94D3
uid       [ uneing.] Andreas Heigl (JUnitDiff - Post) (Testkey) <andreas@heigl.org>

The interesting part is the pub 4096R/0FD194D3 as the part behind the ‘/’ is the key-ID. We’ll need that later on.

To make the key accessible for others we should now send it to a keyserver.

gpg --keyserver pgp.mit.edu --send-key 0FD194D3

You’ll need to replace the 0FD194D3 with your own key-ID!

And while we’re at it, we can also already generate a revocation certificate for the key. Should the key be compromised I can send the revocation certificate to the keyserver to invalidate the signing key.

gpg --output revoke-0FD194D3.asc --gen-revoke 0FD194D3

I need to verify that I want to revoke the key and I’m asked for a reason for revocation. I’m not sure what it’ll be so I choose the first option “No Reason”.
After answering some more stuff I’m asked for the Signing-Passphrase.

That will leave me with a revocation certificate in the file revoke-0FD194D3.asc. I add that to my password-manager for further reference.

Again: Make sure to replace “0FD194D3” with your own keys ID!

That’s it. GPG-Key created and ready to sign!

Encrypting key and adding to repo

Now I need to encrypt that key and add it to the Repository. So for that I need to export the key from my keychain like this:

gpg --export -a 0FD194D3 > keys.asc
gpg --export-secret-key -a 0FD194D3 >> keys.asc

You guessed it: Replace the 0FD194D3 with your own key!

That will leave my public and private key in a single file. Everyone that has that file can sign on my behalf! Keep that file secure at all times!

I don’t want that file to accidentaly show up in my git-repo!

Therefore the next thing I’ll do is to encrypt it.

gpg -c keys.asc

That will ask me again 2 times for a passphrase. I’ll choose a different phrase than the Signing-Passphrase but at least equally secure and store that in my password manager as well. I’ll need that later for the decryption on the CI-Server so I’ll refer to that passphrase as “Decryption-Passphrase”.

It leaves me with a file keys.asc.gpg. I will now add that file to the repository and remove the keys.asc so that I’m not accidentaly adding it to the Repo.

git add keys.asc.gpg
git commit -m "Adds Encrypted Signing key"
rm -rf keys.asc

Great! Key secured and added.

Making Passphrases available

As I want to do the signing on the CI-Server I somehow need to make the two passphrases available to the build-process. One way would be to add them encrypted to the .travis.yml-file but somehow that leaves the encrypted file as well as the passphrase to decrypt it both in the same place. That seemed a bit awkward to me and luckily Travis-CI has a different way of providing the passphrases to the build-Process.

Enter Environment-Variables!

I headed over to the settings of my project and there is a section called “Environment Variables”. There I can add both passphrases. And I also make sure to leave the switch to “Display value in build log” off!

Now I can use $DECRYPT_KEY and SIGN_KEY in .travis.yml without having those values exposed in the file. Note that those variables are not available in PullRequest-builds as it would then be possible for an attacker to alter the .travis.yml so that the value would be displayed in the logs.

Finally signing the build-artifacts

Now I have everything set up so that I can sign the build-artifacts – my PHAR-file.

The build from my last blog-post left me with a PHAR-file in build/junitdiff.phar. Now I want to sign that one with my GPG-Key.

To be able to do that, I first need to import the key into gpg and before I can do that I need to decrypt it.

So I add this to my .travis.yml-file to do so:

before_deploy: 
  - echo $DECRYPT_KEY | gpg --passphrase-fd 0 keys.asc.gpg  
  - gpg --batch --yes --import keys.asc
  - echo $SIGN_KEY | gpg --passphrase-fd 0 -u 0FD194D3 --armor --detach-sig build/junitdiff.phar

Again make sure to replace 0FD194D3 with your own key-ID!

The first command decrypts the encrypted file, the second one then imports that decrypted key into the keychain. As the machine is destroyed after the build I’m not spending much thought here on removing the key afterwards.

The third command then is the one I was after all the time! It creates a detatched signature of the PHAR-file using the key I just imported. The signature-file lies right beneath the signed file.

So now I can add the signature to the files to deploy like this:

deploy:
  …
  file:
    - build/junitdiff.phar
    - build/junitdiff.phar.asc
  …

Yay! Now the automated build not only creates the PHAR-file but also signs it and deploys both files to the deployment-target.

Fin!

Feel free to tell me other ideas on how to do that!

3 thoughts on “Encrypt a build-result – automaticaly

    1. Thanks for pointing that out! And as it is always a security risk to put private keys – even encrypted – into the public I would never do that with a personal key! You should always do that with a special key that is only used for signing the artifacts. And you should also generate the revocation key right in advance to be able to react to a compromise by publishing that revocation key! In the end it’s a matter of weighing risks and benefits.

Comments are closed.