· 5 years ago · May 06, 2020, 09:36 AM
1function processCmd_ca {
2 # Return processing if we don't have at least two space separated arguments passed to the function.
3 local cmdArgs=()
4 read -a cmdArgs <<< "$1"
5 if [ ! -n "${cmdArgs[1]}" ] ; then printf " %s\n" "Error: missing arguments. Try 'help ca'." ; return 1 ; fi
6
7 # If we made it this far, then 'ca <something>' was passed to the function.
8 # We've already split the entire command line into the ${cmdArgs[]} array
9 # by space and can now look at individual arguments.
10
11 case "${cmdArgs[1]}" in
12 'import')
13 # 'import' requires the following syntax: ca import set alias ALIAS
14 # Position in array: 0 1 2 3 4
15
16 if [ -n "${cmdArgs[5]}" ] ; then printf " %s\n" "Error: too many arguments. Try 'help ca'." ; return 1 ; fi
17 if [[ ! "${#cmdArgs[@]}" -gt 4 ]] ; then printf " %s\n" "Error: missing arguments. Try 'help ca'." ; return 1 ; fi
18 if [[ ! "${cmdArgs[2]}" == 'set' ]] ; then printf " %s\n" "Error: invalid arguments. Try 'help ca'." ; return 1 ; fi
19 if [[ ! "${cmdArgs[3]}" == 'alias' ]] ; then printf " %s\n" "Error: invalid arguments. Try 'help ca'." ; return 1 ; fi
20 if [[ "${#cmdArgs[4]}" -gt 32 ]] ; then printf " %s\n" "Error: bad alias value (too long)." ; return 1 ; fi
21 if ! printf -- "%s\n" "${cmdArgs[4]}" | grep -xEq '^[a-zA-Z][a-zA-Z0-9_-]*$' ; then printf " %s\n" "Error: bad alias value (illegal characters)." ; return 1 ; fi
22
23 # If we are still here then the comand input was sane and the alias value plausible.
24 # We just need to check if the provided alias is already taken.
25
26 local caAlias="${cmdArgs[4]}"
27 local dbResult=$(sqlite3 "${ovpngwcDb}" "SELECT COUNT(*) FROM certs WHERE alias='${caAlias}';") #"#
28 if [[ ! "${dbResult}" == '0' ]] ; then printf " %s\n" "Error: alias not available (already taken)." ; return 1; fi
29
30 # If we are still here, then the command input was sane and the alias useable.
31 # We can now prompt the user to paste in the CA certificate they wish to import.
32 printf " %s\n" "Paste the PEM encoded CA certificate you wish to import and send RETURN on an empty line to submit."
33 printf "\n%b\n\n" "$(prompt)${ColorGrayOnGray} begin paste ${ColorOrangeOnGray}│${ColorGrayOnGray} PEM encoded certificate ${ColorReset}${ColorTextOrange}"
34
35 local caImport
36 local pasteCA
37
38 # User needs to paste in the CA in PEM format.
39 # We loop a read with IFS set to nothing until an empty newline is received.
40
41 while IFS='' read -r pasteCA && [[ "${pasteCA}" != "" ]] ; do
42 local caImport+="${pasteCA}"$'\n'
43 done
44
45 printf "%b\n\n" "$(prompt)${ColorGrayOnGray} end paste ${ColorOrangeOnGray}│${ColorGrayOnGray} PEM encoded certificate ${ColorReset}"
46
47 # Check if provided certificate is actually a CA; throw error if it is not.
48 if ! ( printf "%b" "${caImport}" | openssl x509 -noout -ext basicConstraints 2>/dev/null | grep -i 'CA:TRUE' >/dev/null ) ; then printf " %s\n" "Error: input validation failed (not a CA certificate)." ; return 1 ; fi
49
50 printf " %s\n" "Paste the PEM encoded private key matching the above provided CA certificate and send RETURN on an empty line to submit."
51 printf "\n%b\n\n" "$(prompt)${ColorGrayOnGray} begin paste ${ColorOrangeOnGray}│${ColorGrayOnGray} PEM encoded private key ${ColorReset}${ColorTextOrange}"
52
53 local privkeyImport
54 local pasteKey
55
56 # Next, user needs to paste in the private key in PEM format.
57 # We loop a read with IFS set to nothing until an empty newline is received.
58
59 while IFS='' read -r pasteKey && [[ "${pasteKey}" != "" ]] ; do
60 local privkeyImport+="${pasteKey}"$'\n'
61 done
62 printf "%b\n\n" "$(prompt)${ColorGrayOnGray} end paste ${ColorOrangeOnGray}│${ColorGrayOnGray} PEM encoded private key ${ColorReset}"
63
64 # Check if provided key is actually a private key; throw error if it is not
65 if ! ( printf "%b" "${privkeyImport}" | openssl rsa -check -noout 2>/dev/null | grep -i 'RSA key ok' >/dev/null ) ; then printf " %s\n" "Error: input validation failed (not a private key)." ; return 1 ; fi
66
67 # Next we need to check if the supplied private key matches the CA certificate.
68 # They match if they have the same modulus.
69
70 local -r certModulus="$(printf -- "%s" "${caImport}" | openssl x509 -modulus -noout | openssl md5 | awk -- '{ print $2 }')"
71 local -r keyModulus="$(printf -- "%s" "${privkeyImport}" | openssl rsa -modulus -noout | openssl md5 | awk -- '{ print $2 }')"
72
73 if [[ ! "${certModulus}" == "${keyModulus}" ]] ; then printf " %s\n" "Error: private key does not match certificate. Operation failed." ; return 1 ; fi
74
75 # If we made it this far then we have all the basics we need to store
76 # the CA into the database. First off, we extract:
77 # - the 'cn' value,
78 # - the expiration date from the 'notAfter' value,
79 # - private key size from the 'RSA-Private-Key' value,
80
81 local cn="$(printf "%s" "${caImport}" | openssl x509 -noout -subject -nameopt multiline | grep 'commonName' | sed -n 's/ *commonName *= //p')"
82 local expDate="$(printf "%s" "${caImport}" | openssl x509 -enddate -noout | sed -e "s/notAfter=//g" | date -f - +%Y-%m-%d' '%H:%M:%S)"
83 local keySize=$(printf "%s" "${privkeyImport}" | openssl rsa -text -noout | grep "RSA Private-Key" | awk '{ print $3 }' | sed 's/(//g')
84
85 # Next we extract the public key from the private key.
86 local pubkeyImport="$(printf "%s" "${keyImport}" | openssl rsa -pubout 2>/dev/null)"
87
88 # Next, we encrypt and save the CA cert. To do so, we first need to decrypt the
89 # master key which is encrypted with the password of the admin user executing
90 # the command. We have the password of the admin executing the command either in
91 # the ${ovpngwcPassword} variable set by the authentication function or in the
92 # ${initializedPassword} variable set with processCmd_sys '_init' <...> function.
93 # Hence we rewrite the ${ovpngwcPassword} with ${initializedPassword} only if
94 # ${initializedPassword} is set (it is only set on first run of ovpngwc and does
95 # not get configured on subsequent runs).
96
97 if [[ ! -n "${ovpngwcPassword}" ]] ; then ovpngwcPassword="${initializedPassword}" ; fi
98
99 # We fetch the adminkey of the admin executing the command and decrypt it with the
100 # above password ${ovpngwcPassword}.
101
102 local dbResult="$(sqlite3 "${ovpngwcDb}" "SELECT encrypted_key FROM adminkeys INNER JOIN users ON adminkeys.user_id=users.user_id WHERE users.user_id='${ovpngwcAccountId}';")"
103 local decryptedKey="$(printf -- "%s\n" "${dbResult}" | openssl enc -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${ovpngwcPassword}")"
104
105 # With the admin key decrypted, we now symmetrically encrypt the CA cert, the pubkey and the privkey with ${decryptedKey}.
106 local encryptedCAcert="$(openssl enc -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${decryptedKey}" <<< "${caImport}")"
107 local encryptedCAprivkey="$(openssl enc -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${decryptedKey}" <<< "${privkeyImport}")"
108 local encryptedCApubkey="$(openssl enc -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${decryptedKey}" <<< "${pubkeyImport}")"
109
110 # Before inserting the record, we have to check if the provided alias already exists.
111 local dbResult="$(sqlite3 "${ovpngwcDb}" "SELECT COUNT(*) FROM certs WHERE alias='${caAlias}';")" #"#
112
113 # CERTS
114 # +---------+----+-------+------+---------+--------+---------+-------+-------+---------+---------+---------+
115 # | cert_id | cn | alias | cert | privkey | pubkey | keysize | ca_id | is_ca | expires | enabled | commnet |
116 # +---------+----+-------+------+---------+--------+---------+-------+-------+---------+---------+---------+
117
118 # When inserting the record, we need to get the cert_id value for filling in the ca_id
119 # value. However, cert_id is only assigned after the record is inserted so we need to use
120 # the sqlite3's last_insert_rowid() function to retrieve the cert_id value. Since we are
121 # depending on two sequentially executed SQL queries, we wrap everything into a single
122 # transaction. We save the result into ${caId} so that we can reuse the value in the
123 # subsequent UPDATE query.
124
125 local caId="$(sqlite3 "${ovpngwcDb}" "BEGIN TRANSACTION; INSERT INTO certs(cn, alias, cert, privkey, pubkey, keysize, ca_id, is_ca, expires, enabled) VALUES ('${cn}', '${caAlias}', '${encryptedCAcert}', '${encryptedCAprivkey}', '${encryptedCApubkey}', '${keySize}', '0', '1', '${expDate}', '1'); SELECT last_insert_rowid(); COMMIT;")" #"#
126 sqlite3 "${ovpngwcDb}" "UPDATE certs set ca_id='${caId}' WHERE cert_id='${ca_id}';"
127
128 printf " %s\n" "Successfully imported CA."
129
130################
131## THIS WORKS ##
132################
133
134echo "Testing retrieval ..."
135 # With the admin key decrypted, we fetch the CA certificate and symmetrically decrypt it.
136 local encryptedCert=$(sqlite3 "${ovpngwcDb}" "SELECT cert FROM certs WHERE alias='${caAlias}';")
137# printf -- "%s\n" "$encryptedCert" | openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${decryptedKey}"
138echo "${encryptedCert}"
139 # With the certificate decrypted we can now show it to the user
140# printf -- "%s\n" "${decryptedCert}" | openssl x509 -noout -text
141
142echo " decryption test 1"
143openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${decryptedKey}" <<< "${encryptedCert}"
144
145echo " decryption test 2"
146local dbResult="$(sqlite3 "${ovpngwcDb}" "SELECT cert FROM certs WHERE alias='${caAlias}';")"
147local decryptedcert2="$(openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${decryptedKey}" <<< "${dbResult}")"
148printf "%s\n" "${decryptedcert2}"
149
150
151
152 # Create the CA files and folder structure for the imported certificate authorithy.
153 mkdir -p "/etc/ovpngwc/cas/${caAlias}"
154 mkdir -p "/etc/ovpngwc/cas/${caAlias}/certreqs"
155 mkdir -p "/etc/ovpngwc/cas/${caAlias}/certs"
156 mkdir -p "/etc/ovpngwc/cas/${caAlias}/crl"
157 mkdir -p "/etc/ovpngwc/cas/${caAlias}/newcerts"
158 mkdir -p "/etc/ovpngwc/cas/${caAlias}/private"
159 touch "/etc/ovpngwc/cas/${caAlias}/root-ca.index"
160 openssl rand -hex 16 > "/etc/ovpngwc/cas/${caAlias}/root-ca.serial"
161 printf "%s\n" "00" > "/etc/ovpngwc/cas/${caAlias}/root-ca.crlnum"
162
163 # Next, we prepare the OpenSSL CA configuration file taking the certificate subject properties
164 # into consideration. We read the certificate subject (multiline) and use 'awk NR\>1' to
165 # skip the first line openssl returns ('subject='). We then use cut to separate the keys from values
166 # by taking everything left of the first encountered "=" as the key. We repeat the procedure for values
167 # with only slight alteration to the cut -f option to list everything right of the first encountered "=".
168 # We pair the keys and the values into a hash table using a for loop to run through all the keys and match
169 # them to values based on their order of sequence.
170
171 local oldIFS=$IFS
172 local subjectArrKeys=( $(printf -- "%s" "${caImport}" | openssl x509 -noout -subject -nameopt multiline | awk NR\>1 | cut -d '=' -f 1) )
173 local IFS=$'\n' ; local subjectArrValues=( $(printf -- "%s" "${caImport}" | openssl x509 -noout -subject -nameopt multiline | awk NR\>1 | cut -d '=' -f 2-) )
174 local -A subjectHashTable
175
176 local i=0
177 local IFS=$' '
178 for item in "${subjectArrKeys[@]}" ; do
179 local subjectKey="${item}"
180 local subjectValue="${subjectArrValues[$i]}"
181 local subjectHashTable["${subjectKey}"]="${subjectValue}"
182 local i=$((i + 1))
183 done
184 local IFS=$oldIFS
185 unset i item subjectKey subjectValue subjectArrKeys subjectArrValues
186
187 # We can now refer to various subject values and use them directly through the
188 # hash table. We want to compress unneded spaces and tabs if they were entered.
189 # To do this we iterate through the keys of the hash table and use awk to compress
190 # spaces/tabs (awk rebuilds the whole record when you assign something to one of the
191 # fields and in doing soseparates fields with OFS, where OFS by default is a single
192 # space).
193
194 for item in "${!subjectHashTable[@]}" ; do
195 local subjectKey="${item}"
196 local subjectValue="${subjectHashTable[${subjectKey}]}"
197 local subjectValue="$(printf "%s" "${subjectValue}" | awk '{$1=$1; print}')" # Compress unwanted whitespace
198 # Reset the key value
199 local subjectHashTable["${subjectKey}"]="${subjectValue}"
200 done
201 unset item subjectKey subjectValue
202
203
204 printf "%s\n" '# OVPNGWC generated OpenSSL configuration for the Root Certification Authority.' > "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
205 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
206 printf "%s\n" '############################### !!! IMPORTANT !!! ###############################' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
207 printf "%s\n" '# #' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
208 printf "%s\n" '# If you know what you are doing, you may change some CA options here, however #' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
209 printf "%s\n" '# you must not change any values within the specified CRITICAL SECTIONS (if you #' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
210 printf "%s\n" '# change those, ovpngwc integration will break!). #' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
211 printf "%s\n" '# #' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
212 printf "%s\n" '##################################################################################' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
213 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
214 printf "%s\n" '### BEGIN CRITICAL SECTION -------------------------------------------------------' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
215 printf "%s\n" "CA_HOME = /etc/ovpngwc/${caAlias}" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
216 printf "%s\n" 'RANDFILE = $ENV::CA_HOME/private/.rnd' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
217 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
218 printf "%s\n" '[ ca ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
219 printf "%s\n" 'default_ca = root_ca' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
220 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
221 printf "%s\n" '[ root_ca ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
222 printf "%s\n" 'dir = $ENV::CA_HOME' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
223 printf "%s\n" 'certs = $dir/certsdb' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
224 printf "%s\n" 'new_certs_dir = $dir/newcerts' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
225 printf "%s\n" 'crl_dir = $dir/crl' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
226 printf "%s\n" 'serial = $dir/ca.serial' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
227 printf "%s\n" 'database = $dir/ca.index' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
228 printf "%s\n" 'certificate = $dir/ca.cert.pem' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
229 printf "%s\n" 'private_key = $dir/private/root-ca.key.pem' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
230 printf "%s\n" 'crl = $dir/ca.crl' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
231 printf "%s\n" 'crlnumber = $dir/ca.crlnum' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
232 printf "%s\n" '### END CRITICAL SECTION ---------------------------------------------------------' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
233 printf "%s\n" 'default_days = 3650' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
234 printf "%s\n" 'name_opt = multiline, align' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
235 printf "%s\n" 'cert_opt = no_pubkey' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
236 printf "%s\n" 'copy_exetensions = copy' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
237 printf "%s\n" 'crl_extensions = crl_ext' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
238 printf "%s\n" 'default_crl_days = 180' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
239 printf "%s\n" 'default_md = sha256' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
240 printf "%s\n" 'preserve = no' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
241 printf "%s\n" 'email_in_dn = no' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
242 printf "%s\n" 'policy = policy' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
243 printf "%s\n" 'unique_subject = no' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
244 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
245 printf "%s\n" '[ policy ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
246 printf "%s\n" 'countryName = optional' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
247 printf "%s\n" 'stateOrProvinceName = optional' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
248 printf "%s\n" 'localityName = optional' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
249 printf "%s\n" 'organizationName = supplied' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
250 printf "%s\n" 'organizationalUnitName = optional' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
251 printf "%s\n" 'commonName = supplied' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
252 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
253 printf "%s\n" '### BEGIN CRITICAL SECTION -------------------------------------------------------' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
254 printf "%s\n" '[ req ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
255 printf "%s\n" 'default_keyfile = private/root-ca.key.pem' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
256 printf "%s\n" 'req_extensions = ca_req_ext' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
257 printf "%s\n" 'distinguished_name = req_distinguished_name' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
258 printf "%s\n" '### END CRITICAL SECTION ---------------------------------------------------------' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
259 printf "%s\n" 'default_bits = 2048' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
260 printf "%s\n" 'encrypt_key = yes' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
261 printf "%s\n" 'default_md = sha256' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
262 printf "%s\n" 'string_mask = utf8only' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
263 printf "%s\n" 'utf8 = yes' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
264 printf "%s\n" 'prompt = no' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
265 printf "%s\n" 'subjectAltName = @subject_alt_name' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
266 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
267 printf "%s\n" '### BEGIN CRITICAL SECTION -------------------------------------------------------' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
268 printf "%s\n" '[ ca_req_ext ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
269 printf "%s\n" '### END CRITICAL SECTION ---------------------------------------------------------' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
270 printf "%s\n" 'subjectKeyIdentifier = hash' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
271 printf "%s\n" 'subjectAltName = @subject_alt_name' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
272 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
273 printf "%s\n" '### BEGIN CRITICAL SECTION -------------------------------------------------------' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
274 printf "%s\n" '[ req_distinguished_name ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
275 printf "%s\n" '### END CRITICAL SECTION ---------------------------------------------------------' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
276 printf "%s\n" 'countryName = Country Name (2 letter code)' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
277 printf "%s\n" "countryName_min = 2" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
278 printf "%s\n" "countryName_max = 2" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
279 printf "%s\n" "countryName_default = ${subjectHashTable['countryName']}" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
280 printf "%s\n" 'stateOrProvinceName = State or Province Name (full name)' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
281 printf "%s\n" "stateOrProvinceName_default = ${subjectHashTable['stateOrProvinceName']}" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
282 printf "%s\n" 'localityName = Locality Name (eg, city)' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
283 printf "%s\n" "localityName_default = ${subjectHashTable['localityName']}" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
284 printf "%s\n" '0.organizationName = Organization Name (eg, company)' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
285 printf "%s\n" "0.organizationName_default = ${subjectHashTable['organizationName']}" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
286 printf "%s\n" 'organizationalUnitName = Organizational Unit Name (eg, section)' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
287 printf "%s\n" "organizationalUnitName_default = ${subjectHashTable['organizationalUnitName']}" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
288 printf "%s\n" 'commonName = Common Name (eg, name of certificate bearer)' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
289 printf "%s\n" 'commonName_min = 1' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
290 printf "%s\n" 'commonName_max = 64' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
291 printf "%s\n" 'emailAddress = Email Address' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
292 printf "%s\n" "emailAddresse_default = ${subjectHashTable['emailAddress']}" >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
293 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
294 printf "%s\n" '[ ca_ext ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
295 printf "%s\n" 'basicConstraints = critical, CA:true' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
296 printf "%s\n" 'keyUsage = critical, keyCertSign, cRLSign' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
297 printf "%s\n" 'nameConstraints = critical, @name_constraints' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
298 printf "%s\n" 'subjectKeyIdentifier = hash' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
299 printf "%s\n" 'subjectAltName = @subject_alt_name' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
300 printf "%s\n" 'authorityKeyIdentifier = keyid:always' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
301 printf "%s\n" 'issuerAltName = issuer:copy' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
302 printf "%s\n" 'authorityInfoAccess = @auth_info_access' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
303 printf "%s\n" 'crlDistributionPoints = crl_dist' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
304 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
305 printf "%s\n" '[ intermed-ca_ext ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
306 printf "%s\n" 'basicConstraints = critical, CA:true, pathlen:0' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
307 printf "%s\n" 'keyUsage = critical, keyCertSign, cRLSign' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
308 printf "%s\n" 'subjectKeyIdentifier = hash' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
309 printf "%s\n" 'subjectAltName = @subject_alt_name' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
310 printf "%s\n" 'authorityKeyIdentifier = keyid:always' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
311 printf "%s\n" 'issuerAltName = issuer:copy' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
312 printf "%s\n" 'authorityInfoAccess = @auth_info_access' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
313 printf "%s\n" 'crlDistributionPoints = crl_dist' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
314 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
315 printf "%s\n" '[ crl_ext ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
316 printf "%s\n" 'authorityKeyIdentifier = keyid:always' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
317 printf "%s\n" 'issuerAltName = issuer:copy' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
318 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
319 printf "%s\n" '[ subject_alt_name ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
320 printf "%s\n" 'URI = http://ca.example.net/' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
321 printf "%s\n" 'email = certmaster@example.net' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
322 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
323 printf "%s\n" '[ name_constraints ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
324 printf "%s\n" 'permitted;DNS.1 = example.net' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
325 printf "%s\n" 'permitted;DNS.2 = example.org' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
326 printf "%s\n" 'permitted;DNS.3 = lan' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
327 printf "%s\n" 'permitted;DNS.4 = onion' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
328 printf "%s\n" 'permitted;email.1 = example.net' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
329 printf "%s\n" 'permitted;email.2 = example.org' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
330 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
331 printf "%s\n" '[ auth_info_access ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
332 printf "%s\n" 'caIssuers;URI = http://ca.example.net/certs/example.net_RCA.cert.pem' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
333 printf "%s\n" '' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
334 printf "%s\n" '[ crl_dist ]' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
335 printf "%s\n" 'fullname = URI:http://ca.example.net/crl/example.net_RCA.crl' >> "/etc/ovpngwc/cas/${caAlias}/root-ca.conf"
336 ;;
337 'edit')
338 # 'edit' requires the following syntax: ca edit ALIAS set comment "<comment>"
339 # Position in array: 0 1 2 3 4 5+
340
341 if [[ ! "${#cmdArgs[@]}" -gt 5 ]] ; then printf " %s\n" "Error: missing arguments. Try 'help ca'." ; return 1 ; fi
342 if [[ ! "${cmdArgs[3]}" == 'set' ]] ; then printf " %s\n" "Error: invalid arguments. Try 'help ca'." ; return 1 ; fi
343 if [[ ! "${cmdArgs[4]}" == 'comment' ]] ; then print " %s\n" "Error: invalid arguments. Try 'help ca'." ; return 1 ; fi
344
345 # Check if alias value characters are legal and string isn't too long.
346 if ! printf -- "%s\n" "${cmdArgs[2]}" | grep -xEq '^[a-zA-Z][a-zA-Z0-9_-]*$' ; then printf " %s\n" "Error: bad alias value (illegal characters)." ; return 1 ; fi
347 if [[ "${#cmdArgs[2]}" -gt 32 ]] ; then printf " %s\n" "Error: bad alias value (too long)." ; return 1 ; fi
348
349 # 6th argument and onwards must be quoted. We make sure 6th argument is not just a single a quote.
350 # Then we concat the first character of 6th argument and the last character of the last argument in the array into a single string, so
351 # that we can compare the resulting string and see if it is a pair of matching quotes.
352 if [ ! -n "${cmdArgs[6]}" ] && { [[ "${cmdArgs[5]}" == "'" ]] || [[ "${cmdArgs[5]}" == '"' ]] ; } ; then printf " %s\n" "Error: bad comment value." ; return 1 ; fi
353 local valueQuotes="${cmdArgs[5]:0:1}${cmdArgs[${#cmdArgs[@]}-1]: -1}"
354 if ! { [[ "$valueQuotes" == '""' ]] || [[ "$valueQuotes" = "''" ]] ; } ; then printf " %s\n" "Error: bad comment value (use quotes)." ; return 1 ; fi
355
356 # At this point we can confirm comment value is properly quoted. We remove the quotes and verify that is not too long.
357 local value="${cmdArgs[@]:5:${#cmdArgs[@]}}"
358 local value="${value:1}"
359 local value="${value::-1}"
360 if [[ "${#value}" -gt 256 ]] ; then printf " %s\n" "Error: bad comment value (too long)." ; return 1 ; fi
361
362 # If we made it this far, the edit syntax is sane, however we still need to verify
363 # that the alias is a valid ca alias.
364 local dbResult=$(sqlite3 "${ovpngwcDb}" "SELECT COUNT(*) FROM certs WHERE alias='${cmdArgs[2]}';") #"#
365 if [[ "${dbResult}" == '0' ]] ; then printf " %s\n" "Error: bad alias value (no such CA)." ; return 1 ; fi
366
367 # If we are still here, the alias is valid so we go ahead and update the comment.
368 # We guard against SQL injection so we first put the comment into a file.
369
370 printf -- "%s" "${value}" > "/tmp/ovpngwc/ovpngwc.${ovpngwcAccountId}.caComment"
371 local dbResult=$(sqlite3 "${ovpngwcDb}" "UPDATE certs SET comment=READFILE('/tmp/ovpngwc/ovpngwc.${ovpngwcAccountId}.caComment') WHERE alias='${cmdArgs[2]}';") #"#
372
373 # Remove temp file(s).
374 rm -rf "/tmp/ovpngwc/ovpngwc.${ovpngwcAccountId}.*"
375 printf " %s\n" "Value updated." ; return 0
376 ;;
377 'view')
378 # Check if third argument exists.
379 # Likewise, we don't want too many arguments either.
380 if [ ! -n "${cmdArgs[2]}" ] ; then printf " %s\n" "Error: missing arguments. Try 'help ca'." ; return 1 ; fi
381 if [ -n "${cmdArgs[3]}" ] ; then printf " %s\n" "Error: too many arguments. Try 'help ca'." ; return 1 ; fi
382
383 # If we are still here, then we have the right amount of parameters.
384 # We need to check if the alias value characters are legal and string isn't too long.
385 if ! printf -- "%s\n" "${cmdArgs[2]}" | grep -xEq '^[a-zA-Z][a-zA-Z0-9_-]*$' ; then printf " %s\n" "Error: bad alias value (illegal characters)." ; return 1 ; fi
386 if [[ "${#cmdArgs[2]}" -gt 32 ]] ; then printf " %s\n" "Error: bad alias value (too long)." ; return 1 ; fi
387
388 # If we are still here then command format itself is OK, however we have
389 # to check if the provided third argument matches an existing CA alias.
390 local dbResult=$(sqlite3 "${ovpngwcDb}" "SELECT COUNT(*) FROM certs WHERE alias='${cmdArgs[2]}'") #"#
391 if [[ "${dbResult}" == '0' ]] ; then printf " %s\n" "Error: bad alias value (no such CA)." ; return 1 ; fi
392
393 # If we are still here, then we have the correct CA alias.
394 local caAlias="${cmdArgs[2]}"
395
396 # Next we want to load the encrypted certificate, decrypt it and show
397 # it to the user. To do so, we need to fetch the master key encreypted
398 # with the issuing admin's password. The password is stored as either
399 # ${ovpngwcPassword} or ${initializedPassword}, depending on whether
400 # this is the first ever execution of ovpngwc.
401
402 if [[ ! -n "${ovpngwcPassword}" ]] ; then ovpngwcPassword="${initializedPassword}" ; fi
403
404 # Next, We fetch the adminkey of the admin executing the command and decrypt it with the
405 # above password ${ovpngwcPassword}.
406IFS=''
407 local dbResult="$(sqlite3 "${ovpngwcDb}" "SELECT encrypted_key FROM adminkeys INNER JOIN users ON adminkeys.user_id=users.user_id WHERE users.user_id='${ovpngwcAccountId}';")"
408 local decryptedKey="$(printf -- "%s\n" "${dbResult}" | openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${ovpngwcPassword}")"
409
410######################
411## THIS DOESNT WORK ##
412######################
413echo "Testing retrieval ..."
414 # With the admin key decrypted, we fetch the CA certificate and symmetrically decrypt it.
415 local encryptedCert=$(sqlite3 "${ovpngwcDb}" "SELECT cert FROM certs WHERE alias='${caAlias}';")
416
417echo "${encryptedCert}"
418
419
420echo " decryption test 1"
421openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${decryptedKey}" <<< "${encryptedCert}"
422
423echo " decryption test 2"
424local dbResult="$(sqlite3 "${ovpngwcDb}" "SELECT cert FROM certs WHERE alias='${caAlias}';")"
425local decryptedcert2="$(openssl enc -d -aes-256-cbc -md sha512 -pbkdf2 -iter 1000000 -a -salt -k "${decryptedKey}" <<< "${dbResult}")"
426printf "%s\n" "${decryptedcert2}"
427
428
429 ;;
430 *) printf " %s\n" "Error: invalid arguments. Try 'help ca'." ; return 1 ;;
431 esac
432}