// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Xunit; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { public class ClientCertificateFixture : IDisposable { private X509Certificate2 _certificate; private const string _certIssuerPrefix = "CN=IISIntegrationTest_Root"; public X509Certificate2 GetOrCreateCertificate() { if (_certificate != null) { return _certificate; } using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadWrite); var parentKey = CreateKeyMaterial(2048); // Create a cert name with a random guid to avoid name conflicts var parentRequest = new CertificateRequest( _certIssuerPrefix + Guid.NewGuid().ToString(), parentKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); parentRequest.CertificateExtensions.Add( new X509BasicConstraintsExtension( certificateAuthority: true, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: true)); parentRequest.CertificateExtensions.Add( new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation, critical: true)); parentRequest.CertificateExtensions.Add( new X509SubjectKeyIdentifierExtension(parentRequest.PublicKey, false)); var notBefore = DateTimeOffset.Now.AddDays(-1); var notAfter = DateTimeOffset.Now.AddYears(5); var parentCert = parentRequest.CreateSelfSigned(notBefore, notAfter); // Need to export/import the certificate to associate the private key with the cert. var imported = parentCert; var export = parentCert.Export(X509ContentType.Pkcs12, ""); imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); Array.Clear(export, 0, export.Length); // Add the cert to the cert store _certificate = imported; store.Add(certificate: imported); store.Close(); return imported; } } public void Dispose() { if (_certificate == null) { return; } using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine)) { store.Open(OpenFlags.ReadWrite); store.Remove(_certificate); // Remove any extra certs that were left by previous tests. for (var i = store.Certificates.Count - 1; i >= 0; i--) { var cert = store.Certificates[i]; if (cert.Issuer.StartsWith(_certIssuerPrefix)) { store.Remove(cert); } } store.Close(); } } private RSA CreateKeyMaterial(int minimumKeySize) { var rsa = RSA.Create(minimumKeySize); if (rsa.KeySize < minimumKeySize) { throw new InvalidOperationException($"Failed to create a key with a size of {minimumKeySize} bits"); } return rsa; } } }