Created
July 23, 2025 15:36
-
-
Save SeanPesce/3b2d6650ac7ce79cfda7b151ee8c8c2d to your computer and use it in GitHub Desktop.
Shell script to clone a private key, public key, and/or certificate (i.e., the same parameters and metadata are used when generating a new key)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Author: Sean Pesce | |
| # | |
| # This script clones a private key, public key, and/or certificate (i.e., the same parameters and metadata are used when generating a new key). | |
| # Function to check whether a shell command is installed/available | |
| command_exists() { | |
| command -v "$1" >/dev/null 2>&1 | |
| } | |
| # Check if an input file is provided | |
| if [ "$#" -ne 1 ]; then | |
| echo "Usage: $0 <key_or_cert.pem|HTTPS URL>" | |
| exit 1 | |
| fi | |
| URL='' | |
| INPUT_FILE=$1 | |
| PRIVATE_KEY_OUT='cloned_private_key.pem' | |
| PUBLIC_KEY_OUT='cloned_public_key.pem' | |
| #CSR_OUT='cloned.csr' | |
| CERT_OUT='cloned_certificate.pem' | |
| # Extract the public key from the certificate, if necessary | |
| TEMP_CERT='/tmp/orig_cert_to_clone.pem' | |
| TEMP_PUBKEY='/tmp/orig_pub_to_clone.pem' | |
| rm -f "$TEMP_PUBKEY" "$TEMP_CERT" | |
| if [ -n "$(echo "$INPUT_FILE" | grep -Poi '^(https|tls|ssl|wss)://')" ]; then | |
| # If an HTTPS URL was provided, download the certificate | |
| URL="$INPUT_FILE" | |
| #echo "[INFO] Downloading TLS certificate from $URL" | |
| # Extract the hostname and port from the URL | |
| HOST=$(echo "$URL" | awk -F[/:] '{print $4}') | |
| PORT=$(echo "$URL" | awk -F[/] '{print $3}' | awk -F[:] '{print $2}') | |
| if ! ((PORT)); then # Verify that the port is a number | |
| PORT='' | |
| fi | |
| PORT=${PORT:-443} # Default to port 443 if none is specified | |
| echo "[INFO] Downloading TLS certificate from https://$HOST:$PORT" | |
| openssl s_client -connect "$HOST:$PORT" -servername "$HOST" 2>/dev/null </dev/null | \ | |
| sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' > "$TEMP_CERT" | |
| if [ -s "$TEMP_CERT" ]; then | |
| INPUT_FILE="$TEMP_CERT" | |
| echo "[INFO] Downloaded certificate to $TEMP_CERT" | |
| else | |
| echo "[WARNING] Failed to download the TLS certificate for $HOST:$PORT ; assuming local file path \"$INPUT_FILE\"" | |
| fi | |
| fi | |
| openssl x509 -in "$INPUT_FILE" -pubkey -noout -out "$TEMP_PUBKEY" 2>/dev/null | |
| # Determine the key type and algorithm | |
| KEY_TYPE='' | |
| KEY_ALG_TYPE='' | |
| KEY_ALG='' | |
| KEY_SIZE='' | |
| # Check for private key | |
| if openssl rsa -in "$INPUT_FILE" -check -noout 2>/dev/null >/dev/null; then | |
| KEY_ALG_TYPE='rsa' | |
| KEY_ALG='RSA' | |
| KEY_TYPE='private' | |
| KEY_SIZE="$(openssl "$KEY_ALG_TYPE" -in "$INPUT_FILE" -text -noout 2>/dev/null | grep -i private | head -n1 | grep -Po '[0-9]+\s+bit' | cut -d ' ' -f 1)" | |
| echo "[INFO] ${KEY_SIZE}-bit RSA private key detected." | |
| elif openssl ec -in "$INPUT_FILE" -check 2>/dev/null >/dev/null; then | |
| KEY_ALG_TYPE='ec' | |
| KEY_TYPE='private' | |
| KEY_ALG="$(openssl "$KEY_ALG_TYPE" -in "$INPUT_FILE" -noout -text 2>/dev/null | grep "ASN1 OID:" | awk '{print $3}')" | |
| #KEY_SIZE="$(openssl "$KEY_ALG_TYPE" -in "$INPUT_FILE" -noout -text 2>/dev/null | grep "ASN1 OID:" | awk '{print $3}' | grep -Po '^[^0-9]*[0-9]+' | grep -Po '[0-9]+$')" | |
| echo "[INFO] $KEY_ALG ECDSA private key detected." | |
| elif openssl pkey -in "$INPUT_FILE" -text 2>/dev/null >/dev/null; then | |
| KEY_ALG_TYPE='pkey' | |
| KEY_TYPE='private' | |
| KEY_ALG="$(openssl pkey -in "$INPUT_FILE" -text -noout 2>/dev/null | grep -i private | head -n1 | cut -d ' ' -f 1)" | |
| # if [ "$KEY_ALG" == 'ED448' ]; then | |
| # KEY_SIZE='456' # 57 bytes | |
| # fi | |
| echo "[INFO] $KEY_ALG private key detected." | |
| # Check for public key | |
| elif openssl rsa -pubin -in "$INPUT_FILE" -noout 2>/dev/null >/dev/null; then | |
| KEY_ALG_TYPE='rsa' | |
| KEY_ALG='RSA' | |
| KEY_TYPE='public' | |
| KEY_SIZE="$(openssl rsa -pubin -in "$INPUT_FILE" -text -noout 2>/dev/null | grep -i public | head -n1 | grep -Po '[0-9]+\s+bit' | cut -d ' ' -f 1)" | |
| echo "[INFO] ${KEY_SIZE}-bit RSA public key detected." | |
| #elif openssl ec -pubin -in "$INPUT_FILE" -check -noout 2>/dev/null >/dev/null; then | |
| elif [ -n "$(openssl ec -pubin -in "$INPUT_FILE" -noout -text 2>&1 | grep CURVE)" ]; then | |
| KEY_ALG_TYPE='ec' | |
| KEY_TYPE='public' | |
| KEY_ALG="$(openssl ec -pubin -in "$INPUT_FILE" -noout -text 2>/dev/null | grep "ASN1 OID:" | awk '{print $3}')" | |
| echo "[INFO] $KEY_ALG ECDSA public key detected." | |
| elif openssl pkey -pubin -in "$INPUT_FILE" -text 2>/dev/null >/dev/null; then | |
| KEY_ALG_TYPE='pkey' | |
| KEY_TYPE='public' | |
| KEY_ALG="$(openssl pkey -pubin -in "$INPUT_FILE" -text -noout 2>/dev/null | grep -i public | head -n1 | cut -d ' ' -f 1)" | |
| echo "[INFO] $KEY_ALG public key detected." | |
| # Check for certificate | |
| elif openssl rsa -pubin -in "$TEMP_PUBKEY" -noout 2>/dev/null >/dev/null; then | |
| KEY_ALG_TYPE='rsa' | |
| KEY_ALG='RSA' | |
| KEY_TYPE='cert' | |
| KEY_SIZE="$(openssl rsa -pubin -in "$TEMP_PUBKEY" -text -noout 2>/dev/null | grep -i public | head -n1 | grep -Po '[0-9]+\s+bit' | cut -d ' ' -f 1)" | |
| echo "[INFO] ${KEY_SIZE}-bit RSA certificate detected." | |
| #elif openssl ec -pubin -in "$TEMP_PUBKEY" 2>/dev/null >/dev/null; then | |
| elif [ -n "$(openssl ec -pubin -in "$TEMP_PUBKEY" -noout -text 2>&1 | grep CURVE)" ]; then | |
| KEY_ALG_TYPE='ec' | |
| KEY_TYPE='cert' | |
| KEY_ALG="$(openssl ec -pubin -in "$TEMP_PUBKEY" -noout -text 2>/dev/null | grep "ASN1 OID:" | awk '{print $3}')" | |
| echo "[INFO] $KEY_ALG ECDSA certificate detected." | |
| elif openssl pkey -pubin -in "$TEMP_PUBKEY" -text 2>/dev/null >/dev/null; then | |
| KEY_ALG_TYPE='pkey' | |
| KEY_TYPE='cert' | |
| KEY_ALG="$(openssl pkey -pubin -in "$TEMP_PUBKEY" -text -noout 2>/dev/null | grep -i public | head -n1 | cut -d ' ' -f 1)" | |
| echo "[INFO] $KEY_ALG certificate detected." | |
| else | |
| echo "[ERROR] Unknown or unsupported key/certificate/algorithm: $INPUT_FILE" | |
| rm -f "$TEMP_PUBKEY" "$TEMP_CERT" | |
| exit 2 | |
| fi | |
| # Generate the new private key | |
| rm -f "$PRIVATE_KEY_OUT" | |
| if [ "$KEY_ALG_TYPE" == 'rsa' ]; then | |
| openssl genpkey -algorithm "$KEY_ALG" -out "$PRIVATE_KEY_OUT" -pkeyopt "rsa_keygen_bits:$KEY_SIZE" | |
| elif [ "$KEY_ALG_TYPE" == 'ec' ]; then | |
| openssl ecparam -name "$KEY_ALG" -genkey -noout -out "$PRIVATE_KEY_OUT" | |
| elif [ "$KEY_ALG_TYPE" == 'pkey' ]; then | |
| openssl genpkey -algorithm "$KEY_ALG" -out "$PRIVATE_KEY_OUT" | |
| fi | |
| echo "[INFO] Created new private key: $PRIVATE_KEY_OUT" | |
| # Generate the new public key | |
| if [ "$KEY_TYPE" == 'public' ] || [ "$KEY_TYPE" == 'cert' ]; then | |
| rm -f "$PUBLIC_KEY_OUT" | |
| openssl "$KEY_ALG_TYPE" -in "$PRIVATE_KEY_OUT" -pubout > "$PUBLIC_KEY_OUT" 2>/dev/null | |
| echo "[INFO] Created new public key: $PUBLIC_KEY_OUT" | |
| fi | |
| # Generate the new certificate | |
| if [ "$KEY_TYPE" == 'cert' ]; then | |
| SUBJECT="$(openssl x509 -in "$INPUT_FILE" -noout -subject | sed 's/^subject=//' | sed 's/, /\//g' | sed 's/ = /=/g')" | |
| ISSUER="$(openssl x509 -in "$INPUT_FILE" -noout -issuer | sed 's/^issuer=//' | sed 's/, /\//g' | sed 's/ = /=/g')" | |
| SERIAL="$(openssl x509 -in "$INPUT_FILE" -noout -serial | cut -d '=' -f 2)" | |
| NOT_BEFORE_RAW=$(openssl x509 -in "$INPUT_FILE" -noout -startdate | sed 's/notBefore=//') | |
| NOT_AFTER_RAW=$(openssl x509 -in "$INPUT_FILE" -noout -enddate | sed 's/notAfter=//') | |
| DAYS_DIFF=$(( ($(date -d "$NOT_AFTER_RAW" +%s) - $(date -d "$NOT_BEFORE_RAW" +%s)) / 86400 )) | |
| NOT_BEFORE="$(date -d "$NOT_BEFORE_RAW" +"%Y%m%d%H%M%SZ")" | |
| NOT_AFTER="$(date -d "$NOT_AFTER_RAW" +"%Y%m%d%H%M%SZ")" | |
| # @TODO: | |
| # - Issuer | |
| # - Signature algorithm | |
| # - Extensions | |
| CREATE_CERT_CMD="openssl req -new -x509 \ | |
| -key '$PRIVATE_KEY_OUT' \ | |
| -out '$CERT_OUT' \ | |
| -subj '/$SUBJECT' \ | |
| -set_serial '0x$SERIAL' \ | |
| -days '$DAYS_DIFF' " | |
| #-addext "subjectAltName=DNS:example.com,DNS:www.example.com" \ | |
| #-addext "issuerAltName=DNS:issuer.example.com")" | |
| if command_exists faketime ; then | |
| CREATE_CERT_CMD="faketime '$NOT_BEFORE_RAW' $CREATE_CERT_CMD" | |
| else | |
| echo "[WARNING] Install the \"faketime\" package to clone exact certificate expiration dates" | |
| fi | |
| rm -f "$CERT_OUT" | |
| eval "$CREATE_CERT_CMD" | |
| echo "[INFO] Created new certificate: $CERT_OUT" | |
| fi | |
| rm -f "$TEMP_PUBKEY" "$TEMP_CERT" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment