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