HowTo:PGP

From Predictive Chemistry
Revision as of 14:23, 4 June 2014 by David M. Rogers (talk | contribs) (Usage)

Jump to: navigation, search

Online services are the foundation of distributed computing, but security is often overlooked at the beginnings of these projects. Here, I'll explain a really simple way to lock down access to your precious protocols at the transport layer. There are essentially no changes required to the communication protocol. In addition, the method dovetails with the GnuPG suite for message signature and encryption.

Get Started with GnuPG

Why?

GnuPG uses a chain of public-key certificates, similar to the x.509 certificates that we're all familiar with our web-browsers constantly complaining about. Each certificate has some private information (that the owner keeps) and some identifying public information that is distributed like a user-id, name and birthdate, or the like. The difference with those other numbers is that revealing the public key doesn't compromise your security, like a password social security number would. Instead, the public key is used to check that a communication could only have come from the person knowing the private key.

Things get complicated when more than two people get involved. How do I know a person's public key is trusted to do something? PGP does this by having one key sign another key. If I want my webserver to recognize a set of users, I can create a server private key and use it to sign the public keys of trusted users. Then I have a chain of signatures that validate the user's public key. When the user connects, I use that public key to make sure the user knows the corresponding private key.

Secure internet servers use x.509 certificates in a similar way to prove that you can trust them. Your browser has a list of trusted root certificates from places like Verisign and Thawte. When you connect to a random server using HTTPS, the server gives you a public key. Your browser then checks whether that public key has been signed by one of the trusted root certificates it knows about.

Although similar to x.509 server certificates, PGP doesn't have a centrally based set of trusted root certificate authorities. In fact, anyone can sign for anyone else in the PGP scheme. This makes it much more user-centric. I can, for example, sign source code packages I produce and people who have my public key can verify that they haven't been tampered with. I could produce a hierarchy of keys to sign things like code, purchase orders, or server access. I could use these to delegate those responsibilities. At the same time, I could get those keys signed by other people (like the manager of a coding project, an accountant, or a server admin).

Install

Use a package-manager to look for a gnupg or equivalent. <source lang="bash"> apt-get install gnupg # Trisquel / Debian / Mint / Ubuntu fink install gnupg # OSX + Fink yum install gnupg # RHEL / Centos / Scientific Linux / Fedora </source>

Create a Keyring

GnuPG does almost everything through options to the gpg command. Following along with,[1] <source lang="bash"> gpg --gen-key </source>

gpg (GnuPG) 1.4.13; Copyright (C) 2012 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 
Key does not expire at all
Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichhduesseldorf.de>"

Real name: David M. Rogers
Email address: predictivestatmechgmail.com
Comment: 
You selected this USER-ID:
    "David M. Rogers <predictivestatmechgmail.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key.

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
.
gpg: ~/.gnupg/trustdb.gpg: trustdb created
gpg: key 262B259C marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   2048R/262B259C 2013-12-20
      Key fingerprint = D1CE 9C6A 5099 6753 8FC4  81F5 5B64 9362 262B 259C
uid                  David M. Rogers <predictivestatmechgmail.com>
sub   2048R/E494F149 2013-12-20

Next, you'll want to double-check that it worked and make sure gnupg's directory has no read or write permissions for anyone other than yourself. <source lang="bash"> gpg --list-keys ls -ld ~/.gnupg </source>

For the client/server example, we'll also want to write the public key to a separate file. <source lang="bash"> gpg --export 262B259C --ascii >~/.gnupg/predictivestatmech.asc </source>

Next, we'll generate a certificate belonging to my laptop. I'll choose to use this one for allowing users to connect to a new protocol I'm developing. It can't have a password according the GNUTLS manual. <source lang="bash"> gpg --gen-key </source>

gpg (GnuPG) 1.4.13; Copyright (C) 2012 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 3y
Key expires at Mon Dec 19 15:04:37 2016 EST
Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichhduesseldorf.de>"

Real name: kubotan
Email address: davidrogersusf.edu
Comment: MBPR Laptop
You selected this USER-ID:
    "kubotan (MBPR Laptop) <davidrogersusf.edu>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key.

You don't want a passphrase - this is probably a *bad* idea!
I will do it anyway.  You can change your passphrase at any time,
using this program with the option "--edit-key".

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
.
gpg: key 03357392 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: next trustdb check due at 2016-12-19
pub   2048R/03357392 2013-12-20 [expires: 2016-12-19]
      Key fingerprint = C417 3765 AB3A 139B 72C3  F093 D35C FD07 0335 7392
uid                  kubotan (MBPR Laptop) <davidrogersusf.edu>

Note that this key cannot be used for encryption.  You may want to use
the command "--edit-key" to generate a subkey for this purpose.

Following the suggestion, I'll sign this one with my user key. <source lang="bash"> gpg --edit-key kubotan </source>

gpg (GnuPG) 1.4.13; Copyright (C) 2012 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

pub  2048R/03357392  created: 2013-12-20  expires: 2016-12-19  usage: SC  
                     trust: ultimate      validity: ultimate
[ultimate] (1). kubotan (MBPR Laptop) <davidrogersusf.edu>

gpg> sign

pub  2048R/03357392  created: 2013-12-20  expires: 2016-12-19  usage: SC  
                     trust: ultimate      validity: ultimate
 Primary key fingerprint: C417 3765 AB3A 139B 72C3  F093 D35C FD07 0335 7392

     kubotan (MBPR Laptop) <davidrogersusf.edu>

This key is due to expire on 2016-12-19.
Are you sure that you want to sign this key with your
key "David M. Rogers <predictivestatmechgmail.com>" (262B259C)

Really sign? (y/N) y

You need a passphrase to unlock the secret key for
user: "David M. Rogers <predictivestatmechgmail.com>"
2048-bit RSA key, ID 262B259C, created 2013-12-20

                  
gpg> quit
Save changes? (y/N) y

My signature belongs to kubotan's key now, so GPG saves it there. <source lang="bash"> gpg --list-sigs </source>

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: next trustdb check due at 2016-12-19
/Users/rogers/.gnupg/pubring.gpg
--------------------------------
pub   2048R/262B259C 2013-12-20
uid                  David M. Rogers <predictivestatmechgmail.com>
sig 3        262B259C 2013-12-20  David M. Rogers <predictivestatmechgmail.com>
sub   2048R/E494F149 2013-12-20
sig          262B259C 2013-12-20  David M. Rogers <predictivestatmechgmail.com>

pub   2048R/03357392 2013-12-20 [expires: 2016-12-19]
uid                  kubotan (MBPR Laptop) <davidrogersusf.edu>
sig 3        03357392 2013-12-20  kubotan (MBPR Laptop) <davidrogersusf.edu>
sig          262B259C 2013-12-20  David M. Rogers <predictivestatmechgmail.com>

I need both its public and private sections to use in the server code, so I'll extract them, and mark them self-read-only. <source lang="bash"> gpg --armor --export "kubotan" >~/.gnupg/kubotan.asc gpg --armor --export-secret-key "kubotan" >~/.gnupg/kubotan-secret.asc chmod 400 ~/.gnupg/*.asc </source>

These keys are all you need to sign or encrypt messages as well. To see the usage for those, check the chapters in the GPG Mini-Howto.

Server

Next, let's get started with some client/server code. TLS stands for transport-layer-security. Normal communications (e.g. http/telnet/ftp) merrily go about sending messages (commands, files, output, etc.) over TCP. TLS-enabled communications (e.g. HTTPS/SSH/SFTP) start a connection by figuring out how to encrypt the session. They can also check signatures to authenticate the user and the server. After that, all communications are encrypted to prevent data leaking to the local packet sniffing red-team.

Although incorporating a 'STARTTLS' message into your protocol has been recommended if you need to talk before authenticating, the lazy way is to just authenticate and start TLS at connection. The GNU TLS library makes this especially easy by handling certificates, doing the initial TLS handshake, and encrypting/decrypting messages passed through gnutls_record_send / gnutls_record_recv.


Installation

The GNUTLS library is under heavy development, and I found that the examples in the manual[2] would not run with the package manager's version 2.2. So, I installed nettle 2.7.1 (--enable-shared) and gnutls 3.2.7[3] from source. Apparently someone's been using strerror in gnutls, and someone else has been replacing strerror with repl_strerror to teach them a lesson - so we have to deal with the collateral damage. <source lang="bash">

 sed -i.bak -e 's|strerror([^)]*)|""|g' lib/nettle/egd.c
 sed -i.bak -e 's|strerror([^)]*)|""|g' lib/nettle/rnd.c

</source>

Usage

The server code for OpenPGP authentication and client code for the very similar x.509 authentication are available.

But the best starting place for hacking together a client/server pair authenticating using OpenPGP are the generic implementations in gnutls-3.2.7/src/serv.c and gnutls-3.2.7/src/cli.c. Those commands are bundled with gnutls and are easy to test out with <source lang="bash"> gnutls-serv \

   -p 5556 \
   --priority NORMAL:+CTYPE-OPENPGP \
   --pgpkeyring=$HOME/.gnupg/pubring.gpg \
   --pgpcertfile $HOME/.gnupg/kubotan.asc \
   --pgpkeyfile $HOME/.gnupg/kubotan-secret.asc &

gnutls-cli \

   -p 5556 \
   --priority NORMAL:+CTYPE-OPENPGP \
   --pgpkeyring=$HOME/.gnupg/pubring.gpg \
   --no-ca-verification \
   localhost

You will get a segfault if you read an ascii key (.asc) in binary mode or vice-versa. </source>

Some uses of gnutls require a lot of work. For example, the keyring is basically a list of keys. If I want to check whether a given public key exists in the list, I have to go through the following torture: <source lang="C"> /* Test code for OpenPGP Certificates and keyrings. */

  1. ifdef HAVE_CONFIG_H
  2. include <config.h>
  3. endif
  1. include <stdio.h>
  2. include <stdlib.h>
  3. include <string.h>
  4. include <gnutls/gnutls.h>
  5. include <gnutls/openpgp.h>

gnutls_openpgp_keyring_t keyring; gnutls_openpgp_crt_t auth_crt;

gnutls_openpgp_keyring_t load_keyring_file(char *ringfile); gnutls_openpgp_crt_t load_crt_file(char *name); /*int cert_verify_callback(gnutls_session_t session); int tcp_connect(const char *srv, int port); void tcp_close(int sd);*/ char *get_peer_name(gnutls_openpgp_crt_t crt, int idx);

// NOT thread safe!! static const char *bin2hex(const void *bin, size_t bin_size) {

       static char printable[110];
       const unsigned char *_bin = bin;
       char *print;
       size_t i;
       if (bin_size > 50)
               bin_size = 50;
       print = printable;
       for (i = 0; i < bin_size; i++) {
               sprintf(print, "%.2x ", _bin[i]);
               print += 2;
       }
       return printable;

}

int main(int argc, char **argv) {

   int status, ret, loadkey = 0;
   gnutls_openpgp_keyring_t kring;
   gnutls_openpgp_crt_t crt;
   char id[GNUTLS_OPENPGP_KEYID_SIZE];
   if(argc < 3) {
       fprintf(stderr, "Usage: %s <keyring.gpg> <pubkey.asc>\n", argv[0]);
       return 1;
   }
   if(!strcmp(argv[1], "-k")) {
       loadkey = 1;
       argv += 1;
       argc--;
       if(argc < 3) {
           fprintf(stderr, "Usage: %s <keyring.gpg> <pubkey.asc>\n", argv[0]);
           return 1;
       }
   }
   /* Load data */
   gnutls_global_init();
   if( (kring = load_keyring_file(argv[1])) == NULL) {
       fprintf(stderr, "Unable to load keyring from %s.\n", argv[1]);
       return 1;
   }
   /*if(loadkey && gnutls_openpgp_keyring_init(&kring)) {
       perror("gnutls_openpgp_keyring_init");
       return 1;
   }*/
   if( (crt = load_crt_file(argv[2])) == NULL) {
       fprintf(stderr, "Unable to load crt from %s.\n", argv[2]);
       return 1;
   }
   /* Run */
   if( gnutls_openpgp_crt_get_key_id(crt, id) < 0) {
       fprintf(stderr, "get_key_id()\n");
       return -2;
   }
   printf("keyID: %s\n", bin2hex(id, 8));
   if( (ret = gnutls_openpgp_keyring_check_id(kring, id, 0)) < 0) {
       printf("Key not found in keyring.\n");
   } else {
       printf("Key found!\n");
   }
   ret = gnutls_openpgp_crt_verify_ring(crt, kring, 0, &status);
   printf("crt_verify_ring(%d) -> gnutls_certificate_status_t 0%3o\n",
           ret, status);
   gnutls_openpgp_crt_deinit(crt);
   gnutls_openpgp_keyring_deinit(kring);
   gnutls_global_deinit();
   return 0;

} </source> This is only half the battle for message verification. Once I've established the key is in the keyring (hence proving I have trusted them enough to put them in the list), I still have to check that the digital signature on the message was produced by the private key corresponding to the public one I just checked.