├── .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 |
--------------------------------------------------------------------------------