56
56
using IssuerAndSerialNumber = Org . BouncyCastle . Asn1 . Cms . IssuerAndSerialNumber ;
57
57
58
58
using MimeKit . IO ;
59
+ using System . Linq ;
60
+ using Org . BouncyCastle . Tls ;
59
61
60
62
namespace MimeKit . Cryptography {
61
63
/// <summary>
@@ -169,6 +171,9 @@ protected virtual HttpClient HttpClient {
169
171
/// generally issued by a certificate authority (CA).</para>
170
172
/// <para>This method is used to build a certificate chain while verifying
171
173
/// signed content.</para>
174
+ /// <para>It is critical to always load the designated trust anchors
175
+ /// and not the anchor in the end certificate when building a certificate chain
176
+ /// to validated trust.</para>
172
177
/// </remarks>
173
178
/// <returns>The trusted anchors.</returns>
174
179
protected abstract ISet < TrustAnchor > GetTrustedAnchors ( ) ;
@@ -325,6 +330,9 @@ CmsSignedDataStreamGenerator CreateSignedDataGenerator (CmsSigner signer)
325
330
326
331
Stream Sign ( CmsSigner signer , Stream content , bool encapsulate , CancellationToken cancellationToken )
327
332
{
333
+ if ( CheckCertificateRevocation )
334
+ ValidateCertificateChain ( signer . CertificateChain , DateTime . UtcNow , cancellationToken ) ;
335
+
328
336
var signedData = CreateSignedDataGenerator ( signer ) ;
329
337
var memory = new MemoryBlockStream ( ) ;
330
338
@@ -339,6 +347,9 @@ Stream Sign (CmsSigner signer, Stream content, bool encapsulate, CancellationTok
339
347
340
348
async Task < Stream > SignAsync ( CmsSigner signer , Stream content , bool encapsulate , CancellationToken cancellationToken )
341
349
{
350
+ if ( CheckCertificateRevocation )
351
+ await ValidateCertificateChainAsync ( signer . CertificateChain , DateTime . UtcNow , cancellationToken ) ;
352
+
342
353
var signedData = CreateSignedDataGenerator ( signer ) ;
343
354
var memory = new MemoryBlockStream ( ) ;
344
355
@@ -694,20 +705,31 @@ X509Certificate GetCertificate (IStore<X509Certificate> store, SignerID signer)
694
705
/// <returns>The certificate chain, including the specified certificate.</returns>
695
706
protected IList < X509Certificate > BuildCertificateChain ( X509Certificate certificate )
696
707
{
697
- var selector = new X509CertStoreSelector {
698
- Certificate = certificate
699
- } ;
708
+ var selector = new X509CertStoreSelector ( ) ;
700
709
701
- var intermediates = new X509CertificateStore ( ) ;
702
- intermediates . Add ( certificate ) ;
710
+ var userCertificateStore = new X509CertificateStore ( ) ;
711
+ userCertificateStore . Add ( certificate ) ;
703
712
704
- var parameters = new PkixBuilderParameters ( GetTrustedAnchors ( ) , selector ) {
713
+ var issuerStore = GetTrustedAnchors ( ) ;
714
+ var anchorStore = new X509CertificateStore ( ) ;
715
+
716
+ foreach ( var anchor in issuerStore ) {
717
+ anchorStore . Add ( anchor . TrustedCert ) ;
718
+ }
719
+
720
+ var parameters = new PkixBuilderParameters ( issuerStore , selector ) {
705
721
ValidityModel = PkixParameters . PkixValidityModel ,
706
722
IsRevocationEnabled = false ,
707
723
Date = DateTime . UtcNow
708
724
} ;
709
- parameters . AddStoreCert ( intermediates ) ;
710
- parameters . AddStoreCert ( GetIntermediateCertificates ( ) ) ;
725
+ parameters . AddStoreCert ( userCertificateStore ) ;
726
+
727
+ var intermediateStore = GetIntermediateCertificates ( ) ;
728
+
729
+ foreach ( var intermediate in intermediateStore . EnumerateMatches ( new X509CertStoreSelector ( ) ) )
730
+ anchorStore . Add ( intermediate ) ;
731
+
732
+ parameters . AddStoreCert ( anchorStore ) ;
711
733
712
734
var builder = new PkixCertPathBuilder ( ) ;
713
735
var result = builder . Build ( parameters ) ;
@@ -720,6 +742,158 @@ protected IList<X509Certificate> BuildCertificateChain (X509Certificate certific
720
742
return chain ;
721
743
}
722
744
745
+ /// <summary>
746
+ /// Validate an S/MIME certificate chain.
747
+ /// </summary>
748
+ /// <remarks>
749
+ /// <para>Validates an S/MIME certificate chain.</para>
750
+ /// <para>Downloads the CRLs for each certificate in the chain and then validates that the chain
751
+ /// is both valid and that none of the certificates in the chain have been revoked or compromised
752
+ /// in any way.</para>
753
+ /// </remarks>
754
+ /// <returns><c>true</c> if the certificate chain is valid; otherwise, <c>false</c>.</returns>
755
+ /// <param name="chain">The S/MIME certificate chain.</param>
756
+ /// <param name="dateTime">The date and time to use for validation.</param>
757
+ /// <param name="cancellationToken">The cancellation token.</param>
758
+ /// <exception cref="ArgumentNullException">
759
+ /// <paramref name="chain"/> is <see langword="null"/>.
760
+ /// </exception>
761
+ /// <exception cref="ArgumentException">
762
+ /// <paramref name="chain"/> is empty or contains a <see langword="null"/> certificate.
763
+ /// </exception>
764
+ public bool ValidateCertificateChain ( X509CertificateChain chain , DateTime dateTime , CancellationToken cancellationToken = default )
765
+ {
766
+ if ( chain == null )
767
+ throw new ArgumentNullException ( nameof ( chain ) ) ;
768
+
769
+ if ( chain . Count == 0 )
770
+ throw new ArgumentException ( "The certificate chain must contain at least one certificate." , nameof ( chain ) ) ;
771
+
772
+ if ( chain . Any ( certificate => certificate == null ) )
773
+ throw new ArgumentException ( "The certificate chain contains at least one null certificate." , nameof ( chain ) ) ;
774
+
775
+ var selector = new X509CertStoreSelector ( ) ;
776
+
777
+ var userCertificateStore = new X509CertificateStore ( ) ;
778
+ userCertificateStore . AddRange ( chain ) ;
779
+
780
+ var issuerStore = GetTrustedAnchors ( ) ;
781
+ var anchorStore = new X509CertificateStore ( ) ;
782
+
783
+ foreach ( var anchor in issuerStore ) {
784
+ anchorStore . Add ( anchor . TrustedCert ) ;
785
+ }
786
+
787
+ var parameters = new PkixBuilderParameters ( issuerStore , selector ) {
788
+ ValidityModel = PkixParameters . PkixValidityModel ,
789
+ IsRevocationEnabled = false ,
790
+ Date = DateTime . UtcNow
791
+ } ;
792
+ parameters . AddStoreCert ( userCertificateStore ) ;
793
+
794
+ if ( CheckCertificateRevocation ) {
795
+ foreach ( var certificate in chain )
796
+ DownloadCrls ( certificate , cancellationToken ) ;
797
+ }
798
+
799
+ var intermediateStore = GetIntermediateCertificates ( ) ;
800
+
801
+ foreach ( var intermediate in intermediateStore . EnumerateMatches ( new X509CertStoreSelector ( ) ) ) {
802
+ anchorStore . Add ( intermediate ) ;
803
+ if ( CheckCertificateRevocation )
804
+ DownloadCrls ( intermediate , cancellationToken ) ;
805
+ }
806
+
807
+ parameters . AddStoreCert ( anchorStore ) ;
808
+
809
+ if ( CheckCertificateRevocation )
810
+ parameters . AddStoreCrl ( GetCertificateRevocationLists ( ) ) ;
811
+
812
+ try {
813
+ var builder = new PkixCertPathBuilder ( ) ;
814
+ builder . Build ( parameters ) ;
815
+ return true ;
816
+ } catch {
817
+ return false ;
818
+ }
819
+ }
820
+
821
+ /// <summary>
822
+ /// Validate an S/MIME certificate chain.
823
+ /// </summary>
824
+ /// <remarks>
825
+ /// <para>Asynchronously validates an S/MIME certificate chain.</para>
826
+ /// <para>Downloads the CRLs for each certificate in the chain and then validates that the chain
827
+ /// is both valid and that none of the certificates in the chain have been revoked or compromised
828
+ /// in any way.</para>
829
+ /// </remarks>
830
+ /// <returns><c>true</c> if the certificate chain is valid; otherwise, <c>false</c>.</returns>
831
+ /// <param name="chain">The S/MIME certificate chain.</param>
832
+ /// <param name="dateTime">The date and time to use for validation.</param>
833
+ /// <param name="cancellationToken">The cancellation token.</param>
834
+ /// <exception cref="ArgumentNullException">
835
+ /// <paramref name="chain"/> is <see langword="null"/>.
836
+ /// </exception>
837
+ /// <exception cref="ArgumentException">
838
+ /// <paramref name="chain"/> is empty or contains a <see langword="null"/> certificate.
839
+ /// </exception>
840
+ public async Task < bool > ValidateCertificateChainAsync ( X509CertificateChain chain , DateTime dateTime , CancellationToken cancellationToken = default )
841
+ {
842
+ if ( chain == null )
843
+ throw new ArgumentNullException ( nameof ( chain ) ) ;
844
+
845
+ if ( chain . Count == 0 )
846
+ throw new ArgumentException ( "The certificate chain must contain at least one certificate." , nameof ( chain ) ) ;
847
+
848
+ if ( chain . Any ( certificate => certificate == null ) )
849
+ throw new ArgumentException ( "The certificate chain contains at least one null certificate." , nameof ( chain ) ) ;
850
+
851
+ var selector = new X509CertStoreSelector ( ) ;
852
+
853
+ var userCertificateStore = new X509CertificateStore ( ) ;
854
+ userCertificateStore . AddRange ( chain ) ;
855
+
856
+ var issuerStore = GetTrustedAnchors ( ) ;
857
+ var anchorStore = new X509CertificateStore ( ) ;
858
+
859
+ foreach ( var anchor in issuerStore ) {
860
+ anchorStore . Add ( anchor . TrustedCert ) ;
861
+ }
862
+
863
+ var parameters = new PkixBuilderParameters ( issuerStore , selector ) {
864
+ ValidityModel = PkixParameters . PkixValidityModel ,
865
+ IsRevocationEnabled = false ,
866
+ Date = DateTime . UtcNow
867
+ } ;
868
+ parameters . AddStoreCert ( userCertificateStore ) ;
869
+
870
+ if ( CheckCertificateRevocation ) {
871
+ foreach ( var certificate in chain )
872
+ await DownloadCrlsAsync ( certificate , cancellationToken ) . ConfigureAwait ( false ) ;
873
+ }
874
+
875
+ var intermediateStore = GetIntermediateCertificates ( ) ;
876
+
877
+ foreach ( var intermediate in intermediateStore . EnumerateMatches ( new X509CertStoreSelector ( ) ) ) {
878
+ anchorStore . Add ( intermediate ) ;
879
+ if ( CheckCertificateRevocation )
880
+ await DownloadCrlsAsync ( intermediate , cancellationToken ) . ConfigureAwait ( false ) ;
881
+ }
882
+
883
+ parameters . AddStoreCert ( anchorStore ) ;
884
+
885
+ if ( CheckCertificateRevocation )
886
+ parameters . AddStoreCrl ( GetCertificateRevocationLists ( ) ) ;
887
+
888
+ try {
889
+ var builder = new PkixCertPathBuilder ( ) ;
890
+ builder . Build ( parameters ) ;
891
+ return true ;
892
+ } catch {
893
+ return false ;
894
+ }
895
+ }
896
+
723
897
PkixCertPath BuildCertPath ( ISet < TrustAnchor > anchors , IStore < X509Certificate > certificates , IStore < X509Crl > crls , X509Certificate certificate , DateTime signingTime )
724
898
{
725
899
var selector = new X509CertStoreSelector {
@@ -995,7 +1169,7 @@ static IEnumerable<string> EnumerateCrlDistributionPointUrls (X509Certificate ce
995
1169
}
996
1170
}
997
1171
998
- void DownloadCrls ( X509Certificate certificate , CancellationToken cancellationToken )
1172
+ void DownloadCrls ( X509Certificate certificate , CancellationToken cancellationToken = default )
999
1173
{
1000
1174
var nextUpdate = GetNextCertificateRevocationListUpdate ( certificate . IssuerDN ) ;
1001
1175
var now = DateTime . UtcNow ;
@@ -1125,10 +1299,15 @@ DigitalSignatureCollection GetDigitalSignatures (CmsSignedDataParser parser, Can
1125
1299
}
1126
1300
1127
1301
var anchors = GetTrustedAnchors ( ) ;
1302
+ var intermediates = GetIntermediateCertificates ( ) ;
1128
1303
1129
1304
if ( CheckCertificateRevocation ) {
1130
1305
foreach ( var anchor in anchors )
1131
1306
DownloadCrls ( anchor . TrustedCert , cancellationToken ) ;
1307
+
1308
+ foreach ( X509Certificate intermediate in intermediates . EnumerateMatches ( new X509CertStoreSelector ( ) ) ) {
1309
+ DownloadCrls ( intermediate , cancellationToken ) ;
1310
+ }
1132
1311
}
1133
1312
1134
1313
try {
@@ -1179,10 +1358,15 @@ async Task<DigitalSignatureCollection> GetDigitalSignaturesAsync (CmsSignedDataP
1179
1358
}
1180
1359
1181
1360
var anchors = GetTrustedAnchors ( ) ;
1361
+ var intermediates = GetIntermediateCertificates ( ) ;
1182
1362
1183
1363
if ( CheckCertificateRevocation ) {
1184
1364
foreach ( var anchor in anchors )
1185
1365
await DownloadCrlsAsync ( anchor . TrustedCert , cancellationToken ) . ConfigureAwait ( false ) ;
1366
+
1367
+ foreach ( X509Certificate intermediate in intermediates . EnumerateMatches ( new X509CertStoreSelector ( ) ) ) {
1368
+ await DownloadCrlsAsync ( intermediate , cancellationToken ) ;
1369
+ }
1186
1370
}
1187
1371
1188
1372
try {
0 commit comments