├── .editorconfig ├── .github └── workflows │ ├── linting.yml │ ├── test-linux.yml │ ├── test-macos.yml │ └── test-windows.yml ├── .gitignore ├── .tests ├── .lib.sh └── test.sh ├── LICENSE.md ├── Makefile ├── README.md ├── bin ├── ca-gen └── cert-gen └── img ├── 01-settings.png ├── 02-advanced-settings.png ├── 03-authorities.png ├── 04-import.png ├── 05-set-trust.png └── address-bar.png /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # ------------------------------------------------------------------------------------------------- 4 | # Default configuration 5 | # ------------------------------------------------------------------------------------------------- 6 | # top-most EditorConfig file 7 | root = true 8 | 9 | # Default for all files 10 | [*] 11 | charset = utf-8 12 | end_of_line = lf 13 | insert_final_newline = true 14 | trim_trailing_whitespace = true 15 | 16 | 17 | # ------------------------------------------------------------------------------------------------- 18 | # Scripts 19 | # ------------------------------------------------------------------------------------------------- 20 | 21 | [*.sh] 22 | indent_style = tab 23 | indent_size = 4 24 | 25 | [bin/ca-gen] 26 | indent_style = tab 27 | indent_size = 4 28 | 29 | [bin/cert-gen] 30 | indent_style = tab 31 | indent_size = 4 32 | 33 | 34 | # ------------------------------------------------------------------------------------------------- 35 | # Git Repository 36 | # ------------------------------------------------------------------------------------------------- 37 | [.travis.yml] 38 | indent_style = space 39 | indent_size = 2 40 | 41 | [Makefile] 42 | indent_style = tab 43 | indent_size = 4 44 | 45 | 46 | # ------------------------------------------------------------------------------------------------- 47 | # Documentation 48 | # ------------------------------------------------------------------------------------------------- 49 | [*.md] 50 | indent_style = space 51 | trim_trailing_whitespace = false 52 | indent_size = 2 53 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ### 4 | ### Lints all generic and json files in the whole git repository 5 | ### 6 | 7 | name: linting 8 | on: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: False 20 | matrix: 21 | target: 22 | - Linting 23 | name: "[ ${{ matrix.target }} ]" 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@master 27 | 28 | - name: Lint files 29 | run: | 30 | make lint 31 | -------------------------------------------------------------------------------- /.github/workflows/test-linux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: test-linux 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: False 15 | 16 | name: "[test] [linux]" 17 | steps: 18 | # ------------------------------------------------------------ 19 | # Setup 20 | # ------------------------------------------------------------ 21 | - name: Checkout repository 22 | uses: actions/checkout@v2 23 | 24 | # ------------------------------------------------------------ 25 | # Tests: Behaviour 26 | # ------------------------------------------------------------ 27 | - name: test 28 | shell: bash 29 | run: | 30 | make test 31 | -------------------------------------------------------------------------------- /.github/workflows/test-macos.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: test-macos 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | 10 | jobs: 11 | test: 12 | runs-on: macos-latest 13 | strategy: 14 | fail-fast: False 15 | 16 | name: "[test] [macos]" 17 | steps: 18 | # ------------------------------------------------------------ 19 | # Setup 20 | # ------------------------------------------------------------ 21 | - name: Checkout repository 22 | uses: actions/checkout@v2 23 | 24 | # ------------------------------------------------------------ 25 | # Tests: Behaviour 26 | # ------------------------------------------------------------ 27 | - name: test 28 | shell: bash 29 | run: | 30 | make test 31 | -------------------------------------------------------------------------------- /.github/workflows/test-windows.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: test-windows 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | 10 | jobs: 11 | test: 12 | runs-on: windows-latest 13 | strategy: 14 | fail-fast: False 15 | 16 | name: "[test] [windows]" 17 | steps: 18 | # ------------------------------------------------------------ 19 | # Setup 20 | # ------------------------------------------------------------ 21 | - name: Checkout repository 22 | uses: actions/checkout@v2 23 | 24 | # ------------------------------------------------------------ 25 | # Tests: Behaviour 26 | # ------------------------------------------------------------ 27 | - name: test 28 | shell: bash 29 | run: | 30 | make test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tests/tmp 2 | -------------------------------------------------------------------------------- /.tests/.lib.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -u 5 | set -o pipefail 6 | 7 | 8 | # ------------------------------------------------------------------------------------------------- 9 | # Functions 10 | # ------------------------------------------------------------------------------------------------- 11 | 12 | ### 13 | ### Run command 14 | ### 15 | run() { 16 | local cmd="${1}" 17 | local retries=1 18 | local workdir= 19 | local verbose=1 20 | 21 | # retry? 22 | if [ "${#}" -gt "1" ]; then 23 | retries="${2}" 24 | fi 25 | # change directory? 26 | if [ "${#}" -gt "2" ]; then 27 | workdir="${3}" 28 | fi 29 | 30 | # be verbose? 31 | if [ "${#}" -gt "3" ]; then 32 | verbose="${4}" 33 | fi 34 | 35 | local red="\\033[0;31m" 36 | local green="\\033[0;32m" 37 | local yellow="\\033[0;33m" 38 | local reset="\\033[0m" 39 | 40 | # Set command 41 | if [ -n "${workdir}" ]; then 42 | cmd="cd ${workdir} && ${cmd}" 43 | else 44 | cmd="${cmd}" 45 | fi 46 | # Print command? 47 | if [ "${verbose}" -eq "1" ]; then 48 | >&2 printf "${yellow}%s \$${reset} %s\\n" "$(whoami)" "${cmd}" 49 | fi 50 | 51 | for ((i=0; i&2 printf "${green}[%s: in %s rounds]${reset}\\n" "OK" "$((i+1))" 55 | fi 56 | return 0 57 | fi 58 | sleep 1 59 | done 60 | if [ "${verbose}" -eq "1" ]; then 61 | >&2 printf "${red}[%s: in %s rounds]${reset}\\n" "FAIL" "${retries}" 62 | fi 63 | return 1 64 | } 65 | -------------------------------------------------------------------------------- /.tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -u 5 | set -o pipefail 6 | 7 | TEST_PATH="$( cd "$(dirname "$0")" && pwd -P )" 8 | ROOT_PATH="$( cd "${TEST_PATH}/.." && pwd -P )" 9 | # shellcheck disable=SC1090 10 | . "${TEST_PATH}/.lib.sh" 11 | 12 | 13 | # ------------------------------------------------------------------------------------------------- 14 | # Pre-check 15 | # ------------------------------------------------------------------------------------------------- 16 | 17 | ### 18 | ### Clean-up for new round 19 | ### 20 | rm -rf "${TEST_PATH}/tmp" 21 | mkdir "${TEST_PATH}/tmp" 22 | 23 | 24 | ### 25 | ### Do we test in Docker container? 26 | ### 27 | USE_DOCKER=0 28 | if [ "${#}" = "1" ]; then 29 | USE_DOCKER=1 30 | fi 31 | 32 | 33 | # ------------------------------------------------------------------------------------------------- 34 | # Variables 35 | # ------------------------------------------------------------------------------------------------- 36 | 37 | 38 | ### 39 | ### General 40 | ### 41 | DOCKER_NAME="devilbox_openssl_server" 42 | DOCKER_IMAGE="debian:buster-slim" 43 | OPENSSL_PORT=4433 44 | 45 | 46 | ### 47 | ### Certificate Authority 48 | ### 49 | CA_NAME="devilbox.org" 50 | CA_KEYSIZE=2048 51 | CA_VALIDITY=3650 52 | 53 | CA_KEY_NAME="ca.key" 54 | CA_KEY_PATH="${TEST_PATH}/tmp/${CA_KEY_NAME}" 55 | CA_CRT_NAME="ca.crt" 56 | CA_CRT_PATH="${TEST_PATH}/tmp/${CA_CRT_NAME}" 57 | 58 | 59 | ### 60 | ### Certificate 61 | ### 62 | CERT_NAME="localhost" 63 | CERT_KEYSIZE=2048 64 | CERT_VALIDITY=400 65 | 66 | CERT_KEY_NAME="cert.key" 67 | CERT_KEY_PATH="${TEST_PATH}/tmp/${CERT_KEY_NAME}" 68 | CERT_CSR_NAME="cert.csr" 69 | CERT_CSR_PATH="${TEST_PATH}/tmp/${CERT_CSR_NAME}" 70 | CERT_CRT_NAME="cert.crt" 71 | CERT_CRT_PATH="${TEST_PATH}/tmp/${CERT_CRT_NAME}" 72 | 73 | 74 | # ------------------------------------------------------------------------------------------------- 75 | # ENTRYPOINT 76 | # ------------------------------------------------------------------------------------------------- 77 | 78 | echo 79 | echo "# -------------------------------------------------------------------------------------------------" 80 | echo "# Creating Certificate Authority" 81 | echo "# -------------------------------------------------------------------------------------------------" 82 | echo 83 | run "${ROOT_PATH}/bin/ca-gen \ 84 | -v \ 85 | -k ${CA_KEYSIZE} \ 86 | -d ${CA_VALIDITY} \ 87 | -n ${CA_NAME} \ 88 | -c DE \ 89 | -s Berlin \ 90 | -l Berlin \ 91 | -o DevilboxOrg \ 92 | -u DevilboxUnit \ 93 | -e ca@${CA_NAME} \ 94 | ${CA_KEY_PATH} \ 95 | ${CA_CRT_PATH}" 96 | 97 | # Verify CRT 98 | echo 99 | echo "[INFO] Verify CRT" 100 | run "openssl x509 -noout -in ${CA_CRT_PATH}" 101 | echo 102 | 103 | # Verify KEY 104 | echo 105 | echo "[INFO] Verify KEY" 106 | run "openssl rsa -check -noout -in ${CA_KEY_PATH}" 107 | 108 | # Check that KEY matches CRT 109 | echo 110 | echo "[INFO] Verify KEY matches CRT" 111 | run "diff -y \ 112 | <(openssl x509 -noout -modulus -in ${CA_CRT_PATH} | openssl md5) \ 113 | <(openssl rsa -noout -modulus -in ${CA_KEY_PATH} | openssl md5)" 114 | 115 | 116 | echo 117 | echo "# -------------------------------------------------------------------------------------------------" 118 | echo "# Creating Certificate" 119 | echo "# -------------------------------------------------------------------------------------------------" 120 | echo 121 | 122 | run "${ROOT_PATH}/bin/cert-gen \ 123 | -v \ 124 | -k ${CERT_KEYSIZE} \ 125 | -d ${CERT_VALIDITY} \ 126 | -n ${CERT_NAME} \ 127 | -c DE \ 128 | -s Berlin \ 129 | -l Berlin \ 130 | -o SomeOrg \ 131 | -u SomeUnit \ 132 | -e cert@${CERT_NAME} \ 133 | -a '*.${CERT_NAME},www.${CERT_NAME}' \ 134 | ${CA_KEY_PATH} \ 135 | ${CA_CRT_PATH} \ 136 | ${CERT_KEY_PATH} \ 137 | ${CERT_CSR_PATH} \ 138 | ${CERT_CRT_PATH}" 139 | 140 | # Verify CRT 141 | echo 142 | echo "[INFO] Verify CRT" 143 | run "openssl x509 -noout -in ${CERT_CRT_PATH}" 144 | 145 | # Verify KEY 146 | echo 147 | echo "[INFO] Verify KEY" 148 | run "openssl rsa -check -noout -in ${CERT_KEY_PATH}" 149 | 150 | # Verify CSR 151 | echo 152 | echo "[INFO] Verify CSR" 153 | run "openssl req -noout -verify -in ${CERT_CSR_PATH}" 154 | 155 | # Check that KEY matches CRT 156 | echo 157 | echo "[INFO] Verify KEY matches CRT" 158 | run "diff -y \ 159 | <(openssl x509 -noout -modulus -in ${CERT_CRT_PATH} | openssl md5) \ 160 | <(openssl rsa -noout -modulus -in ${CERT_KEY_PATH} | openssl md5)" 161 | 162 | # Check that KEY matches CSR 163 | echo 164 | echo "[INFO] Verify KEY matches CSR" 165 | run "diff -y \ 166 | <(openssl x509 -noout -modulus -in ${CERT_CRT_PATH} | openssl md5) \ 167 | <(openssl req -noout -modulus -in ${CERT_CSR_PATH} | openssl md5)" 168 | 169 | # Check certificate is issued by CA 170 | echo 171 | echo "[INFO] Verify certificate is issued by CA" 172 | run "openssl verify -verbose -CAfile ${CA_CRT_PATH} ${CERT_CRT_PATH}" 173 | 174 | 175 | 176 | ERROR=0 177 | if [ "${USE_DOCKER}" = "1" ]; then 178 | echo 179 | echo "# -------------------------------------------------------------------------------------------------" 180 | echo "# Testing browser certificate (inside Docker container)" 181 | echo "# -------------------------------------------------------------------------------------------------" 182 | echo 183 | 184 | echo "[INFO] Pulling Docker Image" 185 | run "docker pull ${DOCKER_IMAGE}" 186 | 187 | echo 188 | echo "[INFO] Ensuring Docker Image is not running" 189 | run "docker rm -f ${DOCKER_NAME} >/dev/null 2>&1 || true" 190 | 191 | echo 192 | echo "[INFO] Starting Docker Image with OpenSSL server" 193 | run "docker run -d --rm --name ${DOCKER_NAME} -w /data -p '${OPENSSL_PORT}:${OPENSSL_PORT}' -v ${TEST_PATH}/tmp:/data ${DOCKER_IMAGE} sh -c ' 194 | apt-get update -qq && 195 | apt-get install -qq -y curl openssl > /dev/null && 196 | set -x && 197 | openssl s_server -key ${CERT_KEY_NAME} -cert ${CERT_CRT_NAME} -CAfile ${CA_CRT_NAME} -accept ${OPENSSL_PORT} -www' >/dev/null" 198 | 199 | echo 200 | echo "[INFO] Waiting for Docker container to start" 201 | run "sleep 5" 202 | 203 | echo 204 | echo "[INFO] Testing valid https connection with curl" 205 | if ! run "docker exec -w /data ${DOCKER_NAME} curl -sS -o /dev/null -w '%{http_code}' --cacert ${CA_CRT_NAME} 'https://localhost:${OPENSSL_PORT}' | grep 200" "60"; then 206 | ERROR=1 207 | fi 208 | 209 | echo 210 | echo "[INFO] Testing valid https connection with openssl client" 211 | if ! run "echo | openssl s_client -verify 8 -CAfile ${CA_CRT_PATH} >/dev/null" "60"; then 212 | ERROR=1 213 | fi 214 | 215 | echo 216 | echo "[INFO] Validating openssl certificate with openssl client" 217 | if ! run "echo | openssl s_client -verify 8 -CAfile ${CA_CRT_PATH} | grep 'Verify return code: 0 (ok)'" "60"; then 218 | ERROR=1 219 | fi 220 | 221 | echo 222 | echo "[INFO] Show info and clean up" 223 | run "docker logs ${DOCKER_NAME} || true" 224 | run "docker rm -f ${DOCKER_NAME} >/dev/null 2>&1 || true" 225 | 226 | else 227 | echo 228 | echo "# -------------------------------------------------------------------------------------------------" 229 | echo "# Testing browser certificate (on host system)" 230 | echo "# -------------------------------------------------------------------------------------------------" 231 | echo 232 | 233 | echo 234 | echo "[INFO] Ensuring OpenSSL server is not running" 235 | run "ps aux | grep openssl | grep s_server | awk '{print \$2}' | xargs kill 2>/dev/null || true" 236 | 237 | echo 238 | echo "[INFO] Starting OpenSSL server" 239 | run "openssl s_server -key ${CERT_KEY_PATH} -cert ${CERT_CRT_PATH} -CAfile ${CA_CRT_PATH} -accept ${OPENSSL_PORT} -www >/dev/null &" 240 | 241 | echo 242 | echo "[INFO] Waiting for OpensSL server to start" 243 | run "sleep 5" 244 | 245 | echo 246 | echo "[INFO] Testing valid https connection with curl" 247 | if ! run "curl -sS -o /dev/null -w '%{http_code}' --cacert ${CA_CRT_PATH} 'https://localhost:${OPENSSL_PORT}' | grep 200" "60"; then 248 | ERROR=1 249 | fi 250 | 251 | echo 252 | echo "[INFO] Testing valid https connection with openssl client" 253 | if ! run "echo | openssl s_client -verify 8 -CAfile ${CA_CRT_PATH} >/dev/null" "60"; then 254 | ERROR=1 255 | fi 256 | 257 | echo 258 | echo "[INFO] Validating openssl certificate with openssl client" 259 | if ! run "echo | openssl s_client -verify 8 -CAfile ${CA_CRT_PATH} | grep 'Verify return code: 0 (ok)'" "60"; then 260 | ERROR=1 261 | fi 262 | 263 | echo 264 | echo "[INFO] Clean up" 265 | run "ps aux | grep openssl | grep s_server | awk '{print \$2}' | xargs kill 2>/dev/null || true" 266 | 267 | fi 268 | 269 | echo 270 | echo "[INFO] Return success or failure" 271 | exit "${ERROR}" 272 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 cytopia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifneq (,) 2 | .error This Makefile requires GNU Make. 3 | endif 4 | 5 | # ------------------------------------------------------------------------------------------------- 6 | # Default configuration 7 | # ------------------------------------------------------------------------------------------------- 8 | .PHONY: help lint test install 9 | 10 | SHELL = /bin/sh 11 | 12 | MKDIR_P = mkdir -p 13 | 14 | 15 | # ------------------------------------------------------------------------------------------------- 16 | # Default target 17 | # ------------------------------------------------------------------------------------------------- 18 | help: 19 | @echo Options 20 | @echo " make install" 21 | @echo " Install everthing (requires sudo or root)" 22 | @echo 23 | @echo " make lint" 24 | @echo " Shellcheck lint ca-gen and cert-gen" 25 | @echo 26 | @echo " make test" 27 | @echo " Verify that CA and certificates can be generated successfully" 28 | @echo 29 | @echo " make help" 30 | @echo " Show this help screen" 31 | 32 | 33 | # ------------------------------------------------------------------------------------------------- 34 | # Test targets 35 | # ------------------------------------------------------------------------------------------------- 36 | lint: 37 | docker run --rm -v "$(PWD)/bin:/mnt" koalaman/shellcheck:stable ca-gen 38 | docker run --rm -v "$(PWD)/bin:/mnt" koalaman/shellcheck:stable cert-gen 39 | 40 | test: 41 | .tests/test.sh $(ARGS) 42 | 43 | 44 | # ------------------------------------------------------------------------------------------------- 45 | # Install targets 46 | # ------------------------------------------------------------------------------------------------- 47 | install: 48 | 49 | @echo "Installing files" 50 | @echo "" 51 | 52 | @# Create directories 53 | ${MKDIR_P} /usr/local/bin 54 | 55 | @# Install binary 56 | install -m 0755 bin/ca-gen /usr/local/bin/ca-gen 57 | install -m 0755 bin/cert-gen /usr/local/bin/cert-gen 58 | 59 | 60 | @echo "Installation complete:" 61 | @echo "----------------------------------------------------------------------" 62 | @echo "" 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cert-gen 2 | 3 | ![Tag](https://img.shields.io/github/tag/devilbox/cert-gen.svg) 4 | [![Discord](https://img.shields.io/discord/1051541389256704091?color=8c9eff&label=Discord&logo=discord)](https://discord.gg/2wP3V6kBj4) 5 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) 6 | 7 | [![linting](https://github.com/devilbox/cert-gen/workflows/linting/badge.svg)](https://github.com/devilbox/cert-gen/actions/workflows/linting.yml) 8 | [![test-linux](https://github.com/devilbox/cert-gen/workflows/test-linux/badge.svg)](https://github.com/devilbox/cert-gen/actions/workflows/test-linux.yml) 9 | [![test-macos](https://github.com/devilbox/cert-gen/workflows/test-macos/badge.svg)](https://github.com/devilbox/cert-gen/actions/workflows/test-macos.yml) 10 | [![test-windows](https://github.com/devilbox/cert-gen/workflows/test-windows/badge.svg)](https://github.com/devilbox/cert-gen/actions/workflows/test-windows.yml) 11 | 12 | Easily create your own CA and self-signed certificates. 13 | 14 | The generated CA can be imported into Chrome, Firefox or Internet Explorer for local development. 15 | All subsequent created certificates will then be valid SSL certificates to each browser. 16 | 17 | 18 | 19 | **Table of Contents** 20 | 21 | 1. [Available Tools](#available-tools) 22 | 1. [Tools](#tools) 23 | 2. [Requirements](#requirements) 24 | 3. [Installation](#installation) 25 | 2. [Create Certificate Authoriy](#create-certificate-authority) 26 | 1. [Usage: ca-gen](#usage-ca-gen) 27 | 2. [Execute: ca-gen](#execute-ca-gen) 28 | 3. [Example: CA](#example-ca) 29 | 3. [Create SSL Certificate](#create-ssl-certificate) 30 | 1. [Usage: cert-gen](#usage-cert-gen) 31 | 2. [Execute: cert-gen](#execute-cert-gen) 32 | 3. [Example: SSL certificate](#example-ssl-certificate) 33 | 4. [Import CA into Chrome](#import-ca-into-chrome) 34 | 5. [License](#license) 35 | 36 | ---- 37 | 38 | ## Available Tools 39 | 40 | #### Tools 41 | 42 | | Tools | Description | 43 | |--------------------------|-------------| 44 | | [ca-gen](bin/ca-gen) | Creates a certificate authority | 45 | | [cert-gen](bin/cert-gen) | Creates SSL certificates signed by a certificate authority | 46 | 47 | #### Requirements 48 | 49 | * `openssl` 50 | * `bash` 51 | 52 | #### Installation 53 | ```bash 54 | $ sudo make install 55 | ``` 56 | 57 | 58 | ## Create Certificate Authority 59 | 60 | #### Usage: ca-gen 61 | The following shows the general usage for `ca-gen`: 62 | ```bash 63 | USAGE: ca-gen -n CN [-kdcslouev] 64 | ca-gen --help 65 | ca-gen --version 66 | 67 | Required arguments 68 | -n CN Common Name 69 | 70 | Optional arguments 71 | -k int Key size in bits 72 | -d int Validity in days 73 | -c C Subject two letter country name (C) 74 | -s ST Subject state name (ST) 75 | -l L Subject location (L) 76 | -o O Subject organization (O) 77 | -u OU Subject organizational unit (OU) 78 | -e Email Subject email (emailAddress) 79 | -v Verbose output 80 | 81 | Required parameter 82 | Path to output key file 83 | Path to output cert file 84 | ``` 85 | 86 | #### Execute: ca-gen 87 | The following command shows how the CA is generated for the [Devilbox](https://github.com/cytopia/devilbox): 88 | ```bash 89 | $ ca-gen -v -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox -n devilbox.org \ 90 | -e ca@devilbox.org devilbox-rootCA.key devilbox-rootCA.crt 91 | ``` 92 | 93 | #### Example: CA 94 | ```bash 95 | Certificate: 96 | Data: 97 | Version: 3 (0x2) 98 | Serial Number: 99 | 43:4a:c5:d2:87:cc:df:bd:f3:85:c7:9c:76:2e:52:d6:06:64:a5:83 100 | Signature Algorithm: sha256WithRSAEncryption 101 | Issuer: C = DE, ST = Berlin, L = Berlin, O = Devilbox, OU = Devilbox, CN = devilbox.org, emailAddress = ca@devilbox.org, dnQualifier = "1R15BhvvsVfYNh+QeB/77jYmCQE=" 102 | Validity 103 | Not Before: Nov 30 20:48:19 2019 GMT 104 | Not After : Nov 27 20:48:19 2029 GMT 105 | Subject: C = DE, ST = Berlin, L = Berlin, O = Devilbox, OU = Devilbox, CN = devilbox.org, emailAddress = ca@devilbox.org, dnQualifier = "1R15BhvvsVfYNh+QeB/77jYmCQE=" 106 | Subject Public Key Info: 107 | Public Key Algorithm: rsaEncryption 108 | RSA Public-Key: (2048 bit) 109 | Modulus: 110 | 00:9a:ec:70:20:32:85:db:a9:cc:b7:87:08:f8:10: 111 | f8:2f:57:4d:3d:ca:f3:97:1a:b6:a2:20:9f:d8:a3: 112 | 8b:15:4e:6e:92:5e:28:d6:c7:8b:de:da:9c:98:2f: 113 | 23:c8:4e:ae:be:58:ec:14:a5:0d:76:4f:2c:bb:e2: 114 | 22:93:d7:0e:d9:d4:5d:54:25:93:65:11:d7:fd:59: 115 | 63:95:a5:85:98:da:20:4f:bc:d3:aa:d2:ca:d8:e6: 116 | 19:de:9e:ca:da:a0:4f:b6:2d:93:6e:81:c0:b9:34: 117 | 06:25:fd:62:75:83:96:42:12:46:93:53:01:04:17: 118 | 53:18:e3:81:5e:26:20:3d:85:6d:a4:8b:93:ed:9e: 119 | 2a:47:21:6e:54:5b:0c:11:de:a5:fd:eb:d6:37:d9: 120 | b0:49:72:8d:97:68:53:ef:27:69:78:b5:05:75:8a: 121 | 8c:ad:a5:a1:e4:ab:04:88:42:b4:a4:d7:a8:91:99: 122 | e3:f5:32:85:55:df:ec:1f:86:83:03:0e:5b:21:16: 123 | 17:bd:c7:71:7c:a3:8f:4b:e3:8d:ad:cc:0c:d9:6a: 124 | 93:1e:3a:2c:4f:ee:1e:07:90:d3:46:f4:58:2a:f9: 125 | d0:41:68:c4:1d:30:2b:ab:3e:f4:4f:55:ba:37:0c: 126 | 6f:1e:9c:30:d8:81:7e:a1:4e:bc:ae:8a:b2:e7:53: 127 | a3:ad 128 | Exponent: 65537 (0x10001) 129 | X509v3 extensions: 130 | X509v3 Basic Constraints: critical 131 | CA:TRUE 132 | X509v3 Subject Key Identifier: 133 | D5:1D:79:06:1B:EF:B1:57:D8:36:1F:90:78:1F:FB:EE:36:26:09:01 134 | X509v3 Key Usage: critical 135 | Digital Signature, Certificate Sign, CRL Sign 136 | X509v3 Authority Key Identifier: 137 | keyid:D5:1D:79:06:1B:EF:B1:57:D8:36:1F:90:78:1F:FB:EE:36:26:09:01 138 | DirName:/C=DE/ST=Berlin/L=Berlin/O=Devilbox/OU=Devilbox/CN=devilbox.org/emailAddress=ca@devilbox.org/dnQualifier=1R15BhvvsVfYNh+QeB/77jYmCQE= 139 | serial:43:4A:C5:D2:87:CC:DF:BD:F3:85:C7:9C:76:2E:52:D6:06:64:A5:83 140 | 141 | Signature Algorithm: sha256WithRSAEncryption 142 | 7b:6f:4c:56:ae:ef:76:f2:22:69:92:11:09:dd:67:85:5c:61: 143 | a7:cb:3a:0b:6e:af:38:e5:72:33:c1:90:ae:31:2d:e6:74:93: 144 | d3:7d:1e:e0:39:8d:d9:71:4a:bf:04:ba:07:37:99:92:ed:db: 145 | cc:17:fc:f2:04:de:8b:ae:d1:2c:bc:84:fc:7a:c7:95:0a:4f: 146 | 44:00:88:11:2f:ce:b0:a8:c2:18:2c:86:5a:26:6d:a5:5e:fd: 147 | 41:33:52:c1:12:87:26:d2:9b:d4:87:70:58:d0:22:25:f1:47: 148 | 42:57:ca:68:b7:93:3e:0b:ee:9e:e7:24:36:de:a6:5c:eb:cf: 149 | cb:a2:db:5d:d0:d4:35:b3:48:18:f2:96:8b:10:60:af:b8:5d: 150 | 22:ef:19:ed:a7:c9:7e:f5:b9:f8:ca:27:9a:f6:11:bf:b3:36: 151 | 12:35:99:f0:39:dd:5a:d2:f2:d6:48:b2:bf:59:8c:3d:ea:a2: 152 | cf:56:7c:84:95:1c:1c:51:36:4c:5c:1a:d3:20:ed:5c:18:f7: 153 | e5:4a:66:b7:5c:0d:a1:07:a6:d8:7f:4c:5a:b5:c2:fe:3d:d6: 154 | 49:fb:a6:3d:ed:e3:bc:47:3b:22:43:e8:91:31:dd:cd:9b:c2: 155 | ad:d8:6b:01:ed:67:0f:c4:c0:c6:07:40:8b:50:a2:69:18:05: 156 | 3e:3e:85:09 157 | ``` 158 | 159 | 160 | ## Create SSL Certificate 161 | 162 | #### Usage: cert-gen 163 | The following shows the general usage for cert-gen: 164 | ```bash 165 | USAGE: cert-gen -n CN [-kdcsloueav] 166 | cert-gen --help 167 | cert-gen --version 168 | 169 | Required arguments 170 | -n CN Common Name 171 | 172 | Optional arguments 173 | -k int Key size in bits 174 | -d int Validity in days 175 | -c C Subject two letter country name (C) 176 | -s ST Subject state name (ST) 177 | -l L Subject location (L) 178 | -o O Subject organization (O) 179 | -u OU Subject organizational unit (OU) 180 | -e Email Subject email (emailAddress) 181 | -a names Comma separated list of alt names (subjectAltName) 182 | -i ips Comma separated list of alt ip addresses (subjectAltName) 183 | -v Verbose output 184 | 185 | Required parameter 186 | Path to existing CA key file 187 | Path to existing CA crt file 188 | Path to output certificate key file 189 | Path to output certificate csr file 190 | Path to output certificate crt file 191 | ``` 192 | 193 | #### Execute: cert-gen 194 | The following command shows how SSL certificates are generated for the [Devilbox](https://github.com/cytopia/devilbox): 195 | ```bash 196 | $ cert-gen -v -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox \ 197 | -n project.loc -e admin@project.loc \ 198 | -a '*.project.loc,*.www.project.loc' \ 199 | devilbox-rootCA.key \ 200 | devilbox-rootCA.crt \ 201 | project.loc.key \ 202 | project.loc.csr \ 203 | project.loc.crt 204 | ``` 205 | 206 | #### Example: SSL Certificate 207 | ```bash 208 | Certificate: 209 | Data: 210 | Version: 3 (0x2) 211 | Serial Number: 212 | 7a:70:c7:1f:31:f2:8c:69:03:5d:6e:5a:d6:5d:44:97:47:e1:b9:58 213 | Signature Algorithm: sha256WithRSAEncryption 214 | Issuer: C = DE, ST = Berlin, L = Berlin, O = Devilbox, OU = Devilbox, CN = devilbox.org, emailAddress = ca@devilbox.org, dnQualifier = "1R15BhvvsVfYNh+QeB/77jYmCQE=" 215 | Validity 216 | Not Before: Nov 30 20:50:11 2019 GMT 217 | Not After : Mar 4 20:50:11 2022 GMT 218 | Subject: C = DE, ST = Berlin, L = Berlin, O = Devilbox, OU = Devilbox, CN = project.loc, emailAddress = admin@project.loc 219 | Subject Public Key Info: 220 | Public Key Algorithm: rsaEncryption 221 | RSA Public-Key: (2048 bit) 222 | Modulus: 223 | 00:ab:a7:0b:41:cd:5a:00:66:6f:94:15:3b:e5:f1: 224 | 18:84:38:48:3e:6b:7f:0a:c3:4e:51:58:0b:c9:f5: 225 | 85:86:ff:61:69:ea:b8:11:5a:b9:9f:97:c5:22:2a: 226 | d1:f6:91:21:6e:01:ab:46:01:8c:4b:80:ba:74:a7: 227 | ce:5f:5b:a1:ac:a3:e6:0f:ce:19:1c:ae:68:a3:60: 228 | f9:f9:82:c1:ea:d3:eb:e5:84:1d:0c:9c:4d:94:82: 229 | d2:ef:3d:89:ab:0e:15:01:c3:22:8e:cd:7a:49:ae: 230 | 37:9c:39:9d:40:d1:19:8d:13:3a:a6:36:e4:71:1a: 231 | 8a:10:b3:ca:b1:b2:a0:a0:e5:5d:ff:39:f9:7b:70: 232 | 85:01:bd:8f:3b:ce:92:ae:c9:6d:9c:f9:6f:99:5a: 233 | e9:da:bb:28:95:01:9c:40:92:23:f0:1f:68:a3:a7: 234 | d5:fc:ac:44:9a:95:63:bd:5b:6e:bd:c2:19:0e:56: 235 | ab:47:40:57:90:74:d9:25:2e:75:b1:98:b1:82:8e: 236 | f7:4c:b2:42:fb:a5:3c:71:14:8d:55:da:a3:00:8a: 237 | 85:ce:45:91:15:8c:35:86:3f:eb:9f:d7:68:15:bf: 238 | 24:e7:96:49:90:d4:69:71:20:89:c0:c0:c2:cd:63: 239 | d0:66:38:1e:f6:60:d3:24:64:63:36:c0:19:51:23: 240 | 23:ad 241 | Exponent: 65537 (0x10001) 242 | X509v3 extensions: 243 | X509v3 Basic Constraints: critical 244 | CA:FALSE 245 | X509v3 Subject Key Identifier: 246 | 64:AE:B5:56:82:FE:E8:92:BF:9C:E0:F4:27:3D:20:79:21:CA:B4:5D 247 | X509v3 Key Usage: critical 248 | Digital Signature, Key Encipherment 249 | X509v3 Authority Key Identifier: 250 | keyid:D5:1D:79:06:1B:EF:B1:57:D8:36:1F:90:78:1F:FB:EE:36:26:09:01 251 | DirName:/C=DE/ST=Berlin/L=Berlin/O=Devilbox/OU=Devilbox/CN=devilbox.org/emailAddress=ca@devilbox.org/dnQualifier=1R15BhvvsVfYNh+QeB/77jYmCQE= 252 | serial:43:4A:C5:D2:87:CC:DF:BD:F3:85:C7:9C:76:2E:52:D6:06:64:A5:83 253 | 254 | X509v3 Extended Key Usage: 255 | TLS Web Server Authentication, TLS Web Client Authentication 256 | X509v3 Subject Alternative Name: 257 | DNS:project.loc, DNS:*.project.loc, DNS:*.www.project.loc 258 | Signature Algorithm: sha256WithRSAEncryption 259 | 02:73:0d:df:49:da:d0:19:35:c2:fb:1d:99:81:aa:3a:48:51: 260 | 1e:2e:f4:de:50:73:71:17:0c:6d:83:9e:b1:2c:1d:b5:58:c9: 261 | db:f0:a9:9c:db:dc:42:29:37:be:5b:59:4a:04:92:3e:da:5f: 262 | 10:97:ff:6e:d3:23:a1:6a:6f:c9:3f:b5:61:87:6b:a7:e8:ab: 263 | 72:a2:6e:eb:12:e9:89:71:b8:de:7c:63:62:e0:8e:9a:82:b1: 264 | ae:96:67:8a:20:63:2b:75:18:1a:04:36:ed:1e:8c:b5:16:d2: 265 | d4:77:05:5b:54:ee:d8:c4:25:6d:fb:02:2c:dc:e0:dc:2d:37: 266 | 99:71:66:f8:06:24:ff:69:69:50:b1:10:f2:c5:ff:96:28:75: 267 | 8a:e3:78:3c:7c:38:a9:1c:20:3e:1f:f5:dc:d3:ec:3b:ae:ac: 268 | f4:14:45:16:aa:3f:db:eb:ae:b8:1d:0d:4a:76:cb:02:eb:c1: 269 | 00:e2:42:60:90:18:82:8e:3d:01:6b:1f:78:de:d9:a4:7a:df: 270 | 71:1e:aa:7e:7b:87:2c:af:ce:47:5d:be:1f:6a:4c:cd:10:67: 271 | 4e:41:c8:ca:90:fe:ac:2d:a1:92:e5:34:ea:da:ed:d9:9e:2d: 272 | ac:38:81:7d:13:5b:0f:cd:e8:a7:99:a0:1f:54:29:10:64:19: 273 | b6:1a:14:ab 274 | ``` 275 | 276 | 277 | ## Import CA into Chrome 278 | 279 | **1. Open Chrome settings - scroll down and click `Advanced`** 280 | 281 | 282 | 283 | **2. Find and click on `Manage certificates`** 284 | 285 | 286 | 287 | **3. In the tab, navigate to `AUTHORITIES`** 288 | 289 | 290 | 291 | **4. Select `devilbox-ca.crt` from within the Devilbox git directory** 292 | 293 | 294 | 295 | **5. Check all boxes** 296 | 297 | 298 | 299 | 300 | ## License 301 | 302 | **[MIT License](LICENSE.md)** 303 | 304 | Copyright (c) 2018 [cytopia](https://github.com/cytopia) 305 | -------------------------------------------------------------------------------- /bin/ca-gen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -u 5 | set -o pipefail 6 | 7 | NAME="ca-gen" 8 | VERSION="v0.10" 9 | DATE="2022-12-18" 10 | 11 | # Generate default options 12 | DEF_KEYSIZE=2048 13 | DEF_DAYS=3650 14 | DEF_SIGN_SIGNATURE="sha256" 15 | # Subject default options 16 | DEF_COUNTRY= 17 | DEF_STATE= 18 | DEF_CITY= 19 | DEF_ORG= 20 | DEF_UNIT= 21 | DEF_CN= 22 | DEF_EMAIL= 23 | 24 | # Verbosity 25 | DEF_VERBOSE= 26 | 27 | log() { 28 | local type="${1}" # err, warn, info 29 | local message="${2}" # message to log 30 | 31 | if [ "${type}" = "err" ]; then 32 | printf "%s: [ERR] %s\n" "${NAME}" "${message}" 1>&2 # stdout -> stderr 33 | fi 34 | if [ "${type}" = "warn" ]; then 35 | printf "%s: [WARN] %s\n" "${NAME}" "${message}" 1>&2 # stdout -> stderr 36 | fi 37 | if [ "${DEF_VERBOSE:-}" = "1" ]; then 38 | if [ "${type}" = "info" ]; then 39 | printf "%s: [INFO] %s\n" "${NAME}" "${message}" 40 | fi 41 | fi 42 | } 43 | 44 | print_version() { 45 | echo "${NAME}: Version ${VERSION} (${DATE}) by cytopia" 46 | echo "https://github.com/devilbox/cert-gen/" 47 | } 48 | 49 | print_help() { 50 | echo "USAGE: ${NAME} -n CN [-kdcslouev] " 51 | echo " ${NAME} --help" 52 | echo " ${NAME} --version" 53 | echo 54 | echo "Required arguments" 55 | echo " -n CN Common Name" 56 | echo 57 | echo "Optional arguments" 58 | echo " -k int Key size in bits" 59 | echo " -d int Validity in days" 60 | echo " -c C Subject two letter country name (C)" 61 | echo " -s ST Subject state name (ST)" 62 | echo " -l L Subject location (L)" 63 | echo " -o O Subject organization (O)" 64 | echo " -u OU Subject organizational unit (OU)" 65 | echo " -e Email Subject email (emailAddress)" 66 | echo " -v Verbose output" 67 | echo 68 | echo "Required parameter" 69 | echo " Path to output key file" 70 | echo " Path to output cert file" 71 | } 72 | 73 | 74 | ################################################################################ 75 | # Entrypoint: Parse cmd args 76 | ################################################################################ 77 | 78 | # Get options 79 | while [ ${#} -gt 0 ]; do 80 | case "${1}" in 81 | # ---- Help / version 82 | --version) 83 | print_version 84 | exit 85 | ;; 86 | --help) 87 | print_help 88 | exit 89 | ;; 90 | -v) 91 | DEF_VERBOSE=1 92 | shift 93 | ;; 94 | # ---- Options 95 | -k) 96 | shift 97 | if [ -z "${1:-}" ]; then 98 | log "err" "Usage: -k requires an argument." 99 | exit 1 100 | fi 101 | DEF_KEYSIZE="${1}" 102 | shift 103 | ;; 104 | -d) 105 | shift 106 | if [ -z "${1:-}" ]; then 107 | log "err" "Usage: -d requires an argument." 108 | exit 1 109 | fi 110 | DEF_DAYS="${1}" 111 | shift 112 | ;; 113 | -c) 114 | shift 115 | if [ -z "${1:-}" ]; then 116 | log "err" "Usage: -c requires an argument." 117 | exit 1 118 | fi 119 | DEF_COUNTRY="${1}" 120 | shift 121 | ;; 122 | -s) 123 | shift 124 | if [ -z "${1:-}" ]; then 125 | log "err" "Usage: -s requires an argument." 126 | exit 1 127 | fi 128 | DEF_STATE="${1}" 129 | shift 130 | ;; 131 | -l) 132 | shift 133 | if [ -z "${1:-}" ]; then 134 | log "err" "Usage: -l requires an argument." 135 | exit 1 136 | fi 137 | DEF_CITY="${1}" 138 | shift 139 | ;; 140 | -o) 141 | shift 142 | if [ -z "${1:-}" ]; then 143 | log "err" "Usage: -o requires an argument." 144 | exit 1 145 | fi 146 | DEF_ORG="${1}" 147 | shift 148 | ;; 149 | -u) 150 | shift 151 | if [ -z "${1:-}" ]; then 152 | log "err" "Usage: -u requires an argument." 153 | exit 1 154 | fi 155 | DEF_UNIT="${1}" 156 | shift 157 | ;; 158 | -n) 159 | shift 160 | if [ -z "${1:-}" ]; then 161 | log "err" "Usage: -n requires an argument." 162 | exit 1 163 | fi 164 | DEF_CN="${1}" 165 | shift 166 | ;; 167 | -e) 168 | shift 169 | if [ -z "${1:-}" ]; then 170 | log "err" "Usage: -e requires an argument." 171 | exit 1 172 | fi 173 | DEF_EMAIL="${1}" 174 | shift 175 | ;; 176 | # ---- Stop here 177 | --) # End of all options 178 | shift 179 | break 180 | ;; 181 | -*) # Unknown option 182 | log "err" "Usage: Unknown option: ${1}" 183 | exit 1 184 | ;; 185 | *) # No more options 186 | break 187 | ;; 188 | esac 189 | done 190 | 191 | 192 | ################################################################################ 193 | # Entrypoint: Validate cmd args 194 | ################################################################################ 195 | 196 | if [ -z "${DEF_CN}" ]; then 197 | log "err" "Usage: -n is required. See --help for help." 198 | exit 1 199 | fi 200 | 201 | if [ "${#}" -lt "2" ]; then 202 | log "err" "Usage: and are required. See --help for help." 203 | exit 1 204 | fi 205 | 206 | CA_KEY_FILE="${1}" 207 | CA_CRT_FILE="${2}" 208 | 209 | 210 | 211 | ################################################################################ 212 | # Entrypoint: Execute 213 | ################################################################################ 214 | 215 | ### 216 | ### Build subject 217 | ### 218 | SUBJECT= 219 | if [ -n "${DEF_COUNTRY}" ]; then 220 | SUBJECT="${SUBJECT}/C=${DEF_COUNTRY}" 221 | fi 222 | if [ -n "${DEF_STATE}" ]; then 223 | SUBJECT="${SUBJECT}/ST=${DEF_STATE}" 224 | fi 225 | if [ -n "${DEF_CITY}" ]; then 226 | SUBJECT="${SUBJECT}/L=${DEF_CITY}" 227 | fi 228 | if [ -n "${DEF_ORG}" ]; then 229 | SUBJECT="${SUBJECT}/O=${DEF_ORG}" 230 | fi 231 | if [ -n "${DEF_UNIT}" ]; then 232 | SUBJECT="${SUBJECT}/OU=${DEF_UNIT}" 233 | fi 234 | if [ -n "${DEF_CN}" ]; then 235 | SUBJECT="${SUBJECT}/CN=${DEF_CN}" 236 | fi 237 | if [ -n "${DEF_EMAIL}" ]; then 238 | SUBJECT="${SUBJECT}/emailAddress=${DEF_EMAIL}" 239 | fi 240 | 241 | 242 | ### 243 | ### Build commands 244 | ### 245 | 246 | ### 247 | ### 1. Create Key 248 | ### 249 | 250 | # Command 251 | cmd="openssl genrsa \ 252 | -out ${CA_KEY_FILE} \ 253 | ${DEF_KEYSIZE}" 254 | 255 | # Trim newlines/whitespaces 256 | cmd="$( echo "${cmd}" | tr -s " " )" 257 | 258 | 259 | # Execute 260 | log "info" "Create CA KEY file: ${CA_KEY_FILE}" 261 | if ! out="$( eval "${cmd}" 2>&1 )"; then 262 | log "err" "Command: ${cmd}" 263 | log "err" "Output: ${out}" 264 | exit 1 265 | fi 266 | 267 | 268 | ### 269 | ### 2. Create dnQualifier 270 | ### 271 | 272 | # Subject dnQualifier (Public key thumbprint, see SMPTE 430-2-2006 sections 5.3.1, 5.4 and DCI CTP section 2.1.11) 273 | ca_dnq="$( openssl rsa -outform PEM -pubout -in "${CA_KEY_FILE}" 2>/dev/null | openssl base64 -d | dd bs=1 skip=24 2>/dev/null | openssl sha1 -binary | openssl base64 )" 274 | ca_dnq="${ca_dnq//\//\\/}" # echo "${ca_dnq}" | sed 's|/|\\/|g' )" # can have values like '0Za8/aABE05Aroz7le1FOpEdFhk=', note the '/'. protect for name parser 275 | SUBJECT="${SUBJECT}/dnQualifier=${ca_dnq}" 276 | 277 | 278 | ### 279 | ### 3. Create CA 280 | ### 281 | 282 | OPENSSL_CONFIG="$( cat <<'HEREDOC' 283 | [req] 284 | distinguished_name = req_distinguished_name 285 | 286 | [req_distinguished_name] 287 | 288 | [ v3_ca ] 289 | basicConstraints = critical, CA:TRUE 290 | subjectKeyIdentifier = hash 291 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 292 | authorityKeyIdentifier = keyid:always,issuer:always 293 | HEREDOC 294 | )" 295 | 296 | # Command 297 | cmd="openssl req \ 298 | -new \ 299 | -x509 \ 300 | -nodes \ 301 | -${DEF_SIGN_SIGNATURE} \ 302 | -days ${DEF_DAYS} \ 303 | -key ${CA_KEY_FILE} \ 304 | -subj '${SUBJECT}' \ 305 | -extensions v3_ca \ 306 | -config <(echo \"${OPENSSL_CONFIG}\") \ 307 | -out ${CA_CRT_FILE}" 308 | 309 | # Trim newlines/whitespaces 310 | cmd="$( echo "${cmd}" | tr -s " " )" 311 | 312 | 313 | # Execute 314 | log "info" "Create CA CRT file: ${CA_CRT_FILE}" 315 | if ! out="$( eval "${cmd}" 2>&1 )"; then 316 | log "err" "Command: ${cmd}" 317 | log "err" "Output: ${out}" 318 | exit 1 319 | fi 320 | 321 | 322 | ### 323 | ### 4. Validate 324 | ### 325 | log "info" "Verify CA CRT file: ${CA_CRT_FILE}" 326 | if ! out="$( openssl x509 -in "${CA_CRT_FILE}" -text )"; then 327 | log "err" "CA CRT verification failed: ${out}" 328 | exit 1 329 | fi 330 | 331 | log "info" "Verify CA CRT issuer" 332 | if ! out="$( openssl x509 -noout -subject -issuer -in "${CA_CRT_FILE}" )"; then 333 | log "err" "CA CRT issuer failed: ${out}" 334 | exit 1 335 | fi 336 | -------------------------------------------------------------------------------- /bin/cert-gen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -u 5 | set -o pipefail 6 | 7 | NAME="cert-gen" 8 | VERSION="v0.10" 9 | DATE="2022-12-18" 10 | 11 | # Generate default options 12 | DEF_KEYSIZE=2048 13 | DEF_DAYS=825 14 | DEF_SIGN_SIGNATURE="sha256" 15 | # Subject default options 16 | DEF_COUNTRY= 17 | DEF_STATE= 18 | DEF_CITY= 19 | DEF_ORG= 20 | DEF_UNIT= 21 | DEF_CN= 22 | DEF_EMAIL= 23 | 24 | # v3 subject alt names 25 | DEF_ALT_NAME= 26 | DEF_ALT_IP_NAME= 27 | 28 | # Verbosity 29 | DEF_VERBOSE= 30 | 31 | log() { 32 | local type="${1}" # err, warn, info 33 | local message="${2}" # message to log 34 | 35 | if [ "${type}" = "err" ]; then 36 | printf "%s: [ERR] %s\n" "${NAME}" "${message}" 1>&2 # stdout -> stderr 37 | fi 38 | if [ "${type}" = "warn" ]; then 39 | printf "%s: [WARN] %s\n" "${NAME}" "${message}" 1>&2 # stdout -> stderr 40 | fi 41 | if [ "${DEF_VERBOSE:-}" = "1" ]; then 42 | if [ "${type}" = "info" ]; then 43 | printf "%s: [INFO] %s\n" "${NAME}" "${message}" 44 | fi 45 | fi 46 | } 47 | 48 | print_version() { 49 | echo "${NAME}: Version ${VERSION} (${DATE}) by cytopia" 50 | echo "https://github.com/devilbox/cert-gen/" 51 | } 52 | 53 | print_help() { 54 | echo "USAGE: ${NAME} -n CN [-kdcsloueav] " 55 | echo " ${NAME} --help" 56 | echo " ${NAME} --version" 57 | echo 58 | echo "Required arguments" 59 | echo " -n CN Common Name" 60 | echo 61 | echo "Optional arguments" 62 | echo " -k int Key size in bits" 63 | echo " -d int Validity in days" 64 | echo " -c C Subject two letter country name (C)" 65 | echo " -s ST Subject state name (ST)" 66 | echo " -l L Subject location (L)" 67 | echo " -o O Subject organization (O)" 68 | echo " -u OU Subject organizational unit (OU)" 69 | echo " -e Email Subject email (emailAddress)" 70 | echo " -a names Comma separated list of alt names (subjectAltName)" 71 | echo " -i ips Comma separated list of alt ip addresses (subjectAltName)" 72 | echo " -v Verbose output" 73 | echo 74 | echo "Required parameter" 75 | echo " Path to existing CA key file" 76 | echo " Path to existing CA crt file" 77 | echo " Path to output certificate key file" 78 | echo " Path to output certificate csr file" 79 | echo " Path to output certificate crt file" 80 | } 81 | 82 | 83 | ################################################################################ 84 | # Entrypoint: Parse cmd args 85 | ################################################################################ 86 | 87 | # Get options 88 | while [ ${#} -gt 0 ]; do 89 | case "${1}" in 90 | # ---- Help / version 91 | --version) 92 | print_version 93 | exit 94 | ;; 95 | --help) 96 | print_help 97 | exit 98 | ;; 99 | -v) 100 | DEF_VERBOSE=1 101 | shift 102 | ;; 103 | # ---- Options 104 | -k) 105 | shift 106 | if [ -z "${1:-}" ]; then 107 | log "err" "Usage: -k requires an argument." 108 | exit 1 109 | fi 110 | DEF_KEYSIZE="${1}" 111 | shift 112 | ;; 113 | -d) 114 | shift 115 | if [ -z "${1:-}" ]; then 116 | log "err" "Usage: -d requires an argument." 117 | exit 1 118 | fi 119 | DEF_DAYS="${1}" 120 | shift 121 | ;; 122 | -c) 123 | shift 124 | if [ -z "${1:-}" ]; then 125 | log "err" "Usage: -c requires an argument." 126 | exit 1 127 | fi 128 | DEF_COUNTRY="${1}" 129 | shift 130 | ;; 131 | -s) 132 | shift 133 | if [ -z "${1:-}" ]; then 134 | log "err" "Usage: -s requires an argument." 135 | exit 1 136 | fi 137 | DEF_STATE="${1}" 138 | shift 139 | ;; 140 | -l) 141 | shift 142 | if [ -z "${1:-}" ]; then 143 | log "err" "Usage: -l requires an argument." 144 | exit 1 145 | fi 146 | DEF_CITY="${1}" 147 | shift 148 | ;; 149 | -o) 150 | shift 151 | if [ -z "${1:-}" ]; then 152 | log "err" "Usage: -o requires an argument." 153 | exit 1 154 | fi 155 | DEF_ORG="${1}" 156 | shift 157 | ;; 158 | -u) 159 | shift 160 | if [ -z "${1:-}" ]; then 161 | log "err" "Usage: -u requires an argument." 162 | exit 1 163 | fi 164 | DEF_UNIT="${1}" 165 | shift 166 | ;; 167 | -n) 168 | shift 169 | if [ -z "${1:-}" ]; then 170 | log "err" "Usage: -n requires an argument." 171 | exit 1 172 | fi 173 | DEF_CN="${1}" 174 | shift 175 | ;; 176 | -e) 177 | shift 178 | if [ -z "${1:-}" ]; then 179 | log "err" "Usage: -e requires an argument." 180 | exit 1 181 | fi 182 | DEF_EMAIL="${1}" 183 | shift 184 | ;; 185 | -a) 186 | shift 187 | if [ -z "${1:-}" ]; then 188 | log "err" "Usage: -a requires an argument." 189 | exit 1 190 | fi 191 | DEF_ALT_NAME="${1}" 192 | shift 193 | ;; 194 | -i) 195 | shift 196 | if [ -z "${1:-}" ]; then 197 | log "err" "Usage: -i requires an argument." 198 | exit 1 199 | fi 200 | DEF_ALT_IP_NAME="${1}" 201 | shift 202 | ;; 203 | # ---- Stop here 204 | --) # End of all options 205 | shift 206 | break 207 | ;; 208 | -*) # Unknown option 209 | log "err" "Usage: Unknown option: ${1}" 210 | exit 1 211 | ;; 212 | *) # No more options 213 | break 214 | ;; 215 | esac 216 | done 217 | 218 | 219 | ################################################################################ 220 | # Entrypoint: Validate cmd args 221 | ################################################################################ 222 | 223 | if [ -z "${DEF_CN}" ]; then 224 | log "err" "Usage: -n is required. See --help for help." 225 | exit 1 226 | fi 227 | 228 | if [ "${#}" -lt "5" ]; then 229 | log "err" "Usage: and are required. See --help for help." 230 | exit 1 231 | fi 232 | 233 | CA_KEY_FILE="${1}" 234 | CA_CRT_FILE="${2}" 235 | KEY_FILE="${3}" 236 | CSR_FILE="${4}" 237 | CRT_FILE="${5}" 238 | 239 | if [ ! -f "${CA_KEY_FILE}" ]; then 240 | log "err" "Usage: file does not exist in: ${CA_KEY_FILE}" 241 | exit 1 242 | fi 243 | if [ ! -f "${CA_CRT_FILE}" ]; then 244 | log "err" "Usage: file does not exist in: ${CA_CRT_FILE}" 245 | exit 1 246 | fi 247 | 248 | 249 | 250 | 251 | ################################################################################ 252 | # Entrypoint: Execute 253 | ################################################################################ 254 | 255 | ### 256 | ### Build subject 257 | ### 258 | SUBJECT= 259 | if [ -n "${DEF_COUNTRY}" ]; then 260 | SUBJECT="${SUBJECT}/C=${DEF_COUNTRY}" 261 | fi 262 | if [ -n "${DEF_STATE}" ]; then 263 | SUBJECT="${SUBJECT}/ST=${DEF_STATE}" 264 | fi 265 | if [ -n "${DEF_CITY}" ]; then 266 | SUBJECT="${SUBJECT}/L=${DEF_CITY}" 267 | fi 268 | if [ -n "${DEF_ORG}" ]; then 269 | SUBJECT="${SUBJECT}/O=${DEF_ORG}" 270 | fi 271 | if [ -n "${DEF_UNIT}" ]; then 272 | SUBJECT="${SUBJECT}/OU=${DEF_UNIT}" 273 | fi 274 | if [ -n "${DEF_CN}" ]; then 275 | SUBJECT="${SUBJECT}/CN=${DEF_CN}" 276 | fi 277 | if [ -n "${DEF_EMAIL}" ]; then 278 | SUBJECT="${SUBJECT}/emailAddress=${DEF_EMAIL}" 279 | fi 280 | 281 | # Create subject altnames 282 | ALT_NAMES="DNS.1:${DEF_CN}" 283 | i=2 284 | if [ -n "${DEF_ALT_NAME}" ]; then 285 | for cn in ${DEF_ALT_NAME//,/ }; do 286 | ALT_NAMES="${ALT_NAMES},DNS.${i}:${cn}" 287 | done 288 | fi 289 | 290 | i=1 291 | if [ -n "${DEF_ALT_IP_NAME}" ]; then 292 | for cn in ${DEF_ALT_IP_NAME//,/ }; do 293 | ALT_NAMES="${ALT_NAMES},IP.${i}:${cn}" 294 | done 295 | fi 296 | 297 | ### 298 | ### Build commands 299 | ### 300 | 301 | ### 302 | ### 1. Key and Signing Request 303 | ### 304 | 305 | OPENSSL_CONFIG="$( cat <<'HEREDOC' 306 | [req] 307 | distinguished_name = req_distinguished_name 308 | x509_extensions = v3_req 309 | 310 | [req_distinguished_name] 311 | 312 | [ v3_req ] 313 | basicConstraints = critical, CA:FALSE 314 | subjectKeyIdentifier = hash 315 | keyUsage = critical, digitalSignature, keyEncipherment 316 | authorityKeyIdentifier = keyid:always,issuer:always 317 | extendedKeyUsage = serverAuth, clientAuth 318 | subjectAltName=${ALT_NAMES} 319 | HEREDOC 320 | )" 321 | 322 | # Command 323 | cmd="openssl req \ 324 | -newkey rsa:${DEF_KEYSIZE} \ 325 | -${DEF_SIGN_SIGNATURE} \ 326 | -nodes \ 327 | -extensions v3_req \ 328 | -config <(echo \"${OPENSSL_CONFIG}\") \ 329 | -keyout ${KEY_FILE} \ 330 | -subj '${SUBJECT}' \ 331 | -out ${CSR_FILE}" 332 | 333 | # Trim newlines/whitespaces 334 | cmd="$( echo "${cmd}" | tr -s " " )" 335 | 336 | 337 | # Execute 338 | log "info" "Create CSR file: ${CSR_FILE}" 339 | if ! out="$( eval "${cmd}" 2>&1 )"; then 340 | log "err" "Command: ${cmd}" 341 | log "err" "Output: ${out}" 342 | exit 1 343 | fi 344 | 345 | 346 | ### 347 | ### 2. Create Certificate 348 | ### 349 | 350 | # Command 351 | # shellcheck disable=SC1117 352 | cmd="openssl x509 \ 353 | -req \ 354 | -${DEF_SIGN_SIGNATURE} \ 355 | -extensions v3_req \ 356 | -extfile <(echo \"${OPENSSL_CONFIG}\") \ 357 | -days ${DEF_DAYS} \ 358 | -in ${CSR_FILE} \ 359 | -CA ${CA_CRT_FILE} \ 360 | -CAkey ${CA_KEY_FILE} \ 361 | -CAcreateserial \ 362 | -out ${CRT_FILE}" 363 | 364 | # Trim newlines/whitespaces 365 | cmd="$( echo "${cmd}" | tr -s " " )" 366 | 367 | 368 | # Execute 369 | log "info" "Create CRT file: ${CRT_FILE}" 370 | if ! out="$( eval "${cmd}" 2>&1 )"; then 371 | log "err" "Command: ${cmd}" 372 | log "err" "Output: ${out}" 373 | exit 1 374 | fi 375 | 376 | 377 | ### 378 | ### 4. Validate 379 | ### 380 | log "info" "Verify CRT file: ${CRT_FILE}" 381 | if ! out="$( openssl x509 -in "${CRT_FILE}" -text -noout )"; then 382 | log "err" "CRT verification failed: ${out}" 383 | exit 1 384 | fi 385 | 386 | log "info" "Verify CRT against CA file: ${CA_CRT_FILE}" 387 | if ! out="$( openssl verify -verbose -CAfile "${CA_CRT_FILE}" "${CRT_FILE}" )"; then 388 | log "err" "CA verification failed: ${out}" 389 | exit 1 390 | fi 391 | -------------------------------------------------------------------------------- /img/01-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devilbox/cert-gen/f7beb2ac832fe780509cfd7847ad82cd2864f20f/img/01-settings.png -------------------------------------------------------------------------------- /img/02-advanced-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devilbox/cert-gen/f7beb2ac832fe780509cfd7847ad82cd2864f20f/img/02-advanced-settings.png -------------------------------------------------------------------------------- /img/03-authorities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devilbox/cert-gen/f7beb2ac832fe780509cfd7847ad82cd2864f20f/img/03-authorities.png -------------------------------------------------------------------------------- /img/04-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devilbox/cert-gen/f7beb2ac832fe780509cfd7847ad82cd2864f20f/img/04-import.png -------------------------------------------------------------------------------- /img/05-set-trust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devilbox/cert-gen/f7beb2ac832fe780509cfd7847ad82cd2864f20f/img/05-set-trust.png -------------------------------------------------------------------------------- /img/address-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devilbox/cert-gen/f7beb2ac832fe780509cfd7847ad82cd2864f20f/img/address-bar.png --------------------------------------------------------------------------------