From 3f526dfc4a5761787e9fdeeddc160afa9514284d Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Thu, 8 Feb 2024 18:04:06 -0500 Subject: [PATCH] Potentially fix CMS Data Envelope creation for ECC certificates (#1003) * Attempt support for ECC certificates This generates an ephemeral EC-DH key pair for KeyAgreement within the context of the CMS data envelope, using the same underlying curve as the recipient's certificate. Signed-off-by: Alexander Scheel * Bump BouncyCastle to 2.3.0 for SPKI OID information Signed-off-by: Alexander Scheel --------- Signed-off-by: Alexander Scheel --- .../BouncyCastleSecureMimeContext.cs | 47 +++++++++++++++++-- MimeKit/MimeKit.csproj | 2 +- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/MimeKit/Cryptography/BouncyCastleSecureMimeContext.cs b/MimeKit/Cryptography/BouncyCastleSecureMimeContext.cs index ac9f7218c3..c5985bfc8b 100644 --- a/MimeKit/Cryptography/BouncyCastleSecureMimeContext.cs +++ b/MimeKit/Cryptography/BouncyCastleSecureMimeContext.cs @@ -1225,11 +1225,11 @@ public override Stream Verify (Stream signedData, out DigitalSignatureCollection } } - class CmsRecipientInfoGenerator : RecipientInfoGenerator + class OaepAwareRecipientInfoGenerator : RecipientInfoGenerator { readonly CmsRecipient recipient; - public CmsRecipientInfoGenerator (CmsRecipient recipient) + public OaepAwareRecipientInfoGenerator (CmsRecipient recipient) { this.recipient = recipient; } @@ -1268,6 +1268,9 @@ public RecipientInfo Generate (KeyParameter contentEncryptionKey, SecureRandom r var publicKeyInfo = certificate.SubjectPublicKeyInfo; AlgorithmIdentifier keyEncryptionAlgorithm; + // If the recipient explicitly opts in to OAEP encryption (even if + // the underlying certificate is not tagged with an OAEP OID), + // choose OAEP instead. if (publicKey is RsaKeyParameters && recipient.RsaEncryptionPadding?.Scheme == RsaEncryptionPaddingScheme.Oaep) { keyEncryptionAlgorithm = recipient.RsaEncryptionPadding.GetAlgorithmIdentifier (); } else { @@ -1300,7 +1303,45 @@ Stream Envelope (CmsRecipientCollection recipients, Stream content, Cancellation foreach (var recipient in recipients) { if (unique.Add (recipient.Certificate)) { - cms.AddRecipientInfoGenerator (new CmsRecipientInfoGenerator (recipient)); + var cert = recipient.Certificate; + var pub = recipient.Certificate.GetPublicKey(); + if (pub is RsaKeyParameters) { + // Bouncy Castle dispatches OAEP based off the certificate type. + // However, callers of MimeKit expect to use OAEP in S/MIME with + // certificates with PKCS#1v1.5 OIDs as these tend to be more broadly + // compatible across the ecosystem. Thus, buidl our own + // RecipientInfoGenerator and register that for this key. + cms.AddRecipientInfoGenerator (new OaepAwareRecipientInfoGenerator (recipient)); + } else if (pub is ECKeyParameters) { + var ecPub = (ECKeyParameters) pub; + var kg = GeneratorUtilities.GetKeyPairGenerator ("ECDH"); + kg.Init(new ECKeyGenerationParameters (ecPub.Parameters, RandomNumberGenerator)); + var kp = kg.GenerateKeyPair (); + + // TODO: better handle algorithm selection. + if (recipient.RecipientIdentifierType == SubjectIdentifierType.SubjectKeyIdentifier) { + var subjectKeyIdentifier = recipient.Certificate.GetExtensionValue (X509Extensions.SubjectKeyIdentifier); + cms.AddKeyAgreementRecipient ( + CmsEnvelopedDataGenerator.ECDHSha1Kdf, + kp.Public, + kp.Private, + subjectKeyIdentifier.GetOctets (), + pub, + CmsEnvelopedGenerator.Aes128Wrap + ); + } else { + cms.AddKeyAgreementRecipient ( + CmsEnvelopedDataGenerator.ECDHSha1Kdf, + kp.Public, + kp.Private, + cert, + CmsEnvelopedGenerator.Aes128Wrap + ); + } + } else { + var oid = cert.SubjectPublicKeyInfo.Algorithm.Algorithm.ToString(); + throw new ArgumentException("Unknown type of recipient certificate: " + pub.GetType().Name + " (SubjectPublicKeyInfo OID = " + oid + ")"); + } count++; } } diff --git a/MimeKit/MimeKit.csproj b/MimeKit/MimeKit.csproj index 862d6170fd..e84c4a828c 100644 --- a/MimeKit/MimeKit.csproj +++ b/MimeKit/MimeKit.csproj @@ -99,4 +99,4 @@ - \ No newline at end of file +