/// <summary> /// Creates a new endpoint from a url that is not part of the collection. /// </summary> /// <remarks> /// Call the Add() method to add it to the collection. /// </remarks> public ConfiguredEndpoint Create(string url) { // check for security parameters appended to the URL string parameters = null; int index = url.IndexOf("- [", StringComparison.Ordinal); if (index != -1) { parameters = url.Substring(index + 3); url = url.Substring(0, index).Trim(); } MessageSecurityMode securityMode = MessageSecurityMode.SignAndEncrypt; string securityPolicyUri = SecurityPolicies.Basic128Rsa15; bool useBinaryEncoding = true; if (!String.IsNullOrEmpty(parameters)) { string[] fields = parameters.Split(new char[] { '-', '[', ':', ']' }, StringSplitOptions.RemoveEmptyEntries); try { if (fields.Length > 0) { securityMode = (MessageSecurityMode)Enum.Parse(typeof(MessageSecurityMode), fields[0], false); } else { securityMode = MessageSecurityMode.None; } } catch { securityMode = MessageSecurityMode.None; } try { if (fields.Length > 1) { securityPolicyUri = SecurityPolicies.GetUri(fields[1]); } else { securityPolicyUri = SecurityPolicies.None; } } catch { securityPolicyUri = SecurityPolicies.None; } try { if (fields.Length > 2) { useBinaryEncoding = fields[2] == "Binary"; } else { useBinaryEncoding = false; } } catch { useBinaryEncoding = false; } } Uri uri = new Uri(url); EndpointDescription description = new EndpointDescription(); description.EndpointUrl = uri.ToString(); description.SecurityMode = securityMode; description.SecurityPolicyUri = securityPolicyUri; description.Server.ApplicationUri = Utils.UpdateInstanceUri(uri.ToString()); description.Server.ApplicationName = uri.AbsolutePath; if (description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal)) { description.TransportProfileUri = Profiles.UaTcpTransport; description.Server.DiscoveryUrls.Add(description.EndpointUrl); } else if (description.EndpointUrl.StartsWith(Utils.UriSchemeHttps, StringComparison.Ordinal)) { description.TransportProfileUri = Profiles.HttpsBinaryTransport; description.Server.DiscoveryUrls.Add(description.EndpointUrl); } ConfiguredEndpoint endpoint = new ConfiguredEndpoint(this, description, null); endpoint.Configuration.UseBinaryEncoding = useBinaryEncoding; endpoint.UpdateBeforeConnect = true; return(endpoint); }
/// <summary> /// Adds a previous created endpoint to the collection. /// </summary> public void Add(ConfiguredEndpoint item) { Insert(item, -1); }
/// <summary> /// Throws an exception if validation fails. /// </summary> /// <param name="certificates">The certificates to be checked.</param> /// <param name="endpoint">The endpoint for domain validation.</param> /// <exception cref="ServiceResultException">If certificate[0] cannot be accepted</exception> protected virtual async Task InternalValidate(X509Certificate2Collection certificates, ConfiguredEndpoint endpoint) { 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. X509Chain chain = new X509Chain(); chain.ChainPolicy = policy; chain.Build(certificate); // check the chain results. CertificateIdentifier target = trustedCertificate; if (target == null) { target = new CertificateIdentifier(certificate); } ServiceResult sresult = null; 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)) { sresult = new ServiceResult(result, sresult); } } } if (issuer != null) { target = issuer; } } // check whether the chain is complete (if there is a chain) bool issuedByCA = !X509Utils.CompareDistinguishedName(certificate.Subject, certificate.Issuer); bool chainIncomplete = false; if (issuers.Count > 0) { var rootCertificate = issuers[issuers.Count - 1].Certificate; if (!X509Utils.CompareDistinguishedName(rootCertificate.Subject, rootCertificate.Issuer)) { chainIncomplete = true; } } else { if (issuedByCA) { // no issuer found at all chainIncomplete = true; } } // check if certificate issuer is trusted. if (issuedByCA && !isIssuerTrusted && trustedCertificate == null) { var message = CertificateMessage("Certificate Issuer is not trusted.", certificate); sresult = new ServiceResult(StatusCodes.BadCertificateUntrusted, null, null, message, null, sresult); } // check if certificate is trusted. if (trustedCertificate == null && !isIssuerTrusted) { if (m_applicationCertificate == null || !Utils.IsEqual(m_applicationCertificate.RawData, certificate.RawData)) { var message = CertificateMessage("Certificate is not trusted.", certificate); sresult = new ServiceResult(StatusCodes.BadCertificateUntrusted, null, null, message, null, sresult); } } if (endpoint != null && !FindDomain(certificate, endpoint)) { string message = Utils.Format( "The domain '{0}' is not listed in the server certificate.", endpoint.EndpointUrl.DnsSafeHost); sresult = new ServiceResult(StatusCodes.BadCertificateHostNameInvalid, null, null, message, null, sresult ); } // check if certificate is valid for use as app/sw or user cert X509KeyUsageFlags certificateKeyUsage = X509Utils.GetKeyUsage(certificate); if ((certificateKeyUsage & X509KeyUsageFlags.DataEncipherment) == 0) { sresult = new ServiceResult(StatusCodes.BadCertificateUseNotAllowed, null, null, "Usage of certificate is not allowed.", null, sresult); } // check if minimum requirements are met if (m_rejectSHA1SignedCertificates && IsSHA1SignatureAlgorithm(certificate.SignatureAlgorithm)) { sresult = new ServiceResult(StatusCodes.BadCertificatePolicyCheckFailed, null, null, "SHA1 signed certificates are not trusted.", null, sresult); } int keySize = X509Utils.GetRSAPublicKeySize(certificate); if (keySize < m_minimumCertificateKeySize) { sresult = new ServiceResult(StatusCodes.BadCertificatePolicyCheckFailed, null, null, "Certificate doesn't meet minimum key length requirement.", null, sresult); } if (issuedByCA && chainIncomplete) { var message = CertificateMessage("Certificate chain validation incomplete.", certificate); sresult = new ServiceResult(StatusCodes.BadCertificateChainIncomplete, null, null, message, null, sresult); } if (sresult != null) { throw new ServiceResultException(sresult); } }
/// <summary> /// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index. /// </summary> /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param> /// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param> /// <exception cref="T:System.ArgumentOutOfRangeException"> /// <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception> /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception> public void Insert(int index, ConfiguredEndpoint item) { Insert(item, index); }
/// <summary> /// Validates a certificate with domain validation check. /// <see cref="Validate(X509Certificate2Collection)"/> /// </summary> public virtual void Validate(X509Certificate2Collection chain, ConfiguredEndpoint endpoint) { X509Certificate2 certificate = chain[0]; try { lock (m_lock) { InternalValidate(chain, endpoint).GetAwaiter().GetResult(); // add to list of validated certificates. m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); } } catch (ServiceResultException se) { // check for errors that may be suppressed. if (ContainsUnsuppressibleSC(se.Result)) { SaveCertificate(certificate); Utils.Trace(Utils.TraceMasks.Error, "Certificate '{0}' rejected. Reason={1}.", certificate.Subject, se.Result.ToString()); TraceInnerServiceResults(se.Result); throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid); } else { Utils.Trace("Certificate Vaildation failed for '{0}'. Reason={1}", certificate.Subject, se.ToLongString()); TraceInnerServiceResults(se.Result); } // invoke callback. bool accept = false; ServiceResult serviceResult = se.Result; lock (m_callbackLock) { if (m_CertificateValidation != null) { do { CertificateValidationEventArgs args = new CertificateValidationEventArgs(serviceResult, certificate); m_CertificateValidation(this, args); if (args.AcceptAll) { accept = true; serviceResult = null; break; } accept = args.Accept; if (accept) { serviceResult = serviceResult.InnerResult; } else { // report the rejected service result se = new ServiceResultException(serviceResult); } } while (accept && serviceResult != null); } } // throw if rejected. if (!accept) { // write the invalid certificate to rejected store if specified. Utils.Trace(Utils.TraceMasks.Error, "Certificate '{0}' rejected. Reason={1}", certificate.Subject, serviceResult.ToString()); SaveCertificate(certificate); throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid); } // add to list of peers. lock (m_lock) { Utils.Trace("Validation error suppressed for '{0}'.", certificate.Subject); m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData); } } }