Skip to content

Commit

Permalink
Get rid of doAsync nonsense in BouncyCastleSecureMimeContext
Browse files Browse the repository at this point in the history
  • Loading branch information
jstedfast committed Dec 18, 2024
1 parent 0ea2570 commit cf5bdc7
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 53 deletions.
215 changes: 162 additions & 53 deletions MimeKit/Cryptography/BouncyCastleSecureMimeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -883,29 +883,36 @@ internal protected static bool TryGetEncryptionAlgorithm (AlgorithmIdentifier id
return false;
}

async Task<bool> DownloadCrlsOverHttpAsync (string location, Stream stream, bool doAsync, CancellationToken cancellationToken)
bool DownloadCrlOverHttp (string location, Stream stream, CancellationToken cancellationToken)
{
try {
if (doAsync) {
using (var response = await HttpClient.GetAsync (location, cancellationToken).ConfigureAwait (false)) {
#if NET6_0_OR_GREATER
await response.Content.CopyToAsync (stream, cancellationToken).ConfigureAwait (false);
using (var response = HttpClient.GetAsync (location, cancellationToken).GetAwaiter ().GetResult ())
response.Content.CopyToAsync (stream, cancellationToken).GetAwaiter ().GetResult ();
#else
await response.Content.CopyToAsync (stream).ConfigureAwait (false);
cancellationToken.ThrowIfCancellationRequested ();

var request = (HttpWebRequest) WebRequest.Create (location);
using (var response = request.GetResponse ()) {
var content = response.GetResponseStream ();
content.CopyTo (stream, 4096);
}
#endif
}
} else {

return true;
} catch {
return false;
}
}

async Task<bool> DownloadCrlOverHttpAsync (string location, Stream stream, CancellationToken cancellationToken)
{
try {
using (var response = await HttpClient.GetAsync (location, cancellationToken).ConfigureAwait (false)) {
#if NET6_0_OR_GREATER
using (var response = HttpClient.GetAsync (location, cancellationToken).GetAwaiter ().GetResult ())
response.Content.CopyToAsync (stream, cancellationToken).GetAwaiter ().GetResult ();
await response.Content.CopyToAsync (stream, cancellationToken).ConfigureAwait (false);
#else
cancellationToken.ThrowIfCancellationRequested ();

var request = (HttpWebRequest) WebRequest.Create (location);
using (var response = request.GetResponse ()) {
var content = response.GetResponseStream ();
content.CopyTo (stream, 4096);
}
await response.Content.CopyToAsync (stream).ConfigureAwait (false);
#endif
}

Expand All @@ -917,7 +924,7 @@ async Task<bool> DownloadCrlsOverHttpAsync (string location, Stream stream, bool

#if ENABLE_LDAP
// https://msdn.microsoft.com/en-us/library/bb332056.aspx#sdspintro_topic3_lpadconn
bool DownloadCrlsOverLdap (string location, Stream stream, CancellationToken cancellationToken)
bool DownloadCrlOverLdap (string location, Stream stream, CancellationToken cancellationToken)
{
LdapUri uri;

Expand Down Expand Up @@ -964,60 +971,109 @@ bool DownloadCrlsOverLdap (string location, Stream stream, CancellationToken can
}
#endif

async Task DownloadCrlsAsync (X509Certificate certificate, bool doAsync, CancellationToken cancellationToken)
static IEnumerable<string> EnumerateCrlDistributionPointUrls (X509Certificate certificate)
{
var nextUpdate = GetNextCertificateRevocationListUpdate (certificate.IssuerDN);
var now = DateTime.UtcNow;
Asn1OctetString cdp;

if (nextUpdate > now)
return;

if ((cdp = certificate.GetExtensionValue (X509Extensions.CrlDistributionPoints)) == null)
return;
yield break;

var asn1 = Asn1Object.FromByteArray (cdp.GetOctets ());
var crlDistributionPoint = CrlDistPoint.GetInstance (asn1);
var points = crlDistributionPoint.GetDistributionPoints ();

for (int i = 0; i < points.Length; i++) {
var generalNames = GeneralNames.GetInstance (points[i].DistributionPointName.Name).GetNames ();
for (int j = 0; j < generalNames.Length; j++) {
if (generalNames[j].TagNo != GeneralName.UniformResourceIdentifier)
continue;

yield return DerIA5String.GetInstance (generalNames[j].Name).GetString ();
}
}
}

void DownloadCrls (X509Certificate certificate, CancellationToken cancellationToken)
{
var nextUpdate = GetNextCertificateRevocationListUpdate (certificate.IssuerDN);
var now = DateTime.UtcNow;

if (nextUpdate > now)
return;

using (var stream = new MemoryBlockStream ()) {
#if ENABLE_LDAP
var ldapLocations = new List<string> ();
#endif
bool downloaded = false;

for (int i = 0; i < points.Length; i++) {
var generalNames = GeneralNames.GetInstance (points[i].DistributionPointName.Name).GetNames ();
for (int j = 0; j < generalNames.Length && !downloaded; j++) {
if (generalNames[j].TagNo != GeneralName.UniformResourceIdentifier)
continue;
foreach (var location in EnumerateCrlDistributionPointUrls (certificate)) {
if (location.StartsWith ("https://", StringComparison.OrdinalIgnoreCase) ||
location.StartsWith ("http://", StringComparison.OrdinalIgnoreCase)) {
if (DownloadCrlOverHttp (location, stream, cancellationToken)) {
downloaded = true;
break;
}
} else if (location.StartsWith ("ldaps://", StringComparison.OrdinalIgnoreCase) ||
location.StartsWith ("ldap://", StringComparison.OrdinalIgnoreCase)) {
#if ENABLE_LDAP
// Note: delay downloading from LDAP urls in case we find an HTTP url instead since LDAP
// won't be as reliable on Mono systems which do not implement the LDAP functionality.
ldapLocations.Add (location);
#endif
}
}

var location = DerIA5String.GetInstance (generalNames[j].Name).GetString ();
var colon = location.IndexOf (':');
#if ENABLE_LDAP
for (int i = 0; i < ldapLocations.Count && !downloaded; i++)
downloaded = DownloadCrlOverLdap (ldapLocations[i], stream, cancellationToken);
#endif

if (colon == -1)
continue;
if (!downloaded)
return;

var protocol = location.Substring (0, colon).ToLowerInvariant ();
stream.Position = 0;

switch (protocol) {
case "https": case "http":
downloaded = await DownloadCrlsOverHttpAsync (location, stream, doAsync, cancellationToken).ConfigureAwait (false);
break;
var parser = new X509CrlParser ();
foreach (X509Crl crl in parser.ReadCrls (stream))
Import (crl, cancellationToken);
}
}

async Task DownloadCrlsAsync (X509Certificate certificate, CancellationToken cancellationToken)
{
var nextUpdate = GetNextCertificateRevocationListUpdate (certificate.IssuerDN);
var now = DateTime.UtcNow;

if (nextUpdate > now)
return;

using (var stream = new MemoryBlockStream ()) {
#if ENABLE_LDAP
case "ldaps": case "ldap":
// Note: delay downloading from LDAP urls in case we find an HTTP url instead since LDAP
// won't be as reliable on Mono systems which do not implement the LDAP functionality.
ldapLocations.Add (location);
break;
var ldapLocations = new List<string> ();
#endif
bool downloaded = false;

foreach (var location in EnumerateCrlDistributionPointUrls (certificate)) {
if (location.StartsWith ("https://", StringComparison.OrdinalIgnoreCase) ||
location.StartsWith ("http://", StringComparison.OrdinalIgnoreCase)) {
if (await DownloadCrlOverHttpAsync (location, stream, cancellationToken).ConfigureAwait (false)) {
downloaded = true;
break;
}
} else if (location.StartsWith ("ldaps://", StringComparison.OrdinalIgnoreCase) ||
location.StartsWith ("ldap://", StringComparison.OrdinalIgnoreCase)) {
#if ENABLE_LDAP
// Note: delay downloading from LDAP urls in case we find an HTTP url instead since LDAP
// won't be as reliable on Mono systems which do not implement the LDAP functionality.
ldapLocations.Add (location);
#endif
}
}

#if ENABLE_LDAP
for (int i = 0; i < ldapLocations.Count && !downloaded; i++)
downloaded = DownloadCrlsOverLdap (ldapLocations[i], stream, cancellationToken);
downloaded = DownloadCrlOverLdap (ldapLocations[i], stream, cancellationToken);
#endif

if (!downloaded)
Expand All @@ -1027,7 +1083,7 @@ async Task DownloadCrlsAsync (X509Certificate certificate, bool doAsync, Cancell

var parser = new X509CrlParser ();
foreach (X509Crl crl in parser.ReadCrls (stream))
Import (crl, cancellationToken);
await ImportAsync (crl, cancellationToken).ConfigureAwait (false);
}
}

Expand All @@ -1042,9 +1098,8 @@ async Task DownloadCrlsAsync (X509Certificate certificate, bool doAsync, Cancell
/// </remarks>
/// <returns>The digital signatures.</returns>
/// <param name="parser">The CMS signed data parser.</param>
/// <param name="doAsync">Whether the operation should be done asynchronously.</param>
/// <param name="cancellationToken">The cancellation token.</param>
async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataParser parser, bool doAsync, CancellationToken cancellationToken)
DigitalSignatureCollection GetDigitalSignatures (CmsSignedDataParser parser, CancellationToken cancellationToken)
{
var certificates = parser.GetCertificates ();
var signatures = new List<IDigitalSignature> ();
Expand All @@ -1057,7 +1112,7 @@ async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataP

if (CheckCertificateRevocation) {
foreach (var cert in certificates.EnumerateMatches (null))
await DownloadCrlsAsync (cert, doAsync, cancellationToken).ConfigureAwait (false);
DownloadCrls (cert, cancellationToken);
}

if (certificate != null) {
Expand All @@ -1071,7 +1126,61 @@ async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataP

if (CheckCertificateRevocation) {
foreach (var anchor in anchors)
await DownloadCrlsAsync (anchor.TrustedCert, doAsync, cancellationToken).ConfigureAwait (false);
DownloadCrls (anchor.TrustedCert, cancellationToken);
}

try {
signature.Chain = BuildCertPath (anchors, certificates, crls, certificate, signature.CreationDate);
} catch (Exception ex) {
signature.ChainException = ex;
}

signatures.Add (signature);
}

return new DigitalSignatureCollection (signatures);
}

/// <summary>
/// Asynchronously get the list of digital signatures.
/// </summary>
/// <remarks>
/// <para>Asynchronously gets the list of digital signatures.</para>
/// <para>This method is useful to call from within any custom
/// <a href="Overload_MimeKit_Cryptography_SecureMimeContext_VerifyAsync.htm">VerifyAsync</a>
/// method that you may implement in your own class.</para>
/// </remarks>
/// <returns>The digital signatures.</returns>
/// <param name="parser">The CMS signed data parser.</param>
/// <param name="cancellationToken">The cancellation token.</param>
async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataParser parser, CancellationToken cancellationToken)
{
var certificates = parser.GetCertificates ();
var signatures = new List<IDigitalSignature> ();
var crls = parser.GetCrls ();
var store = parser.GetSignerInfos ();

foreach (var signerInfo in store.GetSigners ()) {
var certificate = GetCertificate (certificates, signerInfo.SignerID);
var signature = new SecureMimeDigitalSignature (signerInfo, certificate);

if (CheckCertificateRevocation) {
foreach (var cert in certificates.EnumerateMatches (null))
await DownloadCrlsAsync (cert, cancellationToken).ConfigureAwait (false);
}

if (certificate != null) {
await ImportAsync (certificate, cancellationToken).ConfigureAwait (false);

if (signature.EncryptionAlgorithms.Length > 0 && signature.CreationDate != default (DateTime))
UpdateSecureMimeCapabilities (certificate, signature.EncryptionAlgorithms, signature.CreationDate);
}

var anchors = GetTrustedAnchors ();

if (CheckCertificateRevocation) {
foreach (var anchor in anchors)
await DownloadCrlsAsync (anchor.TrustedCert, cancellationToken).ConfigureAwait (false);
}

try {
Expand Down Expand Up @@ -1124,7 +1233,7 @@ public override DigitalSignatureCollection Verify (Stream content, Stream signat
signed.ContentStream.Dispose ();
}

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

Expand Down Expand Up @@ -1170,7 +1279,7 @@ public override async Task<DigitalSignatureCollection> VerifyAsync (Stream conte
signed.ContentStream.Dispose ();
}

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

Expand Down Expand Up @@ -1211,7 +1320,7 @@ public override DigitalSignatureCollection Verify (Stream signedData, out MimeEn
signed.ContentStream.Dispose ();
}

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

Expand Down Expand Up @@ -1252,7 +1361,7 @@ public override Stream Verify (Stream signedData, out DigitalSignatureCollection
signed.ContentStream.Dispose ();
}

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

return content;
}
Expand Down
1 change: 1 addition & 0 deletions UnitTests/Cryptography/SecureMimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ public void TestArgumentExceptions ()
Assert.Throws<ArgumentNullException> (() => ctx.Import ("fileName", null));
Assert.Throws<ArgumentNullException> (() => ctx.Import ((X509Crl) null));
Assert.Throws<ArgumentNullException> (() => ctx.Import ((X509Certificate) null));
Assert.Throws<ArgumentNullException> (() => ctx.Import ((X509Certificate2) null));
Assert.ThrowsAsync<ArgumentNullException> (() => ctx.ImportAsync ((Stream) null));
Assert.ThrowsAsync<ArgumentNullException> (() => ctx.ImportAsync ((Stream) null, "password"));
Assert.ThrowsAsync<ArgumentNullException> (() => ctx.ImportAsync (stream, null));
Expand Down

0 comments on commit cf5bdc7

Please sign in to comment.