├── .gitignore ├── CHANGES.md ├── LICENSE ├── README.md ├── ca ├── caconfig.cnf.default └── host.cnf.default └── caman /.gitignore: -------------------------------------------------------------------------------- 1 | ca/* 2 | !ca/caconfig.cnf.default 3 | !ca/host.cnf.default 4 | store/* 5 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## caman changes 2 | 3 | ### Changelog 4 | 5 | **0.3.2**, 2016-11-24: Fix from [TakeMeNL](https://github.com/TakeMeNL) 6 | * Exit after "cannot renew" message 7 | 8 | **0.3.1**, 2015-09-17: Fixes from [foudfou](https://github.com/foudfou) 9 | * ``new`` accepts arguments for SAN certificates again 10 | * Changed shebang for better compatibility 11 | * Removed trailing whitespace 12 | 13 | **0.3.0**, 2015-09-08: Add intermediate CA support 14 | * See [Upgrading](#upgrading) below for upgrade instructions 15 | * ``init`` command changed to accept optional path to root caman CA dir 16 | * ``sign`` command changed to create combined chain/crt file 17 | * ``revoke`` command changed to support revoking intermediate CAs 18 | * ``caman`` script now detects and uses absolute paths, so can be installed 19 | system-wide 20 | * Certificates are now signed with ``-notext`` so certificates are smaller and 21 | are accepted by more systems 22 | * Password prompt is now managed by caman, so only needs to be entered once per 23 | command 24 | * ``openssl`` commands are now run in non-interactive mode 25 | 26 | **0.2.0**, 2015-08-21: Add SAN support 27 | * See [Upgrading](#upgrading) below for upgrade instructions 28 | * ``new`` command changed to replace OUN with alt hostnames to support SANs 29 | * Version number now displayed when called with missing or incorrect arguments 30 | 31 | **0.1.0**, 2015-05-16: Initial release 32 | 33 | 34 | 35 | ### Upgrading 36 | 37 | #### Upgrading from 0.2.0 38 | 39 | Host certificates are now created with the ``-notext`` option to suppress the 40 | human-readable text summary at the top of certificates, as it unnecessarily 41 | increases their file size, and can apparently cause problems for some systems. 42 | If you haven't had any problems there is no need to modify existing host 43 | certificates, but you can ``renew`` host certificates to generate new ones 44 | without the text summary, or manually remove the text summaries from your 45 | ``.crt.pem`` and ``.keycrt.pem`` files (the human-readable section which 46 | starts ``Certificate``, before ``-----BEGIN CERTIFICATE-----``). 47 | 48 | No other changes are required for this version; your existing CA can be used as 49 | a root CA without any changes to its config, or to certificates which are 50 | already in use. 51 | 52 | 53 | #### Upgrading from 0.1.0 54 | 55 | The file ``ca/caconfig.cnf.default`` has been changed, so you need to update 56 | your customised ``caconfig/host.cnf``: 57 | * The section ``[ CA_default ]`` has a new setting ``copy_extensions = copy`` 58 | 59 | 60 | The file ``ca/host.cnf.default`` has been changed, and your customised 61 | ``ca/host.cnf`` needs to be changed too: 62 | * The ``organizationalUnitName`` now has to be set manually in this file; 63 | change ``<>`` to an appropriate value for your certificates. This can 64 | be changed manually on a per-host basis once you have run the ``new`` command. 65 | * In the section ``[ v3_req ]``, after the line ``keyUsage``, add the line 66 | ``<>`` 67 | 68 | The changes to ``ca/host.cnf`` do not affect the configuration for existing 69 | hosts. 70 | 71 | * The ``new`` command no longer accepts an OUN; this now has to be set manually 72 | in your ``ca/host.cnf``, and customised per-host as necessary after running 73 | ``new``. 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Richard Terry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## caman 2 | 3 | A self-signing certificate authority manager - create your own certificate 4 | authority, and generate and manage SSL certificates using openssl. 5 | 6 | If you want to see how caman works and why it exists, you read the 7 | accompanying article, 8 | [Self-Signing Certificate Authorities](http://radiac.net/blog/2015/05/self-ca/) 9 | 10 | This document explains how to use caman to 11 | [create a certificate authority](#creating-a-certificate-authority), optionally [use an intermediate CA](#using-an-intermediate-ca), and to 12 | [create, sign, renew and revoke](#managing-host-certificates) host 13 | certificates. 14 | 15 | Version 0.3.2, 2016-11-24. For changelog and upgrade information, see 16 | [Changes](CHANGES.md) 17 | 18 | ### Quickstart 19 | 20 | To create a certificate authority and start signing: 21 | 22 | git clone https://github.com/radiac/caman.git 23 | cd caman 24 | cp ca/caconfig.cnf.default ca/caconfig.cnf && vi ca/caconfig.cnf 25 | cp ca/host.cnf.default ca/host.cnf && vi ca/host.cnf 26 | ./caman init 27 | ./caman new host.example.com 28 | ./caman sign host.example.com 29 | ./caman renew host.example.com 30 | ./caman revoke host.example.com 31 | 32 | Read on to see more details, how you can do this using an intermediate certificate authority, and how to create wildcard and SAN certificates. 33 | 34 | 35 | ### Creating a Certificate Authority 36 | 37 | 1. Make sure ``openssl`` is installed on your system before using caman: 38 | * Debian and Ubuntu: ``sudo apt-get install openssl`` 39 | 40 | 2. Clone this repository: 41 | 42 | git clone https://github.com/radiac/caman.git 43 | 44 | The ``.gitignore`` is set up to ignore all files caman will create. This is 45 | to prevent you from accidentally pushing secrets to a public repository. 46 | 47 | Although these instructions assume you'll keep everything in the cloned 48 | directory, the ``caman`` script operates on the current working directory 49 | and just expects it to contain the ``ca`` directory. 50 | 51 | This means you can move/symlink the script to ``/usr/local/bin/`` to make it 52 | available system-wide, or move the ``ca`` directory into a separate folder 53 | or repository. 54 | 55 | 3. Configure the files in the ``ca`` directory - 56 | (see [Configuration](#configuration)) 57 | 58 | 4. Initialise caman in the current directory: 59 | 60 | cd caman 61 | ./caman init 62 | 63 | * You will be asked for a PEM key - this must be at least 4 characters long, 64 | but the longer the better. Keep it safe - you will need it for most caman 65 | commands. 66 | * If you plan to use a intermediate CAs, this will be your root CA. 67 | 68 | You are now ready to 69 | [create and manage host certificates](#managing-host-certificates). 70 | 71 | 5. Optional: Create an intermediate CA to do your day-to-day signing, so you 72 | can keep your root CA key safe and offline. See 73 | [Using an intermediate CA](#using-an-intermediate-ca) for details. 74 | 75 | 6. Optional: Publish ``ca/ca.crl.pem`` at the URL in your configuration 76 | (or you can you disable CRL in your config). 77 | 78 | 7. Optional: Distribute ``ca/ca.crt.pem`` for your host certificates to be 79 | recognised; see [Distribution](#distribution) for more information 80 | 81 | Keep ``ca/ca.key.pem`` private. If it is compromised, you will need to destroy 82 | your certificate authority and start again. 83 | 84 | 85 | #### Configuration 86 | 87 | Copy the default configs: 88 | 89 | cp ca/caconfig.cnf.default ca/caconfig.cnf 90 | cp ca/host.cnf.default ca/host.cnf 91 | 92 | Edit both files; look for comments starting ``# >>`` for where you need to 93 | make changes. 94 | 95 | Changes to make in ``ca/caconfig.cnf``: 96 | * Change the 6 values under ``[ req_distinguished_name ]``: 97 | * ``countryName``: your two-character country code 98 | * ``stateOrProvinceName``: your state or province 99 | * ``organizationName``: the name of your organisation 100 | * ``organizationUnitName``: your department in the organisation 101 | * ``commonName``: the name of your organisation 102 | * ``emailAddress``: your e-mail address 103 | * Change the CRL distribution points URL under ``[ usr_cert ]`` and 104 | ``[ v3_ca ]``: 105 | * ``crlDistributionPoints``: URL where you will publish your ``ca.crl.pem`` 106 | * If you don't plan on publishing a CRL, comment these lines out, as well as 107 | ``crl_extensions`` and ``crlnumber`` under ``[ CA_default ]``. 108 | * The lifespan of your CA is ``default_days`` - 100 years by default 109 | 110 | In ``ca/host.cnf``: 111 | * Change 5 of the values under ``[ host_distinguished_name ]``: 112 | * ``countryName``: the two-character country code for this host 113 | * ``stateOrProvinceName``: the state or province for this host 114 | * ``organizationName``: the name of the organisation for this host 115 | * ``organizationUnitName``: your department in the organisation 116 | * ``emailAddress``: the e-mail address for the admin for this host 117 | * Do not change ``commonName`` - this is a placeholder which will be set by 118 | caman 119 | * The lifespan of your host certs is ``default_days`` - 10 years by default 120 | 121 | 122 | #### Distribution 123 | 124 | You need to distribute your ``ca/ca.crt.pem`` to clients for your host 125 | certificates to be recognised. 126 | 127 | To install your CA cert system-wide in Debian and Ubuntu: 128 | 129 | sudo cp ca/ca.crt.pem /usr/local/share/ca-certificates/my_ca_name.crt 130 | sudo dpkg-reconfigure ca-certificates 131 | 132 | To install your CA cert system-wide in other Linux distros: 133 | 134 | cp ca/ca.crt.pem "/etc/openssl/certs/$( \ 135 | openssl x509 -inform PEM -subject_hash -in ca/ca.crt.pem | head -1 \ 136 | ).0" 137 | 138 | To install your CA cert system-wide in Windows: 139 | 140 | 1. For Windows Certificate Manager to recognise your certificate, you will need 141 | to remove the ``.pem`` file extension and distribute the file as ``ca.crt``. 142 | 2. Open the file from your filer or Internet Explorer like a normal file; 143 | Windows Certificate Manager will be used automatically. 144 | 3. Click "Install certificate..." and accept all defaults 145 | 146 | Some applications (such as Firefox and Thunderbird) have their own certificate 147 | stores; you may need to install your root certificate in these applications 148 | separately. 149 | 150 | 151 | ### Using an intermediate CA 152 | 153 | When running a CA, it is best practice to use an intermediate CA. You will 154 | publish your root CA's public certificate as normal, but can store your root 155 | CA's private key offline and use your intermediate CA to sign host 156 | certificates. 157 | 158 | If your intermediate CA's private key is then compromised, you can revoke your 159 | current intermediate CA and create a new one, without needing to re-issue your 160 | root CA's public certificate. 161 | 162 | A paranoid user may want to create and use their root key on a machine which is 163 | permanently air-gapped and never connects to a network. If you don't have one 164 | of those available, it should be sufficient to move your root CA to removable 165 | media, kept offline in a secure location. 166 | 167 | Caman supports multiple intermediate CAs from your root CA, and intermediate 168 | CAs can be used to create longer chains of intermediate CAs as desired. 169 | 170 | 171 | #### Creating an intermediate CA 172 | 173 | Creating an intermediate CA is exactly the same as creating a root CA, but 174 | you pass the path of your root CA to the ``init`` command, and only publish 175 | the CRT for your root CA: 176 | 177 | 1. Follow the (standard installation)[#creating-a-certificate-authority] to 178 | create your root CA, including publishing its CRL and distributing its CRT. 179 | 180 | 2. Create a new caman installation for your intermediate CA: 181 | 182 | cd ../ 183 | mv caman caman-root 184 | git clone https://github.com/radiac/caman.git caman-int 185 | cd caman-int 186 | 187 | * Your caman directory names don't need to match the ones in this example; 188 | they don't even need to be caman installations. The ``caman`` script 189 | operates on the current working directory, so if you install it 190 | system-wide, your root and intermediate CAs can start as folders with 191 | nothing but a configured ``ca`` directory. Just make sure you're in the 192 | right directory when you call ``caman``. 193 | 194 | 3. Configure your intermediate CA using the files in ``caman-int/ca`` - (see 195 | [Configuration](#configuration)) 196 | 197 | * Make sure that your ``commonName`` is unique - it must be different to 198 | your root CA's common name, any other intermediate CAs you create, and it 199 | must not match any hosts 200 | * Make sure that your CRL is at a different URL to that of your other CAs. 201 | 202 | 4. Initialise your intermediate CA by passing the caman dir for your root CA as 203 | an argument to ``init``: 204 | 205 | ./caman init ca:../caman-root 206 | 207 | * Note that CA paths are always specified with the ``ca:`` prefix 208 | * You now have your root CA in ``caman-root`` and your intermediate CA in 209 | ``caman-int`` 210 | * Your intermediate CA's chain file is ``caman-int/ca/ca-chain.crt.pem`` 211 | 212 | You are now ready to 213 | [create and manage host certificates](#managing-host-certificates) using 214 | the new intermediate CA. 215 | 216 | 5. Optional: Publish ``caman-int/ca/ca.crl.pem`` at the URL in your 217 | intermediate CA's configuration (or you can you disable CRL in your config). 218 | 219 | 6. Optional: Move your your ``caman-root`` dir to secure offline storage 220 | 221 | Note: you can use a caman intermediate CA to create further intermediate CAs, 222 | should you so wish. 223 | 224 | 225 | #### Managing host certificates with an intermediate CA 226 | 227 | Caman's syntax for managing host certificates is the same whether or not you 228 | are using an intermediate CA, but creating a host certificate with an 229 | intermediate CA will also create a file called ``hostname.chained.crt.pem`` 230 | (with corresponding ``hostname.chained.keycrt.pem``), which is a combined 231 | certificate containing your host's certificate along with the intermediate CA's 232 | trust chain. 233 | 234 | Some servers will want you to use these combined certificates (eg nginx's ``ssl_certificate`` directive, or Dovecot's ``ssl_cert`` setting), whereas others 235 | will want you to use the plain host certificate and provide the chain file in 236 | ``caman-int/ca/ca-chain.crt.pem`` separately (eg Apache's 237 | ``SSLCACertificateFile`` directive). 238 | 239 | 240 | #### Revoking an intermediate CA 241 | 242 | Revoke an intermediate CA from your root CA by passing a CA path to ``revoke``; 243 | instead of a hostname, use the ``ca:`` prefix, and the path to the caman dir 244 | for your intermediate CA: 245 | 246 | cd caman-root 247 | ./caman revoke ca:../caman-int 248 | 249 | Your intermediate CA has now been revoked; publish the updated CRL for your 250 | root CA, ``caman-root/ca/ca.crl.pem``. 251 | 252 | You cannot use caman to renew intermediate CAs; you have to revoke them and 253 | start again. 254 | 255 | 256 | ### Managing host certificates 257 | 258 | Host certificates are found in the ``store`` directory. Each host has its own 259 | directory with the config and signing request, and each sign operation creates 260 | a new directory with today's date. Use the files inside the latest directory. 261 | 262 | #### Add a new host 263 | 264 | ./caman new [ [ ...]] 265 | 266 | * ```` is the main hostname for the certificate 267 | * Use an asterisk subdomain to generate a wildcard certificate 268 | * Add multiple ```` hostnames after the main hostname to create a SAN 269 | certificate 270 | 271 | Examples: 272 | * Single host: ``./caman new myserver.example.com`` 273 | * Wildcard: ``./caman new *.example.com`` 274 | * SAN: ``./caman new myserver.example.com virtual1.example.com virtual2.example.com`` 275 | 276 | This command generates a config file for this host in 277 | ``store/hostname/config.cnf``, using the defaults you configured in 278 | ``ca/host.cnf``. You can edit this file manually to customise it further (for 279 | example, to change the organisational unit name from your default). 280 | 281 | 282 | #### Create a new certificate 283 | 284 | ./caman sign 285 | 286 | This will generate a new private key, CSR, and signed certificate 287 | 288 | 289 | #### Revoke a certificate 290 | 291 | ./caman revoke 292 | 293 | You will need to re-publish ``ca/ca.crl.pem`` after running this command. 294 | 295 | 296 | #### Renew a certificate 297 | 298 | ./caman renew 299 | 300 | This revokes the existing certificate, and then creates a new one, 301 | so is suitable for replacing both expired or compromised host certificates 302 | You will need to re-publish ``ca/ca.crl.pem`` after running this command. 303 | 304 | 305 | ### Contributing 306 | 307 | Contributions are welcome, preferably via pull request. 308 | 309 | Thanks to all contributors, who are listed in [CHANGES](CHANGES.md) 310 | -------------------------------------------------------------------------------- /ca/caconfig.cnf.default: -------------------------------------------------------------------------------- 1 | # 2 | # Certificate authority configuration file for OpenSSL 3 | # 4 | 5 | dir = ./ca 6 | 7 | 8 | #################################################################### 9 | [ ca ] 10 | default_ca = CA_default # The default ca section 11 | 12 | #################################################################### 13 | [ CA_default ] 14 | 15 | database = $dir/index.txt 16 | new_certs_dir = $dir/newcerts 17 | certificate = $dir/ca.crt.pem 18 | serial = $dir/serial 19 | crl = $dir/ca.crl.pem 20 | private_key = $dir/ca.key.pem 21 | RANDFILE = $dir/private/.rand 22 | 23 | x509_extensions = usr_cert 24 | name_opt = ca_default 25 | cert_opt = ca_default 26 | 27 | # V2 CRLs 28 | crl_extensions = crl_ext 29 | crlnumber = $dir/crlnumber 30 | 31 | # Default expiration and encryption policies for certificates 32 | # 30 days for CRL, 100 years for certs 33 | default_crl_days = 30 34 | default_days = 36500 35 | default_md = sha256 36 | preserve = no 37 | policy = policy_match 38 | 39 | # Needed to copy SANs from CSR to cert 40 | copy_extensions = copy 41 | 42 | 43 | #################################################################### 44 | # Default policy to use when generating server certificates 45 | # The following fields must be defined in the server certificate 46 | [ policy_match ] 47 | countryName = supplied 48 | stateOrProvinceName = supplied 49 | organizationName = supplied 50 | organizationalUnitName = supplied 51 | commonName = supplied 52 | emailAddress = supplied 53 | 54 | #################################################################### 55 | # The default root certificate generation policy 56 | [ req ] 57 | default_bits = 4096 58 | default_keyfile = $dir/ca.key.pem 59 | default_md = sha256 60 | prompt = no 61 | distinguished_name = req_distinguished_name 62 | x509_extensions = v3_ca 63 | string_mask = utf8only 64 | 65 | #################################################################### 66 | # Root Certificate Authority distinguished name 67 | # Change these fields to match your local environment 68 | [ req_distinguished_name ] 69 | # >> Change the following 6 variables: 70 | # countryName must be 2 character character code 71 | countryName = CN 72 | stateOrProvinceName = State or province 73 | organizationName = My Organisation 74 | organizationalUnitName = My Certificate Authority 75 | commonName = My Certificate Authority 76 | emailAddress = email@example.com 77 | # << End changes 78 | 79 | #################################################################### 80 | # Extensions to use when generating server certificates 81 | [ usr_cert ] 82 | basicConstraints = CA:FALSE 83 | nsComment = "OpenSSL Generated Certificate" 84 | subjectKeyIdentifier = hash 85 | authorityKeyIdentifier = keyid,issuer 86 | # >> Change the following URL 87 | crlDistributionPoints = URI:http://example.com/ca.crl.pem 88 | # << End changes 89 | 90 | #################################################################### 91 | # Extensions for a CA 92 | [ v3_ca ] 93 | subjectKeyIdentifier = hash 94 | authorityKeyIdentifier = keyid:always,issuer 95 | basicConstraints = CA:true 96 | # >> Change the following URL 97 | crlDistributionPoints = URI:http://example.com/ca.crl.pem 98 | # << End changes 99 | 100 | #################################################################### 101 | # CRL extensions. 102 | [ crl_ext ] 103 | authorityKeyIdentifier = keyid:always 104 | -------------------------------------------------------------------------------- /ca/host.cnf.default: -------------------------------------------------------------------------------- 1 | # 2 | # Host configuration 3 | # 4 | 5 | [ ca ] 6 | default_ca = local_ca 7 | 8 | [ local_ca ] 9 | # Default expiration and encryption policies for certificates 10 | # 10 years for certs 11 | default_days = 3650 12 | 13 | [ req ] 14 | prompt = no 15 | distinguished_name = host_distinguished_name 16 | req_extensions = v3_req 17 | 18 | [ host_distinguished_name ] 19 | # >> Change the following 4 variables: 20 | # countryName must be 2 character character code 21 | countryName = CN 22 | stateOrProvinceName = State or province 23 | organizationName = My Organisation 24 | organizationalUnitName = My Department 25 | emailAddress = email@example.com 26 | # << End changes 27 | commonName = <> 28 | 29 | [ v3_req ] 30 | basicConstraints = CA:FALSE 31 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 32 | <> 33 | -------------------------------------------------------------------------------- /caman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Initialise 5 | # 6 | 7 | VERSION="0.3.2" 8 | 9 | # Settings 10 | DEBUG=false 11 | 12 | # Arguments 13 | CMD=$1 14 | HOST=$2 15 | 16 | # Paths 17 | CAMAN=$(cd "`dirname "${BASH_SOURCE[0]}"`" && pwd)/`basename "${BASH_SOURCE[0]}"` 18 | CAMANDIR=$(pwd) 19 | CADIR_NAME="ca" 20 | CADIR="$CAMANDIR/$CADIR_NAME" 21 | STOREDIR_NAME="store" 22 | STOREDIR="$CAMANDIR/$STOREDIR_NAME" 23 | HOSTDIR="$STOREDIR/$HOST" 24 | DATEDIR=$(date +"%Y-%m-%d") 25 | 26 | # Check cert authority directory exists 27 | if [[ ! -d "$CADIR" ]]; then 28 | echo "Certificate authority directory does not exist" 29 | exit 1 30 | fi 31 | 32 | 33 | 34 | # 35 | # Functions 36 | # 37 | 38 | function call_openssl { 39 | # Wrapper for openssl which caches the CA password and makes it available 40 | # on openssl's password source "fd:0" 41 | if [[ -z ${CAPASS+x} ]] ; then 42 | # CAPASS is not set - get password for this certificate authority 43 | read -sp "Enter CA password for $CAMANDIR: " CAPASS 44 | echo 45 | fi 46 | 47 | # Run the command or exit on failure 48 | if $DEBUG ; then 49 | echo "caman> openssl $@" 50 | fi 51 | printf '%s\n' "$CAPASS" | { 52 | openssl "$@" || exit 1 53 | } || exit 1 54 | } 55 | function call_openssl_ca { 56 | # Run openssl ca in non-interactive mode and pass the CA password 57 | call_openssl ca -batch -passin fd:0 "$@" 58 | } 59 | function call_openssl_req { 60 | # Run openssl req in non-interactive mode and pass the CA password 61 | call_openssl req -batch -passin fd:0 "$@" 62 | } 63 | function call_openssl_genrsa { 64 | # Run openssl genrsa and pass the CA password 65 | call_openssl genrsa -passout fd:0 "$@" 66 | } 67 | 68 | function get_days { 69 | # Find the number of days in the specified config file 70 | local CONFIG=$1 71 | perl -nle 'print $1 if /^default_days\s+=\s+(\d+)\s*$/' "$CONFIG" 72 | } 73 | 74 | function generate_crl { 75 | # Generate the CRL 76 | # OpenSSL arguments: 77 | # ca Command for CA management 78 | # -gencrl Generate a CRL 79 | # -out "..." Path to CRL 80 | # -config "..." Path to CA config 81 | echo "Generating CRL..." 82 | call_openssl_ca -gencrl -out "$CADIR/ca.crl.pem" \ 83 | -config "$CADIR/caconfig.cnf" 84 | } 85 | 86 | function split_ca { 87 | # If the specified hostname starts "ca:", it is a CA path; return the path 88 | # Otherwise returns empty 89 | local HOSTNAME=$1 90 | if [[ $HOSTNAME == ca:* ]] ; then 91 | echo ${HOSTNAME:3} 92 | fi 93 | } 94 | 95 | 96 | # 97 | # Commands 98 | # 99 | 100 | function command_init { 101 | # 102 | # Initialise a new certificate authority 103 | # 104 | 105 | # Check for existing CA certificate 106 | if [[ -f "$CADIR/ca.crt.pem" ]] ; then 107 | echo "Certificate authority already exists" 108 | exit 1 109 | fi 110 | 111 | # Check config exists 112 | if [[ ! -f "$CADIR/caconfig.cnf" ]] ; then 113 | echo "Could not find caconfig.cnf" 114 | exit 1 115 | fi 116 | 117 | # Argument is not a host, it's the path to the root CA 118 | # If given a root CA, check it exists and is a valid caman installation 119 | ROOT_CA=$(split_ca $HOST) 120 | if [[ $ROOT_CA ]] ; then 121 | if [[ ! -d "$ROOT_CA" ]] ; then 122 | echo "Root CA path does not exist" 123 | exit 1 124 | fi 125 | if [[ ! -f "$ROOT_CA/$CADIR_NAME/caconfig.cnf" ]] ; then 126 | echo "Root CA has not been configured: $ROOT_CA/$CADIR_NAME/caconfig.cnf" 127 | exit 1 128 | fi 129 | if [[ ! -f "$ROOT_CA/$CADIR_NAME/ca.crt.pem" ]] ; then 130 | echo "Root CA has not been initialised" 131 | exit 1 132 | fi 133 | else 134 | # Catch if the host doesn't start ca: 135 | if [[ $HOST ]] ; then 136 | echo "Argument for init must be a \"ca:\" path" 137 | exit 1 138 | fi 139 | fi 140 | 141 | # Set up supporting files and dirs 142 | mkdir "$CADIR/newcerts" "$STOREDIR" || exit 1 143 | touch "$CADIR/index.txt" 144 | echo '01' > "$CADIR/serial" 145 | 146 | # Get CA age 147 | DAYS=$(get_days "$CADIR/caconfig.cnf") 148 | if [[ -z $DAYS ]] ; then 149 | echo "Cannot find default_days in caconfig.cnf" 150 | exit 1 151 | fi 152 | 153 | # Create CA key 154 | # OpenSSL arguments: 155 | # genrsa Command for generating an RSA key 156 | # -aes256 Encrypt it with AES 256 157 | # -out "..." Path to key 158 | # 4096 Number of bits 159 | echo "Generating CA private key..." 160 | call_openssl_genrsa -aes256 -out "$CADIR/ca.key.pem" 4096 161 | chmod 400 "$CADIR/ca.key.pem" 162 | 163 | # Different actions depending whether this is a root or intermediate CA 164 | if [[ $ROOT_CA ]] ; then 165 | # Root CA specified 166 | # 167 | # Create CSR 168 | # OpenSSL arguments: 169 | # req Command for CSR management 170 | # -new Generate a new CSR 171 | # -sha256 Force OpenSSL to use SHA256 172 | # -key "..." Path to CA key 173 | # -out "..." Path to save new CSR 174 | # -config "..." Path to CA config 175 | # No need for CA password here 176 | echo "Creating CSR for intermediate CA..." 177 | call_openssl_req -new -sha256 \ 178 | -key "$CADIR/ca.key.pem" \ 179 | -out "$CADIR/ca.csr" \ 180 | -config "$CADIR/caconfig.cnf" -batch 181 | 182 | # Sign it using the root CA 183 | echo "Signing the intermediate CA certificate..." 184 | (cd "$ROOT_CA" && "$CAMAN" sign "ca:$CAMANDIR") || exit 1 185 | 186 | # Create the chain file 187 | # Should be in reverse order (host first, root last) 188 | # We're instructing to distribute the root CA cert, so no need to put 189 | # that in the chain 190 | if [[ -f "$ROOT_CA/$CADIR_NAME/ca-chain.crt.pem" ]] ; then 191 | # Root CA is actually another intermediate; add us to their chain 192 | cat "$CADIR/ca.crt.pem" "$ROOT_CA/$CADIR_NAME/ca-chain.crt.pem" \ 193 | > "$CADIR/ca-chain.crt.pem" \ 194 | || exit 1 195 | else 196 | # Root CA is the root CA; just use our cert for the chain 197 | cp "$CADIR/ca.crt.pem" "$CADIR/ca-chain.crt.pem" || exit 1 198 | fi 199 | 200 | else 201 | # No root CA specified 202 | # 203 | # Create CA certificate 204 | # OpenSSL arguments: 205 | # req Command for CSR management 206 | # -x509 Output a self-signed certificate instead of CSR 207 | # -new Generate a new certificate 208 | # -out Path to signed certificate 209 | # -days $DAYS Number of days this certificate will be valid 210 | # -config "..." Path to CA config 211 | echo "Signing CA public certificate..." 212 | call_openssl_req -x509 -new -key "$CADIR/ca.key.pem" -days $DAYS \ 213 | -out "$CADIR/ca.crt.pem" -config "$CADIR/caconfig.cnf" 214 | fi 215 | 216 | # Create the CRL 217 | echo '01' > "$CADIR/crlnumber" 218 | generate_crl 219 | 220 | echo "Certificate authority created" 221 | } 222 | 223 | function command_new { 224 | # 225 | # Add a new host 226 | # 227 | 228 | # Create new config 229 | mkdir "$HOSTDIR" || exit 1 230 | cp "$CADIR/host.cnf" "$HOSTDIR/config.cnf" 231 | 232 | # Set hostname 233 | perl -pi -e "s/<>/$HOST/g" "$HOSTDIR/config.cnf" 234 | 235 | # Add alt hostnames 236 | CAMAN_ALT_HOSTNAMES="${*:2}" 237 | PERL_ALT_HOSTNAMES=' 238 | if (/<>/) { 239 | my @hosts = split(/ /, "'$CAMAN_ALT_HOSTNAMES'"); 240 | foreach $h (@hosts) { 241 | $h = ($h =~ /^\d+(\.\d+){3}+$/ ? "IP:" : "DNS:") . $h; 242 | } 243 | print "subjectAltName = " . join(", ", @hosts) . "\n" if @hosts; 244 | } else { 245 | print; 246 | } 247 | ' 248 | perl -ni -e "$PERL_ALT_HOSTNAMES" "$HOSTDIR/config.cnf" 249 | 250 | echo "New host added" 251 | } 252 | 253 | function command_sign_host { 254 | # 255 | # Create a new CSR, private key and signed certificate 256 | # 257 | 258 | # Get certificate age 259 | DAYS=$(get_days "$HOSTDIR/config.cnf") 260 | if [[ -z $DAYS ]] ; then 261 | echo "Cannot find default_days in $HOSTDIR/config.cnf" 262 | exit 1 263 | fi 264 | 265 | # Determine where the cert is going 266 | CERTDIR="$HOSTDIR/$DATEDIR" 267 | INSTANCE=1 268 | while [[ -e "$CERTDIR-$INSTANCE" ]] ; do 269 | let INSTANCE++ 270 | done 271 | CERTDIR="$CERTDIR-$INSTANCE" 272 | 273 | # Generate directory for this cert 274 | mkdir "$CERTDIR" || exit 1 275 | 276 | # Create a new CSR and private key 277 | # OpenSSL arguments: 278 | # req Command for CSR management 279 | # -new Generate a new CSR 280 | # -sha256 Force OpenSSL to use SHA256 281 | # -newkey rsa:2048 Generate a new 2048 bit RSA private key 282 | # -nodes Do not encrypt the private key 283 | # -keyout "..." Path to save new key 284 | # -out "..." Path to save new CSR 285 | # -config "..." Path to host config created by ``new`` 286 | # No need for CA password here 287 | echo "Creating CSR and private key..." 288 | call_openssl req -sha256 \ 289 | -newkey rsa:2048 -nodes -keyout "$CERTDIR/$HOST.key.pem" \ 290 | -new -out "$CERTDIR/$HOST.csr" \ 291 | -config "$HOSTDIR/config.cnf" -batch 292 | 293 | # Sign the request 294 | # OpenSSL arguments: 295 | # ca Command for CA management 296 | # -in Path to CSR 297 | # -out Path to signed certificate 298 | # -days $DAYS Number of days this certificate will be valid 299 | # -notext Suppress the text summary at the start of the file 300 | # -config "..." Path to CA config created by ``init`` 301 | echo "Signing the certificate..." 302 | call_openssl_ca \ 303 | -in "$CERTDIR/$HOST.csr" -out "$CERTDIR/$HOST.crt.pem" \ 304 | -days $DAYS -notext \ 305 | -config "$CADIR/caconfig.cnf" 306 | 307 | # Concat the key and certificate 308 | cat "$CERTDIR/$HOST.key.pem" "$CERTDIR/$HOST.crt.pem" \ 309 | > "$CERTDIR/$HOST.keycrt.pem" \ 310 | || exit 1 311 | 312 | # Create chained certs if this is an intermediate CA 313 | if [[ -f "$CADIR/ca-chain.crt.pem" ]] ; then 314 | cat "$CERTDIR/$HOST.crt.pem" "$CADIR/ca-chain.crt.pem" \ 315 | > "$CERTDIR/$HOST.chained.crt.pem" \ 316 | || exit 1 317 | 318 | cat "$CERTDIR/$HOST.key.pem" "$CERTDIR/$HOST.chained.crt.pem" \ 319 | > "$CERTDIR/$HOST.chained.keycrt.pem" \ 320 | || exit 1 321 | fi 322 | echo "Certificate generated" 323 | } 324 | 325 | function command_sign_ca { 326 | # 327 | # Sign an intermediate CA with this certificate 328 | # 329 | 330 | # Check it exists and is a valid caman installation ready to be signed 331 | if [[ ! -d "$INTERMEDIATE_CA" ]] ; then 332 | echo "Intermediate CA path does not exist" 333 | exit 1 334 | fi 335 | if [[ ! -f "$INTERMEDIATE_CA/$CADIR_NAME/caconfig.cnf" ]] ; then 336 | echo "Intermediate CA has not been configured" 337 | exit 1 338 | fi 339 | if [[ -f "$INTERMEDIATE_CA/$CADIR_NAME/ca.crt.pem" ]] ; then 340 | echo "Intermediate CA already exists" 341 | exit 1 342 | fi 343 | if [[ ! -f "$INTERMEDIATE_CA/$CADIR_NAME/ca.csr" ]] ; then 344 | echo "Intermediate CA does not have a CSR" 345 | exit 1 346 | fi 347 | 348 | # Get certificate age from intermediate CA config 349 | DAYS=$(get_days "$INTERMEDIATE_CA/$CADIR_NAME/caconfig.cnf") 350 | if [[ -z $DAYS ]] ; then 351 | echo "Cannot find default_days in intermediate CA caconfig.cnf" 352 | exit 1 353 | fi 354 | 355 | # Sign the request 356 | # OpenSSL arguments: 357 | # ca Command for CA management 358 | # -in Path to CSR 359 | # -out Path to signed certificate 360 | # -days $DAYS Number of days this certificate will be valid 361 | # -notext Suppress the text summary at the start of the file 362 | # -extensions v3_ca Use the v3_ca settings to sign this, not usr_cert 363 | # -config "..." Path to root CA config 364 | call_openssl_ca \ 365 | -in "$INTERMEDIATE_CA/$CADIR_NAME/ca.csr" \ 366 | -out "$INTERMEDIATE_CA/$CADIR_NAME/ca.crt.pem" \ 367 | -days $DAYS -notext \ 368 | -extensions v3_ca \ 369 | -config "$CADIR/caconfig.cnf" 370 | 371 | echo "Intermediate CA certificate generated" 372 | } 373 | 374 | function command_revoke { 375 | # 376 | # Revoke a host certificate 377 | # 378 | local INTERMEDIATE_CA=$(split_ca $HOST) 379 | local REVOKE_CN 380 | 381 | if [[ $INTERMEDIATE_CA ]] ; then 382 | # Check intermediate CA exists 383 | if [[ ! -d "$INTERMEDIATE_CA" ]] ; then 384 | echo "Intermediate CA path does not exist" 385 | exit 1 386 | fi 387 | if [[ ! -f "$INTERMEDIATE_CA/$CADIR_NAME/caconfig.cnf" ]] ; then 388 | echo "Intermediate CA has not been configured" 389 | exit 1 390 | fi 391 | 392 | # Get the common name frmo the intermediate CA's config to use as the 393 | # hostname 394 | REVOKE_CN=$( \ 395 | perl -0ne 'print $1 if /^\[ req_distinguished_name \]\n.+\ncommonName\s+=\s+(.+?)$/sm' \ 396 | "$INTERMEDIATE_CA/$CADIR_NAME/caconfig.cnf" 397 | ) 398 | else 399 | REVOKE_CN="$HOST" 400 | fi 401 | 402 | # Read index of valid cert for this host 403 | INDEX=$( \ 404 | grep -F "/CN=$REVOKE_CN/" $CADIR/index.txt \ 405 | | perl -ne 'print $1 if /^V\t+[0-9Z]+\t\t([0-9A-F]+)\t.*$/' \ 406 | ) 407 | if [[ -z $INDEX ]] ; then 408 | if [[ $INTERMEDIATE_CA ]] ; then 409 | echo "Cannot find valid certificate for specified intermediate CA" 410 | else 411 | echo "Cannot find valid certificate for $REVOKE_CN" 412 | fi 413 | exit 1 414 | fi 415 | echo "Revoking certificate $INDEX..." 416 | 417 | # Revoke the certificate 418 | # OpenSSL arguments: 419 | # ca Command for CA management 420 | # -revoke "..." Certificate to revoke 421 | # -config "..." Path to CA config created by ``init`` 422 | call_openssl_ca \ 423 | -revoke "$CADIR/newcerts/$INDEX.pem" \ 424 | -config "$CADIR/caconfig.cnf" 425 | 426 | # Update the CRL 427 | generate_crl 428 | 429 | if [[ $INTERMEDIATE_CA ]] ; then 430 | echo "Intermediate CA revoked" 431 | else 432 | echo "Certificate revoked" 433 | fi 434 | } 435 | 436 | 437 | # 438 | # Process command 439 | # 440 | 441 | case "$CMD" in 442 | init) 443 | # Initialise a new certificate authority 444 | command_init 445 | 446 | ;; 447 | 448 | new) 449 | # Add a new host, ready to sign 450 | if [[ $(split_ca $HOST) ]] ; then 451 | echo "Cannot add new \"ca:\" path" 452 | exit 1 453 | fi 454 | 455 | command_new "$@" 456 | 457 | ;; 458 | 459 | sign) 460 | # Create a new CSR, private key and signed certificate 461 | # Internal use: if host starts with "ca:", sign the intermediate CA at 462 | # the specified path; called by init 463 | INTERMEDIATE_CA=$(split_ca $HOST) 464 | if [[ $INTERMEDIATE_CA ]] ; then 465 | command_sign_ca 466 | else 467 | command_sign_host 468 | fi 469 | 470 | ;; 471 | 472 | revoke) 473 | # Revoke a host certificate or intermediate CA 474 | command_revoke 475 | 476 | ;; 477 | 478 | renew) 479 | # Renew a host certificate by revoking then signing 480 | if [[ $(split_ca $HOST) ]] ; then 481 | echo "Cannot renew a \"ca:\" path" 482 | exit 1 483 | fi 484 | 485 | command_revoke 486 | command_sign_host 487 | 488 | ;; 489 | 490 | *) 491 | cat <] 495 | caman new [ [ [...]]] 496 | caman sign 497 | caman renew 498 | caman revoke 499 | caman revoke ca: 500 | EOF 501 | 502 | ;; 503 | 504 | esac 505 | 506 | exit 0 507 | --------------------------------------------------------------------------------