Tuesday, December 27, 2016

P12/PKCS12, JKS keystores, and passwords

We ended the day with a situation that confused us, in which Spring allowed us to specify passwords for both a KEYSTORE and the KEY within the keystore, but it only worked when the two passwords were the same. Below is me playing around trying to understand this, ultimately the explanation is this - PKCS12 does NOT support different passwords for the keystore and key. But other keystore formats such as JKS *do* support different passwords for the keystore and key. Spring supports both types of keystores, so it’s generic config file allows the specification of a key password for pkcs12 even when that password can’t – by definition – be used.


# generate a password-protected keypair. The password is used for pbkdf2 encryption of the keypair
openssl genrsa -aes256 -out keypair.pem -passout pass:abc123 2048

# create a self-signed certificate with the keypair from above
openssl req -new -x509 -sha256 -days 1826 -passin pass:abc123 -key keypair.pem -out selfsigned.crt -subj '/C=US/ST=MN/L=Metro/O=Hello/OU=There/CN=localhost'

# create a p12 formatted file containing the private key and the self-signed certificate
openssl pkcs12 -export -out keystore.p12 -inkey keypair.pem -passin pass:abc123 -in selfsigned.crt -password pass:xyz789

# given the above, and using the keystore.p12 file, the following TWO springboot ssl configs both are successful

server.ssl.key-store: keystore.p12
server.ssl.key-store-password: xyz789
server.ssl.keyStoreType: PKCS12

server.ssl.key-store: keystore.p12
server.ssl.key-store-password: xyz789
server.ssl.key-password: xyz789
server.ssl.keyStoreType: PKCS12

# interesting - we can specify the private key password as being identical to the keystore password OR we can not specify the private key password at all.

# let's use some incorrect passwords to learn a bit more. maybe the error message we get will be helpful.

# try this config that specifies an incorrect keystore password
server.ssl.key-store: keystore.p12
server.ssl.key-store-password: wrong
server.ssl.key-password: xyz789
server.ssl.keyStoreType: PKCS12

# when we start the application, it fails with this at the top of the stacktrace:
# java.lang.IllegalArgumentException: java.io.IOException: keystore password was incorrect

# makes sense. ok, what about this config, that specifies an incorrect private key password
server.ssl.key-store: keystore.p12
server.ssl.key-store-password: xyz789
server.ssl.key-password: wrong
server.ssl.keyStoreType: PKCS12

# when we start the application, it fails with this at the top of the stacktrace:
# java.lang.IllegalArgumentException: java.security.UnrecoverableKeyException: Get Key failed

# interesting. what can we infer from this?
# - Both the keystore and private key ARE password protected
# - Both of these passwords are the same. This point is interesting, given how we generated the private key and keystore. The OPENSSL pkcs12 command does NOT have an option to specify different passwords for the keystore and the private key contained within. The keystore that is output from the pkcs12 command MUST be using the same password to encrypt the private key AND the keystore itself. Yet SPRINGBOOT is clearly comfortable with using keystores that have different passwords for the keystore and contained private key...what gives?

# ok, so...let's play with the keystore using java's keytool instead of openssl and see what there is to see.
keytool -importkeystore -help

Imports one or all entries from another keystore

Options:

-srckeystore <srckeystore>            source keystore name
-destkeystore <destkeystore>          destination keystore name
-srcstoretype <srcstoretype>          source keystore type
-deststoretype <deststoretype>        destination keystore type
-srcstorepass <arg>                   source keystore password
-deststorepass <arg>                  destination keystore password
-srcprotected                         source keystore password protected
-srcprovidername <srcprovidername>    source keystore provider name
-destprovidername <destprovidername>  destination keystore provider name
-srcalias <srcalias>                  source alias
-destalias <destalias>                destination alias
-srckeypass <arg>                     source key password
-destkeypass <arg>                    destination key password
-noprompt                             do not prompt
-providerclass <providerclass>        provider class name
-providerarg <arg>                    provider argument
-providerpath <pathlist>              provider classpath
-v                                    verbose output

Use "keytool -help" for all available commands

# Check that out - keytool, unlike openssl, has distinct arguments for the keystore password and key password. Let's try it, importing the existing keystore and exporting it again with the same passwords for the keystore and the key

keytool -importkeystore -srckeystore keystore.p12 -srcalias 1 -destkeystore new-keystore.p12 -deststoretype PKCS12 -srcstorepass xyz789 -srckeypass xyz789 -deststorepass def456 -destkeypass def456

# ok, use that new keystore in our app and see what happens
server.port: 8443
server.ssl.key-store: keystore.p12
server.ssl.key-store-password: def456
server.ssl.keyStoreType: PKCS12

# yup that works. Let's try making the new keystore with different passwords for the keystore and contained key
keytool -importkeystore -srckeystore keystore.p12 -srcalias 1 -destkeystore new-keystore.p12 -deststoretype PKCS12 -srcstorepass xyz789 -srckeypass xyz789 -deststorepass def456 -destkeypass def654

# we get the following output
Warning:  Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -destkeypass value.

# now that is SUPER INTERESTING. the keystore format we're using does NOT support different passwords!

# how about a different format for the keystore – will that allow different passwords? Let's try JKS
keytool -importkeystore -srckeystore keystore.p12 -srcalias 1 -destkeystore new-keystore.jks -deststoretype JKS -srcstorepass xyz789 -srckeypass xyz789 -deststorepass def456 -destkeypass def654

# ok, use that new keystore in our app. note the keystore type is JKS, and we're specifying the two different passwords we used in the above keytool command
server.ssl.key-store: keystore.jks
server.ssl.key-store-password: def456
server.ssl.key-password: def654
server.ssl.keyStoreType: JKS

# yup, that works. different passwords for the keystore and key. Let's make sure that's true. Run the app using the same password for the key as the keystore
server.ssl.key-store: keystore.jks
server.ssl.key-store-password: def456
server.ssl.key-password: def456
server.ssl.keyStoreType: JKS

# when we start the application, it fails with this at the top of the stacktrace
# java.lang.IllegalArgumentException: java.security.UnrecoverableKeyException: Cannot recover key

# so, what have we learned:
- Spring can utilize keystore files in different formats. We've looked at two of them here - PKCS12 and JKS
- Spring allows you to specify unique passwords for the keystore and key within the keystore. If the key password is not provided, Spring uses the keystore password as the key password
- The PKCS12 format does NOT support distinct passwords for the keystore itself and key within the keystore. These two passwords must be identical.
- THe JKS format DOES support different passwords for the keystore itself and the key within the keystore.

# I haven't yet dug into the history of these keystore formats to determine why the above behavior is true, but here's the tl;dr
- if you are using a PKCS12 formatted keystore, your Spring config should look like this
server.ssl.key-store: keystore.p12
server.ssl.key-store-password: <keystore password here>
server.ssl.keyStoreType: PKCS12
- if you are using a JKS formatted keystore, you Spring config can look like this if the keystore and key passwords are the same...
server.ssl.key-store: keystore.jks
server.ssl.key-store-password: <keystore password here>
server.ssl.keyStoreType: JKS
- ... or like this if the keystore and key passwords are different
server.ssl.key-store: keystore.jks
server.ssl.key-store-password: <keystore password here>
server.ssl.key-password: <key password here>
server.ssl.keyStoreType: JKS
- it is never correct to have this because the PKCS12 format does not support it
server.ssl.key-store: keystore.p12
server.ssl.key-store-password: <keystore password here>
server.ssl.key-password: <key password here>
server.ssl.keyStoreType: PKCS12