bcgit / bc-java

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

Exception when unwrapping PKCS7 CMS envelope that encrypted with RSAES-OAEP (1.2.840.113549.1.1.7) asymmetric algorithm #1277

Open adams-y-chen opened 1 year ago

adams-y-chen commented 1 year ago

Symptom:

My java application throws exception when unwrapping PKCS7 CMS envelope that encrypted with RSAES-OAEP. The PKCS7 CMS envelope is created using Microsoft .NET.

RFC Spec and RSAES-OAEP Definition: 1.2.840.113549.1.1.7 IETF RFC 3447 and RFC 8017.

Backtrace:

<#Exception#><org.bouncycastle.cms.CMSException> exception unwrapping key: cannot create cipher: No provider found for 1.2.840.113549.1.1.7
    at org.bouncycastle.cms.jcajce.JceKeyTransRecipient.extractSecretKey(Unknown Source:270)
    at org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient.getRecipientOperator(Unknown Source:0)
    at org.bouncycastle.cms.KeyTransRecipientInformation.getRecipientOperator(Unknown Source:16)
    at org.bouncycastle.cms.RecipientInformation.getContentStream(Unknown Source:0)
    at org.bouncycastle.cms.RecipientInformation.getContent(Unknown Source:0)
...
<#Exception#><org.bouncycastle.operator.OperatorCreationException> Caused by: cannot create cipher: No provider found for 1.2.840.113549.1.1.7
    at org.bouncycastle.operator.jcajce.OperatorHelper.createAsymmetricWrapper(Unknown Source:85)
    at org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper.generateUnwrappedKey(Unknown Source:12)
    at org.bouncycastle.cms.jcajce.JceKeyTransRecipient.extractSecretKey(Unknown Source:230)
    at org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient.getRecipientOperator(Unknown Source:0)
    at org.bouncycastle.cms.KeyTransRecipientInformation.getRecipientOperator(Unknown Source:16)
    at org.bouncycastle.cms.RecipientInformation.getContentStream(Unknown Source:0)
    at org.bouncycastle.cms.RecipientInformation.getContent(Unknown Source:0)
...
<#Exception#><java.security.NoSuchAlgorithmException> Caused by: No provider found for 1.2.840.113549.1.1.7
    at javax.crypto.Cipher.createCipher(Cipher.java:737)
    at javax.crypto.Cipher.getInstance(Cipher.java:620)
    at org.bouncycastle.jcajce.util.DefaultJcaJceHelper.createCipher(Unknown Source:0)
    at org.bouncycastle.operator.jcajce.OperatorHelper.createAsymmetricWrapper(Unknown Source:56)
    at org.bouncycastle.operator.jcajce.JceAsymmetricKeyUnwrapper.generateUnwrappedKey(Unknown Source:12)
    at org.bouncycastle.cms.jcajce.JceKeyTransRecipient.extractSecretKey(Unknown Source:230)
    at org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient.getRecipientOperator(Unknown Source:0)
    at org.bouncycastle.cms.KeyTransRecipientInformation.getRecipientOperator(Unknown Source:16)
    at org.bouncycastle.cms.RecipientInformation.getContentStream(Unknown Source:0)
    at org.bouncycastle.cms.RecipientInformation.getContent(Unknown Source:0)
...

Root cause: The mapping from PKCSObjectIdentifiers.id_RSAES_OAEP (i.e. OID 1.2.840.113549.1.1.7) to RSAES-OAEP algorithm name is missing in asymmetricWrapperAlgNames. See: OperatorHelper.java

This would result in passing the OID string to javax.crypto.Cipher.createCipher instead of a valid Cipher algorithm name. This eventually caused the exception as the OID string is not a valid Cipher algorithm name in JCE/JCA.

To fix the issue, the mapping from PKCSObjectIdentifiers.id_RSAES_OAEP (i.e. OID 1.2.840.113549.1.1.7) to RSAES-OAEP algorithm name is needed in asymmetricWrapperAlgNames in OperatorHelper.java.

Proposal:

Add the mapping from PKCSObjectIdentifiers.id_RSAES_OAEP (i.e. OID 1.2.840.113549.1.1.7) to RSAES-OAEP algorithm name in asymmetricWrapperAlgNames in OperatorHelper.java.

  1. Identify the RSAES-OAEP algorithm name string that shall be added to the mapping.
  2. Add the mapping in a dev branch and create a beta build. Consume the beta build in the test application and validate with test data. This it is to validate JCE and BouncyCastle RSAES-OAEP implementation is compatible with .NET framework implementation.
  3. Other: get more details on Microsoft .NET RSAES-OAEP implementation so we can be certain of the solution.
adams-y-chen commented 1 year ago

Here is a related PR that was proposed by @jensthomassen.

adams-y-chen commented 1 year ago

For those who have similar concern, here is a workaround by specifying the mapping in your application code. However, it is preferred to add this mapping in BouncyCastle library so that user don't have to handle it.

public static byte[] decryptData(
  byte[] encryptedData, 
  PrivateKey decryptionKey) 
  throws CMSException {
        CMSEnvelopedData envelopedData = new CMSEnvelopedData(encryptedData);

        Collection<RecipientInformation> recipients = envelopedData.getRecipientInfos().getRecipients();
        KeyTransRecipientInformation recipientInfo  = (KeyTransRecipientInformation) recipients.iterator().next();
        JceKeyTransRecipient recipient  = new JceKeyTransEnvelopedRecipient(decryptionKey);

        // Provide the mapping in application code.
        final String jceAlgorithmName = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";        
        recipient  .setAlgorithmMapping(PKCSObjectIdentifiers.id_RSAES_OAEP, jceAlgorithmName);

        return recipientInfo.getContent(recipient);
}
dghgit commented 1 year ago

Thanks for the report, this makes it a bit clearer what is going on.

So, part of the issue is going to be the use of the SunJCE which doesn't recognize the OAEP OID, but instead uses "OAEP" for the algorithm parameters and "RSA/ECB/OAEPPadding" for the default cipher name. BC actually supports both, so I've added support for it.

I'm not sure this will fix the other issue though - the PR is "wrong", as is the mapping above, it will work for the specific case but not for OAEP in general. From what you are describing here it may just be that the original submitter didn't realize there were algorithm parameters involved.

Try the bcpkix jar at https://www.bouncycastle.org/betas (173b09 or later) - it has the SunJCE fix in it. This may fix the problem. Let me know how you go.

adams-y-chen commented 1 year ago

@dghgit thanks for getting back to me. I want to clarify so we have mutual understanding of the gap and request.

From what I understand, JCE expects a name string in javax.crypto.Cipher.getInstance() instead of an OID. BouncyCastle takes care of mapping the OID in PKCS7 CMS to the correct cipher name string. What's missing here that RSAES-OAEP (1.2.840.113549.1.1.7) mapping is missing in BouncyCastle CMS code. My solution is to specify the mapping in my application code. Would appreciate if you share be more specific what issue this solution may have?

Also, could you be more specific on the issues that may be caused by https://github.com/bcgit/bc-java/pull/953?

Updated: I haven't tried https://www.bouncycastle.org/betas (173b09 or later) which I doubt will address the issue. My Android app uses JCE from the Android SDK which should not be affected by replace my BouncyCastle from 1.70 with beta build. Please correct me if I misunderstand.

dghgit commented 1 year ago

953 fails to take into account that RSA OAEP has algorithm parameters and is used with SHA-1, SHA-2, SHA-3 and even the XOFs SHAKE-128 and SHAKE-256. Try the beta - it's the bcpkix jar you want.

adams-y-chen commented 1 year ago

@dghgit thanks for explaining it. To reflect on what I heard, JCE javax.crypto.Cipher.getInstance() in the beta build can accept the OID and handling it properly. And as a result, BouncyCastle is not expected to do the mapping from OID to decided the proper cipher name. Please let me if I get your message correctly.

I will figure out how I can consume the beta build in my app before it's available in Maven. In the meantime, do you have an estimation on when the beta build will become GA in maven?

adams-y-chen commented 1 year ago

@dghgit I'd like to have you opinion on another exception in my application after I switched from SpongyCastle to BouncyCastle 1.70.

The exception complains that provider for PBKDFS2 (1.2.840.113549.1.5.12) can't be found when attempts to decrypt PKCS12 keystore file. Do you think if this is also addressed by the latest beta build https://www.bouncycastle.org/betas?

Backtrace:

<#Exception#><java.io.IOException> exception decrypting data - java.security.NoSuchAlgorithmException: no such algorithm: 1.2.840.113549.1.5.12 for provider BC
at com.android.org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.cryptData(PKCS12KeyStoreSpi.java:724)
at com.android.org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.engineLoad(PKCS12KeyStoreSpi.java:959)
at java.security.KeyStore.load(KeyStore.java:1484)
...omitted for security... 
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)

PBKDF2 OID References: http://oid-info.com/get/1.2.840.113549.1.5.12 https://www.rfc-editor.org/rfc/rfc8018.

dghgit commented 1 year ago

We've supported PBKDFS2 for quite a while now. The issue you have here is that the stack trace isn't from Bouncy Castle, it's from the Android fork of it. Spongy Castle is Bouncy Castle, the BC built into Android... well, not quite.

dghgit commented 1 year ago

@adams-y-chen would you confirm if the beta fixed the problem? Thanks.

adams-y-chen commented 1 year ago

@dghgit sorry for getting back to you late. I was pulled away from this. I will get back to it and let you know how it goes.

adams-y-chen commented 1 year ago

@dghgit, I run the test code today with both BouncyCastle beta build and 1.70 release build. However, I'm not able to reproduce the issue today with both builds. This would prevent me from getting a conclusion whether this issue is addressed in the beta build.

To give some context, I encountered compiling issue after come back to my Android repo. After I made some attempts to address the issue which including clean up the reop, I can no longer reproduce the issue by running my Android Unit Test on my desktop with BouncyCastle 1.70.

You mentioned that the issue was tied to SunJCE. I'd like to troubleshoot and check if SunJCE is called by my UTs. Could you share some guidance on the items below?

  1. Could you share the repo code that specify SunJCE dependency in BouncyCastle git repo?
  2. What change is introduced in the BouncyCastle that lead to the supports of RSAES-OAEP (1.2.840.113549.1.1.7) cipher OID? Does the beta build specify a newer SunJCE package release version?
  3. How can I examine if SunJCE or a different JCE implementation is pulled into the dependencies of my test?

I can share my test code, test certificate and PCKS CMS envelope if you need to reproduce on your end.