/// <inheritdoc/> public async Task <KeyHandle> GetKeyHandleAsync(string name, CancellationToken ct) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } // Get key first var keyBundle = await Try.Async(() => _keyVaultClient.GetKeyAsync(name, ct)); if (keyBundle == null) { // If no key - then try getting cert bundle instead var certBundle = await Try.Async( () => _keyVaultClient.GetCertificateAsync(_vaultBaseUrl, name, ct)); if (certBundle != null) { return(KeyVaultKeyHandle.Create(certBundle)); } throw new ResourceNotFoundException("Key with name not found"); } var secretBundle = await Try.Async(() => _keyVaultClient.GetSecretAsync(name, ct)); return(KeyVaultKeyHandle.Create(keyBundle, secretBundle)); }
/// <inheritdoc/> public async Task <Certificate> ImportCertificateAsync(string certificateName, Certificate certificate, Key privateKey, CancellationToken ct) { if (string.IsNullOrEmpty(certificateName)) { throw new ArgumentNullException(nameof(certificateName)); } if (certificate == null) { throw new ArgumentNullException(nameof(certificate)); } if (privateKey == null) { certificate = certificate.Clone(); certificate.IssuerPolicies = null; await _certificates.AddCertificateAsync(certificateName, certificate, null, ct); return(certificate); } try { var password = Guid.NewGuid().ToString(); var pfxBase64 = certificate.ToPfx(privateKey, password).ToBase64String(); // Import bundle var bundle = await _keyVaultClient.ImportCertificateAsync(_vaultBaseUrl, certificateName, pfxBase64, password, CreateCertificatePolicy(certificate, true, _keyStoreIsHsm), CreateCertificateAttributes(certificate.NotBeforeUtc, certificate.NotAfterUtc - certificate.NotBeforeUtc, certificate.NotAfterUtc), null, ct); try { var result = CertificateEx.Create(bundle.Cer, KeyVaultKeyHandle.Create(bundle), certificate.IssuerPolicies); await _certificates.AddCertificateAsync( certificateName, result, bundle.CertificateIdentifier.Identifier, ct); return(result); } catch { await Try.Async(() => _keyVaultClient.DeleteCertificateAsync( _vaultBaseUrl, certificateName, ct)); throw; } } catch (KeyVaultErrorException ex) { throw new ExternalDependencyException("Failed to import certificate", ex); } }
/// <inheritdoc/> public async Task <KeyHandle> ImportKeyAsync(string name, Key key, KeyStoreProperties store, CancellationToken ct) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } if (key == null) { throw new ArgumentNullException(nameof(key)); } try { // Import key var keyBundle = await _keyVaultClient.ImportKeyAsync(_vaultBaseUrl, name, key.ToJsonWebKey(), null, new KeyAttributes { Enabled = true, NotBefore = DateTime.UtcNow }, null, ct); if (store?.Exportable ?? false) { // Store key as json web key secret so we can export it var secretBundle = await _keyVaultClient.SetSecretAsync(_vaultBaseUrl, name, _serializer.SerializeToString(key.ToJsonWebKey()), null, ContentMimeType.Json, new SecretAttributes { Enabled = true, NotBefore = DateTime.UtcNow }, ct); return(KeyVaultKeyHandle.Create(keyBundle, secretBundle)); } return(KeyVaultKeyHandle.Create(keyBundle)); } catch (KeyVaultErrorException kex) { throw new ExternalDependencyException("Failed to import key", kex); } }
/// <inheritdoc/> public async Task <KeyHandle> CreateKeyAsync(string name, CreateKeyParams keyParams, KeyStoreProperties store, CancellationToken ct) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } if (keyParams == null) { throw new ArgumentNullException(nameof(keyParams)); } try { if (!(store?.Exportable ?? false)) { // Create key inside key vault var result = await _keyVaultClient.CreateKeyAsync(_vaultBaseUrl, name, new NewKeyParameters { KeySize = (int?)keyParams.KeySize, CurveName = keyParams.Curve?.ToJsonWebKeyCurveName(), Attributes = new KeyAttributes { Enabled = true, NotBefore = DateTime.UtcNow }, Kty = keyParams.Type.ToKty(_keyStoreIsHsm) }, ct); return(KeyVaultKeyHandle.Create(result)); } // Create key outside and import return(await ImportKeyAsync(name, keyParams.CreateKey(), new KeyStoreProperties { Exportable = true }, ct)); } catch (KeyVaultErrorException kex) { throw new ExternalDependencyException("Failed to create key", kex); } }
public async Task CreateRSARootAndRSAIssuerTestAsync() { using (var mock = Setup((v, q) => { var expected = "SELECT TOP 1 * FROM Certificates c " + "WHERE c.Type = 'Certificate' " + "AND c.CertificateName = 'footca' " + "ORDER BY c.Version DESC"; if (q == expected) { return(v .Where(o => o.Value["Type"] == "Certificate") .Where(o => o.Value["CertificateName"] == "footca") .OrderByDescending(o => o.Value["Version"])); } expected = "SELECT TOP 1 * FROM Certificates c " + "WHERE c.Type = 'Certificate' " + "AND c.CertificateName = 'rootca' " + "ORDER BY c.Version DESC"; if (q == expected) { return(v .Where(o => o.Value["Type"] == "Certificate") .Where(o => o.Value["CertificateName"] == "rootca") .OrderByDescending(o => o.Value["Version"])); } expected = "SELECT TOP 1 * FROM Certificates c " + "WHERE c.Type = 'Certificate' " + "AND c.CertificateId = '" + kTestVaultUri + "/certificates/rootca' " + "ORDER BY c.Version DESC"; if (q == expected) { return(v .Where(o => o.Value["Type"] == "Certificate") .Where(o => o.Value["CertificateName"] == "rootca") .OrderByDescending(o => o.Value["Version"])); } throw new AssertActualExpectedException(expected, q, "Query"); }, out var service, out var client)) { ICertificateStore store = mock.Create <CertificateDatabase>(); ICertificateRepository repo = mock.Create <CertificateDatabase>(); var now = DateTime.UtcNow; using (var rkey = SignatureType.RS256.CreateCsr("CN=thee", true, out var rootcsr)) using (var rootca = rootcsr.CreateSelfSigned(now, now + TimeSpan.FromDays(5))) using (var ikey = SignatureType.RS256.CreateCsr("CN=me", true, out var issuercsr)) using (var issuer = issuercsr.Create(rootca, now, now + TimeSpan.FromHours(3), Guid.NewGuid().ToByteArray())) { await repo.AddCertificateAsync("rootca", rootca.ToCertificate(new IssuerPolicies { SignatureType = SignatureType.RS256, IssuedLifetime = TimeSpan.FromHours(3) }, KeyVaultKeyHandle.Create(kTestVaultUri + "/keys/rkid", null)), kTestVaultUri + "/certificates/rootca"); client.Setup(o => o.GetCertificateWithHttpMessagesAsync( It.Is <string>(a => a == kTestVaultUri), It.Is <string>(a => a == "rootca"), It.IsAny <string>(), It.IsAny <Dictionary <string, List <string> > >(), It.IsAny <CancellationToken>())).Returns(() => { var result = new CertificateBundle( kTestVaultUri + "/certificates/rootca", kTestVaultUri + "/keys/rkid", null, // not exportable null, null, rootca.ToPfx(rkey.ToKey()), null, null, null); return(Task.FromResult(new AzureOperationResponse <CertificateBundle> { Body = result })); }); client.Setup(o => o.CreateCertificateWithHttpMessagesAsync( It.Is <string>(a => a == kTestVaultUri), It.Is <string>(a => a == "footca"), It.IsNotNull <CertificatePolicy>(), It.IsNotNull <CertificateAttributes>(), It.IsAny <IDictionary <string, string> >(), It.IsAny <Dictionary <string, List <string> > >(), It.IsAny <CancellationToken>())).Returns(() => { var result = new CertificateOperation { Status = "InProgress" }; return(Task.FromResult(new AzureOperationResponse <CertificateOperation> { Body = result })); }); client.Setup(o => o.GetCertificateOperationWithHttpMessagesAsync( It.Is <string>(a => a == kTestVaultUri), It.IsAny <string>(), It.IsAny <Dictionary <string, List <string> > >(), It.IsAny <CancellationToken>())).Returns(() => { var result = new CertificateOperation { Csr = issuercsr.CreateSigningRequest(), Status = "Completed" }; return(Task.FromResult(new AzureOperationResponse <CertificateOperation> { Body = result })); }); client.Setup(o => o.GetCertificateWithHttpMessagesAsync( It.Is <string>(a => a == kTestVaultUri), It.Is <string>(a => a == "footca"), It.IsAny <string>(), It.IsAny <Dictionary <string, List <string> > >(), It.IsAny <CancellationToken>())).Returns(() => { var result = new CertificateBundle( kTestVaultUri + "/certificates/footca", kTestVaultUri + "/keys/fkid", null, // not exportable null, null, issuer.ToPfx(ikey.ToKey()), null, null, null); return(Task.FromResult(new AzureOperationResponse <CertificateBundle> { Body = result })); }); client.Setup(o => o.MergeCertificateWithHttpMessagesAsync( It.Is <string>(a => a == kTestVaultUri), It.Is <string>(a => a == "footca"), It.IsAny <IList <byte[]> >(), It.IsAny <CertificateAttributes>(), It.IsAny <IDictionary <string, string> >(), It.IsAny <Dictionary <string, List <string> > >(), It.IsAny <CancellationToken>())).Returns(() => { var result = new CertificateBundle( kTestVaultUri + "/certificates/footca", kTestVaultUri + "/keys/fkid", null, // not exportable null, null, issuer.ToPfx(ikey.ToKey()), null, null, null); return(Task.FromResult(new AzureOperationResponse <CertificateBundle> { Body = result })); }); client.Setup(o => o.SignWithHttpMessagesAsync( It.Is <string>(a => a == kTestVaultUri), // It.Is<string>(a => a == kTestVaultUri + "/keys/rkid"), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), // It.Is<string>(a => a == "RS256"), It.IsAny <byte[]>(), It.IsAny <Dictionary <string, List <string> > >(), It.IsAny <CancellationToken>())).Returns(() => { var result = new KeyOperationResult( kTestVaultUri + "/keys/rkid", new byte[32]); return(Task.FromResult(new AzureOperationResponse <KeyOperationResult> { Body = result })); }); // Run var footca = await service.NewIssuerCertificateAsync("rootca", "footca", X500DistinguishedNameEx.Create("CN=me"), DateTime.UtcNow, new CreateKeyParams { KeySize = 2048, Type = KeyType.RSA }, new IssuerPolicies { IssuedLifetime = TimeSpan.FromHours(1) }); var found = await store.FindLatestCertificateAsync("footca"); // Assert Assert.NotNull(footca); Assert.NotNull(found); Assert.NotNull(footca.IssuerPolicies); Assert.NotNull(footca.KeyHandle); Assert.Null(footca.Revoked); Assert.Equal(TimeSpan.FromHours(3), footca.NotAfterUtc - footca.NotBeforeUtc); Assert.Equal(TimeSpan.FromHours(1), footca.IssuerPolicies.IssuedLifetime); Assert.Equal(SignatureType.RS256, footca.IssuerPolicies.SignatureType); Assert.False(footca.IsSelfSigned()); Assert.True(footca.IsIssuer()); Assert.True(footca.SameAs(found)); Assert.Equal(rootca.Subject, footca.GetIssuerSubjectName()); Assert.True(rootca.SubjectName.SameAs(footca.Issuer)); using (var cert = footca.ToX509Certificate2()) { Assert.Equal(cert.GetSerialNumber(), footca.GetSerialNumberAsBytesLE()); Assert.Equal(cert.SerialNumber, footca.GetSerialNumberAsString()); Assert.Equal(cert.Thumbprint, footca.Thumbprint); } Assert.True(footca.IsValidChain(rootca.ToCertificate().YieldReturn())); } } }
/// <inheritdoc/> public async Task <Certificate> CreateCertificateAndPrivateKeyAsync(string rootCertificate, string certificateName, X500DistinguishedName subjectName, DateTime?notBefore, CreateKeyParams keyParams, Func <byte[], IEnumerable <X509Extension> > extensions, CancellationToken ct) { try { // (0) Retrieve issuer certificate var caCertBundle = await _keyVaultClient.GetCertificateAsync( _vaultBaseUrl, rootCertificate, ct); if (caCertBundle == null) { throw new ResourceNotFoundException("Issuer cert not found."); } var caCert = await _certificates.FindCertificateAsync( caCertBundle.CertificateIdentifier.Identifier); if (caCert?.IssuerPolicies == null) { throw new ArgumentException("Certificate cannot issue."); } // (1) Create key in key vault and get CSR. // policy unknown issuer, new key, exportable key var policyUnknownNewExportable = CreateCertificatePolicy( subjectName.Name, keyParams, false, _keyStoreIsHsm, false, true); var attributes = CreateCertificateAttributes(notBefore, caCert.IssuerPolicies.IssuedLifetime.Value, caCert.NotAfterUtc); var createResult = await CreateCertificateAsync(certificateName, policyUnknownNewExportable, attributes, null, ct); if (createResult.Csr == null) { throw new CryptographicUnexpectedOperationException( "Failed to read CSR from CreateCertificate."); } // decode the CSR and verify consistency var info = createResult.Csr.ToCertificationRequest(); try { // (2) - Issue X509 Certificate with csr and root certificate. // create signed cert var signedcert = await _factory.CreateCertificateAsync(this, caCert, subjectName, info.PublicKey, attributes.NotBefore.Value, attributes.Expires.Value, caCert.IssuerPolicies.SignatureType.Value, false, extensions, ct); // (3) - Complete certificate creation with merger of X509 Certificate. var mergeResult = await _keyVaultClient.MergeCertificateAsync( _vaultBaseUrl, certificateName, new X509Certificate2Collection(signedcert), null, null, ct); // (4) - Get merged certificate and key identifier var mergedCert = await _keyVaultClient.GetCertificateAsync( mergeResult.CertificateIdentifier.Identifier, ct); var cert = CertificateEx.Create(mergedCert.Cer, KeyVaultKeyHandle.Create(mergedCert)); System.Diagnostics.Debug.Assert(!cert.IsIssuer()); await _certificates.AddCertificateAsync(certificateName, cert, mergedCert.CertificateIdentifier.Identifier, ct); return(cert); } catch { await _keyVaultClient.DeleteCertificateAsync( _vaultBaseUrl, certificateName, ct); await Try.Async(() => _keyVaultClient.PurgeDeletedCertificateAsync( _vaultBaseUrl, certificateName, ct)); throw; } } catch (KeyVaultErrorException ex) { throw new ExternalDependencyException( "Failed to create new key pair certificate", ex); } }
/// <inheritdoc/> public async Task <Certificate> NewRootCertificateAsync(string certificateName, X500DistinguishedName subjectName, DateTime?notBefore, TimeSpan lifetime, CreateKeyParams keyParams, IssuerPolicies policies, Func <byte[], IEnumerable <X509Extension> > extensions, CancellationToken ct) { if (string.IsNullOrEmpty(certificateName)) { throw new ArgumentNullException(nameof(certificateName)); } // Validate policies policies = policies.Validate(null, keyParams); string caTempCertIdentifier = null; try { // (1) Create key in key vault and get CSR. // policy self signed, new key, not exportable key var policySelfSignedNewKey = CreateCertificatePolicy( subjectName.Name, keyParams, true, _keyStoreIsHsm, false, false); var tempAttributes = CreateCertificateAttributes( DateTime.UtcNow.AddMinutes(-10), TimeSpan.FromMinutes(10), DateTime.MaxValue); await CreateCertificateAsync(certificateName, policySelfSignedNewKey, tempAttributes, null, ct); // We have the cert - get it and key identifier to do the signing var createdCertificateBundle = await _keyVaultClient.GetCertificateAsync( _vaultBaseUrl, certificateName, ct); caTempCertIdentifier = createdCertificateBundle.CertificateIdentifier.Identifier; // policy unknown issuer, reuse key - not exportable var policyUnknownReuse = CreateCertificatePolicy( subjectName.Name, keyParams, false, _keyStoreIsHsm, true, false); var attributes = CreateCertificateAttributes(notBefore, lifetime, DateTime.MaxValue); // create the CSR var createResult = await CreateCertificateAsync(certificateName, policyUnknownReuse, attributes, null, ct); if (createResult.Csr == null) { throw new CryptographicUnexpectedOperationException( "Failed to read CSR from CreateCertificate."); } // decode the CSR and verify consistency var info = createResult.Csr.ToCertificationRequest(); // (2) - Issue root X509 Certificate with the csr. var signedcert = await _factory.CreateCertificateAsync(this, KeyVaultKeyHandle.Create(createdCertificateBundle), subjectName, info.PublicKey, attributes.NotBefore.Value, attributes.Expires.Value, policies.SignatureType.Value, true, extensions, ct); // (3) - Complete certificate creation with merger of X509 Certificate. var mergeResult = await _keyVaultClient.MergeCertificateAsync( _vaultBaseUrl, certificateName, new X509Certificate2Collection(signedcert), null, null, ct); // (4) - Get merged certificate and key identifier var mergedCert = await _keyVaultClient.GetCertificateAsync( mergeResult.CertificateIdentifier.Identifier, ct); var cert = CertificateEx.Create(mergedCert.Cer, KeyVaultKeyHandle.Create(mergedCert), policies); await _certificates.AddCertificateAsync(certificateName, cert, mergedCert.CertificateIdentifier.Identifier, ct); return(cert); } catch (KeyVaultErrorException kex) { throw new ExternalDependencyException( "Failed to create new Root CA certificate", kex); } finally { if (caTempCertIdentifier != null) { // disable the temp cert for self signing operation var attr = new CertificateAttributes { Enabled = false }; await Try.Async(() => _keyVaultClient.UpdateCertificateAsync( caTempCertIdentifier, null, attr)); } } }