@@ -42,6 +42,10 @@ internal class CertificateManager
42
42
private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " " ;
43
43
private const int UserCancelledErrorCode = 1223 ;
44
44
45
+ // Setting to 0 means we don't append the version byte,
46
+ // which is what all machines currently have.
47
+ public int AspNetHttpsCertificateVersion { get ; set ; } = 1 ;
48
+
45
49
public IList < X509Certificate2 > ListCertificates (
46
50
CertificatePurpose purpose ,
47
51
StoreName storeName ,
@@ -83,7 +87,8 @@ public IList<X509Certificate2> ListCertificates(
83
87
var validCertificates = matchingCertificates
84
88
. Where ( c => c . NotBefore <= now &&
85
89
now <= c . NotAfter &&
86
- ( ! requireExportable || ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) || IsExportable ( c ) ) )
90
+ ( ! requireExportable || ! RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) || IsExportable ( c ) )
91
+ && MatchesVersion ( c ) )
87
92
. ToArray ( ) ;
88
93
89
94
var invalidCertificates = matchingCertificates . Except ( validCertificates ) ;
@@ -117,6 +122,25 @@ public IList<X509Certificate2> ListCertificates(
117
122
bool HasOid ( X509Certificate2 certificate , string oid ) =>
118
123
certificate . Extensions . OfType < X509Extension > ( )
119
124
. Any ( e => string . Equals ( oid , e . Oid . Value , StringComparison . Ordinal ) ) ;
125
+
126
+ bool MatchesVersion ( X509Certificate2 c )
127
+ {
128
+ var byteArray = c . Extensions . OfType < X509Extension > ( )
129
+ . Where ( e => string . Equals ( AspNetHttpsOid , e . Oid . Value , StringComparison . Ordinal ) )
130
+ . Single ( )
131
+ . RawData ;
132
+
133
+ if ( ( byteArray . Length == AspNetHttpsOidFriendlyName . Length && byteArray [ 0 ] == ( byte ) 'A' ) || byteArray . Length == 0 )
134
+ {
135
+ // No Version set, default to 0
136
+ return 0 >= AspNetHttpsCertificateVersion ;
137
+ }
138
+ else
139
+ {
140
+ // Version is in the only byte of the byte array.
141
+ return byteArray [ 0 ] >= AspNetHttpsCertificateVersion ;
142
+ }
143
+ }
120
144
#if ! XPLAT
121
145
bool IsExportable ( X509Certificate2 c ) =>
122
146
( ( c . GetRSAPrivateKey ( ) is RSACryptoServiceProvider rsaPrivateKey &&
@@ -171,10 +195,22 @@ public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffs
171
195
pathLengthConstraint : 0 ,
172
196
critical : true ) ;
173
197
198
+ byte [ ] bytePayload ;
199
+
200
+ if ( AspNetHttpsCertificateVersion != 0 )
201
+ {
202
+ bytePayload = new byte [ 1 ] ;
203
+ bytePayload [ 0 ] = ( byte ) AspNetHttpsCertificateVersion ;
204
+ }
205
+ else
206
+ {
207
+ bytePayload = Encoding . ASCII . GetBytes ( AspNetHttpsOidFriendlyName ) ;
208
+ }
209
+
174
210
var aspNetHttpsExtension = new X509Extension (
175
211
new AsnEncodedData (
176
212
new Oid ( AspNetHttpsOid , AspNetHttpsOidFriendlyName ) ,
177
- Encoding . ASCII . GetBytes ( AspNetHttpsOidFriendlyName ) ) ,
213
+ bytePayload ) ,
178
214
critical : false ) ;
179
215
180
216
extensions . Add ( basicConstraints ) ;
@@ -633,7 +669,7 @@ private static void RemoveCertificateFromKeyChain(string keyChain, X509Certifica
633
669
}
634
670
}
635
671
636
- public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate (
672
+ public DetailedEnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate (
637
673
DateTimeOffset notBefore ,
638
674
DateTimeOffset notAfter ,
639
675
string path = null ,
@@ -645,109 +681,7 @@ public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
645
681
return EnsureValidCertificateExists ( notBefore , notAfter , CertificatePurpose . HTTPS , path , trust , includePrivateKey , password , subject ) ;
646
682
}
647
683
648
- public EnsureCertificateResult EnsureValidCertificateExists (
649
- DateTimeOffset notBefore ,
650
- DateTimeOffset notAfter ,
651
- CertificatePurpose purpose ,
652
- string path = null ,
653
- bool trust = false ,
654
- bool includePrivateKey = false ,
655
- string password = null ,
656
- string subjectOverride = null )
657
- {
658
- if ( purpose == CertificatePurpose . All )
659
- {
660
- throw new ArgumentException ( "The certificate must have a specific purpose." ) ;
661
- }
662
-
663
- var certificates = ListCertificates ( purpose , StoreName . My , StoreLocation . CurrentUser , isValid : true ) . Concat (
664
- ListCertificates ( purpose , StoreName . My , StoreLocation . LocalMachine , isValid : true ) ) ;
665
-
666
- certificates = subjectOverride == null ? certificates : certificates . Where ( c => c . Subject == subjectOverride ) ;
667
-
668
- var result = EnsureCertificateResult . Succeeded ;
669
-
670
- X509Certificate2 certificate = null ;
671
- if ( certificates . Count ( ) > 0 )
672
- {
673
- certificate = certificates . FirstOrDefault ( ) ;
674
- result = EnsureCertificateResult . ValidCertificatePresent ;
675
- }
676
- else
677
- {
678
- try
679
- {
680
- switch ( purpose )
681
- {
682
- case CertificatePurpose . All :
683
- throw new InvalidOperationException ( "The certificate must have a specific purpose." ) ;
684
- case CertificatePurpose . HTTPS :
685
- certificate = CreateAspNetCoreHttpsDevelopmentCertificate ( notBefore , notAfter , subjectOverride ) ;
686
- break ;
687
- default :
688
- throw new InvalidOperationException ( "The certificate must have a purpose." ) ;
689
- }
690
- }
691
- catch
692
- {
693
- return EnsureCertificateResult . ErrorCreatingTheCertificate ;
694
- }
695
-
696
- try
697
- {
698
- certificate = SaveCertificateInStore ( certificate , StoreName . My , StoreLocation . CurrentUser ) ;
699
- }
700
- catch
701
- {
702
- return EnsureCertificateResult . ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore ;
703
- }
704
- }
705
- if ( path != null )
706
- {
707
- try
708
- {
709
- ExportCertificate ( certificate , path , includePrivateKey , password ) ;
710
- }
711
- catch
712
- {
713
- return EnsureCertificateResult . ErrorExportingTheCertificate ;
714
- }
715
- }
716
-
717
- if ( ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) && trust )
718
- {
719
- try
720
- {
721
- TrustCertificate ( certificate ) ;
722
- }
723
- catch ( UserCancelledTrustException )
724
- {
725
- return EnsureCertificateResult . UserCancelledTrustStep ;
726
- }
727
- catch
728
- {
729
- return EnsureCertificateResult . FailedToTrustTheCertificate ;
730
- }
731
- }
732
-
733
- return result ;
734
- }
735
-
736
- // This is just to avoid breaking changes across repos.
737
- // Will be renamed back to EnsureAspNetCoreHttpsDevelopmentCertificate once updates are made elsewhere.
738
- public DetailedEnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate2 (
739
- DateTimeOffset notBefore ,
740
- DateTimeOffset notAfter ,
741
- string path = null ,
742
- bool trust = false ,
743
- bool includePrivateKey = false ,
744
- string password = null ,
745
- string subject = LocalhostHttpsDistinguishedName )
746
- {
747
- return EnsureValidCertificateExists2 ( notBefore , notAfter , CertificatePurpose . HTTPS , path , trust , includePrivateKey , password , subject ) ;
748
- }
749
-
750
- public DetailedEnsureCertificateResult EnsureValidCertificateExists2 (
684
+ public DetailedEnsureCertificateResult EnsureValidCertificateExists (
751
685
DateTimeOffset notBefore ,
752
686
DateTimeOffset notAfter ,
753
687
CertificatePurpose purpose ,
0 commit comments