Fun with x509: extract RSA Public from SSL cert for use with iRule CRYPTO commands
Problem this snippet solves:
iRule CRYPTO commands do not allow the use of SSL / TLS certificates for encryption and decription operations.
This snippet provides a simple way to extract the RSA Public key from a x509 certificate by parsing its ASN.1 structure. The output key can then be given as [-key] input to the CRYPTO::[encrypt|decrypt] commands in combination with the use of the rsa-pub and rsa-priv algorithms [-alg].
The example only encrypts a test string with the Client's SSL certificate to demonstrate the functionality but you have unlimited options here.
For instance, you are not limited to using your client's certificate (e.g. [SSL::cert 0]): any PEM (base64) or DER representation of a public x509 cert will also work. When using the PEM format just don't forget to perform a b64decode before feeding it to the getRSAPubFromX509 proc.
Credits to Kevin Stewart for the original idea and code for performing Extended X509 Certificate Parsing
How to use this snippet:
-
Create Virtual Server
-
Select "http" under "HTTP Profile"
-
Create SSL Profile (Client)
3.1 Configure the "Certificate Key Chain" with a proper or self-signed ssl key pair
3.2 Select "require" under "Client Certificate" -
Assign the SSL Profile to the Virtual Server
-
Create an iRule out of the snippet below and assign it to the Virtual Server
-
Generate traffic by use of an appropriate client SSL key pair
-
JSON Parse the response.
Test decryption with openssl:
Copy the "encryptedMsg" JSON value within the response payload (e.g. using JSON.Parse()) in a file called enc.txt, then decode it from base64 into the encrypted binary string and use the client's private key to decrypt.
base64 -d enc.txt > encbin.txt openssl rsautl -inkey key.pem -decrypt -in encbin.txt
Code :
when HTTP_REQUEST { set cert [SSL::cert 0] set rsaFromX509 [call getRSAPubFromX509 $cert] log local0. "PEM encoded RSA Public: $rsaFromX509" set testString "test string to encrypt" set encBytes [CRYPTO::encrypt -alg rsa-pub -key $rsaFromX509 $testString] set encBytesPrintable [b64encode $encBytes] log local0. "Encrypted b64encoded string: $encBytesPrintable" set jsonResponse "{ \"encryptedMsg\": \"$encBytesPrintable\" }" HTTP::respond 200 content $jsonResponse } proc getRSAPubFromX509 { bincert } { if { [catch { # rsaEncryption OID=1.2.840.113549.1.1.1 - HEX=\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01 set offset [string first \x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01 $bincert] # Shift the offset before the ASN.1 sequence length set offset [expr { $offset - 4 }] binary scan "[string index $bincert $offset][string index $bincert [expr $offset + 1]]" S seqlen # Convert the ASN.1 length to unsigned integer set seqlen [expr {$seqlen & 0xffff}] # Set a pointer to the beginning of the sequence set startSeq [expr $offset - 2] # Set a pointer to the end of the sequence set endSeq [expr $startSeq + 3 + $seqlen] set rsaEncryption [string range $bincert $startSeq $endSeq] } error] } { set rsaEncryption 0 } if { $rsaEncryption == 0 } { return 0 } binary scan $rsaEncryption a* rsaEncryption set encodedKey [b64encode $rsaEncryption] set formattedKey "-----BEGIN PUBLIC KEY-----\x0A" for {set i 0} {$i < [string length $encodedKey]} {incr i 64} { set formattedKey "${formattedKey}\x0A[string range $encodedKey $i [expr {$i + 63}]]" } set formattedKey "${formattedKey}\x0A-----END PUBLIC KEY-----\x0A" return $formattedKey }
Tested this on version:
12.1