Compare commits
	
		
			1 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 310b1d1eab | 
| @@ -0,0 +1,224 @@ | |||||||
|  | # SPDX-License-Identifier: MIT | ||||||
|  | function ssh_keysuite_gen () { | ||||||
|  |     if [[ "${#}" -eq '0' ]]; then | ||||||
|  |         printf -- '%s\n' \ | ||||||
|  |             'Usage:' \ | ||||||
|  |             '' \ | ||||||
|  |             'ssh_keysuite_gen \' \ | ||||||
|  |             '    '"'"'ed25519'"'"' \' \ | ||||||
|  |             '    '"'"'john.doe@example.com_ed25519_'"'"'"$(date +'%F')" \' \ | ||||||
|  |             '    "$(date +'%F')" \' \ | ||||||
|  |             '    "$(pwd)" \' \ | ||||||
|  |             '    '"'"'John Doe <john.doe@example.com>'"'"'' \ | ||||||
|  |             '' \ | ||||||
|  |             'or for an RSA key (here with 4096 bits key size):' \ | ||||||
|  |             '' \ | ||||||
|  |             'ssh_keysuite_gen \' \ | ||||||
|  |             '    '"'"'rsa:4096'"'"' \' \ | ||||||
|  |             '    '"'"'john.doe@example.com_rsa2_4096_'"'"'"$(date +'%F')" \' \ | ||||||
|  |             '    "$(date +'%F')" \' \ | ||||||
|  |             '    "$(pwd)" \' \ | ||||||
|  |             '    '"'"'John Doe <john.doe@example.com>'"'"'' \ | ||||||
|  |             '' \ | ||||||
|  |             'Option '"'"'rsa'"'"' without key size '"'"':<number>'"'"' will generate' \ | ||||||
|  |             'a key with 3072 bits key size.' \ | ||||||
|  |             '' \ | ||||||
|  |             'For an ECDSA key (here with 521 bits elliptic curve size):' \ | ||||||
|  |             '' \ | ||||||
|  |             'ssh_keysuite_gen \' \ | ||||||
|  |             '    '"'"'ecdsa:521'"'"' \' \ | ||||||
|  |             '    '"'"'john.doe@example.com_ecdsa_521_'"'"'"$(date +'%F')" \' \ | ||||||
|  |             '    "$(date +'%F')" \' \ | ||||||
|  |             '    "$(pwd)" \' \ | ||||||
|  |             '    '"'"'John Doe <john.doe@example.com>'"'"'' \ | ||||||
|  |             '' \ | ||||||
|  |             'Option '"'"'ecdsa'"'"' without EC length '"'"':<number>'"'"' will generate' \ | ||||||
|  |             'a key with 256 bits elliptic curve length.' | ||||||
|  |         return | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # We're assuming coreutils and bash internals are available (chmod, | ||||||
|  |     # command, cp, fmt, mktemp, printf etc.), we check for moderately | ||||||
|  |     # unconventional binaries | ||||||
|  |     local SSH_REQUIRED_CMDS=('grep' 'openssl' 'puttygen' 'sed' 'ssh-keygen') | ||||||
|  |     local SSH_MISSING_CMDS=() | ||||||
|  |  | ||||||
|  |     for ssh_required_cmd in "${SSH_REQUIRED_CMDS[@]}"; do | ||||||
|  |         command -v "${ssh_required_cmd}" &> '/dev/null' | ||||||
|  |         if [[ "${?}" -gt '0' ]]; then | ||||||
|  |             SSH_MISSING_CMDS+=("${ssh_required_cmd}") | ||||||
|  |         fi | ||||||
|  |     done | ||||||
|  |     if [[ "${#SSH_MISSING_CMDS[@]}" -gt '0' ]]; then | ||||||
|  |         for ssh_missing_cmd in "${SSH_MISSING_CMDS[@]}"; do | ||||||
|  |             printf -- 'Missing binary: '"'"'%s'"'"'\n' "${ssh_missing_cmd}" | ||||||
|  |         done | ||||||
|  |         printf -- '\n%s\n' 'Please make sure all required commands are available.' | ||||||
|  |         return | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     IFS= read -sp 'Enter password for new SSH key: ' 'SSH_KEY_PW' | ||||||
|  |     echo | ||||||
|  |  | ||||||
|  |     local TMPFILE="$(mktemp)" | ||||||
|  |     local TMPFILE2="$(mktemp)" | ||||||
|  |     trap '[[ -f '"'${TMPFILE}'"' ]] && rm '"'${TMPFILE}'"'; [[ -f '"'${TMPFILE2}'"' ]] && rm '"'${TMPFILE2}'"';' RETURN | ||||||
|  |     printf -- '%s' "${SSH_KEY_PW}" > "${TMPFILE}" | ||||||
|  |     printf -- '%s' "${SSH_KEY_PW}" > "${TMPFILE2}" | ||||||
|  |  | ||||||
|  |     sleep 1 | ||||||
|  |  | ||||||
|  |     local SSH_KEY_ALGO="${1:-ed25519}" | ||||||
|  |     local SSH_KEY_BASENAME="${2:?'Arg 2 must be SSH key file base name such as '"'"'john.doe@example.com_ed25519_2025-09-15'"'"''}" | ||||||
|  |     local SSH_KEY_CREATION_TIMESTAMP="${3:-"$(date +'%F')"}" | ||||||
|  |     local SSH_KEY_OUTPUT_DIR="${4:-"$(pwd)"}" | ||||||
|  |     local SSH_KEY_PURPOSE="${5:?'Arg 5 must be SSH key purpose for use in key comment such as '"'"'John Doe <john.doe@example.com>'"'"''}" | ||||||
|  |  | ||||||
|  |     local SSH_KEY_PATH_RAW="${SSH_KEY_OUTPUT_DIR}"'/'"${SSH_KEY_BASENAME}"'.SSH_KEY_FORMAT.SSH_KEY_FILE_EXTENSION' | ||||||
|  |     local SSH_KEY_COMMENT="${SSH_KEY_PURPOSE}"' ('"${SSH_KEY_CREATION_TIMESTAMP}"')' | ||||||
|  |  | ||||||
|  |     if [[ "${SSH_KEY_ALGO}" =~ ^rsa ]]; then | ||||||
|  |         local SSH_DO_RSA='true' | ||||||
|  |         local SSH_RSA_KEY_LENGTH="${SSH_KEY_ALGO#*:}" | ||||||
|  |         if [[ "${#SSH_RSA_KEY_LENGTH}" -eq '0' || "${SSH_RSA_KEY_LENGTH}" == "${SSH_KEY_ALGO}" ]]; then | ||||||
|  |             SSH_RSA_KEY_LENGTH='' | ||||||
|  |         fi | ||||||
|  |         local SSH_KEY_ALGO='rsa' | ||||||
|  |     elif [[ "${SSH_KEY_ALGO}" =~ ^ecdsa ]]; then | ||||||
|  |         local SSH_DO_ECDSA='true' | ||||||
|  |         local SSH_EC_LENGTH="${SSH_KEY_ALGO#*:}" | ||||||
|  |         if [[ "${#SSH_EC_LENGTH}" -eq '0' || "${SSH_EC_LENGTH}" == "${SSH_KEY_ALGO}" ]]; then | ||||||
|  |             SSH_EC_LENGTH='' | ||||||
|  |         fi | ||||||
|  |         local SSH_KEY_ALGO='ecdsa' | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     if [[ "${SSH_DO_RSA}" || "${SSH_DO_ECDSA}" ]]; then | ||||||
|  |         if [[ "${#SSH_KEY_PW}" -lt '5' ]]; then | ||||||
|  |             printf -- '%s\n' \ | ||||||
|  |                 '' \ | ||||||
|  |                 "$(fmt <<<'Key password must me at least 5 characters long as that is what '"'"'ssh-keygen'"'"' enforces for PEM-encoded private keys for example when converting from an OpenSSH-encoded private key.')" | ||||||
|  |             return | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     local SSH_KEY_FILE_LIST=() | ||||||
|  |  | ||||||
|  |     # Gen OpenSSH-encoded private key | ||||||
|  |     # Can be OpenSSH-encoded private RSA key | ||||||
|  |     # Can be OpenSSH-encoded private EC key | ||||||
|  |     # (a wrapper around all keys supported by OpenSSH) | ||||||
|  |     # BEGIN OPENSSH PRIVATE KEY | ||||||
|  |     local SSH_KEY_FORMAT='openssh-encoded' | ||||||
|  |     local SSH_KEY_FILE_EXTENSION='key' | ||||||
|  |     local SSH_KEY_PATH_OPENSSH="$(sed -r \ | ||||||
|  |         -e 's'$'\x1''SSH_KEY_FORMAT'$'\x1'"${SSH_KEY_FORMAT}"$'\x1''g' \ | ||||||
|  |         -e 's'$'\x1''SSH_KEY_FILE_EXTENSION'$'\x1'"${SSH_KEY_FILE_EXTENSION}"$'\x1''g' \ | ||||||
|  |         <<<"${SSH_KEY_PATH_RAW}")" | ||||||
|  |     ssh-keygen -t "${SSH_KEY_ALGO}"$(if [[ "${SSH_DO_RSA}" && "${SSH_RSA_KEY_LENGTH}" ]]; then printf -- '%s' ' -b '"${SSH_RSA_KEY_LENGTH}"; elif [[ "${SSH_DO_ECDSA}" && "${SSH_EC_LENGTH}" ]]; then printf -- '%s' ' -b '"${SSH_EC_LENGTH}"; fi) -N "${SSH_KEY_PW}" -C "${SSH_KEY_COMMENT}" -f "${SSH_KEY_PATH_OPENSSH}" | ||||||
|  |     SSH_KEY_FILE_LIST+=("${SSH_KEY_PATH_OPENSSH}") | ||||||
|  |     SSH_KEY_FILE_LIST+=("${SSH_KEY_PATH_OPENSSH}"'.pub') | ||||||
|  |  | ||||||
|  |     # Convert to PEM-encoded PKCS#1 key | ||||||
|  |     # (only RSA keys are PKCS#1) | ||||||
|  |     # BEGIN RSA PRIVATE KEY | ||||||
|  |     if [[ "${SSH_DO_RSA}" ]]; then | ||||||
|  |         local SSH_KEY_FORMAT='pem-encoded-pkcs1-rsa' | ||||||
|  |         local SSH_KEY_PATH_PEM_PKCS1_RSA_PRIVATE="$(sed -r \ | ||||||
|  |             -e 's'$'\x1''SSH_KEY_FORMAT'$'\x1'"${SSH_KEY_FORMAT}"$'\x1''g' \ | ||||||
|  |             -e 's'$'\x1''SSH_KEY_FILE_EXTENSION'$'\x1'"${SSH_KEY_FILE_EXTENSION}"$'\x1''g' \ | ||||||
|  |             <<<"${SSH_KEY_PATH_RAW}")" | ||||||
|  |         cp "${SSH_KEY_PATH_OPENSSH}" "${SSH_KEY_PATH_PEM_PKCS1_RSA_PRIVATE}" | ||||||
|  |         ssh-keygen -p -P "${SSH_KEY_PW}" -N "${SSH_KEY_PW}" -m 'pem' -f "${SSH_KEY_PATH_PEM_PKCS1_RSA_PRIVATE}" | ||||||
|  |         SSH_KEY_FILE_LIST+=("${SSH_KEY_PATH_PEM_PKCS1_RSA_PRIVATE}") | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # Convert to PEM-encoded PKCS#8 key | ||||||
|  |     # (a wrapper around both RSA and EC keys) | ||||||
|  |     # BEGIN PRIVATE KEY | ||||||
|  |     # or | ||||||
|  |     # BEGIN ENCRYPTED PRIVATE KEY | ||||||
|  |     if [[ "${SSH_DO_RSA}" || "${SSH_DO_ECDSA}" ]]; then | ||||||
|  |         local SSH_KEY_FORMAT='pem-encoded-pkcs8' | ||||||
|  |         local SSH_KEY_PATH_PEM_PKCS8="$(sed -r \ | ||||||
|  |             -e 's'$'\x1''SSH_KEY_FORMAT'$'\x1'"${SSH_KEY_FORMAT}"$'\x1''g' \ | ||||||
|  |             -e 's'$'\x1''SSH_KEY_FILE_EXTENSION'$'\x1'"${SSH_KEY_FILE_EXTENSION}"$'\x1''g' \ | ||||||
|  |             <<<"${SSH_KEY_PATH_RAW}")" | ||||||
|  |         local SSH_KEY_FORMAT='pem-encoded-SubjectPublicKeyInfo' | ||||||
|  |         local SSH_KEY_PATH_PEM_SPKI_PUBLIC="$(sed -r \ | ||||||
|  |             -e 's'$'\x1''SSH_KEY_FORMAT'$'\x1'"${SSH_KEY_FORMAT}"$'\x1''g' \ | ||||||
|  |             -e 's'$'\x1''SSH_KEY_FILE_EXTENSION'$'\x1'"${SSH_KEY_FILE_EXTENSION}"$'\x1''g' \ | ||||||
|  |             <<<"${SSH_KEY_PATH_RAW}")" | ||||||
|  |         cp "${SSH_KEY_PATH_OPENSSH}" "${SSH_KEY_PATH_PEM_PKCS8}" | ||||||
|  |         ssh-keygen -p -P "${SSH_KEY_PW}" -N "${SSH_KEY_PW}" -m 'pkcs8' -f "${SSH_KEY_PATH_PEM_PKCS8}" | ||||||
|  |         ssh-keygen -e -P "${SSH_KEY_PW}" -f "${SSH_KEY_PATH_PEM_PKCS8}" -m 'pkcs8' > "${SSH_KEY_PATH_PEM_SPKI_PUBLIC}"'.pub' | ||||||
|  |         SSH_KEY_FILE_LIST+=("${SSH_KEY_PATH_PEM_PKCS8}") | ||||||
|  |         SSH_KEY_FILE_LIST+=("${SSH_KEY_PATH_PEM_SPKI_PUBLIC}"'.pub') | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # Convert to PEM-encoded Elliptic Curve key | ||||||
|  |     # (looks like PKCS#1 but is not) | ||||||
|  |     # BEGIN EC PRIVATE KEY | ||||||
|  |     if [[ "${SSH_DO_ECDSA}" ]]; then | ||||||
|  |         local SSH_KEY_FORMAT='pem-encoded-ec' | ||||||
|  |         local SSH_KEY_PATH_PEM_EC_PRIVATE="$(sed -r \ | ||||||
|  |             -e 's'$'\x1''SSH_KEY_FORMAT'$'\x1'"${SSH_KEY_FORMAT}"$'\x1''g' \ | ||||||
|  |             -e 's'$'\x1''SSH_KEY_FILE_EXTENSION'$'\x1'"${SSH_KEY_FILE_EXTENSION}"$'\x1''g' \ | ||||||
|  |             <<<"${SSH_KEY_PATH_RAW}")" | ||||||
|  |         openssl ec -in "${SSH_KEY_PATH_PEM_PKCS8}" -out "${SSH_KEY_PATH_PEM_EC_PRIVATE}" -passin 'file:'"${TMPFILE}" -passout 'file:'"${TMPFILE2}" | ||||||
|  |         SSH_KEY_FILE_LIST+=("${SSH_KEY_PATH_PEM_EC_PRIVATE}") | ||||||
|  |     fi | ||||||
|  |  | ||||||
|  |     # Convert pub key to RFC4716 | ||||||
|  |     local SSH_KEY_FORMAT='rfc4716-encoded' | ||||||
|  |     local SSH_KEY_FILE_EXTENSION='key' | ||||||
|  |     local SSH_KEY_PATH_RFC4716="$(sed -r \ | ||||||
|  |         -e 's'$'\x1''SSH_KEY_FORMAT'$'\x1'"${SSH_KEY_FORMAT}"$'\x1''g' \ | ||||||
|  |         -e 's'$'\x1''SSH_KEY_FILE_EXTENSION'$'\x1''key'$'\x1''g' \ | ||||||
|  |         <<<"${SSH_KEY_PATH_RAW}")" | ||||||
|  |     # Write a proper comment line. ssh-keygen by default writes key type | ||||||
|  |     # and local user account as comment when converting to RFC4716 | ||||||
|  |     # format instead of using the comment already present in source | ||||||
|  |     # file. We write our own proper comment here. Break at 71 | ||||||
|  |     # characters. End each line with a trailing backslash at position | ||||||
|  |     # 72. The first sed comment replaces all end-of-lines ($) with a | ||||||
|  |     # backslash (\). We have to escape the backslash so that the first | ||||||
|  |     # sed command inserts a literal backslash at the end of each line (\\). A | ||||||
|  |     # few lines further down we 'ssh-keygen | sed' again where we | ||||||
|  |     # replace the RFC4716 comment with ours (which now includes literal | ||||||
|  |     # trailing backslashes). In order to preserve literal trailing | ||||||
|  |     # backslashes in "${var}" when doing: | ||||||
|  |     # | ||||||
|  |     #     sed -r 's/g/'"${var}"'/g' | ||||||
|  |     # | ||||||
|  |     # we have to make sure that "${var}" contains three (3) literal | ||||||
|  |     # backslashes. That's why we do '\\\\\\' here. When we then do: | ||||||
|  |     # | ||||||
|  |     #     sed -r 's/g/'"${var}"'/g' | ||||||
|  |     # | ||||||
|  |     # three literal backslashes in "${var}" are interpreted to one. | ||||||
|  |     local SSH_RFC4716_KEYTYPE="$(ssh-keygen -m 'RFC4716' -ef "${SSH_KEY_PATH_OPENSSH}"'.pub' | grep -Pio -- '^(Comment:[^,]+)')" | ||||||
|  |     local SSH_RFC4716_COMMENT="$(fmt --width 71 <<<"${SSH_RFC4716_KEYTYPE}"', '"${SSH_KEY_COMMENT}"'"' \ | ||||||
|  |         | sed -r \ | ||||||
|  |         -e 's'$'\x1''$'$'\x1''\\\\\\'$'\x1''g' \ | ||||||
|  |         -e '$ s'$'\x1''\\+'$'\x1'''$'\x1''g' \ | ||||||
|  |         )" | ||||||
|  |     ssh-keygen -m 'RFC4716' -ef "${SSH_KEY_PATH_OPENSSH}"'.pub' \ | ||||||
|  |         | sed -r 's'$'\x1''^Comment: [^\r\n\f]*'$'\x1'"${SSH_RFC4716_COMMENT}"$'\x1''g' \ | ||||||
|  |         > "${SSH_KEY_PATH_RFC4716}"'.pub' | ||||||
|  |     SSH_KEY_FILE_LIST+=("${SSH_KEY_PATH_RFC4716}"'.pub') | ||||||
|  |  | ||||||
|  |     # Convert to PuTTY PPK | ||||||
|  |     local SSH_KEY_FORMAT='putty-encoded' | ||||||
|  |     local SSH_KEY_FILE_EXTENSION='ppk' | ||||||
|  |     local SSH_KEY_PATH_PUTTY="$(sed -r \ | ||||||
|  |         -e 's'$'\x1''SSH_KEY_FORMAT'$'\x1'"${SSH_KEY_FORMAT}"$'\x1''g' \ | ||||||
|  |         -e 's'$'\x1''SSH_KEY_FILE_EXTENSION'$'\x1'"${SSH_KEY_FILE_EXTENSION}"$'\x1''g' \ | ||||||
|  |         <<<"${SSH_KEY_PATH_RAW}")" | ||||||
|  |     puttygen "${SSH_KEY_PATH_OPENSSH}" --old-passphrase "${TMPFILE}" -o "${SSH_KEY_PATH_PUTTY}" | ||||||
|  |     SSH_KEY_FILE_LIST+=("${SSH_KEY_PATH_PUTTY}") | ||||||
|  |  | ||||||
|  |     for file in "${SSH_KEY_FILE_LIST[@]}"; do | ||||||
|  |         chmod --verbose '0600' "${file}" | ||||||
|  |     done | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user