public void Reset() { s_certGenerator.Reset(); s_crlGenerator.Reset(); _authorityCertificate = null; _isInitialized = false; }
public void Initialize() { if (!_isInitialized) { if (string.IsNullOrWhiteSpace(_authorityCanonicalName)) { throw new ArgumentException("AuthorityCanonicalName must not be an empty string or only whitespace", "AuthorityCanonicalName"); } if (string.IsNullOrWhiteSpace(_password)) { throw new ArgumentException("Password must not be an empty string or only whitespace", "Password"); } Uri dummy; if (string.IsNullOrWhiteSpace(_crlUriRelativePath) && !Uri.TryCreate(_crlUriRelativePath, UriKind.Relative, out dummy)) { throw new ArgumentException("CrlUri must be a valid relative URI", "CrlUriRelativePath"); } _crlUri = string.Format("http://{0}{1}", _crlServiceUri, _crlUriRelativePath); _initializationDateTime = DateTime.UtcNow; _defaultValidityNotBefore = _initializationDateTime.Subtract(_gracePeriod); _defaultValidityNotAfter = _initializationDateTime.Add(_validityPeriod); _random = new SecureRandom(new CryptoApiRandomGenerator()); _keyPairGenerator = new RsaKeyPairGenerator(); _keyPairGenerator.Init(new KeyGenerationParameters(_random, _keyLengthInBits)); _authorityKeyPair = _keyPairGenerator.GenerateKeyPair(); _isInitialized = true; Trace.WriteLine("[CertificateGenerator] initialized with the following configuration:"); Trace.WriteLine(string.Format(" {0} = {1}", "AuthorityCanonicalName", _authorityCanonicalName)); Trace.WriteLine(string.Format(" {0} = {1}", "CrlUri", _crlUri)); Trace.WriteLine(string.Format(" {0} = {1}", "Password", _password)); Trace.WriteLine(string.Format(" {0} = {1}", "ValidityPeriod", _validityPeriod)); Trace.WriteLine(string.Format(" {0} = {1}", "Valid to", _defaultValidityNotAfter)); _authorityCertificate = CreateCertificate(isAuthority: true, isMachineCert: false, signingCertificate: null, certificateCreationSettings: null); } }
// Only the ctor should be calling with isAuthority = true // if isAuthority, value for isMachineCert doesn't matter private X509CertificateContainer CreateCertificate(bool isAuthority, bool isMachineCert, X509Certificate signingCertificate, CertificateCreationSettings certificateCreationSettings) { if (certificateCreationSettings == null) { if (isAuthority) { certificateCreationSettings = new CertificateCreationSettings(); } else { throw new Exception("Parameter certificateCreationSettings cannot be null when isAuthority is false"); } } // Set to default cert creation settings if not set if (certificateCreationSettings.ValidityNotBefore == default(DateTime)) { certificateCreationSettings.ValidityNotBefore = _defaultValidityNotBefore; } if (certificateCreationSettings.ValidityNotAfter == default(DateTime)) { certificateCreationSettings.ValidityNotAfter = _defaultValidityNotAfter; } if (!isAuthority ^ (signingCertificate != null)) { throw new ArgumentException("Either isAuthority == true or signingCertificate is not null"); } string subject = certificateCreationSettings.Subject; // If certificateCreationSettings.SubjectAlternativeNames == null, then we should add exactly one SubjectAlternativeName == Subject // so that the default certificate generated is compatible with mainline scenarios // However, if certificateCreationSettings.SubjectAlternativeNames == string[0], then allow this as this is a legit scenario we want to test out if (certificateCreationSettings.SubjectAlternativeNames == null) { certificateCreationSettings.SubjectAlternativeNames = new string[1] { subject }; } string[] subjectAlternativeNames = certificateCreationSettings.SubjectAlternativeNames; if (!isAuthority && string.IsNullOrWhiteSpace(subject)) { throw new ArgumentException("Certificate Subject must not be an empty string or only whitespace", "creationSettings.Subject"); } EnsureInitialized(); s_certGenerator.Reset(); s_certGenerator.SetSignatureAlgorithm(_signatureAlthorithm); // Tag on the generation time to prevent caching of the cert CRL in Linux X509Name authorityX509Name = CreateX509Name(string.Format("{0} {1}", _authorityCanonicalName, DateTime.Now.ToString("s"))); var serialNum = new BigInteger(64 /*sizeInBits*/, _random).Abs(); var keyPair = isAuthority ? _authorityKeyPair : _keyPairGenerator.GenerateKeyPair(); if (isAuthority) { s_certGenerator.SetIssuerDN(authorityX509Name); s_certGenerator.SetSubjectDN(authorityX509Name); var authorityKeyIdentifier = new AuthorityKeyIdentifier( SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(_authorityKeyPair.Public), new GeneralNames(new GeneralName(authorityX509Name)), serialNum); s_certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, authorityKeyIdentifier); s_certGenerator.AddExtension(X509Extensions.KeyUsage, false, new KeyUsage(X509KeyUsage.DigitalSignature | X509KeyUsage.KeyAgreement | X509KeyUsage.KeyCertSign | X509KeyUsage.KeyEncipherment | X509KeyUsage.CrlSign)); } else { X509Name subjectName = CreateX509Name(subject); s_certGenerator.SetIssuerDN(PrincipalUtilities.GetSubjectX509Principal(signingCertificate)); s_certGenerator.SetSubjectDN(subjectName); s_certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(_authorityKeyPair.Public)); s_certGenerator.AddExtension(X509Extensions.KeyUsage, false, new KeyUsage(X509KeyUsage.DigitalSignature | X509KeyUsage.KeyAgreement | X509KeyUsage.KeyEncipherment)); } s_certGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keyPair.Public)); s_certGenerator.SetSerialNumber(serialNum); s_certGenerator.SetNotBefore(certificateCreationSettings.ValidityNotBefore); s_certGenerator.SetNotAfter(certificateCreationSettings.ValidityNotAfter); s_certGenerator.SetPublicKey(keyPair.Public); s_certGenerator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(isAuthority)); s_certGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth, KeyPurposeID.IdKPClientAuth)); if (!isAuthority) { if (isMachineCert) { List <Asn1Encodable> subjectAlternativeNamesAsAsn1EncodableList = new List <Asn1Encodable>(); // All endpoints should also be in the Subject Alt Names for (int i = 0; i < subjectAlternativeNames.Length; i++) { if (!string.IsNullOrWhiteSpace(subjectAlternativeNames[i])) { // Machine certs can have additional DNS names subjectAlternativeNamesAsAsn1EncodableList.Add(new GeneralName(GeneralName.DnsName, subjectAlternativeNames[i])); } } s_certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, true, new DerSequence(subjectAlternativeNamesAsAsn1EncodableList.ToArray())); } else { if (subjectAlternativeNames.Length > 1) { var subjectAlternativeNamesAsAsn1EncodableList = new Asn1EncodableVector(); // Only add a SAN for the user if there are any for (int i = 1; i < subjectAlternativeNames.Length; i++) { if (!string.IsNullOrWhiteSpace(subjectAlternativeNames[i])) { Asn1EncodableVector otherNames = new Asn1EncodableVector(); otherNames.Add(new DerObjectIdentifier(_upnObjectId)); otherNames.Add(new DerTaggedObject(true, 0, new DerUtf8String(subjectAlternativeNames[i]))); Asn1Object genName = new DerTaggedObject(false, 0, new DerSequence(otherNames)); subjectAlternativeNamesAsAsn1EncodableList.Add(genName); } } s_certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, true, new DerSequence(subjectAlternativeNamesAsAsn1EncodableList)); } } } if (isAuthority || certificateCreationSettings.IncludeCrlDistributionPoint) { var crlDistributionPoints = new DistributionPoint[1] { new DistributionPoint( new DistributionPointName( new GeneralNames( new GeneralName( GeneralName.UniformResourceIdentifier, string.Format("{0}", _crlUri, serialNum.ToString(radix: 16))))), null, null) }; var revocationListExtension = new CrlDistPoint(crlDistributionPoints); s_certGenerator.AddExtension(X509Extensions.CrlDistributionPoints, false, revocationListExtension); } X509Certificate cert = s_certGenerator.Generate(_authorityKeyPair.Private, _random); switch (certificateCreationSettings.ValidityType) { case CertificateValidityType.Revoked: RevokeCertificateBySerialNumber(serialNum.ToString(radix: 16)); break; case CertificateValidityType.Expired: break; default: EnsureCertificateIsValid(cert); break; } // For now, given that we don't know what format to return it in, preserve the formats so we have // the flexibility to do what we need to X509CertificateContainer container = new X509CertificateContainer(); X509CertificateEntry[] chain = new X509CertificateEntry[1]; chain[0] = new X509CertificateEntry(cert); Pkcs12Store store = new Pkcs12StoreBuilder().Build(); store.SetKeyEntry( certificateCreationSettings.FriendlyName != null ? certificateCreationSettings.FriendlyName : string.Empty, new AsymmetricKeyEntry(keyPair.Private), chain); using (MemoryStream stream = new MemoryStream()) { store.Save(stream, _password.ToCharArray(), _random); container.Pfx = stream.ToArray(); } X509Certificate2 outputCert; if (isAuthority) { // don't hand out the private key for the cert when it's the authority outputCert = new X509Certificate2(cert.GetEncoded()); } else { // Otherwise, allow encode with the private key. note that X509Certificate2.RawData will not provide the private key // you will have to re-export this cert if needed outputCert = new X509Certificate2(container.Pfx, _password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); } container.Subject = subject; container.InternalCertificate = cert; container.Certificate = outputCert; container.Thumbprint = outputCert.Thumbprint; Trace.WriteLine("[CertificateGenerator] generated a certificate:"); Trace.WriteLine(string.Format(" {0} = {1}", "isAuthority", isAuthority)); if (!isAuthority) { Trace.WriteLine(string.Format(" {0} = {1}", "Signed by", signingCertificate.SubjectDN)); Trace.WriteLine(string.Format(" {0} = {1}", "Subject (CN) ", subject)); Trace.WriteLine(string.Format(" {0} = {1}", "Subject Alt names ", string.Join(", ", subjectAlternativeNames))); Trace.WriteLine(string.Format(" {0} = {1}", "Friendly Name ", certificateCreationSettings.FriendlyName)); } Trace.WriteLine(string.Format(" {0} = {1}", "HasPrivateKey:", outputCert.HasPrivateKey)); Trace.WriteLine(string.Format(" {0} = {1}", "Thumbprint", outputCert.Thumbprint)); Trace.WriteLine(string.Format(" {0} = {1}", "CertificateValidityType", certificateCreationSettings.ValidityType)); return(container); }
// Only the ctor should be calling with isAuthority = true // if isAuthority, value for isMachineCert doesn't matter private X509CertificateContainer CreateCertificate(bool isAuthority, bool isMachineCert, X509Certificate signingCertificate, CertificateCreationSettings certificateCreationSettings) { if (certificateCreationSettings == null) { if (isAuthority) { certificateCreationSettings = new CertificateCreationSettings(); } else { throw new Exception("Parameter certificateCreationSettings cannot be null when isAuthority is false"); } } // Set to default cert creation settings if not set if (certificateCreationSettings.ValidityNotBefore == default(DateTime)) { certificateCreationSettings.ValidityNotBefore = _defaultValidityNotBefore; } if (certificateCreationSettings.ValidityNotAfter == default(DateTime)) { certificateCreationSettings.ValidityNotAfter = _defaultValidityNotAfter; } if (!isAuthority ^ (signingCertificate != null)) { throw new ArgumentException("Either isAuthority == true or signingCertificate is not null"); } string subject = certificateCreationSettings.Subject; // If certificateCreationSettings.SubjectAlternativeNames == null, then we should add exactly one SubjectAlternativeName == Subject // so that the default certificate generated is compatible with mainline scenarios // However, if certificateCreationSettings.SubjectAlternativeNames == string[0], then allow this as this is a legit scenario we want to test out if (certificateCreationSettings.SubjectAlternativeNames == null) { certificateCreationSettings.SubjectAlternativeNames = new string[1] { subject }; } string[] subjectAlternativeNames = certificateCreationSettings.SubjectAlternativeNames; if (!isAuthority && string.IsNullOrWhiteSpace(subject)) { throw new ArgumentException("Certificate Subject must not be an empty string or only whitespace", "creationSettings.Subject"); } EnsureInitialized(); s_certGenerator.Reset(); s_certGenerator.SetSignatureAlgorithm(_signatureAlthorithm); // Tag on the generation time to prevent caching of the cert CRL in Linux X509Name authorityX509Name = CreateX509Name(string.Format("{0} {1}", _authorityCanonicalName, DateTime.Now.ToString("s"))); var serialNum = new BigInteger(64 /*sizeInBits*/, _random).Abs(); var keyPair = isAuthority ? _authorityKeyPair : _keyPairGenerator.GenerateKeyPair(); if (isAuthority) { s_certGenerator.SetIssuerDN(authorityX509Name); s_certGenerator.SetSubjectDN(authorityX509Name); var authorityKeyIdentifier = new AuthorityKeyIdentifier( SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(_authorityKeyPair.Public), new GeneralNames(new GeneralName(authorityX509Name)), serialNum); s_certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, authorityKeyIdentifier); s_certGenerator.AddExtension(X509Extensions.KeyUsage, false, new KeyUsage(X509KeyUsage.DigitalSignature | X509KeyUsage.KeyAgreement | X509KeyUsage.KeyCertSign | X509KeyUsage.KeyEncipherment | X509KeyUsage.CrlSign)); } else { X509Name subjectName = CreateX509Name(subject); s_certGenerator.SetIssuerDN(PrincipalUtilities.GetSubjectX509Principal(signingCertificate)); s_certGenerator.SetSubjectDN(subjectName); s_certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(_authorityKeyPair.Public)); s_certGenerator.AddExtension(X509Extensions.KeyUsage, false, new KeyUsage(X509KeyUsage.DigitalSignature | X509KeyUsage.KeyAgreement | X509KeyUsage.KeyEncipherment)); } s_certGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keyPair.Public)); s_certGenerator.SetSerialNumber(serialNum); s_certGenerator.SetNotBefore(certificateCreationSettings.ValidityNotBefore); s_certGenerator.SetNotAfter(certificateCreationSettings.ValidityNotAfter); s_certGenerator.SetPublicKey(keyPair.Public); s_certGenerator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(isAuthority)); s_certGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth, KeyPurposeID.IdKPClientAuth)); if (!isAuthority) { if (isMachineCert) { List<Asn1Encodable> subjectAlternativeNamesAsAsn1EncodableList = new List<Asn1Encodable>(); // All endpoints should also be in the Subject Alt Names for (int i = 0; i < subjectAlternativeNames.Length; i++) { if (!string.IsNullOrWhiteSpace(subjectAlternativeNames[i])) { // Machine certs can have additional DNS names subjectAlternativeNamesAsAsn1EncodableList.Add(new GeneralName(GeneralName.DnsName, subjectAlternativeNames[i])); } } s_certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, true, new DerSequence(subjectAlternativeNamesAsAsn1EncodableList.ToArray())); } else { if (subjectAlternativeNames.Length > 1) { var subjectAlternativeNamesAsAsn1EncodableList = new Asn1EncodableVector(); // Only add a SAN for the user if there are any for (int i = 1; i < subjectAlternativeNames.Length; i++) { if (!string.IsNullOrWhiteSpace(subjectAlternativeNames[i])) { Asn1EncodableVector otherNames = new Asn1EncodableVector(); otherNames.Add(new DerObjectIdentifier(_upnObjectId)); otherNames.Add(new DerTaggedObject(true, 0, new DerUtf8String(subjectAlternativeNames[i]))); Asn1Object genName = new DerTaggedObject(false, 0, new DerSequence(otherNames)); subjectAlternativeNamesAsAsn1EncodableList.Add(genName); } } s_certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, true, new DerSequence(subjectAlternativeNamesAsAsn1EncodableList)); } } } if (isAuthority || certificateCreationSettings.IncludeCrlDistributionPoint) { var crlDistributionPoints = new DistributionPoint[1] { new DistributionPoint( new DistributionPointName( new GeneralNames( new GeneralName( GeneralName.UniformResourceIdentifier, string.Format("{0}", _crlUri, serialNum.ToString(radix: 16))))), null, null) }; var revocationListExtension = new CrlDistPoint(crlDistributionPoints); s_certGenerator.AddExtension(X509Extensions.CrlDistributionPoints, false, revocationListExtension); } X509Certificate cert = s_certGenerator.Generate(_authorityKeyPair.Private, _random); switch (certificateCreationSettings.ValidityType) { case CertificateValidityType.Revoked: RevokeCertificateBySerialNumber(serialNum.ToString(radix: 16)); break; case CertificateValidityType.Expired: break; default: EnsureCertificateIsValid(cert); break; } // For now, given that we don't know what format to return it in, preserve the formats so we have // the flexibility to do what we need to X509CertificateContainer container = new X509CertificateContainer(); X509CertificateEntry[] chain = new X509CertificateEntry[1]; chain[0] = new X509CertificateEntry(cert); Pkcs12Store store = new Pkcs12StoreBuilder().Build(); store.SetKeyEntry( certificateCreationSettings.FriendlyName != null ? certificateCreationSettings.FriendlyName : string.Empty, new AsymmetricKeyEntry(keyPair.Private), chain); using (MemoryStream stream = new MemoryStream()) { store.Save(stream, _password.ToCharArray(), _random); container.Pfx = stream.ToArray(); } X509Certificate2 outputCert; if (isAuthority) { // don't hand out the private key for the cert when it's the authority outputCert = new X509Certificate2(cert.GetEncoded()); } else { // Otherwise, allow encode with the private key. note that X509Certificate2.RawData will not provide the private key // you will have to re-export this cert if needed outputCert = new X509Certificate2(container.Pfx, _password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); } container.Subject = subject; container.InternalCertificate = cert; container.Certificate = outputCert; container.Thumbprint = outputCert.Thumbprint; Trace.WriteLine("[CertificateGenerator] generated a certificate:"); Trace.WriteLine(string.Format(" {0} = {1}", "isAuthority", isAuthority)); if (!isAuthority) { Trace.WriteLine(string.Format(" {0} = {1}", "Signed by", signingCertificate.SubjectDN)); Trace.WriteLine(string.Format(" {0} = {1}", "Subject (CN) ", subject)); Trace.WriteLine(string.Format(" {0} = {1}", "Subject Alt names ", string.Join(", ", subjectAlternativeNames))); Trace.WriteLine(string.Format(" {0} = {1}", "Friendly Name ", certificateCreationSettings.FriendlyName)); } Trace.WriteLine(string.Format(" {0} = {1}", "HasPrivateKey:", outputCert.HasPrivateKey)); Trace.WriteLine(string.Format(" {0} = {1}", "Thumbprint", outputCert.Thumbprint)); Trace.WriteLine(string.Format(" {0} = {1}", "CertificateValidityType", certificateCreationSettings.ValidityType)); return container; }