├── create-ca.sh ├── README.md ├── openssl-rootca.conf ├── openssl-intermediate.conf ├── LICENSE.txt └── micro-ca-tool /create-ca.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ $# < 1 ]]; then 3 | echo "Usage: $0 " 4 | echo "Example: $0 intermediate-ca-1" 5 | exit 1 6 | fi 7 | 8 | set -x 9 | mkdir $1 10 | cd $1 11 | ln -s ../micro-ca-tool . 12 | ln -s ../openssl*conf . 13 | 14 | set +x 15 | echo "done." 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | µ-CA-tool 2 | ========= 3 | 4 | Welcome to µ-CA. 5 | This tool will help you to perform basic tasks with your CA, X.509 certificates in general and GnuPG: 6 | 7 | * Create CA as files 8 | * or Create CA on a SmartCard 9 | * or Create CA as files and store on SmartCard 10 | * Create intermediate CA 11 | * Sign other certificates 12 | * Backup CA key with n-of-m scheme key sharing 13 | * Create client certificates with and without SmartCard 14 | * Basic SmartCard functions: Info, Read, Write, Generate keys, Reset 15 | * show SSH keys for use with current SmartCard 16 | 17 | ## Installation 18 | This tool is written in Bash. Other dependencies are OpenSSL, OpenSC, GPG (version 2) and ssss. 19 | 20 | On Debian/Ubuntu just type: 21 | 22 | sudo apt-get install opensc opensc-pkcs11 libengine-pkcs11-openssl openssl gnupg2 23 | 24 | ## SmartCard Support 25 | The µ-CA-Tool was developed with focus on Nitrokey Pro and Crypto Stick. However other OpenPGP-Cards and PKCS#11 compliant SmartCards should work as well, e.g. Nitrokey Storage, Yubikey Neo or the FSFE Fellowship Smart Card. 26 | 27 | ## Related Software 28 | * easy-rsa - https://github.com/OpenVPN/easy-rsa 29 | * CA based on makefile - https://github.com/cornelinux/simple-ca-makefile 30 | 31 | -------------------------------------------------------------------------------- /openssl-rootca.conf: -------------------------------------------------------------------------------- 1 | # OpenSSL root CA configuration file. 2 | # https://jamielinux.com/docs/openssl-certificate-authority/appendix/root-configuration-file.html 3 | 4 | [ ca ] 5 | # `man ca` 6 | default_ca = CA_default 7 | 8 | [ CA_default ] 9 | # Directory and file locations. 10 | dir = . 11 | certs = $dir/certs 12 | crl_dir = $dir/crl 13 | new_certs_dir = $dir/newcerts 14 | database = $dir/index.txt 15 | serial = $dir/serial 16 | RANDFILE = $dir/private/.rand 17 | 18 | # The root key and root certificate. 19 | #private_key = $dir/private/ca.key.pem 20 | #certificate = $dir/certs/ca.cert.pem 21 | 22 | # For certificate revocation lists. 23 | crlnumber = $dir/crlnumber 24 | crl = $dir/crl/ca.crl.pem 25 | crl_extensions = crl_ext 26 | default_crl_days = 30 27 | 28 | # SHA-1 is deprecated, so use SHA-2 instead. 29 | default_md = sha256 30 | 31 | name_opt = ca_default 32 | cert_opt = ca_default 33 | default_days = 375 34 | preserve = no 35 | policy = policy_loose 36 | 37 | [ policy_strict ] 38 | # The root CA should only sign intermediate certificates that match. 39 | # See the POLICY FORMAT section of `man ca`. 40 | countryName = match 41 | stateOrProvinceName = match 42 | organizationName = match 43 | organizationalUnitName = optional 44 | commonName = supplied 45 | emailAddress = optional 46 | 47 | [ policy_loose ] 48 | # Allow the intermediate CA to sign a more diverse range of certificates. 49 | # See the POLICY FORMAT section of the `ca` man page. 50 | countryName = optional 51 | stateOrProvinceName = optional 52 | localityName = optional 53 | organizationName = optional 54 | organizationalUnitName = optional 55 | commonName = supplied 56 | emailAddress = optional 57 | 58 | [ req ] 59 | # Options for the `req` tool (`man req`). 60 | default_bits = 2048 61 | distinguished_name = req_distinguished_name 62 | string_mask = utf8only 63 | 64 | # SHA-1 is deprecated, so use SHA-2 instead. 65 | default_md = sha256 66 | 67 | # Extension to add when the -x509 option is used. 68 | x509_extensions = v3_ca 69 | 70 | [ req_distinguished_name ] 71 | # See . 72 | countryName = Country Name (2 letter code) 73 | stateOrProvinceName = State or Province Name 74 | localityName = Locality Name 75 | 0.organizationName = Organization Name 76 | organizationalUnitName = Organizational Unit Name 77 | commonName = Common Name 78 | emailAddress = Email Address 79 | 80 | # Optionally, specify some defaults. 81 | countryName_default = GB 82 | stateOrProvinceName_default = England 83 | localityName_default = 84 | 0.organizationName_default = Alice Ltd 85 | organizationalUnitName_default = 86 | emailAddress_default = 87 | 88 | [ v3_ca ] 89 | # Extensions for a typical CA (`man x509v3_config`). 90 | subjectKeyIdentifier = hash 91 | authorityKeyIdentifier = keyid:always,issuer 92 | basicConstraints = critical, CA:true 93 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 94 | 95 | [ v3_intermediate_ca ] 96 | # Extensions for a typical intermediate CA (`man x509v3_config`). 97 | subjectKeyIdentifier = hash 98 | authorityKeyIdentifier = keyid:always,issuer 99 | basicConstraints = critical, CA:true, pathlen:0 100 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 101 | 102 | [ usr_cert ] 103 | # Extensions for client certificates (`man x509v3_config`). 104 | basicConstraints = CA:FALSE 105 | nsCertType = client, email 106 | nsComment = "OpenSSL Generated Client Certificate" 107 | subjectKeyIdentifier = hash 108 | authorityKeyIdentifier = keyid,issuer 109 | keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment 110 | extendedKeyUsage = clientAuth, emailProtection 111 | 112 | [ server_cert ] 113 | # Extensions for server certificates (`man x509v3_config`). 114 | basicConstraints = CA:FALSE 115 | nsCertType = server 116 | nsComment = "OpenSSL Generated Server Certificate" 117 | subjectKeyIdentifier = hash 118 | authorityKeyIdentifier = keyid,issuer:always 119 | keyUsage = critical, digitalSignature, keyEncipherment 120 | extendedKeyUsage = serverAuth 121 | 122 | [ crl_ext ] 123 | # Extension for CRLs (`man x509v3_config`). 124 | authorityKeyIdentifier=keyid:always 125 | 126 | [ ocsp ] 127 | # Extension for OCSP signing certificates (`man ocsp`). 128 | basicConstraints = CA:FALSE 129 | subjectKeyIdentifier = hash 130 | authorityKeyIdentifier = keyid,issuer 131 | keyUsage = critical, digitalSignature 132 | extendedKeyUsage = critical, OCSPSigning 133 | -------------------------------------------------------------------------------- /openssl-intermediate.conf: -------------------------------------------------------------------------------- 1 | # OpenSSL intermediate CA configuration file. 2 | # https://jamielinux.com/docs/openssl-certificate-authority/appendix/intermediate-configuration-file.html 3 | 4 | [ ca ] 5 | # `man ca` 6 | default_ca = CA_default 7 | 8 | [ CA_default ] 9 | # Directory and file locations. 10 | dir = . 11 | certs = $dir/certs 12 | crl_dir = $dir/crl 13 | new_certs_dir = $dir/newcerts 14 | database = $dir/index.txt 15 | serial = $dir/serial 16 | RANDFILE = $dir/private/.rand 17 | 18 | # The root key and root certificate. 19 | #private_key = $dir/private/intermediate.key.pem 20 | #certificate = $dir/certs/intermediate.cert.pem 21 | 22 | # For certificate revocation lists. 23 | crlnumber = $dir/crlnumber 24 | crl = $dir/crl/intermediate.crl.pem 25 | crl_extensions = crl_ext 26 | default_crl_days = 30 27 | 28 | # SHA-1 is deprecated, so use SHA-2 instead. 29 | default_md = sha256 30 | 31 | name_opt = ca_default 32 | cert_opt = ca_default 33 | default_days = 375 34 | preserve = no 35 | policy = policy_loose 36 | 37 | [ policy_strict ] 38 | # The root CA should only sign intermediate certificates that match. 39 | # See the POLICY FORMAT section of `man ca`. 40 | countryName = match 41 | stateOrProvinceName = match 42 | organizationName = match 43 | organizationalUnitName = optional 44 | commonName = supplied 45 | emailAddress = optional 46 | 47 | [ policy_loose ] 48 | # Allow the intermediate CA to sign a more diverse range of certificates. 49 | # See the POLICY FORMAT section of the `ca` man page. 50 | countryName = optional 51 | stateOrProvinceName = optional 52 | localityName = optional 53 | organizationName = optional 54 | organizationalUnitName = optional 55 | commonName = supplied 56 | emailAddress = optional 57 | 58 | [ req ] 59 | # Options for the `req` tool (`man req`). 60 | default_bits = 2048 61 | distinguished_name = req_distinguished_name 62 | string_mask = utf8only 63 | 64 | # SHA-1 is deprecated, so use SHA-2 instead. 65 | default_md = sha256 66 | 67 | # Extension to add when the -x509 option is used. 68 | x509_extensions = v3_ca 69 | 70 | [ req_distinguished_name ] 71 | # See . 72 | countryName = Country Name (2 letter code) 73 | stateOrProvinceName = State or Province Name 74 | localityName = Locality Name 75 | 0.organizationName = Organization Name 76 | organizationalUnitName = Organizational Unit Name 77 | commonName = Common Name 78 | emailAddress = Email Address 79 | 80 | # Optionally, specify some defaults. 81 | countryName_default = GB 82 | stateOrProvinceName_default = England 83 | localityName_default = 84 | 0.organizationName_default = Alice Ltd 85 | organizationalUnitName_default = 86 | emailAddress_default = 87 | 88 | [ v3_ca ] 89 | # Extensions for a typical CA (`man x509v3_config`). 90 | subjectKeyIdentifier = hash 91 | authorityKeyIdentifier = keyid:always,issuer 92 | basicConstraints = critical, CA:true 93 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 94 | 95 | [ v3_intermediate_ca ] 96 | # Extensions for a typical intermediate CA (`man x509v3_config`). 97 | subjectKeyIdentifier = hash 98 | authorityKeyIdentifier = keyid:always,issuer 99 | basicConstraints = critical, CA:true, pathlen:0 100 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 101 | 102 | [ usr_cert ] 103 | # Extensions for client certificates (`man x509v3_config`). 104 | basicConstraints = CA:FALSE 105 | nsCertType = client, email 106 | nsComment = "OpenSSL Generated Client Certificate" 107 | subjectKeyIdentifier = hash 108 | authorityKeyIdentifier = keyid,issuer 109 | keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment 110 | extendedKeyUsage = clientAuth, emailProtection 111 | 112 | [ server_cert ] 113 | # Extensions for server certificates (`man x509v3_config`). 114 | basicConstraints = CA:FALSE 115 | nsCertType = server 116 | nsComment = "OpenSSL Generated Server Certificate" 117 | subjectKeyIdentifier = hash 118 | authorityKeyIdentifier = keyid,issuer:always 119 | keyUsage = critical, digitalSignature, keyEncipherment 120 | extendedKeyUsage = serverAuth 121 | 122 | [ crl_ext ] 123 | # Extension for CRLs (`man x509v3_config`). 124 | authorityKeyIdentifier=keyid:always 125 | 126 | [ ocsp ] 127 | # Extension for OCSP signing certificates (`man ocsp`). 128 | basicConstraints = CA:FALSE 129 | subjectKeyIdentifier = hash 130 | authorityKeyIdentifier = keyid,issuer 131 | keyUsage = critical, digitalSignature 132 | extendedKeyUsage = critical, OCSPSigning 133 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /micro-ca-tool: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## 3 | ## micro CA tool 4 | ## (C) 2015 SektionEins GmbH / Ben Fuhrmannek 5 | ## 6 | ## Licensed under the Apache License, Version 2.0 (the "License"); 7 | ## you may not use this file except in compliance with the License. 8 | ## You may obtain a copy of the License at 9 | ## 10 | ## http://www.apache.org/licenses/LICENSE-2.0 11 | ## 12 | ## Unless required by applicable law or agreed to in writing, software 13 | ## distributed under the License is distributed on an "AS IS" BASIS, 14 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ## See the License for the specific language governing permissions and 16 | ## limitations under the License. 17 | ## 18 | 19 | ## global defaults 20 | g_configfile="./micro-ca-tool.config" 21 | g_debug=0 22 | g_version="0.1" 23 | g_bin_search_path=( "/opt/local/bin" "/usr/local/bin" "/Library/OpenSC/bin" "/usr/local/opt/openssl/bin" ) 24 | g_engine_pkcs11_path=( /usr/lib/engines /usr/local/lib /Library/OpenSC/lib/engines ) 25 | g_opensc_pkcs11_path=( /usr/lib/x86_64-linux-gnu /Library/OpenSC/lib /usr/local/lib ) 26 | 27 | ## getopts 28 | function parse_options() { 29 | local opt 30 | while getopts "c:vh" opt; do 31 | case "$opt" in 32 | c) g_configfile="$OPTARG" ;; 33 | v) g_debug=1 ;; 34 | h) 35 | first_time_user 36 | msg "Usage: $0 [-c ] [-v] [-h] [ ]" 37 | msg " -c specify alternative config file" 38 | msg " -v be verbose" 39 | msg " -h show this help message" 40 | msg " optional [ ] directly calls a function, then exits." 41 | exit 0 42 | ;; 43 | *) exit 1 44 | esac 45 | done 46 | } 47 | 48 | ## 49 | 50 | function banner() { 51 | cat <"${cfg_ca_dir}/serial" 153 | fi 154 | if [[ ! -f "${cfg_ca_dir}/index.txt" ]]; then 155 | touch "${cfg_ca_dir}/index.txt" 156 | touch "${cfg_ca_dir}/index.txt.attr" 157 | fi 158 | ;; 159 | *) 160 | msg_error "$1: unknown app" 161 | return 162 | ;; 163 | esac 164 | save_config 165 | } 166 | 167 | function is_executable() { 168 | if [[ -z "$1" ]]; then 169 | return 1 170 | fi 171 | if which "$1" >/dev/null; then 172 | return 0 173 | fi 174 | return 1 175 | } 176 | 177 | function find_executable() { 178 | local dir fn fullfn 179 | local -a options 180 | local varname="$1" 181 | local prompt="$2" 182 | 183 | if is_executable ${!varname}; then 184 | msg_debug "using ${!varname}" 185 | return 186 | fi 187 | 188 | for fn in "${@:3}"; do 189 | if which "$fn" >/dev/null; then 190 | options=( "${options[@]}" "$fn" ) 191 | fi 192 | 193 | for dir in "${g_bin_search_path[@]}"; do 194 | fullfn="$dir/$fn" 195 | if [[ -x "$fullfn" ]]; then 196 | options=( "${options[@]}" "$fullfn" ) 197 | fi 198 | done 199 | done 200 | 201 | if [[ ${#options[@]} -eq 0 ]]; then 202 | readinput -v "$varname" -f is_executable "$prompt" 203 | msg_debug "using ${!varname}" 204 | else 205 | 206 | msg_input "$prompt" 207 | 208 | select fullfn in "${options[@]}"; do 209 | if [[ -z "$fullfn" ]]; then 210 | fullfn="$REPLY" 211 | fi 212 | 213 | if is_executable "$fullfn"; then 214 | eval "$varname=\"\$fullfn\"" 215 | msg_debug "using ${!varname}" 216 | return 217 | else 218 | msg_error "$fullfn: not executable" 219 | fi 220 | done 221 | fi 222 | } 223 | 224 | function find_file() { 225 | local dir varname="$1" prompt="$2" fn="$3" fullfn 226 | local -a options 227 | 228 | if [[ -n ${!varname} && -f ${!varname} ]] ; then 229 | msg_debug "using ${!varname}" 230 | return 231 | fi 232 | 233 | for dir in ${@:4}; do 234 | fullfn="$dir/$fn" 235 | if [[ -f "$fullfn" ]]; then 236 | options=( "${options[@]}" "$fullfn" ) 237 | fi 238 | done 239 | 240 | if [[ ${#options[@]} -eq 0 ]]; then 241 | readinput -v "$varname" -f validate_file_exists "$prompt" 242 | msg_debug "using ${!varname}" 243 | else 244 | 245 | msg_input "$prompt" 246 | 247 | select fullfn in "${options[@]}"; do 248 | if [[ -z "$fullfn" ]]; then 249 | fullfn="$REPLY" 250 | fi 251 | 252 | if validate_file_exists "$fullfn"; then 253 | eval "$varname=\"\$fullfn\"" 254 | msg_debug "using ${!varname}" 255 | return 256 | fi 257 | done 258 | fi 259 | } 260 | 261 | ## 262 | 263 | function readinput() { 264 | local opt 265 | local varname default_value validator_func dontask=0 266 | OPTIND=1 267 | while getopts "v:d:f:n" opt; do 268 | case "$opt" in 269 | v) varname="$OPTARG" ;; 270 | d) default_value="$OPTARG" ;; 271 | f) validator_func="$OPTARG" ;; 272 | n) dontask=1 ;; 273 | esac 274 | done 275 | shift $((OPTIND-1)) 276 | local prompt="$1" 277 | 278 | local input 279 | if [[ -n "$varname" && -n "${!varname}" ]]; then 280 | default_value="${!varname}" 281 | fi 282 | 283 | while true; do 284 | if [[ $dontask -eq 1 && -n "${!varname}" ]]; then 285 | dontask=0 286 | msg "${prompt} [${!varname}]" 287 | else 288 | read -er -p "[?] ${prompt} [$default_value] " input 289 | fi 290 | 291 | if [[ -z "$input" ]]; then 292 | input="$default_value" 293 | fi 294 | 295 | if [[ -n "$validator_func" ]]; then 296 | $validator_func "$input" 297 | if [[ $? != 0 ]]; then 298 | continue 299 | fi 300 | fi 301 | break 302 | done 303 | 304 | if [[ -n "$varname" ]]; then 305 | eval "$varname=\"\$input\"" 306 | else 307 | REPLY="$input" 308 | fi 309 | } 310 | 311 | function readinput_yesno() { 312 | readinput -f validate_yesno "$@" 313 | [[ "$REPLY" == "y" ]] 314 | } 315 | 316 | ## 317 | 318 | function showmenu() { 319 | local opt 320 | local prompt="" 321 | local menuname="unknown" 322 | local title="" 323 | OPTIND=1 324 | while getopts "p:n:t:" opt; do 325 | case "$opt" in 326 | p) prompt="$OPTARG" ;; 327 | n) menuname="$OPTARG" ;; 328 | t) title="$OPTARG" ;; 329 | esac 330 | done 331 | shift $((OPTIND-1)) 332 | local -a options=( "${@:1}" ) 333 | 334 | while true; do 335 | echo "" 336 | msg "$title" 337 | 338 | local i=0 339 | local k v input 340 | local -a keys=() 341 | local cmd="" 342 | while [[ $i -lt ${#options[@]} ]]; do 343 | k=${options[$i]} 344 | v=${options[$((i+1))]} 345 | keys=( "${keys[@]}" "$k" ) 346 | if [[ "x$k" == "x" ]]; then k=""; fi 347 | printf -- " | %10s %s\n" "$k" "$v" 348 | i=$((i+2)) 349 | done 350 | 351 | read -er -p "$prompt> " input 352 | if [[ $? -gt 0 ]]; then 353 | break 354 | fi 355 | 356 | for k in "${keys[@]}"; do 357 | if [[ "$input" == "$k" ]]; then 358 | cmd="do_${menuname}_${k}" 359 | break 360 | fi 361 | done 362 | 363 | if [[ "x$cmd" != "x" ]]; then 364 | $cmd 365 | if [[ "$?" == 2 ]]; then break; fi 366 | else 367 | msg ":( invalid command" 368 | fi 369 | done 370 | } 371 | 372 | ## openssl helper 373 | 374 | function openssl_engine() { 375 | echo "engine dynamic -pre \"SO_PATH:${cfg_file_engine_pkcs11}\" -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre \"MODULE_PATH:${cfg_file_opensc_pkcs11}\"" 376 | } 377 | 378 | ## menus 379 | 380 | function mainmenu() { 381 | local -a options=( 382 | "ca" "Certificate Authority" 383 | "sc" "SmartCard functions PKCS#11/PKCS#15" 384 | "gpg" "GNUPG shortcuts" 385 | "ssl" "openssl and related shortcuts" 386 | "q" "Quit") 387 | showmenu -t "MAIN MENU" -p "/" -n main "${options[@]}" 388 | } 389 | 390 | function do_main_q() { 391 | return 2 392 | } 393 | 394 | function do_main_ca() { 395 | local -a options=( 396 | "new-ca" "Generate new self-signed Root-CA" 397 | "new-key" "Generate new RSA private key file for the CA" 398 | "new-csr" "Generate a CSR for an intermediate CA (requires private key or smartcard)" 399 | "sign-csr" "Sign a Certificate Signing Request" 400 | "tocard" "Copy CA key and certificate to smartcard" 401 | "nofm" "encrypt CA's private key with Shamir's secret sharing scheme (n-of-m)" 402 | "nofm-recovery" "decrypt private key" 403 | "" "Back") 404 | showmenu -t "CA MENU" -p "CA" -n ca "${options[@]}" 405 | } 406 | 407 | function do_ca_() { 408 | return 2 409 | } 410 | 411 | function do_ca_new-ca() { 412 | need_resources openssl 413 | need_cfg cfg_ca_dir cfg_ca_keyfile/no_override cfg_ca_certfile/no_override cfg_ca_rsa_bits cfg_ca_days 414 | 415 | local keyfile="${cfg_ca_dir}/$cfg_ca_keyfile" 416 | local certfile="${cfg_ca_dir}/$cfg_ca_certfile" 417 | call "$cfg_app_openssl" genrsa -out "$keyfile" "$cfg_ca_rsa_bits" 418 | call chmod 600 "$keyfile" 419 | call "$cfg_app_openssl" req -x509 -new -nodes -key "$keyfile" -days "$cfg_ca_days" -out "$certfile" 420 | } 421 | 422 | function do_ca_new-key() { 423 | need_resources openssl 424 | need_cfg cfg_ca_dir cfg_ca_keyfile/no_override cfg_ca_rsa_bits 425 | local keyfile="${cfg_ca_dir}/$cfg_ca_keyfile" 426 | call "$cfg_app_openssl" genrsa -out "$keyfile" "$cfg_ca_rsa_bits" 427 | call chmod 600 "$keyfile" 428 | } 429 | 430 | function do_ca_tocard() { 431 | need_resources pkcs15-init 432 | need_cfg cfg_ca_dir cfg_ca_keyfile cfg_ca_certfile 433 | local keyfile="${cfg_ca_dir}/$cfg_ca_keyfile" 434 | local certfile="${cfg_ca_dir}/$cfg_ca_certfile" 435 | 436 | msg_warning "THIS ACTION OVERRIDES PREVIOUSLY STORED KEYS" 437 | if ! readinput_yesno -d n "Write key and certificate to card?"; then 438 | return 439 | fi 440 | call "$cfg_app_pkcs15_init" --delete-objects privkey,pubkey,cert --store-private-key "$keyfile" --id 3 --auth-id 3 --verify-pin 441 | call "$cfg_app_pkcs15_init" --store-certificate "$certfile" --id 3 --auth-id 3 --verify-pin 442 | } 443 | 444 | function do_ca_new-csr() { 445 | need_cfg cfg_ca_hsmmode 446 | local outfn="intermediateCA.csr" 447 | local subj="/CN=Example Intermediate CA" 448 | 449 | readinput -d "$subj" "Subject"; subj="$REPLY" 450 | readinput -d "$outfn" "CSR Filename" -f validate_file_not_exists; outfn="$REPLY" 451 | 452 | if [[ "${cfg_ca_hsmmode}" == "y" ]]; then 453 | need_resources openssl engine_pkcs11 opensc_pkcs11 454 | local key_id="02:03" 455 | readinput -d "$key_id" "Private Key (:)"; key_id="$REPLY" 456 | 457 | ( openssl_engine ; 458 | echo "req -new -engine pkcs11 -key \"$key_id\" -keyform engine -out \"$outfn\" -config ./openssl-intermediate.conf -subj \"$subj\"" ) | call "${cfg_app_openssl}" 459 | else 460 | need_resources openssl 461 | need_cfg cfg_ca_dir cfg_ca_keyfile 462 | 463 | local keyfile="${cfg_ca_dir}/$cfg_ca_keyfile" 464 | 465 | call ${cfg_app_openssl} req -new -key "$keyfile" -out "$outfn" -config ./openssl-intermediate.conf -subj "$subj" 466 | fi 467 | } 468 | 469 | function do_ca_sign-csr() { 470 | need_cfg cfg_ca_hsmmode 471 | local is_intca=n subj csrfile="request.csr" intca 472 | 473 | readinput -d "$csrfile" -f validate_file_exists "CSR file"; csrfile="$REPLY" 474 | 475 | readinput_yesno -d $is_intca "Sign intermediate CA?"; is_intca="$REPLY" 476 | if [[ "$is_intca" == "y" ]]; then 477 | intca="-extensions v3_intermediate_ca" 478 | fi 479 | 480 | readinput -d "$subj" "Subject, e.g. /CN=Admin-1 or empty to use CSR Subject"; subj="$REPLY" 481 | if [[ -n "$subj" ]]; then 482 | subj=( -subj "$subj" ) 483 | else 484 | subj=() 485 | fi 486 | 487 | local outfn="$(basename "$csrfile" .csr).crt" 488 | readinput -d "$outfn" "Output file" 489 | 490 | need_cfg cfg_ca_dir cfg_ca_certfile cfg_ca_signdays 491 | local certfile="${cfg_ca_dir}/$cfg_ca_certfile" 492 | 493 | call "${cfg_app_openssl}" req -in "$csrfile" -noout -text 494 | if ! readinput_yesno -d n "Sign this CSR?"; then 495 | msg_debug "not signing" 496 | return 497 | fi 498 | 499 | need_resource casetup 500 | 501 | if [[ "${cfg_ca_hsmmode}" == "y" ]]; then 502 | need_resources openssl engine_pkcs11 opensc_pkcs11 503 | local key_id="02:03" 504 | readinput -d "$key_id" "Private Key (:)"; key_id="$REPLY" 505 | 506 | 507 | local openssl_cmd="ca -engine pkcs11 -keyfile \"$key_id\" -keyform engine -config ./openssl-rootca.conf -batch -days $cfg_ca_signdays "${subj[@]}" $intca -cert \"$certfile\" -out \"$outfn\" -infiles \"$csrfile\" " 508 | msg_debug "$(openssl_engine)" 509 | msg_debug "$openssl_cmds" 510 | ( openssl_engine ; echo "$openssl_cmd" ) | call "${cfg_app_openssl}" 511 | else 512 | need_cfg cfg_ca_keyfile 513 | call "${cfg_app_openssl}" ca -keyfile "${cfg_ca_dir}/${cfg_ca_keyfile}" -config ./openssl-rootca.conf -batch -days $cfg_ca_signdays "${subj[@]}" $intca -cert "$certfile" -out "$outfn" -infiles "$csrfile" 514 | fi 515 | } 516 | 517 | function do_ca_nofm() { 518 | need_resources openssl ssss-split 519 | need_cfg cfg_ca_dir cfg_ca_keyfile 520 | local keyfile="${cfg_ca_dir}/$cfg_ca_keyfile" 521 | local n=3 m=5 522 | 523 | readinput -d $n "Threshold (n)"; n="$REPLY" 524 | readinput -d $m "Shares (m)"; m="$REPLY" 525 | 526 | local pw=$(${cfg_app_openssl} rand -hex 16) 527 | echo "$pw" | call "${cfg_app_ssss_split}" -t "$n" -n "$m" 528 | echo "$pw" | call ${cfg_app_openssl} enc -aes-256-cbc -in "$keyfile" -out "${keyfile}.enc" -pass stdin -a 529 | unset pw 530 | } 531 | 532 | function do_ca_nofm-recovery() { 533 | need_resources openssl ssss-combine 534 | need_cfg cfg_ca_dir cfg_ca_keyfile/nocheck 535 | local keyfile="${cfg_ca_dir}/$cfg_ca_keyfile" 536 | local keyfileenc="${keyfile}.enc" 537 | 538 | if [[ ! -r "$keyfileenc" ]]; then 539 | msg_error "$keyfileenc: file not found or not readable" 540 | return 541 | fi 542 | if [[ -f "$keyfile" ]]; then 543 | msg_error "$keyfile: keyfile already exists. no need for recovery." 544 | return 545 | fi 546 | 547 | local n=3 548 | readinput -d $n "Threshold (n)"; n="$REPLY" 549 | 550 | msg_input "Enter $n password shares:" 551 | ${cfg_app_ssss_combine} -t "$n" -Q 2>&1 | call ${cfg_app_openssl} enc -aes-256-cbc -d -in "$keyfileenc" -out "${keyfile}" -pass stdin -a 552 | call ${cfg_app_openssl} rsa -in "$keyfile" -check -noout 553 | } 554 | 555 | function do_main_gpg() { 556 | local -a options=( 557 | "v" "show gpg version" 558 | "cs" "smartcard status" 559 | "ce" "smartcard edit" 560 | "pin" "change pin/admin pin or unblock pin" 561 | "kill" "kill scdaemon (useful for PKCS#11 operations)" 562 | "agent" "connect to a running gpg agent" 563 | "" "Back") 564 | showmenu -t "GPG MENU" -p "GPG" -n gpg "${options[@]}" 565 | 566 | } 567 | 568 | function do_gpg_cs() { 569 | need_resource gpg 570 | call "${cfg_app_gpg}" --card-status 571 | } 572 | function do_gpg_ce() { 573 | need_resource gpg 574 | call "${cfg_app_gpg}" --card-edit 575 | } 576 | 577 | function do_gpg_v() { 578 | need_resource gpg 579 | call "${cfg_app_gpg}" --version 580 | } 581 | 582 | function do_gpg_pin() { 583 | need_resource gpg 584 | call "${cfg_app_gpg}" --change-pin 585 | } 586 | 587 | function do_gpg_kill() { 588 | call killall -9 scdaemon 589 | } 590 | 591 | function do_gpg_agent() { 592 | need_resource gpg-connect-agent 593 | call "${cfg_app_gpg_connect_agent}" 594 | } 595 | 596 | function do_gpg_() { 597 | return 2 598 | } 599 | 600 | function do_main_sc() { 601 | local -a options=( 602 | "i" "List public keys, private keys and certificates" 603 | "ssh" "Show SSH public key with pkcs15-tool" 604 | "ssh11" "Show SSH public key with openssh via PKCS#11" 605 | "new-key" "Generate new public/private key pair on smartcard" 606 | "new-csr" "Generate Certificate Signing Request" 607 | "store-key" "Store private key on smartcard" 608 | "store-crt" "Store certificate on smartcard" 609 | "read-crt" "Retrieve certificate from smartcard" 610 | "cryptostick-reset-REALLY" "Reset Cryptostick + Nitrokey Pro/Start to defaults" 611 | "" "Back") 612 | showmenu -t "SMARTCARD MENU" -p "SC" -n sc "${options[@]}" 613 | } 614 | 615 | function do_sc_i() { 616 | need_resource pkcs15-tool 617 | call "${cfg_app_pkcs15_tool}" --list-keys --list-certificates --list-public-keys 618 | } 619 | 620 | function do_sc_ssh() { 621 | need_resource pkcs15-tool 622 | need_cfg tmp_key_id 623 | call "${cfg_app_pkcs15_tool}" --read-ssh-key "$tmp_key_id" 624 | } 625 | 626 | function do_sc_ssh11() { 627 | need_resources ssh-keygen opensc_pkcs11 628 | call ${cfg_app_ssh_keygen} -D ${cfg_file_opensc_pkcs11} 629 | } 630 | 631 | function do_sc_new-key() { 632 | local key_spec="rsa/2048" 633 | need_resource pkcs15-init 634 | 635 | readinput -d $key_spec "Key Specification"; key_spec="$REPLY" 636 | need_cfg tmp_key_id tmp_auth_id 637 | 638 | msg_warning "IF SET, THIS WILL OVERRIDE A PREVIOUS KEY WITH ID $tmp_key_id." 639 | if ! readinput_yesno -d n "Please confirm operation"; then 640 | return 641 | fi 642 | 643 | OPENSC_DEBUG=${OPENSC_DEBUG:-2} call time "${cfg_app_pkcs15_init}" \ 644 | --delete-objects privkey,pubkey,cert \ 645 | --generate-key "$key_spec" --id "$tmp_key_id" --auth-id "$tmp_auth_id" --verify-pin 646 | } 647 | 648 | function do_sc_new-csr() { 649 | local outfn="usercert-$(printf "%04d" $(($RANDOM % 10000))).csr" 650 | local subj="/CN=Hans Wurst" key_id="02:03" 651 | 652 | need_resources openssl engine_pkcs11 opensc_pkcs11 653 | readinput -d "$subj" "Subject"; subj="$REPLY" 654 | readinput -d "$outfn" "CSR Filename"; outfn="$REPLY" 655 | readinput -d "$key_id" "Private Key (:)"; key_id="$REPLY" 656 | 657 | ( openssl_engine ; 658 | echo "req -new -engine pkcs11 -key \"$key_id\" -keyform engine -out \"$outfn\" -subj \"$subj\"" ) | call "${cfg_app_openssl}" 659 | 660 | } 661 | 662 | function do_sc_store-key() { 663 | need_resources pkcs15-init 664 | local keyfile key_id=03 auth_id=03 665 | 666 | readinput -d "$keyfile" -f validate_file_exists "Key file (PEM)"; keyfile="$REPLY" 667 | need_cfg tmp_key_id tmp_auth_id 668 | 669 | msg_warning "IF SET, THIS WILL OVERRIDE A PREVIOUS KEY WITH ID $tmp_key_id." 670 | if ! readinput_yesno -d n "Please confirm operation"; then 671 | return 672 | fi 673 | 674 | call "$cfg_app_pkcs15_init" --delete-objects privkey,pubkey,cert --store-private-key "$keyfile" --id $tmp_key_id --auth-id $tmp_auth_id --verify-pin 675 | } 676 | 677 | function do_sc_store-crt() { 678 | need_resources pkcs15-init 679 | local certfile 680 | 681 | readinput -d "$certfile" -f validate_file_exists "Certificate file (PEM)"; certfile="$REPLY" 682 | need_cfg tmp_key_id tmp_auth_id 683 | 684 | msg_warning "IF SET, THIS WILL OVERRIDE A PREVIOUS KEY WITH ID $tmp_key_id." 685 | if ! readinput_yesno -d n "Please confirm operation"; then 686 | return 687 | fi 688 | call "$cfg_app_pkcs15_init" --delete-objects cert --store-certificate "$certfile" --id $tmp_key_id --auth-id $tmp_auth_id --verify-pin 689 | } 690 | 691 | function do_sc_read-crt() { 692 | need_resources pkcs15-tool 693 | need_cfg tmp_key_id 694 | local certfile="output-${tmp_key_id}.crt" 695 | 696 | readinput -d "$certfile" -f validate_file_not_exists "Output certificate file"; certfile="$REPLY" 697 | 698 | call "$cfg_app_pkcs15_tool" -r "$tmp_key_id" >$certfile 699 | } 700 | 701 | function do_sc_cryptostick-reset-REALLY() { 702 | need_resource opensc-tool 703 | msg_warning "THIS WILL DESTROY ALL KEYS AND RESET PINS." 704 | if ! readinput_yesno -d n "Please confirm factory reset of Cryptostick/Nitrokey"; then 705 | return 706 | fi 707 | 708 | call "${cfg_app_opensc_tool}" \ 709 | -s 00:20:00:81:08:40:40:40:40:40:40:40:40 \ 710 | -s 00:20:00:81:08:40:40:40:40:40:40:40:40 \ 711 | -s 00:20:00:81:08:40:40:40:40:40:40:40:40 \ 712 | -s 00:20:00:81:08:40:40:40:40:40:40:40:40 \ 713 | -s 00:20:00:83:08:40:40:40:40:40:40:40:40 \ 714 | -s 00:20:00:83:08:40:40:40:40:40:40:40:40 \ 715 | -s 00:20:00:83:08:40:40:40:40:40:40:40:40 \ 716 | -s 00:20:00:83:08:40:40:40:40:40:40:40:40 \ 717 | -s 00:e6:00:00 \ 718 | -s 00:44:00:00 && msg "done." 719 | } 720 | 721 | function do_sc_() { 722 | return 2 723 | } 724 | 725 | function do_main_ssl() { 726 | local -a options=( 727 | "gen-dh" "Generates DH (Diffie-Hellman) parameters" 728 | "i" "x509 certificate info" 729 | "new-csr" "Generate new private key and CSR" 730 | "new-pair" "Generate new RSA private/public key pair" 731 | "new-ec" "Generate new EC privace/public key pair" 732 | "ssh2pem" "Convert RSA public key from RFC4716 (SSH) format to PEM" 733 | "pem2ssh" "Convert RSA public key from PEM format to RFC4716" 734 | "" "Back") 735 | showmenu -t "OPENSSL MENU" -p "SSL" -n ssl "${options[@]}" 736 | } 737 | 738 | function do_ssl_gen-dh() { 739 | need_resources openssl 740 | need_cfg cfg_ca_dir cfg_dh_file/no_override cfg_dh_size 741 | 742 | local dhfile="${cfg_ca_dir}/$cfg_dh_file" 743 | call "$cfg_app_openssl" genpkey -genparam -algorithm DH -pkeyopt dh_paramgen_prime_len:${cfg_dh_size} -out "$dhfile" 744 | call chmod 644 "$dhfile" 745 | } 746 | 747 | function do_ssl_i() { 748 | need_resources openssl 749 | local certfile 750 | readinput -d "input.crt" -f validate_ca_file_exists "Certificate file"; certfile="$REPLY" 751 | call "${cfg_app_openssl}" x509 -in "$certfile" -text -noout 752 | } 753 | 754 | function do_ssl_new-csr() { 755 | need_resources openssl 756 | local outbase="usercert-$(printf "%04d" $(($RANDOM % 10000)))" 757 | local outcsr="${outbase}.csr" 758 | local outkey="${outbase}.key" 759 | local key_spec="rsa:4096" 760 | readinput -d "$outcsr" -f validate_file_not_exists "CSR file"; outcsr="$REPLY" 761 | readinput -d "$outkey" -f validate_file_not_exists "Key file"; outkey="$REPLY" 762 | readinput -d $key_spec "Key Specification"; key_spec="$REPLY" 763 | 764 | call "${cfg_app_openssl}" req -out "$outcsr" -new -newkey "$key_spec" -nodes -keyout "$outkey" 765 | call chmod 600 "$outkey" 766 | } 767 | 768 | function do_ssl_new-pair() { 769 | need_resources openssl 770 | local outbase="usercert-$(printf "%04d" $(($RANDOM % 10000)))" 771 | local outpub="${outbase}.pub" 772 | local outkey="${outbase}.key" 773 | local rsa_bits="4096" 774 | 775 | readinput -d $rsa_bits "RSA bits (e.g. 2048 or 4096)"; rsa_bits="$REPLY" 776 | readinput -d "$outkey" -f validate_file_not_exists "Key file"; outkey="$REPLY" 777 | readinput -d "$outpub" -f validate_file_not_exists "Public key file"; outpub="$REPLY" 778 | 779 | call "$cfg_app_openssl" genrsa -out "$outkey" "$rsa_bits" 780 | call chmod 600 "$outkey" 781 | call "$cfg_app_openssl" rsa -in "$outkey" -pubout -out "$outpub" 782 | } 783 | 784 | function do_ssl_new-ec() { 785 | need_resources openssl 786 | local outbase="usercert-$(printf "%04d" $(($RANDOM % 10000)))" 787 | local outpub="${outbase}.pub" 788 | local outkey="${outbase}.key" 789 | local curve="prime256v1" 790 | 791 | call "$cfg_app_openssl" ecparam -list_curves 792 | readinput -d $curve "EC curve"; curve="$REPLY" 793 | readinput -d "$outkey" -f validate_file_not_exists "Key file"; outkey="$REPLY" 794 | readinput -d "$outpub" -f validate_file_not_exists "Public key file"; outpub="$REPLY" 795 | 796 | call "$cfg_app_openssl" ecparam -name $curve -genkey -noout -out "$outkey" 797 | call chmod 600 "$outkey" 798 | call "$cfg_app_openssl" ec -in "$outkey" -pubout -out "$outpub" 799 | } 800 | 801 | function do_ssl_ssh2pem() { 802 | need_resources ssh-keygen 803 | local infile="~/.ssh/id_rsa.pub" 804 | local outfile="id_rsa.pem" 805 | 806 | readinput -d "$infile" -f validate_file_exists_home "Input: SSH public key file"; infile="${REPLY/\~/$HOME}" 807 | readinput -d "$outfile" -f validate_file_not_exists_home "Output: PEM file"; outfile="${REPLY/\~/$HOME}" 808 | 809 | msg_debug "calling: $cfg_app_ssh_keygen -f $infile -e -m pem >$outfile" 810 | "$cfg_app_ssh_keygen" -f "$infile" -e -m pem >"$outfile" 811 | } 812 | 813 | function do_ssl_pem2ssh() { 814 | need_resources ssh-keygen 815 | local infile="id_rsa.pem" 816 | local outfile="id_rsa.pub" 817 | 818 | readinput -d "$infile" -f validate_file_exists_home "Input: RSA public key PEM file"; infile="${REPLY/\~/$HOME}" 819 | readinput -d "$outfile" -f validate_file_not_exists_home "Output: SSH keyfile"; outfile="${REPLY/\~/$HOME}" 820 | 821 | msg_debug "calling: $cfg_app_ssh_keygen -f $infile -e -m pem >$outfile" 822 | "$cfg_app_ssh_keygen" -f "$infile" -i -m PEM >"$outfile" 823 | } 824 | 825 | function do_ssl_() { 826 | return 2 827 | } 828 | 829 | 830 | ## 831 | 832 | function need_cfg() { 833 | for name in "$@"; do 834 | case "$name" in 835 | cfg_ca_dir) readinput -n -v cfg_ca_dir -d . -f validate_dir_writable "CA directory" ;; 836 | cfg_ca_keyfile) readinput -n -v cfg_ca_keyfile -d rootCA.key -f validate_ca_file_exists "CA private key file" ;; 837 | cfg_ca_keyfile/no_override) readinput -v cfg_ca_keyfile -d rootCA.key -f validate_ca_file_override "CA private key file" ;; 838 | cfg_ca_keyfile/nocheck) readinput -n -v cfg_ca_keyfile -d rootCA.key "CA private key file" ;; 839 | cfg_ca_certfile) readinput -v cfg_ca_certfile -d rootCA.crt -f validate_ca_file_exists "CA public key file" ;; 840 | cfg_ca_certfile/no_override) readinput -v cfg_ca_certfile -d rootCA.crt -f validate_ca_file_override "CA public key file" ;; 841 | cfg_ca_rsa_bits) readinput -v cfg_ca_rsa_bits -d 4096 "RSA bits (e.g. 1024, 2048, 3072, 4096, 8192)?" ;; 842 | cfg_ca_days) readinput -v cfg_ca_days -d 3650 "Validity period in days" ;; 843 | cfg_ca_signdays) readinput -v cfg_ca_signdays -d 3600 "Validity period in days" ;; 844 | cfg_ca_hsmmode) readinput_yesno -d y -v cfg_ca_hsmmode "Use HSM / hardware token?" ;; 845 | cfg_dh_file/no_override) readinput -v cfg_dh_file -d dh.pem -f validate_dh_file_override "Diffie-Hellman file" ;; 846 | cfg_dh_size) readinput -v cfg_dh_size -d 4096 "Diffie-Hellman bits (e.g. 1024, 2048, 3072, 4096, 8192)?" ;; 847 | tmp_key_id) 848 | msg "Key IDs for Cryptostick/Nitrokey: 01=signature key, 02=encryption key, 03=authentication key" 849 | readinput -v tmp_key_id -d 03 "Key ID" 850 | ;; 851 | tmp_auth_id) 852 | msg "Auth IDs for Cryptostick/Nitrokey: 01=user signature pin, 02=user pin, 03=admin pin" 853 | readinput -v tmp_auth_id -d 03 "Auth ID" 854 | ;; 855 | *) msg_error "internal error: unknown cfg '$name'" ;; 856 | esac 857 | done 858 | save_config 859 | } 860 | 861 | ## validators 862 | 863 | function validate_yesno() { 864 | case "$1" in 865 | y|n) return 0 ;; 866 | *) return 1 ;; 867 | esac 868 | } 869 | 870 | function validate_dir_writable() { 871 | if [[ ! -d "$1" ]]; then 872 | if readinput_yesno -d y "Directory does not exist. Create?"; then 873 | call mkdir -p "$1" 874 | call chmod 700 "$1" 875 | fi 876 | fi 877 | [[ -w "$1" ]] 878 | } 879 | 880 | 881 | function validate_ca_file_override() { 882 | local keyfile="${cfg_ca_dir}/$1" 883 | if [[ -f "$keyfile" ]]; then 884 | msg_error "Certificate file '$keyfile' already exists. Please remove manually if you really know what you are doing." 885 | return 1 886 | fi 887 | return 0 888 | } 889 | 890 | function validate_ca_file_exists() { 891 | local keyfile="${cfg_ca_dir}/$1" 892 | if [[ ! -r "$keyfile" ]]; then 893 | msg_error "Certificate file '$keyfile' does not exist or is not readable." 894 | return 1 895 | fi 896 | return 0 897 | } 898 | 899 | function validate_ca_file_not_exists() { 900 | local keyfile="${cfg_ca_dir}/$1" 901 | if [[ -f "$keyfile" ]]; then 902 | msg_error "Certificate file '$keyfile' already exists." 903 | return 1 904 | fi 905 | return 0 906 | } 907 | 908 | function validate_file_exists() { 909 | if [[ ! -f "$1" ]]; then 910 | msg_error "$1: file not found" 911 | return 1 912 | fi 913 | return 0 914 | } 915 | 916 | function validate_file_not_exists() { 917 | if [[ -f "$1" ]]; then 918 | msg_error "$1: file exists" 919 | return 1 920 | fi 921 | return 0 922 | } 923 | 924 | function validate_file_exists_home() { 925 | if [[ ! -f "${1/\~/$HOME}" ]]; then 926 | msg_error "$1: file not found" 927 | return 1 928 | fi 929 | return 0 930 | } 931 | 932 | function validate_file_not_exists_home() { 933 | if [[ -f "${1/\~/$HOME}" ]]; then 934 | msg_error "$1: file exists" 935 | return 1 936 | fi 937 | return 0 938 | } 939 | 940 | function validate_dh_file_override() { 941 | local dhfile="${cfg_ca_dir}/$1" 942 | if [[ -f "$dhfile" ]]; then 943 | msg_error "Diffie-Hellman file '$dhfile' already exists. Please remove manually if you really know what you are doing." 944 | return 1 945 | fi 946 | return 0 947 | } 948 | 949 | ## config 950 | 951 | function save_config() { 952 | local opt 953 | echo "## autogenerated config file for micro-ca-tool $g_version" >$g_configfile 954 | for opt in "${!cfg_ca@}" "${!cfg_app@}" "${!cfg_file@}"; do 955 | echo "$opt=\"${!opt/\"/\\\"}\"" >>$g_configfile 956 | done 957 | } 958 | 959 | function read_config() { 960 | if [[ -f "$g_configfile" ]]; then 961 | . "$g_configfile" 962 | else 963 | first_time_user 964 | save_config 965 | fi 966 | } 967 | 968 | function first_time_user() { 969 | fold -s <