Skip to content

Commit

Permalink
Port to BouncyCastle.Cryptography 2.1.1
Browse files Browse the repository at this point in the history
Fixes issue #865

BouncyCastle.Cryptography 2.1.1 is causing some unit tests to fail and I don't know why.

1. Decompressing S/MIME-compressed data fails with an exception statring that the compression
   algorithm used is unknown/invalid, but the compression algorithm used was ZLib. WTF?
2. A bunch of S/MIME tests are failing due to "Org.BouncyCastle.Asn1.Asn1ParsingException:
   long form definite-length more than 31 bits" when verifying/decrypting. This is thrown by
   CmsSignedDataParser.GetCertificates(). No idea how to fix this.
  • Loading branch information
jstedfast committed Mar 5, 2023
1 parent 52de59a commit d256611
Show file tree
Hide file tree
Showing 24 changed files with 263 additions and 192 deletions.
3 changes: 1 addition & 2 deletions MimeKit/Cryptography/AsymmetricAlgorithmExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;

namespace MimeKit.Cryptography
{
namespace MimeKit.Cryptography {
/// <summary>
/// Extension methods for System.Security.Cryptography.AsymmetricAlgorithm.
/// </summary>
Expand Down
162 changes: 92 additions & 70 deletions MimeKit/Cryptography/BouncyCastleSecureMimeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@

using MimeKit.IO;

namespace MimeKit.Cryptography
{
namespace MimeKit.Cryptography {
/// <summary>
/// A Secure MIME (S/MIME) cryptography context.
/// </summary>
Expand Down Expand Up @@ -140,7 +139,7 @@ public bool CheckCertificateRevocation {
/// </remarks>
/// <returns>The certificate on success; otherwise <c>null</c>.</returns>
/// <param name="selector">The search criteria for the certificate.</param>
protected abstract X509Certificate GetCertificate (IX509Selector selector);
protected abstract X509Certificate GetCertificate (ISelector<X509Certificate> selector);

/// <summary>
/// Get the private key for the certificate matching the specified selector.
Expand All @@ -151,7 +150,7 @@ public bool CheckCertificateRevocation {
/// </remarks>
/// <returns>The private key on success; otherwise, <c>null</c>.</returns>
/// <param name="selector">The search criteria for the private key.</param>
protected abstract AsymmetricKeyParameter GetPrivateKey (IX509Selector selector);
protected abstract AsymmetricKeyParameter GetPrivateKey (ISelector<X509Certificate> selector);

/// <summary>
/// Get the trusted anchors.
Expand All @@ -163,7 +162,7 @@ public bool CheckCertificateRevocation {
/// signed content.</para>
/// </remarks>
/// <returns>The trusted anchors.</returns>
protected abstract HashSet GetTrustedAnchors ();
protected abstract ISet<TrustAnchor> GetTrustedAnchors ();

/// <summary>
/// Get the intermediate certificates.
Expand All @@ -176,7 +175,7 @@ public bool CheckCertificateRevocation {
/// signed content.</para>
/// </remarks>
/// <returns>The intermediate certificates.</returns>
protected abstract IX509Store GetIntermediateCertificates ();
protected abstract IStore<X509Certificate> GetIntermediateCertificates ();

/// <summary>
/// Get the certificate revocation lists.
Expand All @@ -187,7 +186,7 @@ public bool CheckCertificateRevocation {
/// itself or by the owner of the revoked certificate.
/// </remarks>
/// <returns>The certificate revocation lists.</returns>
protected abstract IX509Store GetCertificateRevocationLists ();
protected abstract IStore<X509Crl> GetCertificateRevocationLists ();

/// <summary>
/// Get the date &amp; time for the next scheduled certificate revocation list update for the specified issuer.
Expand Down Expand Up @@ -646,9 +645,9 @@ async Task<Stream> SignAsync (CmsSigner signer, Stream content, bool encapsulate
return await SignAsync (cmsSigner, content, cancellationToken).ConfigureAwait (false);
}

X509Certificate GetCertificate (IX509Store store, SignerID signer)
X509Certificate GetCertificate (IStore<X509Certificate> store, SignerID signer)
{
var matches = store.GetMatches (signer);
var matches = store.EnumerateMatches (signer);

foreach (X509Certificate certificate in matches)
return certificate;
Expand Down Expand Up @@ -677,10 +676,10 @@ protected IList<X509Certificate> BuildCertificateChain (X509Certificate certific

var parameters = new PkixBuilderParameters (GetTrustedAnchors (), selector);
parameters.ValidityModel = PkixParameters.PkixValidityModel;
parameters.AddStore (intermediates);
parameters.AddStore (GetIntermediateCertificates ());
parameters.AddStoreCert (intermediates);
parameters.AddStoreCert (GetIntermediateCertificates ());
parameters.IsRevocationEnabled = false;
parameters.Date = new DateTimeObject (DateTime.UtcNow);
parameters.Date = DateTime.UtcNow;

var builder = new PkixCertPathBuilder ();
var result = builder.Build (parameters);
Expand All @@ -693,29 +692,29 @@ protected IList<X509Certificate> BuildCertificateChain (X509Certificate certific
return chain;
}

PkixCertPath BuildCertPath (HashSet anchors, IX509Store certificates, IX509Store crls, X509Certificate certificate, DateTime signingTime)
PkixCertPath BuildCertPath (ISet<TrustAnchor> anchors, IStore<X509Certificate> certificates, IStore<X509Crl> crls, X509Certificate certificate, DateTime signingTime)
{
var selector = new X509CertStoreSelector ();
selector.Certificate = certificate;

var intermediates = new X509CertificateStore ();
intermediates.Add (certificate);

foreach (X509Certificate cert in certificates.GetMatches (null))
foreach (X509Certificate cert in certificates.EnumerateMatches (null))
intermediates.Add (cert);

var parameters = new PkixBuilderParameters (anchors, selector);
parameters.AddStore (intermediates);
parameters.AddStore (crls);
parameters.AddStoreCert (intermediates);
parameters.AddStoreCrl (crls);

parameters.AddStore (GetIntermediateCertificates ());
parameters.AddStore (GetCertificateRevocationLists ());
parameters.AddStoreCert (GetIntermediateCertificates ());
parameters.AddStoreCrl (GetCertificateRevocationLists ());

parameters.ValidityModel = PkixParameters.PkixValidityModel;
parameters.IsRevocationEnabled = false;

if (signingTime != default (DateTime))
parameters.Date = new DateTimeObject (signingTime);
parameters.Date = signingTime;

var builder = new PkixCertPathBuilder ();
var result = builder.Build (parameters);
Expand Down Expand Up @@ -1015,9 +1014,9 @@ async Task DownloadCrlsAsync (X509Certificate certificate, bool doAsync, Cancell
/// <param name="cancellationToken">The cancellation token.</param>
async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataParser parser, bool doAsync, CancellationToken cancellationToken)
{
var certificates = parser.GetCertificates ("Collection");
var certificates = parser.GetCertificates ();
var signatures = new List<IDigitalSignature> ();
var crls = parser.GetCrls ("Collection");
var crls = parser.GetCrls ();
var store = parser.GetSignerInfos ();

foreach (SignerInformation signerInfo in store.GetSigners ()) {
Expand Down Expand Up @@ -1077,12 +1076,17 @@ async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataP
if (signatureData == null)
throw new ArgumentNullException (nameof (signatureData));

var parser = new CmsSignedDataParser (new CmsTypedStream (content), signatureData);
var signed = parser.GetSignedContent ();
using (var parser = new CmsSignedDataParser (new CmsTypedStream (content), signatureData)) {
var signed = parser.GetSignedContent ();

signed.Drain ();
try {
signed.ContentStream.CopyTo (Stream.Null, 4096);
} finally {
signed.ContentStream.Dispose ();
}

return GetDigitalSignaturesAsync (parser, false, cancellationToken).GetAwaiter ().GetResult ();
return GetDigitalSignaturesAsync (parser, false, cancellationToken).GetAwaiter ().GetResult ();
}
}

/// <summary>
Expand All @@ -1106,20 +1110,25 @@ async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataP
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
public override Task<DigitalSignatureCollection> VerifyAsync (Stream content, Stream signatureData, CancellationToken cancellationToken = default (CancellationToken))
public override async Task<DigitalSignatureCollection> VerifyAsync (Stream content, Stream signatureData, CancellationToken cancellationToken = default (CancellationToken))
{
if (content == null)
throw new ArgumentNullException (nameof (content));

if (signatureData == null)
throw new ArgumentNullException (nameof (signatureData));

var parser = new CmsSignedDataParser (new CmsTypedStream (content), signatureData);
var signed = parser.GetSignedContent ();
using (var parser = new CmsSignedDataParser (new CmsTypedStream (content), signatureData)) {
var signed = parser.GetSignedContent ();

signed.Drain ();
try {
await signed.ContentStream.CopyToAsync (Stream.Null, 4096).ConfigureAwait (false);
} finally {
signed.ContentStream.Dispose ();
}

return GetDigitalSignaturesAsync (parser, true, cancellationToken);
return await GetDigitalSignaturesAsync (parser, true, cancellationToken).ConfigureAwait (false);
}
}

/// <summary>
Expand Down Expand Up @@ -1149,13 +1158,18 @@ async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataP
if (signedData == null)
throw new ArgumentNullException (nameof (signedData));

var parser = new CmsSignedDataParser (signedData);
var signed = parser.GetSignedContent ();
using (var parser = new CmsSignedDataParser (signedData)) {
var signed = parser.GetSignedContent ();

entity = MimeEntity.Load (signed.ContentStream, cancellationToken);
signed.Drain ();
try {
entity = MimeEntity.Load (signed.ContentStream, cancellationToken);
signed.ContentStream.CopyTo (Stream.Null, 4096);
} finally {
signed.ContentStream.Dispose ();
}

return GetDigitalSignaturesAsync (parser, false, cancellationToken).GetAwaiter ().GetResult ();
return GetDigitalSignaturesAsync (parser, false, cancellationToken).GetAwaiter ().GetResult ();
}
}

/// <summary>
Expand All @@ -1182,17 +1196,23 @@ async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataP
if (signedData == null)
throw new ArgumentNullException (nameof (signedData));

var parser = new CmsSignedDataParser (signedData);
var signed = parser.GetSignedContent ();
var content = new MemoryBlockStream ();
using (var parser = new CmsSignedDataParser (signedData)) {
var signed = parser.GetSignedContent ();
var content = new MemoryBlockStream ();

signed.ContentStream.CopyTo (content, 4096);
content.Position = 0;
signed.Drain ();
try {
signed.ContentStream.CopyTo (content, 4096);
content.Position = 0;
} catch {
content.Dispose ();
} finally {
signed.ContentStream.Dispose ();
}

signatures = GetDigitalSignaturesAsync (parser, false, cancellationToken).GetAwaiter ().GetResult ();
signatures = GetDigitalSignaturesAsync (parser, false, cancellationToken).GetAwaiter ().GetResult ();

return content;
return content;
}
}

class CmsRecipientInfoGenerator : RecipientInfoGenerator
Expand Down Expand Up @@ -1499,24 +1519,25 @@ async Task<MimeEntity> DecryptAsync (Stream encryptedData, bool doAsync, Cancell
if (encryptedData == null)
throw new ArgumentNullException (nameof (encryptedData));

var parser = new CmsEnvelopedDataParser (encryptedData);
var recipients = parser.GetRecipientInfos ();
var algorithm = parser.EncryptionAlgorithmID;
AsymmetricKeyParameter key;
using (var parser = new CmsEnvelopedDataParser (encryptedData)) {
var recipients = parser.GetRecipientInfos ();
var algorithm = parser.EncryptionAlgorithmID;
AsymmetricKeyParameter key;

foreach (RecipientInformation recipient in recipients.GetRecipients ()) {
if ((key = GetPrivateKey (recipient.RecipientID)) == null)
continue;
foreach (RecipientInformation recipient in recipients.GetRecipients ()) {
if ((key = GetPrivateKey (recipient.RecipientID)) == null)
continue;

var content = recipient.GetContentStream (key);
var content = recipient.GetContentStream (key);

try {
if (doAsync)
return await MimeEntity.LoadAsync (content.ContentStream, false, cancellationToken).ConfigureAwait (false);
try {
if (doAsync)
return await MimeEntity.LoadAsync (content.ContentStream, false, cancellationToken).ConfigureAwait (false);

return MimeEntity.Load (content.ContentStream, false, cancellationToken);
} finally {
content.ContentStream.Dispose ();
return MimeEntity.Load (content.ContentStream, false, cancellationToken);
} finally {
content.ContentStream.Dispose ();
}
}
}

Expand Down Expand Up @@ -1577,21 +1598,22 @@ async Task DecryptToAsync (Stream encryptedData, Stream decryptedData, bool doAs
if (decryptedData == null)
throw new ArgumentNullException (nameof (decryptedData));

var parser = new CmsEnvelopedDataParser (encryptedData);
var recipients = parser.GetRecipientInfos ();
var algorithm = parser.EncryptionAlgorithmID;
AsymmetricKeyParameter key;
using (var parser = new CmsEnvelopedDataParser (encryptedData)) {
var recipients = parser.GetRecipientInfos ();
var algorithm = parser.EncryptionAlgorithmID;
AsymmetricKeyParameter key;

foreach (RecipientInformation recipient in recipients.GetRecipients ()) {
if ((key = GetPrivateKey (recipient.RecipientID)) == null)
continue;
foreach (RecipientInformation recipient in recipients.GetRecipients ()) {
if ((key = GetPrivateKey (recipient.RecipientID)) == null)
continue;

var content = recipient.GetContentStream (key);
if (doAsync)
await content.ContentStream.CopyToAsync (decryptedData, 4096, cancellationToken).ConfigureAwait (false);
else
content.ContentStream.CopyTo (decryptedData, 4096);
return;
var content = recipient.GetContentStream (key);
if (doAsync)
await content.ContentStream.CopyToAsync (decryptedData, 4096, cancellationToken).ConfigureAwait (false);
else
content.ContentStream.CopyTo (decryptedData, 4096);
return;
}
}

throw new CmsException ("A suitable private key could not be found for decrypting.");
Expand Down
7 changes: 4 additions & 3 deletions MimeKit/Cryptography/CmsSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ public class CmsSigner
/// </remarks>
CmsSigner ()
{
UnsignedAttributes = new AttributeTable (new Dictionary<DerObjectIdentifier, Asn1Encodable> ());
SignedAttributes = new AttributeTable (new Dictionary<DerObjectIdentifier, Asn1Encodable> ());
UnsignedAttributes = new AttributeTable (new Dictionary<DerObjectIdentifier, object> ());
SignedAttributes = new AttributeTable (new Dictionary<DerObjectIdentifier, object> ());
//RsaSignaturePadding = RsaSignaturePadding.Pkcs1;
DigestAlgorithm = DigestAlgorithm.Sha256;
}
Expand Down Expand Up @@ -183,7 +183,8 @@ public CmsSigner (X509Certificate certificate, AsymmetricKeyParameter key, Subje

void LoadPkcs12 (Stream stream, string password, SubjectIdentifierType signerIdentifierType)
{
var pkcs12 = new Pkcs12Store (stream, password.ToCharArray ());
var pkcs12 = new Pkcs12StoreBuilder ().Build ();
pkcs12.Load (stream, password.ToCharArray ());
bool hasPrivateKey = false;

foreach (string alias in pkcs12.Aliases) {
Expand Down
Loading

0 comments on commit d256611

Please sign in to comment.