bcgit / bc-java

Bouncy Castle Java Distribution (Mirror)
https://www.bouncycastle.org/java.html
MIT License
2.3k stars 1.14k forks source link

Null Pointer Exception for deserialized object of class ProvSecretKeySpec from the old BC-FIPS version 1.0.1 with the newer version 1.0.2.4 #1628

Open yusuf4u52 opened 6 months ago

yusuf4u52 commented 6 months ago

I have secretKey serialized and store in the keystore file with the older version of bc-fips jar 1.0.1.

But when a deserialized the file with the newer version of the jar 1.0.2.4 I get NullPointerException for getKey. This is due to missing property private final AtomicBoolean hasBeenDestroyed = new AtomicBoolean(false); in the deserialized object.

What are my migration options?

cipherboy commented 6 months ago

@yusuf4u52 Do you have an example key store (not with real keys!) you'd mind sharing? Also the full stack trace would be appreciated. Is this the BCFKS or the Java keystore (JKS)?

(I'm having a hard time seeing how this would be triggered without a reproducer, in particular. hasBeenDestroyed is constructed in newer versions via setting it directly in the class, so it should never be null. And the BCFKS encrypts the contents of the key and uses a secret key factory to parse it, which means we're not serializing/deserializing the key instance/class (and thus, could have a missing attribute/parameter if we were say, JSON encoding), but instead the encoded contents of the raw key (usually just a byte array)).

yusuf4u52 commented 6 months ago

It is Java JCEKS keystore and I am attaching the sample keystore file.

What I meant to say is that the deserialization works but the ProvSecretKeySpec object I get has hasBeenDestroyed set to null and so the getEncoded or any other get method on ProvSecretKeySpec returns Null Pointer Exception.

keystore.zip

I will paste the stack trace in a bit.

yusuf4u52 commented 6 months ago

Throwable=[java.lang.NullPointerException org.bouncycastle.jcajce.provider.ProvSecretKeySpec.isDestroyed(Unknown Source) org.bouncycastle.jcajce.provider.KeyUtil.checkDestroyed(Unknown Source) org.bouncycastle.jcajce.provider.ProvSecretKeySpec.getFormat(Unknown Source) java.base/java.security.Provider$Service.supportsKeyFormat(Unknown Source) java.base/java.security.Provider$Service.supportsParameter(Unknown Source) java.base/javax.crypto.Cipher.chooseProvider(Unknown Source) java.base/javax.crypto.Cipher.init(Unknown Source) java.base/javax.crypto.Cipher.init(Unknown Source)

yusuf4u52 commented 6 months ago

Throwable=[java.lang.NullPointerException org.bouncycastle.jcajce.provider.ProvSecretKeySpec.isDestroyed(Unknown Source) org.bouncycastle.jcajce.provider.KeyUtil.checkDestroyed(Unknown Source) org.bouncycastle.jcajce.provider.ProvSecretKeySpec.getEncoded(Unknown Source)

cipherboy commented 6 months ago

@yusuf4u52 Sorry, mind giving me a hint as to what the keystore/entry passwords are? :-)

But I think I see the issue either way: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java uses https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/javax/crypto/SealedObject.java (https://docs.oracle.com/javase/8/docs/api/javax/crypto/SealedObject.html), which as the docs point out:

Given any Serializable object, one can create a SealedObject that encapsulates the original object, in serialized format (i.e., a "deep copy"), and seals (encrypts) its serialized contents, using a cryptographic algorithm such as AES, to protect its confidentiality. The encrypted content can later be decrypted (with the corresponding algorithm using the correct decryption key) and de-serialized, yielding the original object.

While ProvSecretKeySpec is nominally Serializable, it (and everywhere else) doesn't handle adding new attributes so it isn't cross version Serializable. ProvSecretKeySpec.readObject would need to set hasBeenDestroyed properly.

Is using BCFKS possible? I think, because it doesn't use SealedObject, it won't have this issue. Though the migration path would look like:

  1. Load JCEKS on bc 1.0.1
  2. Write keys using BCFKS
  3. Load from BCFKS on 1.0.2.4.

Notably, the 1.0.2 usage guide doesn't say the use of the JKS with secret keys is allowed... So I think using BCFKS would be most ideal.

dghgit commented 6 months ago

Okay, so the earlier keys didn't implement destroyable. I think you should be able to convert the key store to BCFKS - it will handle AES, Camellia, TripleDES, ARIA, SEED, and HMAC keys.

If it's not one of those, you'll need to convert it by hand. You could use a custom version of the FIPS provider which dealt with the missing field and then reserialise it, at which point you'll find the key store should work with 1.0.2.4.

yusuf4u52 commented 6 months ago

Yes the ProvSecretKeySpec.readObject would need to set hasBeenDestroyed properly.

I am thinking of going the custom version route.