/// <summary> /// Creates a new key pair with certificate offline and signs it with KeyVault. /// </summary> public override async Task <Opc.Ua.Gds.Server.X509Certificate2KeyPair> NewKeyPairRequestAsync( ApplicationRecordDataType application, string subjectName, string[] domainNames, string privateKeyFormat, string privateKeyPassword) { if (!privateKeyFormat.Equals("PFX", StringComparison.OrdinalIgnoreCase) && !privateKeyFormat.Equals("PEM", StringComparison.OrdinalIgnoreCase)) { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Invalid private key format"); } DateTime notBefore = DateTime.UtcNow.AddDays(-1); // create public/private key pair using (RSA keyPair = RSA.Create(Configuration.DefaultCertificateKeySize)) { await LoadPublicAssets().ConfigureAwait(false); string authorityInformationAccess = BuildAuthorityInformationAccessUrl(); // sign public key with KeyVault var signedCert = await KeyVaultCertFactory.CreateSignedCertificate( application.ApplicationUri, application.ApplicationNames.Count > 0?application.ApplicationNames[0].Text : "ApplicationName", subjectName, domainNames, Configuration.DefaultCertificateKeySize, notBefore, notBefore.AddMonths(Configuration.DefaultCertificateLifetime), Configuration.DefaultCertificateHashSize, Certificate, keyPair, new KeyVaultSignatureGenerator(_keyVaultServiceClient, _caCertKeyIdentifier, Certificate), extensionUrl : authorityInformationAccess); // Create a PEM or PFX using (var signedCertWithPrivateKey = CreateCertificateWithPrivateKey(signedCert, keyPair)) { byte[] privateKey; if (privateKeyFormat.Equals("PFX", StringComparison.OrdinalIgnoreCase)) { privateKey = signedCertWithPrivateKey.Export(X509ContentType.Pfx, privateKeyPassword); } else if (privateKeyFormat.Equals("PEM", StringComparison.OrdinalIgnoreCase)) { privateKey = CertificateFactory.ExportPrivateKeyAsPEM(signedCertWithPrivateKey); } else { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Invalid private key format"); } return(new Opc.Ua.Gds.Server.X509Certificate2KeyPair(new X509Certificate2(signedCertWithPrivateKey.RawData), privateKeyFormat, privateKey)); } } }
/// <summary> /// Creates a new signed application certificate in group id. /// </summary> /// <remarks> /// The key for the certificate is created in KeyVault, then exported. /// In order to deleted the created key, the impersonated user needs /// create, get and delete rights for KeyVault certificates /// </remarks> public async Task <X509Certificate2> CreateSignedKeyPairCertAsync( string caCertId, X509Certificate2 issuerCert, string applicationUri, string applicationName, string subjectName, string[] domainNames, DateTime notBefore, DateTime notAfter, int keySize, int hashSize, KeyVaultSignatureGenerator generator, string authorityInformationAccess, CancellationToken ct = default) { CertificateOperation createResult = null; var certName = KeyStoreName(caCertId, Guid.NewGuid().ToString()); try { // policy unknown issuer, new key, exportable var policyUnknownNewExportable = CreateCertificatePolicy(subjectName, keySize, false, false, true); var attributes = CreateCertificateAttributes(notBefore, notAfter); // create the CSR createResult = await _keyVaultClient.CreateCertificateAsync( _vaultBaseUrl, certName, policyUnknownNewExportable, attributes, null, ct) .ConfigureAwait(false); if (createResult.Csr == null) { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Failed to read CSR from CreateCertificate."); } // decode the CSR and verify consistency var pkcs10CertificationRequest = new Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest(createResult.Csr); var info = pkcs10CertificationRequest.GetCertificationRequestInfo(); if (createResult.Csr == null || pkcs10CertificationRequest == null || !pkcs10CertificationRequest.Verify()) { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Invalid CSR."); } // create the self signed app cert var publicKey = KeyVaultCertFactory.GetRSAPublicKey(info.SubjectPublicKeyInfo); var signedcert = await KeyVaultCertFactory.CreateSignedCertificate( applicationUri, applicationName, subjectName, domainNames, (ushort)keySize, notBefore, notAfter, (ushort)hashSize, issuerCert, publicKey, generator, extensionUrl : authorityInformationAccess); // merge signed cert with keystore var mergeResult = await _keyVaultClient.MergeCertificateAsync( _vaultBaseUrl, certName, new X509Certificate2Collection(signedcert) ); X509Certificate2 keyPair = null; var secret = await _keyVaultClient.GetSecretAsync(mergeResult.SecretIdentifier.Identifier, ct); if (secret.ContentType == CertificateContentType.Pfx) { var certBlob = Convert.FromBase64String(secret.Value); keyPair = CertificateFactory.CreateCertificateFromPKCS12(certBlob, string.Empty); } else if (secret.ContentType == CertificateContentType.Pem) { Encoding encoder = Encoding.UTF8; var privateKey = encoder.GetBytes(secret.Value.ToCharArray()); keyPair = CertificateFactory.CreateCertificateWithPEMPrivateKey(signedcert, privateKey, string.Empty); } return(keyPair); } catch { throw new ServiceResultException(StatusCodes.BadInternalError, "Failed to create new key pair certificate"); } finally { try { var deletedCertBundle = await _keyVaultClient.DeleteCertificateAsync(_vaultBaseUrl, certName, ct); await _keyVaultClient.PurgeDeletedCertificateAsync(_vaultBaseUrl, certName, ct); } catch { // intentionally fall through, purge may fail } } }
/// <summary> /// Creates a new Root CA certificate in group id, tags it for trusted or issuer store. /// </summary> public async Task <X509Certificate2> CreateCACertificateAsync( string id, string subject, DateTime notBefore, DateTime notAfter, int keySize, int hashSize, bool trusted, string crlDistributionPoint = null, CancellationToken ct = default) { try { // delete pending operations await _keyVaultClient.DeleteCertificateOperationAsync(_vaultBaseUrl, id); } catch { // intentionally ignore errors } string caTempCertIdentifier = null; try { // policy self signed, new key var policySelfSignedNewKey = CreateCertificatePolicy(subject, keySize, true, false); var tempAttributes = CreateCertificateAttributes(DateTime.UtcNow.AddMinutes(-10), DateTime.UtcNow.AddMinutes(10)); var createKey = await _keyVaultClient.CreateCertificateAsync( _vaultBaseUrl, id, policySelfSignedNewKey, tempAttributes, null, ct) .ConfigureAwait(false); CertificateOperation operation; do { await Task.Delay(1000); operation = await _keyVaultClient.GetCertificateOperationAsync(_vaultBaseUrl, id, ct); } while (operation.Status == "inProgress" && !ct.IsCancellationRequested); if (operation.Status != "completed") { throw new ServiceResultException(StatusCodes.BadUnexpectedError, "Failed to create new key pair."); } var createdCertificateBundle = await _keyVaultClient.GetCertificateAsync(_vaultBaseUrl, id).ConfigureAwait(false); var caCertKeyIdentifier = createdCertificateBundle.KeyIdentifier.Identifier; caTempCertIdentifier = createdCertificateBundle.CertificateIdentifier.Identifier; // policy unknown issuer, reuse key var policyUnknownReuse = CreateCertificatePolicy(subject, keySize, false, true); var attributes = CreateCertificateAttributes(notBefore, notAfter); var tags = CreateCertificateTags(id, trusted); // create the CSR var createResult = await _keyVaultClient.CreateCertificateAsync( _vaultBaseUrl, id, policyUnknownReuse, attributes, tags, ct) .ConfigureAwait(false); if (createResult.Csr == null) { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Failed to read CSR from CreateCertificate."); } // decode the CSR and verify consistency var pkcs10CertificationRequest = new Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest(createResult.Csr); var info = pkcs10CertificationRequest.GetCertificationRequestInfo(); if (createResult.Csr == null || pkcs10CertificationRequest == null || !pkcs10CertificationRequest.Verify()) { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Invalid CSR."); } // create the self signed root CA cert var publicKey = KeyVaultCertFactory.GetRSAPublicKey(info.SubjectPublicKeyInfo); var signedcert = await KeyVaultCertFactory.CreateSignedCertificate( null, null, subject, null, (ushort)keySize, notBefore, notAfter, (ushort)hashSize, null, publicKey, new KeyVaultSignatureGenerator(this, caCertKeyIdentifier, null), true, crlDistributionPoint); // merge Root CA cert with var mergeResult = await _keyVaultClient.MergeCertificateAsync( _vaultBaseUrl, id, new X509Certificate2Collection(signedcert) ); return(signedcert); } catch (KeyVaultErrorException kex) { var ex = kex; throw new ServiceResultException(StatusCodes.BadInternalError, "Failed to create new Root CA certificate"); } finally { if (caTempCertIdentifier != null) { try { // disable the temp cert for self signing operation var attr = new CertificateAttributes() { Enabled = false }; await _keyVaultClient.UpdateCertificateAsync(caTempCertIdentifier, null, attr); } catch { // intentionally ignore error } } } }
/// <summary> /// Creates a KeyVault signed certficate from signing request. /// </summary> public override async Task <X509Certificate2> SigningRequestAsync( ApplicationRecordDataType application, string[] domainNames, byte[] certificateRequest ) { try { var pkcs10CertificationRequest = new Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest(certificateRequest); if (!pkcs10CertificationRequest.Verify()) { throw new ServiceResultException(StatusCodes.BadInvalidArgument, "CSR signature invalid."); } var info = pkcs10CertificationRequest.GetCertificationRequestInfo(); var altNameExtension = GetAltNameExtensionFromCSRInfo(info); if (altNameExtension != null) { if (altNameExtension.Uris.Count > 0) { if (!altNameExtension.Uris.Contains(application.ApplicationUri)) { throw new ServiceResultException(StatusCodes.BadCertificateUriInvalid, "CSR AltNameExtension does not match " + application.ApplicationUri); } } if (altNameExtension.IPAddresses.Count > 0 || altNameExtension.DomainNames.Count > 0) { var domainNameList = new List <string>(); domainNameList.AddRange(altNameExtension.DomainNames); domainNameList.AddRange(altNameExtension.IPAddresses); domainNames = domainNameList.ToArray(); } } var authorityInformationAccess = BuildAuthorityInformationAccessUrl(); DateTime notBefore = DateTime.UtcNow.AddDays(-1); await LoadPublicAssets().ConfigureAwait(false); var signingCert = Certificate; { var publicKey = KeyVaultCertFactory.GetRSAPublicKey(info.SubjectPublicKeyInfo); return(await KeyVaultCertFactory.CreateSignedCertificate( application.ApplicationUri, application.ApplicationNames.Count > 0?application.ApplicationNames[0].Text : "ApplicationName", info.Subject.ToString(), domainNames, Configuration.DefaultCertificateKeySize, notBefore, notBefore.AddMonths(Configuration.DefaultCertificateLifetime), Configuration.DefaultCertificateHashSize, signingCert, publicKey, new KeyVaultSignatureGenerator(_keyVaultServiceClient, _caCertKeyIdentifier, signingCert), extensionUrl : authorityInformationAccess )); } } catch (Exception ex) { if (ex is ServiceResultException) { throw ex as ServiceResultException; } throw new ServiceResultException(StatusCodes.BadInvalidArgument, ex.Message); } }