public async Task <X509Certificate2Collection> GetCertificates() { X509Certificate2Collection collection = new X509Certificate2Collection(); if (!String.IsNullOrEmpty(this.StorePath)) { ICertificateStore store = null; try { store = OpenStore(); collection = await store.Enumerate().ConfigureAwait(false); } catch (Exception) { Utils.LogError("Could not load certificates from store: {0}.", this.StorePath); } finally { store?.Close(); } } foreach (CertificateIdentifier trustedCertificate in TrustedCertificates) { X509Certificate2 certificate = await trustedCertificate.Find().ConfigureAwait(false); if (certificate != null) { collection.Add(certificate); } } return(collection); }
private async Task <bool> UpdateStoreCertificates( CertificateTrustList trustList, X509Certificate2Collection updatedCerts) { bool result = true; ICertificateStore store = null; try { store = trustList.OpenStore(); var storeCerts = await store.Enumerate().ConfigureAwait(false); foreach (var cert in storeCerts) { if (!updatedCerts.Contains(cert)) { if (!store.Delete(cert.Thumbprint).Result) { result = false; } } else { updatedCerts.Remove(cert); } } foreach (var cert in updatedCerts) { await store.Add(cert).ConfigureAwait(false); } } catch { result = false; } finally { store?.Close(); } return(result); }
private async Task <bool> UpdateStoreCrls( CertificateTrustList trustList, X509CRLCollection updatedCrls) { bool result = true; ICertificateStore store = null; try { store = trustList.OpenStore(); var storeCrls = await store.EnumerateCRLs().ConfigureAwait(false); foreach (var crl in storeCrls) { if (!updatedCrls.Contains(crl)) { if (!await store.DeleteCRL(crl).ConfigureAwait(false)) { result = false; } } else { updatedCrls.Remove(crl); } } foreach (var crl in updatedCrls) { await store.AddCRL(crl).ConfigureAwait(false); } } catch { result = false; } finally { store?.Close(); } return(result); }
/// <summary> /// Returns the certificate information for a trusted issuer certificate. /// </summary> private async Task <CertificateIdentifier> GetIssuer( X509Certificate2 certificate, CertificateIdentifierCollection explicitList, CertificateStoreIdentifier certificateStore, bool checkRecovationStatus) { 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)) { if (status != StatusCodes.BadNotSupported && status != StatusCodes.BadCertificateRevocationUnknown) { throw new ServiceResultException(status); } } } return(new CertificateIdentifier(certificates[ii], options)); } } } } finally { store.Close(); } } // not a trusted issuer. return(null); }
/// <summary> /// Revoke the CA signed certificate. /// The issuer CA public key, the private key and the crl reside in the storepath. /// The CRL number is increased by one and existing CRL for the issuer are deleted from the store. /// </summary> public static async Task <X509CRL> RevokeCertificateAsync( string storePath, X509Certificate2 certificate, string issuerKeyFilePassword = null ) { X509CRL updatedCRL = null; try { string subjectName = certificate.IssuerName.Name; string keyId = null; string serialNumber = null; // caller may want to create empty CRL using the CA cert itself bool isCACert = IsCertificateAuthority(certificate); // find the authority key identifier. X509AuthorityKeyIdentifierExtension authority = FindAuthorityKeyIdentifier(certificate); if (authority != null) { keyId = authority.KeyId; serialNumber = authority.SerialNumber; } else { throw new ArgumentException("Certificate does not contain an Authority Key"); } if (!isCACert) { if (serialNumber == certificate.SerialNumber || Utils.CompareDistinguishedName(certificate.Subject, certificate.Issuer)) { throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Cannot revoke self signed certificates"); } } X509Certificate2 certCA = null; using (ICertificateStore store = CertificateStoreIdentifier.OpenStore(storePath)) { if (store == null) { throw new ArgumentException("Invalid store path/type"); } certCA = await FindIssuerCABySerialNumberAsync(store, certificate.Issuer, serialNumber); if (certCA == null) { throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Cannot find issuer certificate in store."); } if (!certCA.HasPrivateKey) { throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Issuer certificate has no private key, cannot revoke certificate."); } CertificateIdentifier certCAIdentifier = new CertificateIdentifier(certCA); certCAIdentifier.StorePath = storePath; certCAIdentifier.StoreType = CertificateStoreIdentifier.DetermineStoreType(storePath); X509Certificate2 certCAWithPrivateKey = await certCAIdentifier.LoadPrivateKey(issuerKeyFilePassword); if (certCAWithPrivateKey == null) { throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Failed to load issuer private key. Is the password correct?"); } List <X509CRL> certCACrl = store.EnumerateCRLs(certCA, false); var certificateCollection = new X509Certificate2Collection() { certificate }; updatedCRL = RevokeCertificate(certCAWithPrivateKey, certCACrl, certificateCollection); store.AddCRL(updatedCRL); // delete outdated CRLs from store foreach (X509CRL caCrl in certCACrl) { store.DeleteCRL(caCrl); } store.Close(); } } catch (Exception e) { throw e; } return(updatedCRL); }
/// <summary> /// Creates a self signed application instance certificate. /// </summary> /// <param name="storeType">Type of certificate store (Directory) <see cref="CertificateStoreType"/>.</param> /// <param name="storePath">The store path (syntax depends on storeType).</param> /// <param name="password">The password to use to protect the certificate.</param> /// <param name="applicationUri">The application uri (created if not specified).</param> /// <param name="applicationName">Name of the application (optional if subjectName is specified).</param> /// <param name="subjectName">The subject used to create the certificate (optional if applicationName is specified).</param> /// <param name="domainNames">The domain names that can be used to access the server machine (defaults to local computer name if not specified).</param> /// <param name="keySize">Size of the key (1024, 2048 or 4096).</param> /// <param name="startTime">The start time.</param> /// <param name="lifetimeInMonths">The lifetime of the key in months.</param> /// <param name="hashSizeInBits">The hash size in bits.</param> /// <param name="isCA">if set to <c>true</c> then a CA certificate is created.</param> /// <param name="issuerCAKeyCert">The CA cert with the CA private key.</param> /// <returns>The certificate with a private key.</returns> public static X509Certificate2 CreateCertificate( string storeType, string storePath, string password, string applicationUri, string applicationName, string subjectName, IList <String> domainNames, ushort keySize, DateTime startTime, ushort lifetimeInMonths, ushort hashSizeInBits, bool isCA = false, X509Certificate2 issuerCAKeyCert = null, byte[] publicKey = null) { if (issuerCAKeyCert != null) { if (!issuerCAKeyCert.HasPrivateKey) { throw new NotSupportedException("Cannot sign with a CA certificate without a private key."); } } if (publicKey != null && issuerCAKeyCert == null) { throw new NotSupportedException("Cannot use a public key without a CA certificate with a private key."); } // set default values. X509Name subjectDN = SetSuitableDefaults( ref applicationUri, ref applicationName, ref subjectName, ref domainNames, ref keySize, ref lifetimeInMonths); using (var cfrg = new CertificateFactoryRandomGenerator()) { // cert generators SecureRandom random = new SecureRandom(cfrg); X509V3CertificateGenerator cg = new X509V3CertificateGenerator(); // Serial Number BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random); cg.SetSerialNumber(serialNumber); // subject and issuer DN X509Name issuerDN = null; if (issuerCAKeyCert != null) { issuerDN = new CertificateFactoryX509Name(issuerCAKeyCert.Subject); } else { // self signed issuerDN = subjectDN; } cg.SetIssuerDN(issuerDN); cg.SetSubjectDN(subjectDN); // valid for cg.SetNotBefore(startTime); cg.SetNotAfter(startTime.AddMonths(lifetimeInMonths)); // set Private/Public Key AsymmetricKeyParameter subjectPublicKey; AsymmetricKeyParameter subjectPrivateKey; if (publicKey == null) { var keyGenerationParameters = new KeyGenerationParameters(random, keySize); var keyPairGenerator = new RsaKeyPairGenerator(); keyPairGenerator.Init(keyGenerationParameters); AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair(); subjectPublicKey = subjectKeyPair.Public; subjectPrivateKey = subjectKeyPair.Private; } else { // special case, if a cert is signed by CA, the private key of the cert is not needed subjectPublicKey = PublicKeyFactory.CreateKey(publicKey); subjectPrivateKey = null; } cg.SetPublicKey(subjectPublicKey); // add extensions // Subject key identifier cg.AddExtension(X509Extensions.SubjectKeyIdentifier.Id, false, new SubjectKeyIdentifier(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subjectPublicKey))); // Basic constraints cg.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(isCA)); // Authority Key identifier references the issuer cert or itself when self signed AsymmetricKeyParameter issuerPublicKey; BigInteger issuerSerialNumber; if (issuerCAKeyCert != null) { issuerPublicKey = GetPublicKeyParameter(issuerCAKeyCert); issuerSerialNumber = GetSerialNumber(issuerCAKeyCert); if (startTime.AddMonths(lifetimeInMonths) > issuerCAKeyCert.NotAfter) { cg.SetNotAfter(issuerCAKeyCert.NotAfter); } } else { issuerPublicKey = subjectPublicKey; issuerSerialNumber = serialNumber; } cg.AddExtension(X509Extensions.AuthorityKeyIdentifier.Id, false, new AuthorityKeyIdentifier(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issuerPublicKey), new GeneralNames(new GeneralName(issuerDN)), issuerSerialNumber)); if (!isCA) { // Key usage cg.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.DataEncipherment | KeyUsage.DigitalSignature | KeyUsage.NonRepudiation | KeyUsage.KeyCertSign | KeyUsage.KeyEncipherment)); // Extended Key usage cg.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(new List <DerObjectIdentifier>() { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1"), // server auth new DerObjectIdentifier("1.3.6.1.5.5.7.3.2"), // client auth })); // subject alternate name List <GeneralName> generalNames = new List <GeneralName>(); generalNames.Add(new GeneralName(GeneralName.UniformResourceIdentifier, applicationUri)); generalNames.AddRange(CreateSubjectAlternateNameDomains(domainNames)); cg.AddExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(generalNames.ToArray())); } else { // Key usage CA cg.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.CrlSign | KeyUsage.DigitalSignature | KeyUsage.KeyCertSign)); } // sign certificate AsymmetricKeyParameter signingKey; if (issuerCAKeyCert != null) { // signed by issuer signingKey = GetPrivateKeyParameter(issuerCAKeyCert); } else { // self signed signingKey = subjectPrivateKey; } ISignatureFactory signatureFactory = new Asn1SignatureFactory(GetRSAHashAlgorithm(hashSizeInBits), signingKey, random); Org.BouncyCastle.X509.X509Certificate x509 = cg.Generate(signatureFactory); // convert to X509Certificate2 X509Certificate2 certificate = null; if (subjectPrivateKey == null) { // create the cert without the private key certificate = new X509Certificate2(x509.GetEncoded()); } else { // note: this cert has a private key! certificate = CreateCertificateWithPrivateKey(x509, null, subjectPrivateKey, random); } Utils.Trace(Utils.TraceMasks.Security, "Created new certificate: {0}", certificate.Thumbprint); // add cert to the store. if (!String.IsNullOrEmpty(storePath) && !String.IsNullOrEmpty(storeType)) { using (ICertificateStore store = CertificateStoreIdentifier.CreateStore(storeType)) { if (store == null) { throw new ArgumentException("Invalid store type"); } store.Open(storePath); store.Add(certificate, password).Wait(); store.Close(); } } return(certificate); } }
public ModuleConfiguration(string applicationName) { // set reasonable defaults Configuration = new ApplicationConfiguration() { ApplicationName = applicationName }; Configuration.ApplicationUri = "urn:" + Utils.GetHostName() + ":microsoft:" + Configuration.ApplicationName; Configuration.ApplicationType = ApplicationType.ClientAndServer; Configuration.TransportQuotas = new TransportQuotas { OperationTimeout = 15000 }; Configuration.ClientConfiguration = new ClientConfiguration(); Configuration.ServerConfiguration = new ServerConfiguration(); // initialize stack tracing Configuration.TraceConfiguration = new TraceConfiguration() { TraceMasks = Program.OpcStackTraceMask }; Utils.SetTraceOutput(Utils.TraceOutput.FileOnly); if (string.IsNullOrEmpty(Program.LogFileName)) { if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_LOGP"))) { Configuration.TraceConfiguration.OutputFilePath = Environment.GetEnvironmentVariable("_GW_LOGP"); } else { Configuration.TraceConfiguration.OutputFilePath = "./Logs/" + Configuration.ApplicationName + ".log.txt"; } } else { Configuration.TraceConfiguration.OutputFilePath = Program.LogFileName; } Configuration.TraceConfiguration.ApplySettings(); Trace($"Current directory is: {Directory.GetCurrentDirectory()}"); Trace($"Log file is: {Utils.GetAbsoluteFilePath(Configuration.TraceConfiguration.OutputFilePath, true, false, false, true)}"); Trace($"opcstacktracemask set to: 0x{Program.OpcStackTraceMask:X} ({Program.OpcStackTraceMask})"); Configuration.SecurityConfiguration = new SecurityConfiguration(); // Trusted cert store configuration. Configuration.SecurityConfiguration.TrustedPeerCertificates = new CertificateTrustList(); Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType = Program.OpcTrustedCertStoreType; if (string.IsNullOrEmpty(Program.OpcTrustedCertStorePath)) { // Set default. Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Program.OpcTrustedCertStoreType == CertificateStoreType.X509Store ? Program.OpcTrustedCertX509StorePathDefault : Program.OpcTrustedCertDirectoryStorePathDefault; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_TPC_SP"))) { // Use environment variable. Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Environment.GetEnvironmentVariable("_TPC_SP"); } } else { Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Program.OpcTrustedCertStorePath; } Trace($"Trusted Peer Certificate store type is: {Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType}"); Trace($"Trusted Peer Certificate store path is: {Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); // Trusted issuer cert store configuration. Configuration.SecurityConfiguration.TrustedIssuerCertificates = new CertificateTrustList(); Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = Program.OpcIssuerCertStoreType; Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = Program.OpcIssuerCertStorePath; Trace($"Trusted Issuer store type is: {Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType}"); Trace($"Trusted Issuer Certificate store path is: {Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath}"); // Rejected cert store configuration. Configuration.SecurityConfiguration.RejectedCertificateStore = new CertificateTrustList(); Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType = Program.OpcRejectedCertStoreType; Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath = Program.OpcRejectedCertStorePath; Trace($"Rejected certificate store type is: {Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType}"); Trace($"Rejected Certificate store path is: {Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath}"); Configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier() { StoreType = Program.OpcOwnCertStoreType, StorePath = Program.OpcOwnCertStorePath, SubjectName = Configuration.ApplicationName }; Trace($"Application Certificate store type is: {Configuration.SecurityConfiguration.ApplicationCertificate.StoreType}"); Trace($"Application Certificate store path is: {Configuration.SecurityConfiguration.ApplicationCertificate.StorePath}"); // Use existing certificate, if it is there. X509Certificate2 certificate = Configuration.SecurityConfiguration.ApplicationCertificate.Find(true).Result; if (certificate == null) { Trace($"Create a self-signed Application certificate valid from yesterday for {CertificateFactory.defaultLifeTime} months,"); Trace($"with a {CertificateFactory.defaultKeySize} bit key and {CertificateFactory.defaultHashSize} bit hash."); certificate = CertificateFactory.CreateCertificate( Configuration.SecurityConfiguration.ApplicationCertificate.StoreType, Configuration.SecurityConfiguration.ApplicationCertificate.StorePath, null, Configuration.ApplicationUri, Configuration.ApplicationName, Configuration.ApplicationName, null, CertificateFactory.defaultKeySize, DateTime.UtcNow - TimeSpan.FromDays(1), CertificateFactory.defaultLifeTime, CertificateFactory.defaultHashSize, false, null, null ); Configuration.SecurityConfiguration.ApplicationCertificate.Certificate = certificate ?? throw new Exception("OPC UA application certificate could not be created! Cannot continue without it!"); // Trust myself if requested. if (Program.TrustMyself) { // Ensure it is trusted try { ICertificateStore store = Configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); if (store == null) { Trace($"Could not open trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); } else { try { Trace($"Adding publisher certificate to trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); X509Certificate2 publicKey = new X509Certificate2(certificate.RawData); store.Add(publicKey).Wait(); } finally { store.Close(); } } } catch (Exception e) { Trace(e, $"Could not add publisher certificate to trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); } } else { Trace("Publisher certificate is not added to trusted peer store."); } } else { Trace("Application certificate found in Application Certificate Store"); } Configuration.ApplicationUri = Utils.GetApplicationUriFromCertificate(certificate); Trace($"Application certificate is for Application URI: {Configuration.ApplicationUri}"); // patch our base address if (Configuration.ServerConfiguration.BaseAddresses.Count == 0) { Configuration.ServerConfiguration.BaseAddresses.Add($"opc.tcp://{Configuration.ApplicationName.ToLowerInvariant()}:{Program.PublisherServerPort}{Program.PublisherServerPath}"); } foreach (var endpoint in Configuration.ServerConfiguration.BaseAddresses) { Trace($"Publisher server Endpoint URL: {endpoint}"); } // Set LDS registration interval Configuration.ServerConfiguration.MaxRegistrationInterval = Program.LdsRegistrationInterval; Trace($"LDS(-ME) registration intervall set to {Program.LdsRegistrationInterval} ms (0 means no registration)"); // add sign & encrypt policy ServerSecurityPolicy newPolicy = new ServerSecurityPolicy() { SecurityMode = MessageSecurityMode.SignAndEncrypt, SecurityPolicyUri = SecurityPolicies.Basic256Sha256 }; Configuration.ServerConfiguration.SecurityPolicies.Add(newPolicy); Trace($"Security policy {newPolicy.SecurityPolicyUri} with mode {newPolicy.SecurityMode} added"); // the OperationTimeout should be twice the minimum value for PublishingInterval * KeepAliveCount, so set to 120s Configuration.TransportQuotas.OperationTimeout = Program.OpcOperationTimeout; Trace($"OperationTimeout set to {Configuration.TransportQuotas.OperationTimeout}"); // allow SHA1 certificates for now as many OPC Servers still use them Configuration.SecurityConfiguration.RejectSHA1SignedCertificates = false; Trace($"Rejection of SHA1 signed certificates is {(Configuration.SecurityConfiguration.RejectSHA1SignedCertificates ? "enabled" : "disabled")}"); // allow 1024 minimum key size as many OPC Servers still use them Configuration.SecurityConfiguration.MinimumCertificateKeySize = 1024; Trace($"Minimum certificate key size set to {Configuration.SecurityConfiguration.MinimumCertificateKeySize}"); // validate the configuration now Configuration.Validate(Configuration.ApplicationType).Wait(); }
/// <summary> /// Synchronous helper implementation of CheckApplicationInstanceCertificate for C++ Proxy /// </summary> public static void CheckApplicationInstanceCertificate(ApplicationConfiguration configuration) { // create a default certificate id none specified. CertificateIdentifier id = configuration.SecurityConfiguration.ApplicationCertificate; if (id == null) { id = new CertificateIdentifier(); id.StoreType = Utils.DefaultStoreType; id.StorePath = Utils.DefaultStorePath; id.SubjectName = configuration.ApplicationName; } // check for certificate with a private key. X509Certificate2 certificate = id.Find(true).Result; if (certificate != null) { return; } // construct the subject name from the List <string> hostNames = new List <string>(); hostNames.Add(Utils.GetHostName()); string commonName = Utils.Format("CN={0}", configuration.ApplicationName); string domainName = Utils.Format("DC={0}", hostNames[0]); string subjectName = Utils.Format("{0}, {1}", commonName, domainName); // create a new certificate with a new public key pair. certificate = CertificateFactory.CreateCertificate( id.StoreType, id.StorePath, null, configuration.ApplicationUri, configuration.ApplicationName, subjectName, hostNames, 2048, DateTime.UtcNow - TimeSpan.FromHours(1), 120, 256, false, null, null); // update and save the configuration file. id.Certificate = certificate; configuration.SaveToFile(configuration.SourceFilePath); // add certificate to the trusted peer store so other applications will trust it. ICertificateStore store = configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); try { X509Certificate2Collection certificateCollection = store.FindByThumbprint(certificate.Thumbprint).Result; if (certificateCollection != null) { store.Add(certificateCollection[0]).Wait(); } } finally { store.Close(); } // tell the certificate validator about the new certificate. configuration.CertificateValidator.Update(configuration.SecurityConfiguration).Wait(); }
/// <summary> /// Adds the certificate to the Trusted Certificate Store /// </summary> /// <param name="configuration">The application's configuration which specifies the location of the TrustedStore.</param> /// <param name="certificate">The certificate to register.</param> private static async Task AddToTrustedStore(ApplicationConfiguration configuration, X509Certificate2 certificate) { if (certificate == null) { throw new ArgumentNullException(nameof(certificate)); } string storePath = null; if (configuration != null && configuration.SecurityConfiguration != null && configuration.SecurityConfiguration.TrustedPeerCertificates != null) { storePath = configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath; } if (String.IsNullOrEmpty(storePath)) { Utils.Trace(Utils.TraceMasks.Information, "WARNING: Trusted peer store not specified."); return; } try { ICertificateStore store = configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); if (store == null) { Utils.Trace("Could not open trusted peer store. StorePath={0}", storePath); return; } try { // check if it already exists. X509Certificate2Collection existingCertificates = await store.FindByThumbprint(certificate.Thumbprint); if (existingCertificates.Count > 0) { return; } Utils.Trace(Utils.TraceMasks.Information, "Adding certificate to trusted peer store. StorePath={0}", storePath); List <string> subjectName = X509Utils.ParseDistinguishedName(certificate.Subject); // check for old certificate. X509Certificate2Collection certificates = await store.Enumerate(); for (int ii = 0; ii < certificates.Count; ii++) { if (X509Utils.CompareDistinguishedName(certificates[ii], subjectName)) { if (certificates[ii].Thumbprint == certificate.Thumbprint) { return; } await store.Delete(certificates[ii].Thumbprint); break; } } // add new certificate. X509Certificate2 publicKey = new X509Certificate2(certificate.RawData); await store.Add(publicKey); } finally { store.Close(); } } catch (Exception e) { Utils.Trace(e, "Could not add certificate to trusted peer store. StorePath={0}", storePath); } }
/// <summary> /// Updates an item in the view. /// </summary> protected override void UpdateItem(ListViewItem listItem, object item) { X509Certificate2 certificate = item as X509Certificate2; if (certificate == null) { base.UpdateItem(listItem, item); return; } listItem.SubItems[0].Text = null; listItem.SubItems[1].Text = null; listItem.SubItems[2].Text = null; listItem.SubItems[3].Text = null; listItem.SubItems[4].Text = null; listItem.SubItems[5].Text = null; if (certificate != null) { List <string> fields = Utils.ParseDistinguishedName(certificate.Subject); for (int ii = 0; ii < fields.Count; ii++) { if (fields[ii].StartsWith("CN=")) { listItem.SubItems[0].Text = fields[ii].Substring(3); } if (fields[ii].StartsWith("DC=")) { listItem.SubItems[1].Text = fields[ii].Substring(3); } } if (String.IsNullOrEmpty(listItem.SubItems[0].Text)) { listItem.SubItems[0].Text = String.Format("{0}", certificate.Subject); } // determine certificate type. foreach (X509Extension extension in certificate.Extensions) { X509BasicConstraintsExtension basicContraints = extension as X509BasicConstraintsExtension; if (basicContraints != null) { if (basicContraints.CertificateAuthority) { listItem.SubItems[1].Text = "CA"; } else { listItem.SubItems[1].Text = "End-Entity"; } break; } } // check if a private key is available. if (certificate.HasPrivateKey) { listItem.SubItems[2].Text = "Yes"; } else { listItem.SubItems[2].Text = "No"; if (m_storeId != null) { ICertificateStore store = m_storeId.OpenStore(); try { if (store.GetPrivateKeyFilePath(certificate.Thumbprint) != null) { listItem.SubItems[2].Text = "Yes (No Access)"; } } catch (Exception e) { listItem.SubItems[2].Text = e.Message; } finally { store.Close(); } } } // look up domains. IList <string> domains = Utils.GetDomainsFromCertficate(certificate); StringBuilder buffer = new StringBuilder(); for (int ii = 0; ii < domains.Count; ii++) { if (buffer.Length > 0) { buffer.Append(";"); } buffer.Append(domains[ii]); } listItem.SubItems[3].Text = buffer.ToString(); listItem.SubItems[4].Text = Utils.GetApplicationUriFromCertificate(certificate); listItem.SubItems[5].Text = String.Format("{0:yyyy-MM-dd}", certificate.NotAfter); } listItem.ImageKey = GuiUtils.Icons.Certificate; listItem.Tag = item; }
/// <summary> /// Revoke the CA signed certificate. /// The issuer CA public key, the private key and the crl reside in the storepath. /// The CRL number is increased by one and existing CRL for the issuer are deleted from the store. /// </summary> public static async Task RevokeCertificateAsync( string storePath, X509Certificate2 certificate, string issuerKeyFilePassword = null ) { try { string subjectName = certificate.IssuerName.Name; string keyId = null; string serialNumber = null; // caller may want to create empty CRL using the CA cert itself bool isCACert = IsCertificateAuthority(certificate); // find the authority key identifier. X509AuthorityKeyIdentifierExtension authority = FindAuthorityKeyIdentifier(certificate); if (authority != null) { keyId = authority.KeyId; serialNumber = authority.SerialNumber; } else { throw new ArgumentException("Certificate does not contain an Authority Key"); } if (!isCACert) { if (serialNumber == certificate.SerialNumber || Utils.CompareDistinguishedName(certificate.Subject, certificate.Issuer)) { throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Cannot revoke self signed certificates"); } } X509Certificate2 certCA = null; using (ICertificateStore store = CertificateStoreIdentifier.OpenStore(storePath)) { if (store == null) { throw new ArgumentException("Invalid store path/type"); } certCA = await FindIssuerCABySerialNumberAsync(store, certificate.Issuer, serialNumber); if (certCA == null) { throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Cannot find issuer certificate in store."); } if (!certCA.HasPrivateKey) { throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Issuer certificate has no private key, cannot revoke certificate."); } CertificateIdentifier certCAIdentifier = new CertificateIdentifier(certCA); certCAIdentifier.StorePath = storePath; certCAIdentifier.StoreType = CertificateStoreIdentifier.DetermineStoreType(storePath); X509Certificate2 certCAWithPrivateKey = await certCAIdentifier.LoadPrivateKey(issuerKeyFilePassword); if (certCAWithPrivateKey == null) { throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Failed to load issuer private key. Is the password correct?"); } List <X509CRL> certCACrl = store.EnumerateCRLs(certCA, false); using (var cfrg = new CertificateFactoryRandomGenerator()) { // cert generators SecureRandom random = new SecureRandom(cfrg); BigInteger crlSerialNumber = BigInteger.Zero; Org.BouncyCastle.X509.X509Certificate bcCertCA = new X509CertificateParser().ReadCertificate(certCA.RawData); AsymmetricKeyParameter signingKey = GetPrivateKeyParameter(certCAWithPrivateKey); ISignatureFactory signatureFactory = new Asn1SignatureFactory(GetRSAHashAlgorithm(defaultHashSize), signingKey, random); X509V2CrlGenerator crlGen = new X509V2CrlGenerator(); crlGen.SetIssuerDN(bcCertCA.IssuerDN); crlGen.SetThisUpdate(DateTime.UtcNow); crlGen.SetNextUpdate(DateTime.UtcNow.AddMonths(12)); // merge all existing revocation list X509CrlParser parser = new X509CrlParser(); foreach (X509CRL caCrl in certCACrl) { X509Crl crl = parser.ReadCrl(caCrl.RawData); crlGen.AddCrl(crl); var crlVersion = GetCrlNumber(crl); if (crlVersion.IntValue > crlSerialNumber.IntValue) { crlSerialNumber = crlVersion; } } if (isCACert) { // add a dummy revoked cert crlGen.AddCrlEntry(BigInteger.One, DateTime.UtcNow, CrlReason.Superseded); } else { // add the revoked cert crlGen.AddCrlEntry(GetSerialNumber(certificate), DateTime.UtcNow, CrlReason.PrivilegeWithdrawn); } crlGen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(bcCertCA)); // set new serial number crlSerialNumber = crlSerialNumber.Add(BigInteger.One); crlGen.AddExtension(X509Extensions.CrlNumber, false, new CrlNumber(crlSerialNumber)); // generate updated CRL Org.BouncyCastle.X509.X509Crl updatedCrl = crlGen.Generate(signatureFactory); // add updated CRL to store X509CRL updatedCRL = new X509CRL(updatedCrl.GetEncoded()); store.AddCRL(updatedCRL); // delete outdated CRLs from store foreach (X509CRL caCrl in certCACrl) { store.DeleteCRL(caCrl); } } store.Close(); } } catch (Exception e) { throw e; } }
/// <summary> /// Configures all OPC stack settings /// </summary> public async Task ConfigureAsync() { // Instead of using a Config.xml we configure everything programmatically. // // OPC UA Application configuration // PublisherOpcApplicationConfiguration = new ApplicationConfiguration(); // Passed in as command line argument PublisherOpcApplicationConfiguration.ApplicationName = ApplicationName; PublisherOpcApplicationConfiguration.ApplicationUri = $"urn:{Utils.GetHostName()}:{PublisherOpcApplicationConfiguration.ApplicationName}:microsoft:"; PublisherOpcApplicationConfiguration.ProductUri = "https://github.com/Azure/iot-edge-opc-publisher"; PublisherOpcApplicationConfiguration.ApplicationType = ApplicationType.ClientAndServer; // // Security configuration // PublisherOpcApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration(); // Application certificate PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(); PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StoreType = OpcOwnCertStoreType; PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StorePath = OpcOwnCertStorePath; PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.SubjectName = PublisherOpcApplicationConfiguration.ApplicationName; Logger.Information($"Application Certificate store type is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StoreType}"); Logger.Information($"Application Certificate store path is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StorePath}"); Logger.Information($"Application Certificate subject name is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.SubjectName}"); // Use existing certificate, if it is there. X509Certificate2 certificate = await PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Find(true); if (certificate == null) { Logger.Information($"No existing Application certificate found. Create a self-signed Application certificate valid from yesterday for {CertificateFactory.defaultLifeTime} months,"); Logger.Information($"with a {CertificateFactory.defaultKeySize} bit key and {CertificateFactory.defaultHashSize} bit hash."); certificate = CertificateFactory.CreateCertificate( PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StoreType, PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StorePath, null, PublisherOpcApplicationConfiguration.ApplicationUri, PublisherOpcApplicationConfiguration.ApplicationName, PublisherOpcApplicationConfiguration.ApplicationName, null, CertificateFactory.defaultKeySize, DateTime.UtcNow - TimeSpan.FromDays(1), CertificateFactory.defaultLifeTime, CertificateFactory.defaultHashSize, false, null, null ); PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate = certificate ?? throw new Exception("OPC UA application certificate can not be created! Cannot continue without it!"); } else { Logger.Information("Application certificate found in Application Certificate Store"); } PublisherOpcApplicationConfiguration.ApplicationUri = Utils.GetApplicationUriFromCertificate(certificate); Logger.Information($"Application certificate is for Application URI '{PublisherOpcApplicationConfiguration.ApplicationUri}', Application '{PublisherOpcApplicationConfiguration.ApplicationName} and has Subject '{PublisherOpcApplicationConfiguration.ApplicationName}'"); // TrustedIssuerCertificates PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates = new CertificateTrustList(); PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = OpcIssuerCertStoreType; PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = OpcIssuerCertStorePath; Logger.Information($"Trusted Issuer store type is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.StoreType}"); Logger.Information($"Trusted Issuer Certificate store path is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.StorePath}"); // TrustedPeerCertificates PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates = new CertificateTrustList(); PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StoreType = OpcTrustedCertStoreType; if (string.IsNullOrEmpty(OpcTrustedCertStorePath)) { // Set default. PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath = OpcTrustedCertStoreType == X509Store ? OpcTrustedCertX509StorePathDefault : OpcTrustedCertDirectoryStorePathDefault; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_TPC_SP"))) { // Use environment variable. PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Environment.GetEnvironmentVariable("_TPC_SP"); } } else { PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath = OpcTrustedCertStorePath; } Logger.Information($"Trusted Peer Certificate store type is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StoreType}"); Logger.Information($"Trusted Peer Certificate store path is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); // RejectedCertificateStore PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore = new CertificateTrustList(); PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StoreType = OpcRejectedCertStoreType; PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StorePath = OpcRejectedCertStorePath; Logger.Information($"Rejected certificate store type is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StoreType}"); Logger.Information($"Rejected Certificate store path is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StorePath}"); // AutoAcceptUntrustedCertificates // This is a security risk and should be set to true only for debugging purposes. PublisherOpcApplicationConfiguration.SecurityConfiguration.AutoAcceptUntrustedCertificates = false; // RejectSHA1SignedCertificates // We allow SHA1 certificates for now as many OPC Servers still use them PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectSHA1SignedCertificates = false; Logger.Information($"Rejection of SHA1 signed certificates is {(PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectSHA1SignedCertificates ? "enabled" : "disabled")}"); // MinimunCertificatesKeySize // We allow a minimum key size of 1024 bit, as many OPC UA servers still use them PublisherOpcApplicationConfiguration.SecurityConfiguration.MinimumCertificateKeySize = 1024; Logger.Information($"Minimum certificate key size set to {PublisherOpcApplicationConfiguration.SecurityConfiguration.MinimumCertificateKeySize}"); // We make the default reference stack behavior configurable to put our own certificate into the trusted peer store. if (TrustMyself) { // Ensure it is trusted try { ICertificateStore store = PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); if (store == null) { Logger.Information($"Can not open trusted peer store. StorePath={PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); } else { try { Logger.Information($"Adding publisher certificate to trusted peer store. StorePath={PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); X509Certificate2 publicKey = new X509Certificate2(certificate.RawData); await store.Add(publicKey); } finally { store.Close(); } } } catch (Exception e) { Logger.Information(e, $"Can not add publisher certificate to trusted peer store. StorePath={PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); } } else { Logger.Information("Publisher certificate is not added to trusted peer store."); } // // TransportConfigurations // PublisherOpcApplicationConfiguration.TransportQuotas = new TransportQuotas(); PublisherOpcApplicationConfiguration.TransportQuotas.MaxByteStringLength = 4 * 1024 * 1024; PublisherOpcApplicationConfiguration.TransportQuotas.MaxMessageSize = 4 * 1024 * 1024; // the maximum string length could be set to ajust for large number of nodes when reading the list of published nodes PublisherOpcApplicationConfiguration.TransportQuotas.MaxStringLength = OpcMaxStringLength; // the OperationTimeout should be twice the minimum value for PublishingInterval * KeepAliveCount, so set to 120s PublisherOpcApplicationConfiguration.TransportQuotas.OperationTimeout = OpcOperationTimeout; Logger.Information($"OperationTimeout set to {PublisherOpcApplicationConfiguration.TransportQuotas.OperationTimeout}"); // // ServerConfiguration // PublisherOpcApplicationConfiguration.ServerConfiguration = new ServerConfiguration(); // BaseAddresses if (PublisherOpcApplicationConfiguration.ServerConfiguration.BaseAddresses.Count == 0) { // We do not use the localhost replacement mechanism of the configuration loading, to immediately show the base address here PublisherOpcApplicationConfiguration.ServerConfiguration.BaseAddresses.Add($"opc.tcp://{Utils.GetHostName()}:{PublisherServerPort}{PublisherServerPath}"); } foreach (var endpoint in PublisherOpcApplicationConfiguration.ServerConfiguration.BaseAddresses) { Logger.Information($"Publisher server base address: {endpoint}"); } // SecurityPolicies // We do not allow security policy SecurityPolicies.None, but always high security ServerSecurityPolicy newPolicy = new ServerSecurityPolicy() { SecurityMode = MessageSecurityMode.SignAndEncrypt, SecurityPolicyUri = SecurityPolicies.Basic256Sha256 }; PublisherOpcApplicationConfiguration.ServerConfiguration.SecurityPolicies.Add(newPolicy); Logger.Information($"Security policy {newPolicy.SecurityPolicyUri} with mode {newPolicy.SecurityMode} added"); // MaxRegistrationInterval PublisherOpcApplicationConfiguration.ServerConfiguration.MaxRegistrationInterval = LdsRegistrationInterval; Logger.Information($"LDS(-ME) registration intervall set to {LdsRegistrationInterval} ms (0 means no registration)"); // // TraceConfiguration // // // TraceConfiguration // PublisherOpcApplicationConfiguration.TraceConfiguration = new TraceConfiguration(); PublisherOpcApplicationConfiguration.TraceConfiguration.TraceMasks = OpcStackTraceMask; PublisherOpcApplicationConfiguration.TraceConfiguration.ApplySettings(); Utils.Tracing.TraceEventHandler += new EventHandler <TraceEventArgs>(LoggerOpcUaTraceHandler); Logger.Information($"opcstacktracemask set to: 0x{OpcStackTraceMask:X}"); // add default client configuration PublisherOpcApplicationConfiguration.ClientConfiguration = new ClientConfiguration(); // validate the configuration now await PublisherOpcApplicationConfiguration.Validate(PublisherOpcApplicationConfiguration.ApplicationType); }
/// <summary> /// Creates an application instance certificate if one does not already exist. /// </summary> public static void CheckApplicationInstanceCertificate(ApplicationConfiguration configuration) { // create a default certificate id none specified. CertificateIdentifier id = configuration.SecurityConfiguration.ApplicationCertificate; if (id == null) { id = new CertificateIdentifier(); id.StoreType = CertificateStoreType.Windows; id.StorePath = "LocalMachine\\My"; id.SubjectName = configuration.ApplicationName; } // check for certificate with a private key. X509Certificate2 certificate = id.Find(true); if (certificate != null) { //This UA application already has an instance certificate SaveCertificate(certificate); return; } //This UA application does not have an instance certificate. Create one automatically // construct the subject name from the List <string> hostNames = new List <string>(); hostNames.Add(System.Net.Dns.GetHostName()); string commonName = Utils.Format("CN={0}", configuration.ApplicationName); string domainName = Utils.Format("DC={0}", hostNames[0]); string subjectName = Utils.Format("{0}, {1}", commonName, domainName); // check if a distinguished name was specified. if (id.SubjectName.IndexOf("=", StringComparison.Ordinal) != -1) { List <string> fields = Utils.ParseDistinguishedName(id.SubjectName); bool commonNameFound = false; bool domainNameFound = false; for (int ii = 0; ii < fields.Count; ii++) { string field = fields[ii]; if (field.StartsWith("CN=")) { fields[ii] = commonName; commonNameFound = true; continue; } if (field.StartsWith("DC=")) { fields[ii] = domainName; domainNameFound = true; continue; } } if (!commonNameFound) { fields.Insert(0, commonName); } if (!domainNameFound) { fields.Insert(0, domainName); } StringBuilder buffer = new StringBuilder(); for (int ii = 0; ii < fields.Count; ii++) { if (buffer.Length > 0) { buffer.Append(", "); } buffer.Append(fields[ii]); } subjectName = buffer.ToString(); } // create a new certificate with a new public key pair. certificate = CertificateFactory.CreateCertificate( id.StoreType, id.StorePath, configuration.ApplicationUri, configuration.ApplicationName, subjectName, hostNames, 1024, 120); // update and save the configuration file. id.Certificate = certificate; configuration.SaveToFile(configuration.SourceFilePath); // add certificate to the trusted peer store so other applications will trust it. ICertificateStore store = configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); try { X509Certificate2 certificate2 = store.FindByThumbprint(certificate.Thumbprint); if (certificate2 == null) { store.Add(certificate); } } finally { store.Close(); } // tell the certificate validator about the new certificate. configuration.CertificateValidator.Update(configuration.SecurityConfiguration); SaveCertificate(certificate); }
public ModuleConfiguration(string applicationName) { // set reasonable defaults Configuration = new ApplicationConfiguration(); Configuration.ApplicationName = applicationName; Configuration.ApplicationUri = "urn:" + Utils.GetHostName() + ":microsoft:" + Configuration.ApplicationName; Configuration.ApplicationType = ApplicationType.ClientAndServer; Configuration.TransportQuotas = new TransportQuotas { OperationTimeout = 15000 }; Configuration.ClientConfiguration = new ClientConfiguration(); Configuration.ServerConfiguration = new ServerConfiguration(); // enable logging Configuration.TraceConfiguration = new TraceConfiguration(); Configuration.TraceConfiguration.TraceMasks = Utils.TraceMasks.Error | Utils.TraceMasks.Security | Utils.TraceMasks.StackTrace | Utils.TraceMasks.StartStop; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_LOGP"))) { Configuration.TraceConfiguration.OutputFilePath = Environment.GetEnvironmentVariable("_GW_LOGP"); } else { Configuration.TraceConfiguration.OutputFilePath = "./Logs/" + Configuration.ApplicationName + ".log.txt"; } Configuration.TraceConfiguration.ApplySettings(); if (Configuration.SecurityConfiguration == null) { Configuration.SecurityConfiguration = new SecurityConfiguration(); } if (Configuration.SecurityConfiguration.TrustedPeerCertificates == null) { Configuration.SecurityConfiguration.TrustedPeerCertificates = new CertificateTrustList(); } if (Configuration.SecurityConfiguration.TrustedIssuerCertificates == null) { Configuration.SecurityConfiguration.TrustedIssuerCertificates = new CertificateTrustList(); } if (Configuration.SecurityConfiguration.RejectedCertificateStore == null) { Configuration.SecurityConfiguration.RejectedCertificateStore = new CertificateTrustList(); } if (Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType == null) { Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType = "Directory"; } if (Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath == null) { Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = "CertificateStores/UA Applications"; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_TPC_SP"))) { Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Environment.GetEnvironmentVariable("_TPC_SP"); } } if (Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType == null) { Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = "Directory"; } if (Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath == null) { Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = "CertificateStores/UA Certificate Authorities"; } if (Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType == null) { Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType = "Directory"; } if (Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath == null) { Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath = "CertificateStores/Rejected Certificates"; } Configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(); Configuration.SecurityConfiguration.ApplicationCertificate.StoreType = "X509Store"; Configuration.SecurityConfiguration.ApplicationCertificate.StorePath = "CurrentUser\\UA_MachineDefault"; Configuration.SecurityConfiguration.ApplicationCertificate.SubjectName = Configuration.ApplicationName; X509Certificate2 certificate = Configuration.SecurityConfiguration.ApplicationCertificate.Find(true).Result; if (certificate == null) { certificate = CertificateFactory.CreateCertificate( Configuration.SecurityConfiguration.ApplicationCertificate.StoreType, Configuration.SecurityConfiguration.ApplicationCertificate.StorePath, null, Configuration.ApplicationUri, Configuration.ApplicationName, Configuration.ApplicationName, null, CertificateFactory.defaultKeySize, DateTime.UtcNow - TimeSpan.FromDays(1), CertificateFactory.defaultLifeTime, CertificateFactory.defaultHashSize, false, null, null ); } if (certificate == null) { throw new Exception("OPC UA application certificate could not be created, cannot continue without it!"); } Configuration.SecurityConfiguration.ApplicationCertificate.Certificate = certificate; Configuration.ApplicationUri = Utils.GetApplicationUriFromCertificate(certificate); // Ensure it is trusted try { ICertificateStore store = Configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); if (store == null) { Program.Trace("Could not open trusted peer store. StorePath={0}", Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath); } else { try { Program.Trace(Utils.TraceMasks.Information, "Adding certificate to trusted peer store. StorePath={0}", Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath); X509Certificate2 publicKey = new X509Certificate2(certificate.RawData); store.Add(publicKey).Wait(); } finally { store.Close(); } } } catch (Exception e) { Program.Trace(e, "Could not add certificate to trusted peer store. StorePath={0}", Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath); } // patch our base address if (Configuration.ServerConfiguration.BaseAddresses.Count == 0) { Configuration.ServerConfiguration.BaseAddresses.Add("opc.tcp://" + Configuration.ApplicationName.ToLowerInvariant() + ":62222/UA/Publisher"); } // tighten security policy by removing security policy "none" foreach (ServerSecurityPolicy policy in Configuration.ServerConfiguration.SecurityPolicies) { if (policy.SecurityMode == MessageSecurityMode.None) { Configuration.ServerConfiguration.SecurityPolicies.Remove(policy); break; } } // turn off LDS registration Configuration.ServerConfiguration.MaxRegistrationInterval = 0; // add sign & encrypt policy ServerSecurityPolicy newPolicy = new ServerSecurityPolicy(); newPolicy.SecurityMode = MessageSecurityMode.SignAndEncrypt; newPolicy.SecurityPolicyUri = SecurityPolicies.Basic128Rsa15; Configuration.ServerConfiguration.SecurityPolicies.Add(newPolicy); // the OperationTimeout should be twice the minimum value for PublishingInterval * KeepAliveCount, so set to 120s Configuration.TransportQuotas.OperationTimeout = 120000; // allow SHA1 certificates for now as many OPC Servers still use them Configuration.SecurityConfiguration.RejectSHA1SignedCertificates = false; // allow 1024 minimum key size as many OPC Servers still use them Configuration.SecurityConfiguration.MinimumCertificateKeySize = 1024; // validate the configuration now Configuration.Validate(Configuration.ApplicationType).Wait(); }
public OpcStackConfiguration(string applicationName) { // Instead of using a Config.xml we configure everything programmatically. // // OPC UA Application configuration // Configuration = new ApplicationConfiguration(); // Passed in as command line argument Configuration.ApplicationName = applicationName; Configuration.ApplicationUri = $"urn:{Utils.GetHostName()}:{Configuration.ApplicationName}:microsoft:"; Configuration.ProductUri = "https://github.com/Azure/iot-edge-opc-publisher"; Configuration.ApplicationType = ApplicationType.ClientAndServer; // // Security configuration // Configuration.SecurityConfiguration = new SecurityConfiguration(); // Application certificate Configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(); Configuration.SecurityConfiguration.ApplicationCertificate.StoreType = Program.OpcOwnCertStoreType; Configuration.SecurityConfiguration.ApplicationCertificate.StorePath = Program.OpcOwnCertStorePath; Configuration.SecurityConfiguration.ApplicationCertificate.SubjectName = Configuration.ApplicationName; Trace($"Application Certificate store type is: {Configuration.SecurityConfiguration.ApplicationCertificate.StoreType}"); Trace($"Application Certificate store path is: {Configuration.SecurityConfiguration.ApplicationCertificate.StorePath}"); Trace($"Application Certificate subject name is: {Configuration.SecurityConfiguration.ApplicationCertificate.SubjectName}"); // Use existing certificate, if it is there. X509Certificate2 certificate = Configuration.SecurityConfiguration.ApplicationCertificate.Find(true).Result; if (certificate == null) { Trace($"No existing Application certificate found. Create a self-signed Application certificate valid from yesterday for {CertificateFactory.defaultLifeTime} months,"); Trace($"with a {CertificateFactory.defaultKeySize} bit key and {CertificateFactory.defaultHashSize} bit hash."); certificate = CertificateFactory.CreateCertificate( Configuration.SecurityConfiguration.ApplicationCertificate.StoreType, Configuration.SecurityConfiguration.ApplicationCertificate.StorePath, null, Configuration.ApplicationUri, Configuration.ApplicationName, Configuration.ApplicationName, null, CertificateFactory.defaultKeySize, DateTime.UtcNow - TimeSpan.FromDays(1), CertificateFactory.defaultLifeTime, CertificateFactory.defaultHashSize, false, null, null ); Configuration.SecurityConfiguration.ApplicationCertificate.Certificate = certificate ?? throw new Exception("OPC UA application certificate could not be created! Cannot continue without it!"); } else { Trace("Application certificate found in Application Certificate Store"); } Configuration.ApplicationUri = Utils.GetApplicationUriFromCertificate(certificate); Trace($"Application certificate is for Application URI '{Configuration.ApplicationUri}', Application '{Configuration.ApplicationName} and has Subject '{Configuration.ApplicationName}'"); // TrustedIssuerCertificates Configuration.SecurityConfiguration.TrustedIssuerCertificates = new CertificateTrustList(); Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = Program.OpcIssuerCertStoreType; Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = Program.OpcIssuerCertStorePath; Trace($"Trusted Issuer store type is: {Configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType}"); Trace($"Trusted Issuer Certificate store path is: {Configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath}"); // TrustedPeerCertificates Configuration.SecurityConfiguration.TrustedPeerCertificates = new CertificateTrustList(); Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType = Program.OpcTrustedCertStoreType; if (string.IsNullOrEmpty(Program.OpcTrustedCertStorePath)) { // Set default. Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Program.OpcTrustedCertStoreType == CertificateStoreType.X509Store ? Program.OpcTrustedCertX509StorePathDefault : Program.OpcTrustedCertDirectoryStorePathDefault; if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_TPC_SP"))) { // Use environment variable. Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Environment.GetEnvironmentVariable("_TPC_SP"); } } else { Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Program.OpcTrustedCertStorePath; } Trace($"Trusted Peer Certificate store type is: {Configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType}"); Trace($"Trusted Peer Certificate store path is: {Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); // RejectedCertificateStore Configuration.SecurityConfiguration.RejectedCertificateStore = new CertificateTrustList(); Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType = Program.OpcRejectedCertStoreType; Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath = Program.OpcRejectedCertStorePath; Trace($"Rejected certificate store type is: {Configuration.SecurityConfiguration.RejectedCertificateStore.StoreType}"); Trace($"Rejected Certificate store path is: {Configuration.SecurityConfiguration.RejectedCertificateStore.StorePath}"); // AutoAcceptUntrustedCertificates // This is a security risk and should be set to true only for debugging purposes. Configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates = false; // RejectSHA1SignedCertificates // We allow SHA1 certificates for now as many OPC Servers still use them Configuration.SecurityConfiguration.RejectSHA1SignedCertificates = false; Trace($"Rejection of SHA1 signed certificates is {(Configuration.SecurityConfiguration.RejectSHA1SignedCertificates ? "enabled" : "disabled")}"); // MinimunCertificatesKeySize // We allow a minimum key size of 1024 bit, as many OPC UA servers still use them Configuration.SecurityConfiguration.MinimumCertificateKeySize = 1024; Trace($"Minimum certificate key size set to {Configuration.SecurityConfiguration.MinimumCertificateKeySize}"); // We make the default reference stack behavior configurable to put our own certificate into the trusted peer store. if (Program.TrustMyself) { // Ensure it is trusted try { ICertificateStore store = Configuration.SecurityConfiguration.TrustedPeerCertificates.OpenStore(); if (store == null) { Trace($"Could not open trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); } else { try { Trace($"Adding publisher certificate to trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); X509Certificate2 publicKey = new X509Certificate2(certificate.RawData); store.Add(publicKey).Wait(); } finally { store.Close(); } } } catch (Exception e) { Trace(e, $"Could not add publisher certificate to trusted peer store. StorePath={Configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath}"); } } else { Trace("Publisher certificate is not added to trusted peer store."); } // // TransportConfigurations // Configuration.TransportQuotas = new TransportQuotas(); // the OperationTimeout should be twice the minimum value for PublishingInterval * KeepAliveCount, so set to 120s Configuration.TransportQuotas.OperationTimeout = Program.OpcOperationTimeout; Trace($"OperationTimeout set to {Configuration.TransportQuotas.OperationTimeout}"); // // ServerConfiguration // Configuration.ServerConfiguration = new ServerConfiguration(); // BaseAddresses if (Configuration.ServerConfiguration.BaseAddresses.Count == 0) { // We do not use the localhost replacement mechanism of the configuration loading, to immediately show the base address here Configuration.ServerConfiguration.BaseAddresses.Add($"opc.tcp://{Utils.GetHostName()}:{Program.PublisherServerPort}{Program.PublisherServerPath}"); } foreach (var endpoint in Configuration.ServerConfiguration.BaseAddresses) { Trace($"Publisher server base address: {endpoint}"); } // SecurityPolicies // We do not allow security policy SecurityPolicies.None, but always high security ServerSecurityPolicy newPolicy = new ServerSecurityPolicy() { SecurityMode = MessageSecurityMode.SignAndEncrypt, SecurityPolicyUri = SecurityPolicies.Basic256Sha256 }; Configuration.ServerConfiguration.SecurityPolicies.Add(newPolicy); Trace($"Security policy {newPolicy.SecurityPolicyUri} with mode {newPolicy.SecurityMode} added"); // MaxRegistrationInterval Configuration.ServerConfiguration.MaxRegistrationInterval = Program.LdsRegistrationInterval; Trace($"LDS(-ME) registration intervall set to {Program.LdsRegistrationInterval} ms (0 means no registration)"); // // TraceConfiguration // Configuration.TraceConfiguration = new TraceConfiguration(); // Due to a bug in a stack we need to do console output ourselve. Utils.SetTraceOutput(Utils.TraceOutput.FileOnly); // OutputFilePath if (string.IsNullOrEmpty(Program.LogFileName)) { if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GW_LOGP"))) { Configuration.TraceConfiguration.OutputFilePath = Environment.GetEnvironmentVariable("_GW_LOGP"); } else { Configuration.TraceConfiguration.OutputFilePath = "./Logs/" + Configuration.ApplicationName + ".log.txt"; } } else { Configuration.TraceConfiguration.OutputFilePath = Program.LogFileName; } // DeleteOnLoad Configuration.TraceConfiguration.DeleteOnLoad = false; // TraceMasks Configuration.TraceConfiguration.TraceMasks = Program.OpcStackTraceMask; // Apply the settings Configuration.TraceConfiguration.ApplySettings(); Trace($"Current directory is: {Directory.GetCurrentDirectory()}"); Trace($"Log file is: {Utils.GetAbsoluteFilePath(Configuration.TraceConfiguration.OutputFilePath, true, false, false, true)}"); Trace($"opcstacktracemask set to: 0x{Program.OpcStackTraceMask:X} ({Program.OpcStackTraceMask})"); Configuration.ClientConfiguration = new ClientConfiguration(); // validate the configuration now Configuration.Validate(Configuration.ApplicationType).Wait(); }