77using System . Linq ;
88using Xunit ;
99
10- namespace System . Security . Cryptography . X509Certificates . Tests . RevocationTests
10+ namespace System . Security . Cryptography . X509Certificates . Tests . Common
1111{
1212 // This class represents only a portion of what is required to be a proper Certificate Authority.
1313 //
1414 // Please do not use it as the basis for any real Public/Private Key Infrastructure (PKI) system
1515 // without understanding all of the portions of proper CA management that you're skipping.
1616 //
1717 // At minimum, read the current baseline requirements of the CA/Browser Forum.
18+
19+ [ Flags ]
20+ public enum PkiOptions
21+ {
22+ None = 0 ,
23+
24+ IssuerRevocationViaCrl = 1 << 0 ,
25+ IssuerRevocationViaOcsp = 1 << 1 ,
26+ EndEntityRevocationViaCrl = 1 << 2 ,
27+ EndEntityRevocationViaOcsp = 1 << 3 ,
28+
29+ CrlEverywhere = IssuerRevocationViaCrl | EndEntityRevocationViaCrl ,
30+ OcspEverywhere = IssuerRevocationViaOcsp | EndEntityRevocationViaOcsp ,
31+ AllIssuerRevocation = IssuerRevocationViaCrl | IssuerRevocationViaOcsp ,
32+ AllEndEntityRevocation = EndEntityRevocationViaCrl | EndEntityRevocationViaOcsp ,
33+ AllRevocation = CrlEverywhere | OcspEverywhere ,
34+
35+ IssuerAuthorityHasDesignatedOcspResponder = 1 << 16 ,
36+ RootAuthorityHasDesignatedOcspResponder = 1 << 17 ,
37+ NoIssuerCertDistributionUri = 1 << 18 ,
38+ NoRootCertDistributionUri = 1 << 18 ,
39+ }
40+
1841 internal sealed class CertificateAuthority : IDisposable
1942 {
2043 private static readonly Asn1Tag s_context0 = new Asn1Tag ( TagClass . ContextSpecific , 0 ) ;
@@ -35,7 +58,7 @@ internal sealed class CertificateAuthority : IDisposable
3558
3659 private static readonly X509KeyUsageExtension s_eeKeyUsage =
3760 new X509KeyUsageExtension (
38- X509KeyUsageFlags . DigitalSignature ,
61+ X509KeyUsageFlags . DigitalSignature | X509KeyUsageFlags . KeyEncipherment | X509KeyUsageFlags . DataEncipherment ,
3962 critical : false ) ;
4063
4164 private static readonly X509EnhancedKeyUsageExtension s_ocspResponderEku =
@@ -46,6 +69,14 @@ internal sealed class CertificateAuthority : IDisposable
4669 } ,
4770 critical : false ) ;
4871
72+ private static readonly X509EnhancedKeyUsageExtension s_tlsServerEku =
73+ new X509EnhancedKeyUsageExtension (
74+ new OidCollection
75+ {
76+ new Oid ( "1.3.6.1.5.5.7.3.1" , null )
77+ } ,
78+ false ) ;
79+
4980 private static readonly X509EnhancedKeyUsageExtension s_tlsClientEku =
5081 new X509EnhancedKeyUsageExtension (
5182 new OidCollection
@@ -137,15 +168,16 @@ internal X509Certificate2 CreateSubordinateCA(
137168 ekuExtension : null ) ;
138169 }
139170
140- internal X509Certificate2 CreateEndEntity ( string subject , RSA publicKey )
171+ internal X509Certificate2 CreateEndEntity ( string subject , RSA publicKey , X509Extension altName )
141172 {
142173 return CreateCertificate (
143174 subject ,
144175 publicKey ,
145176 TimeSpan . FromSeconds ( 2 ) ,
146177 s_eeConstraints ,
147178 s_eeKeyUsage ,
148- s_tlsClientEku ) ;
179+ s_tlsServerEku ,
180+ altName : altName ) ;
149181 }
150182
151183 internal X509Certificate2 CreateOcspSigner ( string subject , RSA publicKey )
@@ -219,7 +251,8 @@ private X509Certificate2 CreateCertificate(
219251 X509BasicConstraintsExtension basicConstraints ,
220252 X509KeyUsageExtension keyUsage ,
221253 X509EnhancedKeyUsageExtension ekuExtension ,
222- bool ocspResponder = false )
254+ bool ocspResponder = false ,
255+ X509Extension altName = null )
223256 {
224257 if ( _cdpExtension == null && CdpUri != null )
225258 {
@@ -262,6 +295,11 @@ private X509Certificate2 CreateCertificate(
262295 request . CertificateExtensions . Add ( ekuExtension ) ;
263296 }
264297
298+ if ( altName != null )
299+ {
300+ request . CertificateExtensions . Add ( altName ) ;
301+ }
302+
265303 byte [ ] serial = new byte [ sizeof ( long ) ] ;
266304 RandomNumberGenerator . Fill ( serial ) ;
267305
@@ -793,5 +831,130 @@ private enum CertStatus
793831 OK ,
794832 Revoked ,
795833 }
834+
835+ internal static void BuildPrivatePki (
836+ PkiOptions pkiOptions ,
837+ out RevocationResponder responder ,
838+ out CertificateAuthority rootAuthority ,
839+ out CertificateAuthority intermediateAuthority ,
840+ out X509Certificate2 endEntityCert ,
841+ string testName = null ,
842+ bool registerAuthorities = true ,
843+ bool pkiOptionsInSubject = false ,
844+ string subjectName = null )
845+ {
846+ bool rootDistributionViaHttp = ! pkiOptions . HasFlag ( PkiOptions . NoRootCertDistributionUri ) ;
847+ bool issuerRevocationViaCrl = pkiOptions . HasFlag ( PkiOptions . IssuerRevocationViaCrl ) ;
848+ bool issuerRevocationViaOcsp = pkiOptions . HasFlag ( PkiOptions . IssuerRevocationViaOcsp ) ;
849+ bool issuerDistributionViaHttp = ! pkiOptions . HasFlag ( PkiOptions . NoIssuerCertDistributionUri ) ;
850+ bool endEntityRevocationViaCrl = pkiOptions . HasFlag ( PkiOptions . EndEntityRevocationViaCrl ) ;
851+ bool endEntityRevocationViaOcsp = pkiOptions . HasFlag ( PkiOptions . EndEntityRevocationViaOcsp ) ;
852+
853+ Assert . True (
854+ issuerRevocationViaCrl || issuerRevocationViaOcsp ||
855+ endEntityRevocationViaCrl || endEntityRevocationViaOcsp ,
856+ "At least one revocation mode is enabled" ) ;
857+
858+ // All keys created in this method are smaller than recommended,
859+ // but they only live for a few seconds (at most),
860+ // and never communicate out of process.
861+ const int KeySize = 1024 ;
862+
863+ using ( RSA rootKey = RSA . Create ( KeySize ) )
864+ using ( RSA intermedKey = RSA . Create ( KeySize ) )
865+ using ( RSA eeKey = RSA . Create ( KeySize ) )
866+ {
867+ var rootReq = new CertificateRequest (
868+ BuildSubject ( "A Revocation Test Root" , testName , pkiOptions , pkiOptionsInSubject ) ,
869+ rootKey ,
870+ HashAlgorithmName . SHA256 ,
871+ RSASignaturePadding . Pkcs1 ) ;
872+
873+ X509BasicConstraintsExtension caConstraints =
874+ new X509BasicConstraintsExtension ( true , false , 0 , true ) ;
875+
876+ rootReq . CertificateExtensions . Add ( caConstraints ) ;
877+ var rootSkid = new X509SubjectKeyIdentifierExtension ( rootReq . PublicKey , false ) ;
878+ rootReq . CertificateExtensions . Add (
879+ rootSkid ) ;
880+
881+ DateTimeOffset start = DateTimeOffset . UtcNow ;
882+ DateTimeOffset end = start . AddMonths ( 3 ) ;
883+
884+ // Don't dispose this, it's being transferred to the CertificateAuthority
885+ X509Certificate2 rootCert = rootReq . CreateSelfSigned ( start . AddDays ( - 2 ) , end . AddDays ( 2 ) ) ;
886+ responder = RevocationResponder . CreateAndListen ( ) ;
887+
888+ string certUrl = $ "{ responder . UriPrefix } cert/{ rootSkid . SubjectKeyIdentifier } .cer";
889+ string cdpUrl = $ "{ responder . UriPrefix } crl/{ rootSkid . SubjectKeyIdentifier } .crl";
890+ string ocspUrl = $ "{ responder . UriPrefix } ocsp/{ rootSkid . SubjectKeyIdentifier } ";
891+
892+ rootAuthority = new CertificateAuthority (
893+ rootCert ,
894+ rootDistributionViaHttp ? certUrl : null ,
895+ issuerRevocationViaCrl ? cdpUrl : null ,
896+ issuerRevocationViaOcsp ? ocspUrl : null ) ;
897+
898+ // Don't dispose this, it's being transferred to the CertificateAuthority
899+ X509Certificate2 intermedCert ;
900+
901+ {
902+ X509Certificate2 intermedPub = rootAuthority . CreateSubordinateCA (
903+ BuildSubject ( "A Revocation Test CA" , testName , pkiOptions , pkiOptionsInSubject ) ,
904+ intermedKey ) ;
905+
906+ intermedCert = intermedPub . CopyWithPrivateKey ( intermedKey ) ;
907+ intermedPub . Dispose ( ) ;
908+ }
909+
910+ X509SubjectKeyIdentifierExtension intermedSkid =
911+ intermedCert . Extensions . OfType < X509SubjectKeyIdentifierExtension > ( ) . Single ( ) ;
912+
913+ certUrl = $ "{ responder . UriPrefix } cert/{ intermedSkid . SubjectKeyIdentifier } .cer";
914+ cdpUrl = $ "{ responder . UriPrefix } crl/{ intermedSkid . SubjectKeyIdentifier } .crl";
915+ ocspUrl = $ "{ responder . UriPrefix } ocsp/{ intermedSkid . SubjectKeyIdentifier } ";
916+
917+ intermediateAuthority = new CertificateAuthority (
918+ intermedCert ,
919+ issuerDistributionViaHttp ? certUrl : null ,
920+ endEntityRevocationViaCrl ? cdpUrl : null ,
921+ endEntityRevocationViaOcsp ? ocspUrl : null ) ;
922+
923+ X509Extension altName = null ;
924+
925+ if ( ! String . IsNullOrEmpty ( subjectName ) )
926+ {
927+ SubjectAlternativeNameBuilder builder = new SubjectAlternativeNameBuilder ( ) ;
928+ builder . AddDnsName ( subjectName ) ;
929+ altName = builder . Build ( ) ;
930+ }
931+
932+ endEntityCert = intermediateAuthority . CreateEndEntity (
933+ BuildSubject ( subjectName ?? "A Revocation Test Cert" , testName , pkiOptions , pkiOptionsInSubject ) ,
934+ eeKey ,
935+ altName ) ;
936+ endEntityCert = endEntityCert . CopyWithPrivateKey ( eeKey ) ;
937+ }
938+
939+ if ( registerAuthorities )
940+ {
941+ responder . AddCertificateAuthority ( rootAuthority ) ;
942+ responder . AddCertificateAuthority ( intermediateAuthority ) ;
943+ }
944+ }
945+
946+ private static string BuildSubject (
947+ string cn ,
948+ string testName ,
949+ PkiOptions pkiOptions ,
950+ bool includePkiOptions )
951+ {
952+ if ( includePkiOptions )
953+ {
954+ return $ "CN=\" { cn } \" , O=\" { testName } \" , OU=\" { pkiOptions } \" ";
955+ }
956+
957+ return $ "CN=\" { cn } \" , O=\" { testName } \" ";
958+ }
796959 }
797960}
0 commit comments