/// <summary> /// Initializes the object with a UA identity token /// </summary> private void Initialize(UserIdentityToken token) { if (token == null) { throw new ArgumentNullException(nameof(token)); } m_grantedRoleIds = new NodeIdCollection(); m_token = token; UserNameIdentityToken usernameToken = token as UserNameIdentityToken; if (usernameToken != null) { m_tokenType = UserTokenType.UserName; m_issuedTokenType = null; m_displayName = usernameToken.UserName; return; } X509IdentityToken x509Token = token as X509IdentityToken; if (x509Token != null) { m_tokenType = UserTokenType.Certificate; m_issuedTokenType = null; if (x509Token.Certificate != null) { m_displayName = x509Token.Certificate.Subject; } else { X509Certificate2 cert = CertificateFactory.Create(x509Token.CertificateData, true); m_displayName = cert.Subject; } return; } IssuedIdentityToken issuedToken = token as IssuedIdentityToken; if (issuedToken != null) { if (issuedToken.IssuedTokenType == Ua.IssuedTokenType.JWT) { if (issuedToken.DecryptedTokenData == null || issuedToken.DecryptedTokenData.Length == 0) { throw new ArgumentException("JSON Web Token has no data associated with it.", nameof(token)); } m_tokenType = UserTokenType.IssuedToken; m_issuedTokenType = new XmlQualifiedName("", Opc.Ua.Profiles.JwtUserToken); m_displayName = "JWT"; return; } else { throw new NotSupportedException("Only JWT Issued Tokens are supported!"); } } AnonymousIdentityToken anonymousToken = token as AnonymousIdentityToken; if (anonymousToken != null) { m_tokenType = UserTokenType.Anonymous; m_issuedTokenType = null; m_displayName = "Anonymous"; return; } throw new ArgumentException("Unrecognized UA user identity token type.", nameof(token)); }
/// <summary> /// Returns if a self signed certificate is properly signed. /// </summary> private static bool IsSignatureValid(X509Certificate2 cert) { return(CertificateFactory.VerifySelfSigned(cert)); }
/// <summary> /// Throws an exception if validation fails. /// </summary> /// <param name="certificates">The certificates to be checked.</param> /// <exception cref="ServiceResultException">If certificate[0] cannot be accepted</exception> protected virtual async Task InternalValidate(X509Certificate2Collection certificates) { X509Certificate2 certificate = certificates[0]; // check for previously validated certificate. X509Certificate2 certificate2 = null; if (m_validatedCertificates.TryGetValue(certificate.Thumbprint, out certificate2)) { if (Utils.IsEqual(certificate2.RawData, certificate.RawData)) { return; } } CertificateIdentifier trustedCertificate = await GetTrustedCertificate(certificate); // get the issuers (checks the revocation lists if using directory stores). List <CertificateIdentifier> issuers = new List <CertificateIdentifier>(); bool isIssuerTrusted = await GetIssuers(certificates, issuers); // setup policy chain X509ChainPolicy policy = new X509ChainPolicy(); policy.RevocationFlag = X509RevocationFlag.EntireChain; policy.RevocationMode = X509RevocationMode.NoCheck; policy.VerificationFlags = X509VerificationFlags.NoFlag; foreach (CertificateIdentifier issuer in issuers) { if ((issuer.ValidationOptions & CertificateValidationOptions.SuppressRevocationStatusUnknown) != 0) { policy.VerificationFlags |= X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown; policy.VerificationFlags |= X509VerificationFlags.IgnoreCtlSignerRevocationUnknown; policy.VerificationFlags |= X509VerificationFlags.IgnoreEndRevocationUnknown; policy.VerificationFlags |= X509VerificationFlags.IgnoreRootRevocationUnknown; } // we did the revocation check in the GetIssuers call. No need here. policy.RevocationMode = X509RevocationMode.NoCheck; policy.ExtraStore.Add(issuer.Certificate); } // build chain. bool chainStatusChecked = false; X509Chain chain = new X509Chain(); chain.ChainPolicy = policy; chain.Build(certificate); // check the chain results. CertificateIdentifier target = trustedCertificate; if (target == null) { target = new CertificateIdentifier(certificate); } for (int ii = 0; ii < chain.ChainElements.Count; ii++) { X509ChainElement element = chain.ChainElements[ii]; CertificateIdentifier issuer = null; if (ii < issuers.Count) { issuer = issuers[ii]; } // check for chain status errors. if (element.ChainElementStatus.Length > 0) { foreach (X509ChainStatus status in element.ChainElementStatus) { ServiceResult result = CheckChainStatus(status, target, issuer, (ii != 0)); if (ServiceResult.IsBad(result)) { // check untrusted certificates. if (trustedCertificate == null) { ServiceResult errorResult = new ServiceResult( result.StatusCode, result.SymbolicId, result.NamespaceUri, result.LocalizedText, result.AdditionalInfo, StatusCodes.BadCertificateUntrusted); throw new ServiceResultException(errorResult); } throw new ServiceResultException(result); } chainStatusChecked = true; } } else { chainStatusChecked = true; } if (issuer != null) { target = issuer; } } // check whether the chain is complete (if there is a chain) bool issuedByCA = !Utils.CompareDistinguishedName(certificate.Subject, certificate.Issuer); bool chainIncomplete = false; if (issuers.Count > 0) { var rootCertificate = issuers[issuers.Count - 1].Certificate; if (!Utils.CompareDistinguishedName(rootCertificate.Subject, rootCertificate.Issuer)) { chainIncomplete = true; } } else { if (issuedByCA) { // no issuer found at all chainIncomplete = true; } } if (issuedByCA && (!chainStatusChecked || chainIncomplete)) { throw ServiceResultException.Create( StatusCodes.BadCertificateChainIncomplete, "Certificate chain validation incomplete.\r\nSubjectName: {0}\r\nIssuerName: {1}", certificate.SubjectName.Name, certificate.IssuerName.Name); } // check if certificate issuer is trusted. if (issuedByCA && !isIssuerTrusted && trustedCertificate == null) { if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData)) { throw ServiceResultException.Create( StatusCodes.BadCertificateUntrusted, "Certificate issuer is not trusted.\r\nSubjectName: {0}\r\nIssuerName: {1}", certificate.SubjectName.Name, certificate.IssuerName.Name); } } // check if certificate is trusted. if (trustedCertificate == null && !isIssuerTrusted) { if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData)) { throw ServiceResultException.Create( StatusCodes.BadCertificateUntrusted, "Certificate is not trusted.\r\nSubjectName: {0}\r\nIssuerName: {1}", certificate.SubjectName.Name, certificate.IssuerName.Name); } } // check if certificate is valid for use as app/sw or user cert X509KeyUsageFlags certificateKeyUsage = CertificateFactory.GetKeyUsage(certificate); if ((certificateKeyUsage & X509KeyUsageFlags.DataEncipherment) == 0) { throw new ServiceResultException(StatusCodes.BadCertificateUseNotAllowed, "Usage of certificate is not allowed."); } // check if minimum requirements are met if (m_rejectSHA1SignedCertificates && IsSHA1SignatureAlgorithm(certificate.SignatureAlgorithm)) { throw new ServiceResultException(StatusCodes.BadCertificatePolicyCheckFailed, "SHA1 signed certificates are not trusted"); } int keySize = CertificateFactory.GetRSAPublicKeySize(certificate); if (keySize < m_minimumCertificateKeySize) { throw new ServiceResultException(StatusCodes.BadCertificatePolicyCheckFailed, "Certificate doesn't meet minimum key length requirement"); } }
/// <summary> /// Returns the certificate information for a trusted issuer certificate. /// </summary> private async Task <CertificateIdentifier> GetIssuer( X509Certificate2 certificate, CertificateIdentifierCollection explicitList, CertificateStoreIdentifier certificateStore, bool checkRecovationStatus) { // check if self-signed. if (Utils.CompareDistinguishedName(certificate.Subject, certificate.Issuer)) { return(null); } string subjectName = certificate.IssuerName.Name; string keyId = null; string serialNumber = null; // find the authority key identifier. X509AuthorityKeyIdentifierExtension authority = FindAuthorityKeyIdentifier(certificate); if (authority != null) { keyId = authority.KeyId; serialNumber = authority.SerialNumber; } // check in explicit list. if (explicitList != null) { for (int ii = 0; ii < explicitList.Count; ii++) { X509Certificate2 issuer = await explicitList[ii].Find(false); if (issuer != null) { if (!IsIssuerAllowed(issuer)) { continue; } if (Match(issuer, subjectName, serialNumber, keyId)) { // can't check revocation. return(new CertificateIdentifier(issuer, CertificateValidationOptions.SuppressRevocationStatusUnknown)); } } } } // check in certificate store. if (certificateStore != null) { ICertificateStore store = certificateStore.OpenStore(); try { X509Certificate2Collection certificates = await store.Enumerate(); for (int ii = 0; ii < certificates.Count; ii++) { X509Certificate2 issuer = certificates[ii]; if (issuer != null) { if (!IsIssuerAllowed(issuer)) { continue; } if (Match(issuer, subjectName, serialNumber, keyId)) { CertificateValidationOptions options = certificateStore.ValidationOptions; // already checked revocation for file based stores. windows based stores always suppress. options |= CertificateValidationOptions.SuppressRevocationStatusUnknown; if (checkRecovationStatus) { StatusCode status = store.IsRevoked(issuer, certificate); if (StatusCode.IsBad(status) && status != StatusCodes.BadNotSupported) { if (status == StatusCodes.BadCertificateRevocationUnknown) { if (CertificateFactory.IsCertificateAuthority(certificate)) { status.Code = StatusCodes.BadCertificateIssuerRevocationUnknown; } if (m_rejectUnknownRevocationStatus) { throw new ServiceResultException(status); } } else { throw new ServiceResultException(status); } } } return(new CertificateIdentifier(certificates[ii], options)); } } } } finally { store.Close(); } } // not a trusted issuer. return(null); }
public void InitializeSinglePolicy( Type contractType, ApplicationConfiguration configuration, BindingFactory bindingFactory, EndpointConfiguration endpointConfiguration, List <EndpointDescription> endpoints, MessageSecurityMode securityMode, string securityPolicyUri) { // allow any url to match. System.ServiceModel.ServiceBehaviorAttribute behavoir = this.Description.Behaviors.Find <System.ServiceModel.ServiceBehaviorAttribute>(); behavoir.AddressFilterMode = System.ServiceModel.AddressFilterMode.Any; // specify service credentials ServiceCredentials credentials = new ServiceCredentials(); credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom; credentials.ClientCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine; credentials.ClientCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck; credentials.ClientCertificate.Authentication.CustomCertificateValidator = configuration.CertificateValidator.GetChannelValidator(); if (configuration.SecurityConfiguration.ApplicationCertificate != null) { X509Certificate2 certificate = configuration.SecurityConfiguration.ApplicationCertificate.Find(true); if (certificate != null) { credentials.ServiceCertificate.Certificate = CertificateFactory.Load(certificate, true); } } this.Description.Behaviors.Add(credentials); // check if explicitly specified. ServiceThrottlingBehavior throttle = this.Description.Behaviors.Find <ServiceThrottlingBehavior>(); if (throttle == null) { throttle = new ServiceThrottlingBehavior(); throttle.MaxConcurrentCalls = 1000; throttle.MaxConcurrentInstances = 100; throttle.MaxConcurrentSessions = 100; this.Description.Behaviors.Add(throttle); } // add the endpoints for each base address. foreach (Uri baseAddress in this.BaseAddresses) { ServiceEndpoint endpoint = null; // find endpoint configuration. EndpointDescription description = null; foreach (EndpointDescription current in endpoints) { if (new Uri(current.EndpointUrl) == baseAddress) { description = current; break; } } // skip endpoints without a matching base address. if (description == null) { continue; } // set the supported profiles. description.TransportProfileUri = Profiles.WsHttpXmlOrBinaryTransport; // create the SOAP XML binding Binding binding = bindingFactory.Create(baseAddress.Scheme, description, endpointConfiguration); // add the session endpoint. endpoint = this.AddServiceEndpoint(contractType, binding, baseAddress, baseAddress); // set the protection level if (securityMode == MessageSecurityMode.Sign) { endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.Sign; } // update the max items in graph (set to an low value by default). foreach (OperationDescription operation in endpoint.Contract.Operations) { operation.Behaviors.Find <DataContractSerializerOperationBehavior>().MaxItemsInObjectGraph = Int32.MaxValue; } } }
/// <summary> /// Loads the private key from a PFX file in the certificate store. /// </summary> public X509Certificate2 LoadPrivateKey(string thumbprint, string subjectName, string password) { if (m_certificateSubdir == null || !m_certificateSubdir.Exists) { return(null); } if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName)) { return(null); } foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der")) { try { X509Certificate2 certificate = new X509Certificate2(file.FullName); if (!String.IsNullOrEmpty(thumbprint)) { if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.CurrentCultureIgnoreCase)) { continue; } } if (!String.IsNullOrEmpty(subjectName)) { if (!Utils.CompareDistinguishedName(subjectName, certificate.Subject)) { if (subjectName.Contains("=")) { continue; } if (!Utils.ParseDistinguishedName(certificate.Subject).Any(s => s.Equals("CN=" + subjectName, StringComparison.OrdinalIgnoreCase))) { continue; } } } string fileRoot = file.Name.Substring(0, file.Name.Length - file.Extension.Length); StringBuilder filePath = new StringBuilder(); filePath.Append(m_privateKeySubdir.FullName); filePath.Append(Path.DirectorySeparatorChar); filePath.Append(fileRoot); FileInfo privateKeyFile = new FileInfo(filePath.ToString() + ".pfx"); password = password ?? String.Empty; try { certificate = new X509Certificate2( privateKeyFile.FullName, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet); if (CertificateFactory.VerifyRSAKeyPair(certificate, certificate, true)) { return(certificate); } } catch (Exception) { certificate = new X509Certificate2( privateKeyFile.FullName, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); if (CertificateFactory.VerifyRSAKeyPair(certificate, certificate, true)) { return(certificate); } } } catch (Exception e) { Utils.Trace(e, "Could not load private key for certificate " + subjectName); } } return(null); }
/// <summary> /// Loads the private key from a PFX/PEM file in the certificate store. /// </summary> public async Task <X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, string password) { if (NoPrivateKeys || m_certificateSubdir == null || !m_certificateSubdir.Exists) { return(null); } if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName)) { return(null); } // on some platforms, specifically in virtualized environments, // reloading a previously created and saved private key may fail on the first attempt. const int retryDelay = 100; int retryCounter = 3; while (retryCounter-- > 0) { bool certificateFound = false; Exception importException = null; foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der")) { try { X509Certificate2 certificate = new X509Certificate2(file.FullName); if (!String.IsNullOrEmpty(thumbprint)) { if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)) { continue; } } if (!String.IsNullOrEmpty(subjectName)) { if (!X509Utils.CompareDistinguishedName(subjectName, certificate.Subject)) { if (subjectName.Contains('=')) { continue; } if (!X509Utils.ParseDistinguishedName(certificate.Subject).Any(s => s.Equals("CN=" + subjectName, StringComparison.Ordinal))) { continue; } } } // skip if not RSA certificate if (X509Utils.GetRSAPublicKeySize(certificate) < 0) { continue; } string fileRoot = file.Name.Substring(0, file.Name.Length - file.Extension.Length); StringBuilder filePath = new StringBuilder() .Append(m_privateKeySubdir.FullName) .Append(Path.DirectorySeparatorChar) .Append(fileRoot); X509KeyStorageFlags[] storageFlags = { X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet }; FileInfo privateKeyFilePfx = new FileInfo(filePath + ".pfx"); FileInfo privateKeyFilePem = new FileInfo(filePath + ".pem"); password = password ?? String.Empty; if (privateKeyFilePfx.Exists) { certificateFound = true; foreach (var flag in storageFlags) { try { certificate = new X509Certificate2( privateKeyFilePfx.FullName, password, flag); if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true)) { Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PFX private key for [{0}].", certificate.Thumbprint); return(certificate); } } catch (Exception ex) { importException = ex; certificate?.Dispose(); } } } // if PFX file doesn't exist, check for PEM file. else if (privateKeyFilePem.Exists) { certificateFound = true; try { byte[] pemDataBlob = File.ReadAllBytes(privateKeyFilePem.FullName); certificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(certificate, pemDataBlob, password); if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true)) { Utils.LogInfo(Utils.TraceMasks.Security, "Imported the PEM private key for [{0}].", certificate.Thumbprint); return(certificate); } } catch (Exception exception) { certificate?.Dispose(); importException = exception; } } else { Utils.LogError(Utils.TraceMasks.Security, "A private key for the certificate with thumbprint [{0}] does not exist.", certificate.Thumbprint); continue; } } catch (Exception e) { Utils.LogError(e, "Could not load private key for certificate {0}", subjectName); } } // found a certificate, but some error occurred if (certificateFound) { Utils.LogError(Utils.TraceMasks.Security, "The private key for the certificate with subject {0} failed to import.", subjectName); if (importException != null) { Utils.LogError(importException, "Certificate import failed."); } } else { if (!String.IsNullOrEmpty(thumbprint)) { Utils.LogError(Utils.TraceMasks.Security, "A Private key for the certificate with thumbpint {0} was not found.", thumbprint); } // if no private key was found, no need to retry break; } // retry within a few ms if (retryCounter > 0) { Utils.LogInfo(Utils.TraceMasks.Security, "Retry to import private key after {0} ms.", retryDelay); await Task.Delay(retryDelay).ConfigureAwait(false); } } return(null); }