# PicKI/README version 0x03, 2012-02-20 ** WHO wrote this? WHY? PicKI is by /dev/rob0 . I tend to be rather picky, and I believed that existing tools like easy-rsa and ssl-admin were too big and unwieldy. PicKI can accomplish most of what they can do with far less code and fuss. ** WHAT is this? (includes more WHY) This is a simple framework for an openssl(1)-based SSL certificate authority. It has the directory structure and a configuration file in the top directory, called "environment". The purpose is for openvpn(8) certificates, although it could probably be used for anything which requires SSL certificates. The major design goal is scriptability. These can be used as-is at the command line or easily called from a web interface. The few small scripts have a small and consistent set of exit codes, which a web programmer can check for status. See below for notes for Web frontend writers. Another design goal is readability. I looked in easy-rsa's pkiadmin script to try to learn the openssl commands, but I ran out of aspirin before finding what I needed there. I think if you are well-founded in shell concepts, reading through the files here will clearly show you openssl's black magic in issuing certificates. It includes bash scripts in the scripts/ subdirectory to generate the SSL CA itself, and then generate client or server keys and certificate signing requests (CSR), and then to sign those with the CA. There is also one which bundles up all files a VPN client would need in a zip. Yes, GNU bash is required. Even as simple as these scripts are, I like bash features. It would not be difficult at all to adapt them to POSIX sh, but someone else can do that if they need it. I don't, I use bash. Note to POSIX porters: you'd have to change all [[ ... ]] to POSIX "[ ... ]" or test(1), and quote all variables therein. I don't know if the ${param:=word} and ${param:?word} syntax in "environment" is supported in sh, so check that too. ** HOW do I start? (includes WHERE) Edit "environment", paying particular attention to the block that proclaims it needs customization. All these commands should take place in the $PicKIdir directory as set in "environment". Set a value for cName, the commonName (hostname) of your openvpn server: cName=vpn.example.com Then source the environment file in your bash shell (does NOT need to be root) as such: . environment Next, create the CA: scripts/new-ca This is only done once. When the certificate is created successfully, that script removes its own executable bits. If you need to start over you can delete all the files and "chmod +x scripts/new-ca" before you run it again. Next, create your server key and certificate: scripts/new server Then sign it: scripts/sign server Bundle up all the files it needs for secure transfer to the server: scripts/zipemup You now have a zips/vpn.example.com.zip file which contains the files needed on the server. To continue on and generate a client key and certificate, first set the client's commonName: cName=client.example.com Then run the same scripts without the "server" argument: scripts/new && scripts/sign And finally create the bundle for the client: scripts/zipemup You now have a zips/client.example.com.zip file which contains all the files needed on that client. Each such zipfile must be securely transferred to the client, because the key needs to be kept secret. You can repeat those steps for as many clients as you need. How far will this scale? I do not know. I did test it going over 256, which worked fine with only one minor issue: serial numbers changed from 2-digit to 4-digit. Also note that the OpenSSL folks say that ca(1) is for a "minimal" CA. Ask them what constitutes "minimal". But I suspect that openvpn limits would be hit before CA limits. A geek can choose to do it right: he can make his own key and send you the CSR. In this case export the client's commonName: cName=geek.example.com Save his CSR to your req/ directory: cp /path/to/his.request.pem req/$cName.csr Then sign it as normal: scripts/sign And finally create the bundle for the client: scripts/zipemup This bundle will not contain a key and thus will not require secure transfer to the geek. (That's the whole idea about how public key cryptography should work.) The new zips/$cName.zip file is given the permissions from your normal shell's umask, typically 0644. That is it! That is all this does. ** WHAT is here? File Contents ==== ======== README This file, yo. certs/ Directory containing all CA-signed certificates indexed by their commonName environment The configuration file index.txt The text database of certificates, maintained by ca(1): do not edit index.txt.attr Constraints from ca(1): do not edit keys/ Directory containing all CA-generated keys (restricted permissions 0700) openssl.cnf The config file for openssl(1) commands private/ Directory containing the CA's own key (restricted permissions 0700) req/ Directory containing all CA-generated CSRs. When a certificate is nearing expiration, the existing CSR can be signed again if you delete or rename "certs/$cName.cert". scripts/ Directory containing scripts, detailed below serial Text file maintained by ca(1) with the serial number of the NEXT certificate: do not edit. Initially contains "00". signed/ Directory containing all CA-signed certificates indexed by serial number, maintained by ca(1) zips/ Directory containing all zipfiles generated for each commonName (restricted permissions 0700) zips/README Instructions for the user which are included in zipfiles. scripts/new Command to create a new key and CSR scripts/new-ca Command to create a new CA (can only run once) scripts/revoke UNIMPLEMENTED command to revoke a certificate scripts/sign Command to sign a CSR scripts/zipemup Command to create a new zipfile for a commonName After you have started using your CA, the ca(1) application will keep previous copies of the files it maintains (see list), appending ".old" to the filename. This can give you a handy shortcut to revoke a cert which had a mistake or typo. ** SCRIPTS usage detail ALL of the following require a certificate commonName in the shell environment as "$cName", unless otherwise noted. They will accept a commonName as an argument ONLY if "$cName" is missing, and ONLY the first token will be read as the commonName. "$cName" set in the environment will always take priority. ALL of these require that the "environment" configuration file be sourced into the running shell's environment: ". environment". ALL of these expect to be executed from the top-level pki directory: use the relative path, "scripts/SCRIPTNAME" to run it. ALL of these return 0 on success or Exit Codes as shown. WARNING: while there are reasonable safeguards to prevent common mistakes, you should pay close attention to your environment file and the values that are in the shell environment at runtime. Easy-rsa and ssl-admin probably do more error checking; if you need that, consider one of those projects. * new-ca Usage: scripts/new-ca Result: New CA key and self-signed certificate is created. Files: private/ca.key certs/ca-cert.pem Notes: does require environment but not "$cName". Disables itself ("chmod -x $0") upon successful completion. Exit Codes: 65 if private/ca.key exists 66 if certs/ca-cert.pem exists other nonzero exit code from openssl req(1) errors, or possibly from chmod(1) if the chmod of itself fails * new Usage: scripts/new [ commonName ] Or: scripts/new server [ commonName ] Result: New key and CSR created for "$cName". A server CSR and dhparam file is created if second form is used or if $server is set in the environment. Files: keys/$cName.key req/$cName.csr certs/${cName}-dh$PicKIbits.pem (server only) Notes: If key exists in the keys/ directory, only the CSR is created. If CSR exists, abort. (The existing CSR can be reused unless changes are needed in the certificate's Subject field.) Exit Codes: 65 if cName not set 66 if req/$cName.csr exists other nonzero exit code from openssl dhparam(1) or req(1) errors, or possibly from chmod(1) if the chmod of the key fails * sign Usage: scripts/sign [ commonName ] Or: scripts/sign server [ commonName ] Result: New certificate created for "$cName". A server certificate is created if second form is used or if $server is set in the environment. Files: signed/$(