Merge pull request #385 from AlinMoldovean/v1.03

Implement certificate chain support for #362
This commit is contained in:
Alin Moldovean
2018-03-08 12:49:47 +02:00
committed by GitHub
18 changed files with 543 additions and 120 deletions
+83 -30
View File
@@ -199,7 +199,7 @@ namespace Opc.Ua.Client
m_instanceCertificate.Thumbprint);
}
// load certificate chain
// load certificate chain.
m_instanceCertificateChain = new X509Certificate2Collection(m_instanceCertificate);
List<CertificateIdentifier> issuers = new List<CertificateIdentifier>();
configuration.CertificateValidator.GetIssuers(m_instanceCertificate, issuers).Wait();
@@ -835,6 +835,7 @@ namespace Opc.Ua.Client
}
X509Certificate2 clientCertificate = null;
X509Certificate2Collection clientCertificateChain = null;
if (endpointDescription.SecurityPolicyUri != SecurityPolicies.None)
{
@@ -849,6 +850,19 @@ namespace Opc.Ua.Client
{
throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate cannot be found.");
}
// load certificate chain.
if (configuration.SecurityConfiguration.SendCertificateChain)
{
clientCertificateChain = new X509Certificate2Collection(clientCertificate);
List<CertificateIdentifier> issuers = new List<CertificateIdentifier>();
await configuration.CertificateValidator.GetIssuers(clientCertificate, issuers);
for (int i = 0; i < issuers.Count; i++)
{
clientCertificateChain.Add(issuers[i].Certificate);
}
}
}
// initialize the channel which will be created with the server.
@@ -857,6 +871,7 @@ namespace Opc.Ua.Client
endpointDescription,
endpointConfiguration,
clientCertificate,
clientCertificateChain,
messageContext);
// create the session object.
@@ -890,6 +905,7 @@ namespace Opc.Ua.Client
template.m_endpoint.Description,
template.m_endpoint.Configuration,
template.m_instanceCertificate,
template.m_configuration.SecurityConfiguration.SendCertificateChain ? template.m_instanceCertificateChain : null,
template.m_configuration.CreateMessageContext());
// create the session object.
@@ -968,10 +984,9 @@ namespace Opc.Ua.Client
}
}
EndpointDescription endpoint = m_endpoint.Description;
// create the client signature.
byte[] dataToSign = Utils.Append(endpoint.ServerCertificate, m_serverNonce);
byte[] dataToSign = Utils.Append(m_serverCertificate != null ? m_serverCertificate.RawData : null, m_serverNonce);
EndpointDescription endpoint = m_endpoint.Description;
SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, endpoint.SecurityPolicyUri, dataToSign);
// check that the user identity is supported by the endpoint.
@@ -1026,6 +1041,7 @@ namespace Opc.Ua.Client
m_endpoint.Description,
m_endpoint.Configuration,
m_instanceCertificate,
m_configuration.SecurityConfiguration.SendCertificateChain ? m_instanceCertificateChain : null,
MessageContext);
// disposes the existing channel.
@@ -1978,7 +1994,7 @@ namespace Opc.Ua.Client
string securityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
// get the identity token.
// get the identity token.
if (identity == null)
{
identity = new UserIdentity();
@@ -2011,13 +2027,20 @@ namespace Opc.Ua.Client
requireEncryption = identityPolicy.SecurityPolicyUri != SecurityPolicies.None;
}
// validate the server certificate.
// validate the server certificate /certificate chain.
X509Certificate2 serverCertificate = null;
byte[] certificateData = m_endpoint.Description.ServerCertificate;
if (certificateData != null && certificateData.Length > 0 && requireEncryption)
{
serverCertificate = Utils.ParseCertificateBlob(certificateData);
m_configuration.CertificateValidator.Validate(serverCertificate);
X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(certificateData);
if (serverCertificateChain.Count > 0)
{
serverCertificate = serverCertificateChain[0];
}
m_configuration.CertificateValidator.Validate(serverCertificateChain);
if (checkDomain)
{
@@ -2025,11 +2048,6 @@ namespace Opc.Ua.Client
}
// save for reconnect
m_checkDomain = checkDomain;
//X509Certificate2Collection certificateChain = Utils.ParseCertificateChainBlob(certificateData);
//if (certificateChain.Count > 0)
// serverCertificate = certificateChain[0];
//m_configuration.CertificateValidator.Validate(certificateChain);
}
// create a nonce.
@@ -2045,6 +2063,19 @@ namespace Opc.Ua.Client
// send the application instance certificate for the client.
byte[] clientCertificateData = m_instanceCertificate != null ? m_instanceCertificate.RawData : null;
byte[] clientCertificateChainData = null;
if (m_instanceCertificateChain != null && m_instanceCertificateChain.Count > 0 && m_configuration.SecurityConfiguration.SendCertificateChain)
{
List<byte> clientCertificateChain = new List<byte>();
for (int i = 0; i < m_instanceCertificateChain.Count; i++)
{
clientCertificateChain.AddRange(m_instanceCertificateChain[i].RawData);
}
clientCertificateChainData = clientCertificateChain.ToArray();
}
ApplicationDescription clientDescription = new ApplicationDescription();
@@ -2103,7 +2134,7 @@ namespace Opc.Ua.Client
m_endpoint.EndpointUrl.ToString(),
sessionName,
clientNonce,
clientCertificateData,
clientCertificateChainData != null ? clientCertificateChainData : clientCertificateData,
sessionTimeout,
(uint)MessageContext.MaxMessageSize,
out sessionId,
@@ -2129,13 +2160,27 @@ namespace Opc.Ua.Client
//we need to call CloseSession if CreateSession was successful but some other exception is thrown
try
{
// verify that the server returned the same instance certificate.
if (serverCertificateData != null && !Utils.IsEqual(serverCertificateData, m_endpoint.Description.ServerCertificate))
{
throw ServiceResultException.Create(
StatusCodes.BadCertificateInvalid,
"Server did not return the certificate used to create the secure channel.");
try
{
// verify for certificate chain in endpoint.
X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(m_endpoint.Description.ServerCertificate);
if (serverCertificateChain.Count > 0 && !Utils.IsEqual(serverCertificateData, serverCertificateChain[0].RawData))
{
throw ServiceResultException.Create(
StatusCodes.BadCertificateInvalid,
"Server did not return the certificate used to create the secure channel.");
}
}
catch (Exception)
{
throw ServiceResultException.Create(
StatusCodes.BadCertificateInvalid,
"Server did not return the certificate used to create the secure channel.");
}
}
if (serverSignature == null || serverSignature.Signature == null)
@@ -2191,7 +2236,6 @@ namespace Opc.Ua.Client
}
}
// find the matching description (TBD - check domains against certificate).
bool found = false;
Uri expectedUrl = Utils.ParseUri(m_endpoint.Description.EndpointUrl);
@@ -2238,9 +2282,24 @@ namespace Opc.Ua.Client
if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature))
{
throw ServiceResultException.Create(
StatusCodes.BadApplicationSignatureInvalid,
"Server did not provide a correct signature for the nonce data provided by the client.");
// validate the signature with complete chain if the check with leaf certificate failed.
if (clientCertificateChainData != null)
{
dataToSign = Utils.Append(clientCertificateChainData, clientNonce);
if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature))
{
throw ServiceResultException.Create(
StatusCodes.BadApplicationSignatureInvalid,
"Server did not provide a correct signature for the nonce data provided by the client.");
}
}
else
{
throw ServiceResultException.Create(
StatusCodes.BadApplicationSignatureInvalid,
"Server did not provide a correct signature for the nonce data provided by the client.");
}
}
// get a validator to check certificates provided by server.
@@ -2270,7 +2329,7 @@ namespace Opc.Ua.Client
ValidateSoftwareCertificates(softwareCertificates);
// create the client signature.
dataToSign = Utils.Append(serverCertificateData, serverNonce);
dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce);
SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign);
// select the security policy for the user token.
@@ -2406,13 +2465,7 @@ namespace Opc.Ua.Client
string securityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
// create the client signature.
byte[] serverCertificateData = null;
if (m_serverCertificate != null)
{
serverCertificateData = m_serverCertificate.RawData;
}
// create the client signature.
byte[] dataToSign = Utils.Append(serverCertificateData, serverNonce);
byte[] dataToSign = Utils.Append(m_serverCertificate != null ? m_serverCertificate.RawData : null, serverNonce);
SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign);
// choose a default token.
@@ -341,7 +341,8 @@ namespace Opc.Ua.Server
{
try
{
parsedClientCertificate = CertificateFactory.Create(clientCertificate, true);
X509Certificate2Collection clientCertificateChain = Utils.ParseCertificateChainBlob(clientCertificate);
parsedClientCertificate = clientCertificateChain[0];
if (context.SecurityPolicyUri != SecurityPolicies.None)
{
@@ -357,7 +358,7 @@ namespace Opc.Ua.Server
"The URI specified in the ApplicationDescription does not match the URI in the Certificate.");
}
CertificateValidator.Validate(parsedClientCertificate);
CertificateValidator.Validate(clientCertificateChain);
}
}
catch (Exception e)
@@ -395,29 +396,44 @@ namespace Opc.Ua.Server
out sessionId,
out authenticationToken,
out serverNonce,
out revisedSessionTimeout);
out revisedSessionTimeout);
lock (m_lock)
{
{
// return the application instance certificate for the server.
if (requireEncryption)
{
serverCertificate = InstanceCertificate.RawData;
// check if complete chain should be sent.
if (Configuration.SecurityConfiguration.SendCertificateChain && InstanceCertificateChain != null && InstanceCertificateChain.Count >0)
{
List<byte> serverCertificateChain = new List<byte>();
for (int i = 0; i < InstanceCertificateChain.Count; i++)
{
serverCertificateChain.AddRange(InstanceCertificateChain[i].RawData);
}
serverCertificate = serverCertificateChain.ToArray();
}
else
{
serverCertificate = InstanceCertificate.RawData;
}
}
// return the endpoints supported by the server.
serverEndpoints = GetEndpointDescriptions(endpointUrl, BaseAddresses, null);
// return the software certificates assigned to the server.
serverSoftwareCertificates = new SignedSoftwareCertificateCollection(ServerProperties.SoftwareCertificates);
// sign the nonce provided by the client.
serverSignature = null;
// sign the client nonce (if provided).
if (parsedClientCertificate != null && clientNonce != null)
{
byte[] dataToSign = Utils.Append(clientCertificate, clientNonce);
byte[] dataToSign = Utils.Append(parsedClientCertificate.RawData, clientNonce);
serverSignature = SecurityPolicies.Sign(InstanceCertificate, context.SecurityPolicyUri, dataToSign);
}
}
@@ -2682,7 +2698,7 @@ namespace Opc.Ua.Server
serverDescription.ApplicationType = configuration.ApplicationType;
serverDescription.ProductUri = configuration.ProductUri;
serverDescription.DiscoveryUrls = GetDiscoveryUrls();
endpoints = new EndpointDescriptionCollection();
IList<EndpointDescription> endpointsForHost = null;
@@ -69,9 +69,9 @@ namespace Opc.Ua.Server
byte[] clientNonce,
byte[] serverNonce,
string sessionName,
ApplicationDescription clientDescription,
string endpointUrl,
X509Certificate2 clientCertificate,
ApplicationDescription clientDescription,
string endpointUrl,
X509Certificate2 clientCertificate,
double sessionTimeout,
uint maxResponseMessageSize,
double maxRequestAge,
@@ -547,10 +547,34 @@ namespace Opc.Ua.Server
if (!SecurityPolicies.Verify(m_clientCertificate, m_endpoint.SecurityPolicyUri, dataToSign, clientSignature))
{
throw new ServiceResultException(StatusCodes.BadApplicationSignatureInvalid);
// verify for certificate chain in endpoint.
// validate the signature with complete chain if the check with leaf certificate failed.
X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(m_endpoint.ServerCertificate);
if (serverCertificateChain.Count > 1)
{
List<byte> serverCertificateChainList = new List<byte>();
for (int i = 0; i < serverCertificateChain.Count; i++)
{
serverCertificateChainList.AddRange(serverCertificateChain[i].RawData);
}
byte[] serverCertificateChainData = serverCertificateChainList.ToArray();
dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce);
if (!SecurityPolicies.Verify(m_clientCertificate, m_endpoint.SecurityPolicyUri, dataToSign, clientSignature))
{
throw new ServiceResultException(StatusCodes.BadApplicationSignatureInvalid);
}
}
else
{
throw new ServiceResultException(StatusCodes.BadApplicationSignatureInvalid);
}
}
}
if (!m_activated)
{
// must active the session on the channel that was used to create it.
@@ -565,7 +589,7 @@ namespace Opc.Ua.Server
if (clientSoftwareCertificates != null && clientSoftwareCertificates.Count > 0)
{
throw new ServiceResultException(StatusCodes.BadInvalidArgument);
}
}
}
// validate the user identity token.
@@ -1024,11 +1048,35 @@ namespace Opc.Ua.Server
if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri))
{
throw new ServiceResultException(StatusCodes.BadUserSignatureInvalid, "Invalid user signature!");
// verify for certificate chain in endpoint.
// validate the signature with complete chain if the check with leaf certificate failed.
X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(m_endpoint.ServerCertificate);
if (serverCertificateChain.Count > 1)
{
List<byte> serverCertificateChainList = new List<byte>();
for (int i = 0; i < serverCertificateChain.Count; i++)
{
serverCertificateChainList.AddRange(serverCertificateChain[i].RawData);
}
byte[] serverCertificateChainData = serverCertificateChainList.ToArray();
dataToSign = Utils.Append(serverCertificateChainData, m_serverNonce);
if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri))
{
throw new ServiceResultException(StatusCodes.BadUserSignatureInvalid, "Invalid user signature!");
}
}
else
{
throw new ServiceResultException(StatusCodes.BadUserSignatureInvalid, "Invalid user signature!");
}
}
}
}
// validate user identity token.
return token;
}
@@ -1043,7 +1091,7 @@ namespace Opc.Ua.Server
IUserIdentity effectiveIdentity)
{
if (identityToken == null) throw new ArgumentNullException("identityToken");
lock (m_lock)
{
bool changed = m_effectiveIdentity == null && effectiveIdentity != null;
@@ -1057,7 +1105,7 @@ namespace Opc.Ua.Server
m_identityToken = identityToken;
m_identity = identity;
m_effectiveIdentity = effectiveIdentity;
// update diagnostics.
lock (DiagnosticsLock)
{
@@ -1097,39 +1145,39 @@ namespace Opc.Ua.Server
}
ServiceCounterDataType counter = null;
switch (requestType)
{
case RequestType.Read: { counter = m_diagnostics.ReadCount; break; }
case RequestType.HistoryRead: { counter = m_diagnostics.HistoryReadCount; break; }
case RequestType.Write: { counter = m_diagnostics.WriteCount; break; }
case RequestType.HistoryUpdate: { counter = m_diagnostics.HistoryUpdateCount; break; }
case RequestType.Call: { counter = m_diagnostics.CallCount; break; }
case RequestType.CreateMonitoredItems: { counter = m_diagnostics.CreateMonitoredItemsCount; break; }
case RequestType.ModifyMonitoredItems: { counter = m_diagnostics.ModifyMonitoredItemsCount; break; }
case RequestType.SetMonitoringMode: { counter = m_diagnostics.SetMonitoringModeCount; break; }
case RequestType.SetTriggering: { counter = m_diagnostics.SetTriggeringCount; break; }
case RequestType.DeleteMonitoredItems: { counter = m_diagnostics.DeleteMonitoredItemsCount; break; }
case RequestType.CreateSubscription: { counter = m_diagnostics.CreateSubscriptionCount; break; }
case RequestType.ModifySubscription: { counter = m_diagnostics.ModifySubscriptionCount; break; }
case RequestType.SetPublishingMode: { counter = m_diagnostics.SetPublishingModeCount; break; }
case RequestType.Publish: { counter = m_diagnostics.PublishCount; break; }
case RequestType.Republish: { counter = m_diagnostics.RepublishCount; break; }
case RequestType.TransferSubscriptions: { counter = m_diagnostics.TransferSubscriptionsCount; break; }
case RequestType.DeleteSubscriptions: { counter = m_diagnostics.DeleteSubscriptionsCount; break; }
case RequestType.AddNodes: { counter = m_diagnostics.AddNodesCount; break; }
case RequestType.AddReferences: { counter = m_diagnostics.AddReferencesCount; break; }
case RequestType.DeleteNodes: { counter = m_diagnostics.DeleteNodesCount; break; }
case RequestType.DeleteReferences: { counter = m_diagnostics.DeleteReferencesCount; break; }
case RequestType.Browse: { counter = m_diagnostics.BrowseCount; break; }
case RequestType.BrowseNext: { counter = m_diagnostics.BrowseNextCount; break; }
case RequestType.TranslateBrowsePathsToNodeIds: { counter = m_diagnostics.TranslateBrowsePathsToNodeIdsCount; break; }
case RequestType.QueryFirst: { counter = m_diagnostics.QueryFirstCount; break; }
case RequestType.QueryNext: { counter = m_diagnostics.QueryNextCount; break; }
case RequestType.RegisterNodes: { counter = m_diagnostics.RegisterNodesCount; break; }
case RequestType.UnregisterNodes: { counter = m_diagnostics.UnregisterNodesCount; break; }
{
case RequestType.Read: { counter = m_diagnostics.ReadCount; break; }
case RequestType.HistoryRead: { counter = m_diagnostics.HistoryReadCount; break; }
case RequestType.Write: { counter = m_diagnostics.WriteCount; break; }
case RequestType.HistoryUpdate: { counter = m_diagnostics.HistoryUpdateCount; break; }
case RequestType.Call: { counter = m_diagnostics.CallCount; break; }
case RequestType.CreateMonitoredItems: { counter = m_diagnostics.CreateMonitoredItemsCount; break; }
case RequestType.ModifyMonitoredItems: { counter = m_diagnostics.ModifyMonitoredItemsCount; break; }
case RequestType.SetMonitoringMode: { counter = m_diagnostics.SetMonitoringModeCount; break; }
case RequestType.SetTriggering: { counter = m_diagnostics.SetTriggeringCount; break; }
case RequestType.DeleteMonitoredItems: { counter = m_diagnostics.DeleteMonitoredItemsCount; break; }
case RequestType.CreateSubscription: { counter = m_diagnostics.CreateSubscriptionCount; break; }
case RequestType.ModifySubscription: { counter = m_diagnostics.ModifySubscriptionCount; break; }
case RequestType.SetPublishingMode: { counter = m_diagnostics.SetPublishingModeCount; break; }
case RequestType.Publish: { counter = m_diagnostics.PublishCount; break; }
case RequestType.Republish: { counter = m_diagnostics.RepublishCount; break; }
case RequestType.TransferSubscriptions: { counter = m_diagnostics.TransferSubscriptionsCount; break; }
case RequestType.DeleteSubscriptions: { counter = m_diagnostics.DeleteSubscriptionsCount; break; }
case RequestType.AddNodes: { counter = m_diagnostics.AddNodesCount; break; }
case RequestType.AddReferences: { counter = m_diagnostics.AddReferencesCount; break; }
case RequestType.DeleteNodes: { counter = m_diagnostics.DeleteNodesCount; break; }
case RequestType.DeleteReferences: { counter = m_diagnostics.DeleteReferencesCount; break; }
case RequestType.Browse: { counter = m_diagnostics.BrowseCount; break; }
case RequestType.BrowseNext: { counter = m_diagnostics.BrowseNextCount; break; }
case RequestType.TranslateBrowsePathsToNodeIds: { counter = m_diagnostics.TranslateBrowsePathsToNodeIdsCount; break; }
case RequestType.QueryFirst: { counter = m_diagnostics.QueryFirstCount; break; }
case RequestType.QueryNext: { counter = m_diagnostics.QueryNextCount; break; }
case RequestType.RegisterNodes: { counter = m_diagnostics.RegisterNodesCount; break; }
case RequestType.UnregisterNodes: { counter = m_diagnostics.UnregisterNodesCount; break; }
}
if (counter != null)
{
counter.TotalCount = counter.TotalCount + 1;
@@ -83,6 +83,9 @@
<StoreType>Directory</StoreType>
<StorePath>%CommonApplicationData%\OPC Foundation\CertificateStores\RejectedCertificates</StorePath>
</RejectedCertificateStore>
<!-- Specifies whether the complete certificate chain should be sent for CA signed certificates. -->
<SendCertificateChain>false</SendCertificateChain>
</SecurityConfiguration>
@@ -227,6 +227,7 @@ namespace Opc.Ua.Sample.Controls
endpoint.Description,
endpoint.Configuration,
clientCertificate,
m_configuration.SecurityConfiguration.SendCertificateChain ? clientCertificateChain : null,
m_messageContext);
// create the session.
@@ -796,6 +796,7 @@ namespace Opc.Ua
m_rejectSHA1SignedCertificates = true;
m_minCertificateKeySize = CertificateFactory.defaultKeySize;
m_addAppCertToTrustedStore = true;
m_sendCertificateChain = false;
}
/// <summary>
@@ -943,6 +944,7 @@ namespace Opc.Ua
get { return m_minCertificateKeySize; }
set { m_minCertificateKeySize = value; }
}
/// <summary>
/// Gets or sets a value indicating whether the application cert should be copied to the trusted store.
/// </summary>
@@ -955,6 +957,19 @@ namespace Opc.Ua
get { return m_addAppCertToTrustedStore; }
set { m_addAppCertToTrustedStore = value; }
}
/// <summary>
/// Gets or sets a value indicating whether the application should send the complete certificate chain.
/// </summary>
/// <remarks>
/// If set to true the complete certificate chain will be sent for CA signed certificates.
/// </remarks>
[DataMember(IsRequired = false, EmitDefaultValue = false, Order = 9)]
public bool SendCertificateChain
{
get { return m_sendCertificateChain; }
set { m_sendCertificateChain = value; }
}
#endregion
#region Private Fields
@@ -968,6 +983,7 @@ namespace Opc.Ua
private bool m_rejectSHA1SignedCertificates;
private ushort m_minCertificateKeySize;
private bool m_addAppCertToTrustedStore;
private bool m_sendCertificateChain;
#endregion
}
#endregion
@@ -48,6 +48,10 @@
<xs:element name="RejectedCertificateStore" type="CertificateStoreIdentifier" minOccurs="0" />
<xs:element name="AutoAcceptUntrustedCertificates" type="xs:boolean" minOccurs="0" />
<xs:element name="UserRoleDirectory" type="xs:string" minOccurs="0" nillable="true" />
<xs:element name="RejectSHA1SignedCertificates" type="xs:boolean" minOccurs="0" />
<xs:element name="MinimumCertificateKeySize" type="xs:unsignedShort" minOccurs="0" />
<xs:element name="AddAppCertToTrustedStore" type="xs:boolean" minOccurs="0" />
<xs:element name="SendCertificateChain" type="xs:boolean" minOccurs="0" />
</xs:sequence>
</xs:complexType>
@@ -10,19 +10,13 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using Opc.Ua.Bindings;
namespace Opc.Ua
{
/// <summary>
/// A channel object used by clients to access a UA service.
/// </summary>
/// A channel object used by clients to access a UA service.
/// </summary>
public partial class SessionChannel
{
#region Constructors
@@ -41,6 +35,27 @@ namespace Opc.Ua
EndpointConfiguration endpointConfiguration,
X509Certificate2 clientCertificate,
ServiceMessageContext messageContext)
{
return Create(configuration, description, endpointConfiguration, clientCertificate, null, messageContext);
}
/// <summary>
/// Creates a new transport channel that supports the ISessionChannel service contract.
/// </summary>
/// <param name="configuration">The application configuration.</param>
/// <param name="description">The description for the endpoint.</param>
/// <param name="endpointConfiguration">The configuration to use with the endpoint.</param>
/// <param name="clientCertificate">The client certificate.</param>
/// <param name="clientCertificateChain">The client certificate chain.</param>
/// <param name="messageContext">The message context to use when serializing the messages.</param>
/// <returns></returns>
public static ITransportChannel Create(
ApplicationConfiguration configuration,
EndpointDescription description,
EndpointConfiguration endpointConfiguration,
X509Certificate2 clientCertificate,
X509Certificate2Collection clientCertificateChain,
ServiceMessageContext messageContext)
{
// create a UA binary channel.
ITransportChannel channel = CreateUaBinaryChannel(
@@ -48,8 +63,9 @@ namespace Opc.Ua
description,
endpointConfiguration,
clientCertificate,
clientCertificateChain,
messageContext);
return channel;
}
@@ -669,6 +669,27 @@ namespace Opc.Ua
EndpointConfiguration endpointConfiguration,
X509Certificate2 clientCertificate,
ServiceMessageContext messageContext)
{
return CreateUaBinaryChannel(configuration, description, endpointConfiguration, clientCertificate, null, messageContext);
}
/// <summary>
/// Creates a new UA-binary transport channel if requested. Null otherwise.
/// </summary>
/// <param name="configuration">The application configuration.</param>
/// <param name="description">The description for the endpoint.</param>
/// <param name="endpointConfiguration">The configuration to use with the endpoint.</param>
/// <param name="clientCertificate">The client certificate.</param>
/// <param name="clientCertificateChain">The client certificate chain.</param>
/// <param name="messageContext">The message context to use when serializing the messages.</param>
/// <returns></returns>
public static ITransportChannel CreateUaBinaryChannel(
ApplicationConfiguration configuration,
EndpointDescription description,
EndpointConfiguration endpointConfiguration,
X509Certificate2 clientCertificate,
X509Certificate2Collection clientCertificateChain,
ServiceMessageContext messageContext)
{
bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp);
bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps);
@@ -693,7 +714,7 @@ namespace Opc.Ua
if (!useUaTcp && !useHttps)
{
throw ServiceResultException.Create(
StatusCodes.BadServiceUnsupported,
StatusCodes.BadProtocolVersionUnsupported,
"Unsupported transport profile\r\n");
}
@@ -706,6 +727,7 @@ namespace Opc.Ua
settings.Description = description;
settings.Configuration = endpointConfiguration;
settings.ClientCertificate = clientCertificate;
settings.ClientCertificateChain = clientCertificateChain;
if (description.ServerCertificate != null && description.ServerCertificate.Length > 0)
{
+59 -1
View File
@@ -528,6 +528,22 @@ namespace Opc.Ua
}
}
/// <summary>
/// Gets the instance certificate chain.
/// </summary>
protected X509Certificate2Collection InstanceCertificateChain
{
get
{
return m_instanceCertificateChain;
}
private set
{
m_instanceCertificateChain = value;
}
}
/// <summary>
/// The non-configurable properties for the server.
/// </summary>
@@ -737,6 +753,19 @@ namespace Opc.Ua
if (requireEncryption)
{
description.ServerCertificate = InstanceCertificate.RawData;
// check if complete chain should be sent.
if (configuration.SecurityConfiguration.SendCertificateChain && InstanceCertificateChain != null && InstanceCertificateChain.Count >0)
{
List<byte> serverCertificateChain = new List<byte>();
for (int i = 0; i < InstanceCertificateChain.Count; i++)
{
serverCertificateChain.AddRange(InstanceCertificateChain[i].RawData);
}
description.ServerCertificate = serverCertificateChain.ToArray();
}
}
endpoints.Add( description );
@@ -749,10 +778,15 @@ namespace Opc.Ua
settings.Descriptions = endpoints;
settings.Configuration = endpointConfiguration;
settings.ServerCertificate = this.InstanceCertificate;
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
settings.NamespaceUris = this.MessageContext.NamespaceUris;
settings.Factory = this.MessageContext.Factory;
settings.ServerCertificate = this.InstanceCertificate;
if (configuration.SecurityConfiguration.SendCertificateChain)
{
settings.ServerCertificateChain = this.InstanceCertificateChain;
}
ITransportListener listener = new Opc.Ua.Bindings.UaTcpChannelListener();
@@ -855,6 +889,19 @@ namespace Opc.Ua
if (InstanceCertificate != null)
{
description.ServerCertificate = InstanceCertificate.RawData;
// check if complete chain should be sent.
if (configuration.SecurityConfiguration.SendCertificateChain && InstanceCertificateChain != null && InstanceCertificateChain.Count > 0)
{
List<byte> serverCertificateChain = new List<byte>();
for (int i = 0; i < InstanceCertificateChain.Count; i++)
{
serverCertificateChain.AddRange(InstanceCertificateChain[i].RawData);
}
description.ServerCertificate = serverCertificateChain.ToArray();
}
}
description.SecurityMode = bestPolicy.SecurityMode;
@@ -1324,6 +1371,16 @@ namespace Opc.Ua
"Server does not have access to the private key for the instance certificate.");
}
// load certificate chain.
InstanceCertificateChain = new X509Certificate2Collection(InstanceCertificate);
List<CertificateIdentifier> issuers = new List<CertificateIdentifier>();
configuration.CertificateValidator.GetIssuers(InstanceCertificateChain, issuers).Wait();
for (int i = 0; i < issuers.Count; i++)
{
InstanceCertificateChain.Add(issuers[i].Certificate);
}
// use the message context from the configuration to ensure the channels are using the same one.
MessageContext = configuration.CreateMessageContext();
@@ -1487,6 +1544,7 @@ namespace Opc.Ua
private object m_serverError;
private object m_certificateValidator;
private object m_instanceCertificate;
private X509Certificate2Collection m_instanceCertificateChain;
private object m_serverProperties;
private object m_configuration;
private object m_serverDescription;
@@ -344,6 +344,7 @@ namespace Opc.Ua.Bindings
m_bufferManager,
m_quotas,
m_serverCertificate,
m_serverCertificateChain,
m_descriptions);
if (m_callback != null)
@@ -26,7 +26,6 @@ namespace Opc.Ua.Bindings
/// </summary>
public class TcpServerChannel : UaSCUaBinaryChannel
{
#region Constructors
/// <summary>
/// Attaches the object to an existing socket.
/// </summary>
@@ -38,7 +37,24 @@ namespace Opc.Ua.Bindings
X509Certificate2 serverCertificate,
EndpointDescriptionCollection endpoints)
:
base(contextId, bufferManager, quotas, serverCertificate, endpoints, MessageSecurityMode.None, SecurityPolicies.None)
this(contextId, listener, bufferManager, quotas, serverCertificate, null, endpoints)
{
}
#region Constructors
/// <summary>
/// Attaches the object to an existing socket.
/// </summary>
public TcpServerChannel(
string contextId,
UaTcpChannelListener listener,
BufferManager bufferManager,
ChannelQuotas quotas,
X509Certificate2 serverCertificate,
X509Certificate2Collection serverCertificateChain,
EndpointDescriptionCollection endpoints)
:
base(contextId, bufferManager, quotas, serverCertificate, serverCertificateChain, endpoints, MessageSecurityMode.None, SecurityPolicies.None)
{
m_listener = listener;
m_queuedResponses = new SortedDictionary<uint, IServiceResponse>();
@@ -135,7 +151,7 @@ namespace Opc.Ua.Bindings
// need to assign a new token id.
token.TokenId = GetNewTokenId();
// put channel back in open state.
// put channel back in open state.
ActivateToken(token);
State = TcpChannelState.Open;
@@ -735,7 +751,7 @@ namespace Opc.Ua.Bindings
/// </summary>
private bool ProcessOpenSecureChannelRequest(uint messageType, ArraySegment<byte> messageChunk)
{
// validate the channel state.
// validate the channel state.
if (State != TcpChannelState.Opening && State != TcpChannelState.Open)
{
ForceChannelFault(StatusCodes.BadTcpMessageTypeInvalid, "Client sent an unexpected OpenSecureChannel message.");
@@ -984,6 +1000,7 @@ namespace Opc.Ua.Bindings
TcpMessageType.Open,
requestId,
ServerCertificate,
ServerCertificateChain,
ClientCertificate,
new ArraySegment<byte>(buffer, 0, buffer.Length));
@@ -15,6 +15,7 @@ using System.Text;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Collections.Generic;
namespace Opc.Ua.Bindings
{
@@ -56,6 +57,15 @@ namespace Opc.Ua.Bindings
get { return m_serverCertificate; }
}
/// <summary>
/// The server certificate chain.
/// </summary>
protected X509Certificate2Collection ServerCertificateChain
{
get { return m_serverCertificateChain; }
set { m_serverCertificateChain = value; }
}
/// <summary>
/// The security mode used with the channel.
/// </summary>
@@ -92,6 +102,14 @@ namespace Opc.Ua.Bindings
set { m_clientCertificate = value; }
}
/// <summary>
/// The client certificate chain.
/// </summary>
internal X509Certificate2Collection ClientCertificateChain
{
get { return m_clientCertificateChain; }
set { m_clientCertificateChain = value; }
}
/// <summary>
/// Creates a new nonce.
@@ -399,8 +417,35 @@ namespace Opc.Ua.Bindings
string securityPolicyUri,
X509Certificate2 senderCertificate,
X509Certificate2 receiverCertificate)
{
int senderCertificateSize = 0;
WriteAsymmetricMessageHeader(
encoder,
messageType,
secureChannelId,
securityPolicyUri,
senderCertificate,
null,
receiverCertificate,
out senderCertificateSize);
}
/// <summary>
/// Writes the asymmetric security header to the buffer.
/// </summary>
protected void WriteAsymmetricMessageHeader(
BinaryEncoder encoder,
uint messageType,
uint secureChannelId,
string securityPolicyUri,
X509Certificate2 senderCertificate,
X509Certificate2Collection senderCertificateChain,
X509Certificate2 receiverCertificate,
out int senderCertificateSize)
{
int start = encoder.Position;
senderCertificateSize = 0;
encoder.WriteUInt32(null, messageType);
encoder.WriteUInt32(null, 0);
@@ -409,7 +454,36 @@ namespace Opc.Ua.Bindings
if (SecurityMode != MessageSecurityMode.None)
{
encoder.WriteByteString(null, senderCertificate.RawData);
if (senderCertificateChain != null && senderCertificateChain.Count >0)
{
X509Certificate2 currentCertificate = senderCertificateChain[0];
int maxSenderCertificateSize = GetMaxSenderCertificateSize(currentCertificate, securityPolicyUri);
List<byte> senderCertificateList = new List<byte>(currentCertificate.RawData);
senderCertificateSize = currentCertificate.RawData.Length;
for (int i = 1; i < senderCertificateChain.Count; i++)
{
currentCertificate = senderCertificateChain[i];
senderCertificateSize += currentCertificate.RawData.Length;
if (senderCertificateSize < maxSenderCertificateSize)
{
senderCertificateList.AddRange(currentCertificate.RawData);
}
else
{
senderCertificateSize -= currentCertificate.RawData.Length;
break;
}
}
encoder.WriteByteString(null, senderCertificateList.ToArray());
}
else
{
encoder.WriteByteString(null, senderCertificate.RawData);
}
encoder.WriteByteString(null, GetThumbprintBytes(receiverCertificate.Thumbprint));
}
else
@@ -452,7 +526,7 @@ namespace Opc.Ua.Bindings
}
/// <summary>
/// Sends a OpenSecureChannel response.
/// Sends a OpenSecureChannel request.
/// </summary>
protected BufferCollection WriteAsymmetricMessage(
uint messageType,
@@ -460,6 +534,20 @@ namespace Opc.Ua.Bindings
X509Certificate2 senderCertificate,
X509Certificate2 receiverCertificate,
ArraySegment<byte> messageBody)
{
return WriteAsymmetricMessage(messageType, requestId, senderCertificate, null, receiverCertificate, messageBody);
}
/// <summary>
/// Sends a OpenSecureChannel request.
/// </summary>
protected BufferCollection WriteAsymmetricMessage(
uint messageType,
uint requestId,
X509Certificate2 senderCertificate,
X509Certificate2Collection senderCertificateChain,
X509Certificate2 receiverCertificate,
ArraySegment<byte> messageBody)
{
bool success = false;
BufferCollection chunksToSend = new BufferCollection();
@@ -468,18 +556,39 @@ namespace Opc.Ua.Bindings
try
{
int headerSize = GetAsymmetricHeaderSize(SecurityPolicyUri, senderCertificate);
int signatureSize = GetAsymmetricSignatureSize(senderCertificate);
BinaryEncoder encoder = new BinaryEncoder(buffer, 0, SendBufferSize, Quotas.MessageContext);
int headerSize = 0;
WriteAsymmetricMessageHeader(
encoder,
messageType | TcpMessageType.Intermediate,
ChannelId,
SecurityPolicyUri,
senderCertificate,
receiverCertificate);
if (senderCertificateChain != null && senderCertificateChain.Count >0)
{
int senderCertificateSize = 0;
WriteAsymmetricMessageHeader(
encoder,
messageType | TcpMessageType.Intermediate,
ChannelId,
SecurityPolicyUri,
senderCertificate,
senderCertificateChain,
receiverCertificate,
out senderCertificateSize);
headerSize = GetAsymmetricHeaderSize(SecurityPolicyUri, senderCertificate, senderCertificateSize);
}
else
{
WriteAsymmetricMessageHeader(
encoder,
messageType | TcpMessageType.Intermediate,
ChannelId,
SecurityPolicyUri,
senderCertificate,
receiverCertificate);
headerSize = GetAsymmetricHeaderSize(SecurityPolicyUri, senderCertificate);
}
int signatureSize = GetAsymmetricSignatureSize(senderCertificate);
// save the header.
ArraySegment<byte> header = new ArraySegment<byte>(buffer, 0, headerSize);
@@ -637,11 +746,11 @@ namespace Opc.Ua.Bindings
protected void ReadAsymmetricMessageHeader(
BinaryDecoder decoder,
X509Certificate2 receiverCertificate,
out uint secureChannelId,
out X509Certificate2 senderCertificate,
out uint secureChannelId,
out X509Certificate2Collection senderCertificateChain,
out string securityPolicyUri)
{
senderCertificate = null;
senderCertificateChain = null;
uint messageType = decoder.ReadUInt32(null);
uint messageSize = decoder.ReadUInt32(null);
@@ -665,14 +774,14 @@ namespace Opc.Ua.Bindings
"The asymmetric security header could not be parsed.");
}
// verify sender certificate.
// verify sender certificate chain.
if (certificateData != null && certificateData.Length > 0)
{
senderCertificate = CertificateFactory.Create(certificateData, true);
senderCertificateChain = Utils.ParseCertificateChainBlob(certificateData);
try
{
string thumbprint = senderCertificate.Thumbprint;
string thumbprint = senderCertificateChain[0].Thumbprint;
if (thumbprint == null)
{
@@ -790,19 +899,38 @@ namespace Opc.Ua.Bindings
BinaryDecoder decoder = new BinaryDecoder(buffer.Array, buffer.Offset, buffer.Count, Quotas.MessageContext);
string securityPolicyUri = null;
X509Certificate2Collection senderCertificateChain;
// parse the security header.
ReadAsymmetricMessageHeader(
decoder,
receiverCertificate,
out channelId,
out senderCertificate,
out senderCertificateChain,
out securityPolicyUri);
if (senderCertificateChain != null && senderCertificateChain.Count > 0)
{
senderCertificate = senderCertificateChain[0];
}
else
{
senderCertificate = null;
}
// validate the sender certificate.
if (senderCertificate != null && Quotas.CertificateValidator != null && securityPolicyUri != SecurityPolicies.None)
{
Quotas.CertificateValidator.Validate(senderCertificate);
CertificateValidator certificateValidator = Quotas.CertificateValidator as CertificateValidator;
if (certificateValidator != null)
{
certificateValidator.Validate(senderCertificateChain);
}
else
{
Quotas.CertificateValidator.Validate(senderCertificate);
}
}
// check if this is the first open secure channel request.
@@ -1095,7 +1223,9 @@ namespace Opc.Ua.Bindings
private bool m_discoveryOnly;
private EndpointDescription m_selectedEndpoint;
private X509Certificate2 m_serverCertificate;
private X509Certificate2Collection m_serverCertificateChain;
private X509Certificate2 m_clientCertificate;
private X509Certificate2Collection m_clientCertificateChain;
private bool m_uninitialized;
#endregion
}
@@ -24,6 +24,7 @@ namespace Opc.Ua.Bindings
public partial class UaSCUaBinaryChannel : IMessageSink, IDisposable
{
#region Constructors
/// <summary>
/// Attaches the object to an existing socket.
/// </summary>
@@ -35,6 +36,22 @@ namespace Opc.Ua.Bindings
EndpointDescriptionCollection endpoints,
MessageSecurityMode securityMode,
string securityPolicyUri)
:
this(contextId, bufferManager, quotas, serverCertificate, null, endpoints, securityMode, securityPolicyUri)
{ }
/// <summary>
/// Attaches the object to an existing socket.
/// </summary>
public UaSCUaBinaryChannel(
string contextId,
BufferManager bufferManager,
ChannelQuotas quotas,
X509Certificate2 serverCertificate,
X509Certificate2Collection serverCertificateChain,
EndpointDescriptionCollection endpoints,
MessageSecurityMode securityMode,
string securityPolicyUri)
{
if (bufferManager == null) throw new ArgumentNullException("bufferManager");
if (quotas == null) throw new ArgumentNullException("quotas");
@@ -75,6 +92,7 @@ namespace Opc.Ua.Bindings
m_bufferManager = bufferManager;
m_quotas = quotas;
m_serverCertificate = serverCertificate;
m_serverCertificateChain = serverCertificateChain;
m_endpoints = endpoints;
m_securityMode = securityMode;
m_securityPolicyUri = securityPolicyUri;
@@ -39,6 +39,23 @@ namespace Opc.Ua.Bindings
X509Certificate2 clientCertificate,
X509Certificate2 serverCertificate,
EndpointDescription endpoint)
:
this(contextId, bufferManager, socketFactory, quotas, clientCertificate, null, serverCertificate, endpoint)
{
}
/// <summary>
/// Creates a channel for for a client.
/// </summary>
public UaSCUaBinaryClientChannel(
string contextId,
BufferManager bufferManager,
IMessageSocketFactory socketFactory,
ChannelQuotas quotas,
X509Certificate2 clientCertificate,
X509Certificate2Collection clientCertificateChain,
X509Certificate2 serverCertificate,
EndpointDescription endpoint)
:
base(
contextId,
@@ -61,6 +78,7 @@ namespace Opc.Ua.Bindings
}
ClientCertificate = clientCertificate;
ClientCertificateChain = clientCertificateChain;
}
m_requests = new Dictionary<uint, WriteOperation>();
@@ -471,7 +489,7 @@ namespace Opc.Ua.Bindings
request.ClientNonce = token.ClientNonce;
request.RequestedLifetime = (uint)Quotas.SecurityTokenLifetime;
// encode the request.
// encode the request.
byte[] buffer = BinaryEncoder.EncodeMessage(request, Quotas.MessageContext);
// write the asymmetric message.
@@ -479,6 +497,7 @@ namespace Opc.Ua.Bindings
TcpMessageType.Open,
m_handshakeOperation.RequestId,
ClientCertificate,
ClientCertificateChain,
ServerCertificate,
new ArraySegment<byte>(buffer, 0, buffer.Length));
@@ -144,6 +144,7 @@ namespace Opc.Ua.Bindings
m_messageSocketFactory,
m_quotas,
m_settings.ClientCertificate,
m_settings.ClientCertificateChain,
m_settings.ServerCertificate,
m_settings.Description);
@@ -389,6 +390,7 @@ namespace Opc.Ua.Bindings
m_messageSocketFactory,
m_quotas,
m_settings.ClientCertificate,
m_settings.ClientCertificateChain,
m_settings.ServerCertificate,
m_settings.Description);
}
@@ -213,7 +213,7 @@ namespace Opc.Ua
if (certificate == null)
{
certificate = CertificateFactory.Create(m_certificateData, true);
}
}
bool valid = SecurityPolicies.Verify(
certificate,
-1
View File
@@ -2585,7 +2585,6 @@ namespace Opc.Ua
certificatesBytes.RemoveRange(0, certificate.RawData.Length);
}
return certificateChain;
}