Skip to content

Instantly share code, notes, and snippets.

@SeanPesce
Created July 23, 2025 15:36
Show Gist options
  • Save SeanPesce/3b2d6650ac7ce79cfda7b151ee8c8c2d to your computer and use it in GitHub Desktop.
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)
#!/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