/// <summary> /// Processes an CloseSecureChannel request message. /// </summary> private bool ProcessCloseSecureChannelRequest(uint messageType, ArraySegment <byte> messageChunk) { // validate security on the message. TcpChannelToken token = null; uint requestId = 0; uint sequenceNumber = 0; ArraySegment <byte> messageBody; try { messageBody = ReadSymmetricMessage(messageChunk, true, out token, out requestId, out sequenceNumber); // check for replay attacks. if (!VerifySequenceNumber(sequenceNumber, "ProcessCloseSecureChannelRequest")) { throw new ServiceResultException(StatusCodes.BadSequenceNumberInvalid); } } catch (Exception e) { throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, e, "Could not verify security on CloseSecureChannel request."); } BufferCollection chunksToProcess = null; try { // check if it is necessary to wait for more chunks. if (!TcpMessageType.IsFinal(messageType)) { SaveIntermediateChunk(requestId, messageBody); return(false); } // get the chunks to process. chunksToProcess = GetSavedChunks(requestId, messageBody); CloseSecureChannelRequest request = BinaryDecoder.DecodeMessage( new ArraySegmentStream(chunksToProcess), typeof(CloseSecureChannelRequest), Quotas.MessageContext) as CloseSecureChannelRequest; if (request == null) { throw ServiceResultException.Create(StatusCodes.BadStructureMissing, "Could not parse CloseSecureChannel request body."); } // send the response. // SendCloseSecureChannelResponse(requestId, token, request); } catch (Exception e) { Utils.Trace(e, "Unexpected error processing OpenSecureChannel request."); } finally { if (chunksToProcess != null) { chunksToProcess.Release(BufferManager, "ProcessCloseSecureChannelRequest"); } Utils.Trace( "TCPSERVERCHANNEL ProcessCloseSecureChannelRequest Socket={0:X8}, ChannelId={1}, TokenId={2}", (Socket != null)?Socket.Handle:0, (CurrentToken != null)?CurrentToken.ChannelId:0, (CurrentToken != null)?CurrentToken.TokenId:0); // close the channel. ChannelClosed(); } return(false); }
/// <summary> /// Checks for a valid application instance certificate. /// </summary> /// <param name="silent">if set to <c>true</c> no dialogs will be displayed.</param> /// <param name="minimumKeySize">Minimum size of the key.</param> /// <param name="lifeTimeInMonths">The lifetime in months.</param> public async Task <bool> CheckApplicationInstanceCertificate( bool silent, ushort minimumKeySize, ushort lifeTimeInMonths) { Utils.Trace(Utils.TraceMasks.Information, "Checking application instance certificate."); if (m_applicationConfiguration == null) { await LoadApplicationConfiguration(silent).ConfigureAwait(false); } ApplicationConfiguration configuration = m_applicationConfiguration; bool certificateValid = false; // find the existing certificate. CertificateIdentifier id = configuration.SecurityConfiguration.ApplicationCertificate; if (id == null) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "Configuration file does not specify a certificate."); } X509Certificate2 certificate = await id.Find(true).ConfigureAwait(false); // check that it is ok. if (certificate != null) { certificateValid = await CheckApplicationInstanceCertificate(configuration, certificate, silent, minimumKeySize).ConfigureAwait(false); } else { // check for missing private key. certificate = await id.Find(false).ConfigureAwait(false); if (certificate != null) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "Cannot access certificate private key. Subject={0}", certificate.Subject); } // check for missing thumbprint. if (!String.IsNullOrEmpty(id.Thumbprint)) { if (!String.IsNullOrEmpty(id.SubjectName)) { CertificateIdentifier id2 = new CertificateIdentifier(); id2.StoreType = id.StoreType; id2.StorePath = id.StorePath; id2.SubjectName = id.SubjectName; certificate = await id2.Find(true).ConfigureAwait(false); } if (certificate != null) { var message = new StringBuilder(); message.AppendLine("Thumbprint was explicitly specified in the configuration."); message.AppendLine("Another certificate with the same subject name was found."); message.AppendLine("Use it instead?"); message.AppendLine("Requested: {0}"); message.AppendLine("Found: {1}"); if (!await ApproveMessage(String.Format(message.ToString(), id.SubjectName, certificate.Subject), silent).ConfigureAwait(false)) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, message.ToString(), id.SubjectName, certificate.Subject); } } else { var message = new StringBuilder(); message.AppendLine("Thumbprint was explicitly specified in the configuration. "); message.AppendLine("Cannot generate a new certificate."); throw ServiceResultException.Create(StatusCodes.BadConfigurationError, message.ToString()); } } } if ((certificate == null) || !certificateValid) { certificate = await CreateApplicationInstanceCertificate(configuration, minimumKeySize, lifeTimeInMonths).ConfigureAwait(false); if (certificate == null) { var message = new StringBuilder(); message.AppendLine("There is no cert with subject {0} in the configuration."); message.AppendLine(" Please generate a cert for your application,"); message.AppendLine(" then copy the new cert to this location:"); message.AppendLine(" {1}"); throw ServiceResultException.Create(StatusCodes.BadConfigurationError, message.ToString(), id.SubjectName, id.StorePath ); } } else { if (configuration.SecurityConfiguration.AddAppCertToTrustedStore) { // ensure it is trusted. await AddToTrustedStore(configuration, certificate).ConfigureAwait(false); } } return(true); }
/// <inheritdoc/> public void WriteExtensionObject(string property, ExtensionObject value) { if (value == null) { WriteNull(property); return; } var encoding = value.Encoding; var typeId = value.TypeId; var body = value.Body; if (body is IEncodeable encodeable) { if (NodeId.IsNull(typeId)) { typeId = encodeable.TypeId; value.TypeId = typeId; // Also fix the extension object } if (!UseReversibleEncoding) { PushObject(property); encodeable.Encode(this); PopObject(); return; } if (UseAdvancedEncoding) { encoding = ExtensionObjectEncoding.Json; } switch (encoding) { case ExtensionObjectEncoding.Binary: body = encodeable.AsBinary(Context); break; case ExtensionObjectEncoding.Json: body = encodeable; // Encode as json down below. break; case ExtensionObjectEncoding.EncodeableObject: case ExtensionObjectEncoding.None: case ExtensionObjectEncoding.Xml: // Force xml encoding = ExtensionObjectEncoding.Xml; body = encodeable.AsXmlElement(Context); break; default: throw ServiceResultException.Create( StatusCodes.BadEncodingError, "Unexpected encoding encountered while " + $"encoding ExtensionObject:{value.Encoding}"); } } else { if (NodeId.IsNull(typeId) && !UseAdvancedEncoding) { throw ServiceResultException.Create( StatusCodes.BadEncodingError, "Cannot encode extension object without type id."); } } PushObject(property); WriteExpandedNodeId("TypeId", typeId); if (body != null) { switch (encoding) { case ExtensionObjectEncoding.Xml: WriteEncoding("Encoding", encoding); WriteXmlElement("Body", body as XmlElement); break; case ExtensionObjectEncoding.Json: WriteEncoding("Encoding", encoding); if (body is EncodeableJToken jt) { // Write encodeable token as json raw body = jt.JToken; } else if (body is IEncodeable o) { PushObject("Body"); o.Encode(this); PopObject(); break; } PushObject("Body"); _writer.WritePropertyName(nameof(EncodeableJToken.JToken)); switch (body) { case JToken token: _writer.WriteRaw(token.ToString()); break; case string json: _writer.WriteRaw(json); break; case byte[] buffer: _writer.WriteValue(buffer); break; default: throw ServiceResultException.Create( StatusCodes.BadEncodingError, "Unexpected value encountered while " + $"encoding body:{body}"); } PopObject(); break; case ExtensionObjectEncoding.Binary: WriteByteString("Body", body as byte[]); break; } } PopObject(); }
/// <summary> /// Validates request header and returns a request context. /// </summary> /// <remarks> /// This method verifies that the session id is valid and that it uses secure channel id /// associated with current thread. It also verifies that the timestamp is not too /// and that the sequence number is not out of order (update requests only). /// </remarks> public virtual OperationContext ValidateRequest(RequestHeader requestHeader, RequestType requestType) { if (requestHeader == null) { throw new ArgumentNullException(nameof(requestHeader)); } Session session = null; try { lock (m_lock) { // check for create session request. if (requestType == RequestType.CreateSession || requestType == RequestType.ActivateSession) { return(new OperationContext(requestHeader, requestType)); } // find session. if (!m_sessions.TryGetValue(requestHeader.AuthenticationToken, out session)) { var handler = m_validateSessionLessRequest; if (handler != null) { var args = new ValidateSessionLessRequestEventArgs(requestHeader.AuthenticationToken, requestType); handler(this, args); if (ServiceResult.IsBad(args.Error)) { throw new ServiceResultException(args.Error); } return(new OperationContext(requestHeader, requestType, args.Identity)); } throw new ServiceResultException(StatusCodes.BadSessionIdInvalid); } // validate request header. session.ValidateRequest(requestHeader, requestType); // return context. return(new OperationContext(requestHeader, requestType, session)); } } catch (Exception e) { ServiceResultException sre = e as ServiceResultException; if (sre != null && sre.StatusCode == StatusCodes.BadSessionNotActivated) { if (session != null) { CloseSession(session.Id); } } throw new ServiceResultException(e, StatusCodes.BadUnexpectedError); } }
/// <summary> /// Reconnects to the server. /// </summary> private async Task <bool> DoReconnect() { // try a reconnect. if (!m_reconnectFailed) { try { if (m_reverseConnectManager != null) { var connection = await m_reverseConnectManager.WaitForConnection( new Uri(m_session.Endpoint.EndpointUrl), m_session.Endpoint.Server.ApplicationUri ).ConfigureAwait(false); m_session.Reconnect(connection); } else { m_session.Reconnect(); } // monitored items should start updating on their own. return(true); } catch (Exception exception) { // recreate the session if it has been closed. ServiceResultException sre = exception as ServiceResultException; // check if the server endpoint could not be reached. if ((sre != null && (sre.StatusCode == StatusCodes.BadTcpInternalError || sre.StatusCode == StatusCodes.BadCommunicationError || sre.StatusCode == StatusCodes.BadNotConnected || sre.StatusCode == StatusCodes.BadTimeout))) { // check if reconnecting is still an option. if (m_session.LastKeepAliveTime.AddMilliseconds(m_session.SessionTimeout) > DateTime.UtcNow) { Utils.Trace("Calling OnReconnectSession in {0} ms.", m_reconnectPeriod); return(false); } } m_reconnectFailed = true; } } // re-create the session. try { Session session; if (m_reverseConnectManager != null) { var connection = await m_reverseConnectManager.WaitForConnection( new Uri(m_session.Endpoint.EndpointUrl), m_session.Endpoint.Server.ApplicationUri ).ConfigureAwait(false); session = Session.Recreate(m_session, connection); } else { session = Session.Recreate(m_session); } m_session.Close(); m_session = session; return(true); } catch (Exception exception) { Utils.Trace("Could not reconnect the Session. {0}", exception.Message); return(false); } }
/// <summary> /// Reads the next block of data from the socket. /// </summary> private void ReadNextBlock() { Socket socket = null; // check if already closed. lock (m_socketLock) { if (m_socket == null) { if (m_receiveBuffer != null) { m_bufferManager.ReturnBuffer(m_receiveBuffer, "ReadNextBlock"); m_receiveBuffer = null; } m_readState = ReadState.NotConnected; return; } socket = m_socket; // avoid stale ServiceException when socket is disconnected if (!socket.Connected) { m_readState = ReadState.NotConnected; return; } } BufferManager.LockBuffer(m_receiveBuffer); var args = new SocketAsyncEventArgs(); try { m_readState = ReadState.Receive; args.SetBuffer(m_receiveBuffer, m_bytesReceived, m_bytesToReceive - m_bytesReceived); args.Completed += m_readComplete; if (!socket.ReceiveAsync(args)) { // I/O completed synchronously if (args.SocketError != SocketError.Success) { throw ServiceResultException.Create(StatusCodes.BadTcpInternalError, args.SocketError.ToString()); } // set state to inner complete m_readState = ReadState.ReadComplete; m_readComplete(null, args); } } catch (ServiceResultException) { args?.Dispose(); BufferManager.UnlockBuffer(m_receiveBuffer); throw; } catch (Exception ex) { args?.Dispose(); BufferManager.UnlockBuffer(m_receiveBuffer); throw ServiceResultException.Create(StatusCodes.BadTcpInternalError, ex, "BeginReceive failed."); } }
/// <summary> /// Gets the application access rules for the specified URL. /// </summary> public static IList <HttpAccessRule> GetAccessRules(string url) { List <HttpAccessRule> accessRules = new List <HttpAccessRule>(); HttpError error = NativeMethods.HttpInitialize( new HTTPAPI_VERSION(1, 0), HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not initialize HTTP library.\r\nError={0}", error); } HTTP_SERVICE_CONFIG_URLACL_QUERY query = new HTTP_SERVICE_CONFIG_URLACL_QUERY(); query.QueryDesc = HTTP_SERVICE_CONFIG_QUERY_TYPE.HttpServiceConfigQueryNext; if (!String.IsNullOrEmpty(url)) { query.QueryDesc = HTTP_SERVICE_CONFIG_QUERY_TYPE.HttpServiceConfigQueryExact; query.KeyDesc.pUrlPrefix = url; } IntPtr pInput = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_URLACL_QUERY))); NativeMethods.ZeroMemory(pInput, Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_URLACL_QUERY))); IntPtr pOutput = IntPtr.Zero; try { for (query.dwToken = 0; error == HttpError.NO_ERROR; query.dwToken++) { Marshal.StructureToPtr(query, pInput, true); int requiredBufferLength = 0; error = NativeMethods.HttpQueryServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo, pInput, Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_URLACL_QUERY)), pOutput, requiredBufferLength, out requiredBufferLength, IntPtr.Zero); if (error == HttpError.ERROR_NO_MORE_ITEMS) { break; } if (!String.IsNullOrEmpty(url)) { if (error == HttpError.ERROR_FILE_NOT_FOUND) { break; } } if (error != HttpError.ERROR_INSUFFICIENT_BUFFER) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not read access rules for HTTP url.\r\nError={1}, Url={0}", url, error); } pOutput = Marshal.AllocHGlobal(requiredBufferLength); NativeMethods.ZeroMemory(pOutput, requiredBufferLength); error = NativeMethods.HttpQueryServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo, pInput, Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_URLACL_QUERY)), pOutput, requiredBufferLength, out requiredBufferLength, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not read access rules for HTTP url.\r\nError={1}, Url={0}", url, error); } HTTP_SERVICE_CONFIG_URLACL_SET result = (HTTP_SERVICE_CONFIG_URLACL_SET)Marshal.PtrToStructure( pOutput, typeof(HTTP_SERVICE_CONFIG_URLACL_SET)); // parse the SDDL and update the access list. ParseSddl(result.KeyDesc.pUrlPrefix, result.ParamDesc.pStringSecurityDescriptor, accessRules); Marshal.FreeHGlobal(pOutput); pOutput = IntPtr.Zero; // all done if requesting the results for a single url. if (!String.IsNullOrEmpty(url)) { break; } } } finally { if (pInput != IntPtr.Zero) { Marshal.DestroyStructure(pInput, typeof(HTTP_SERVICE_CONFIG_URLACL_QUERY)); Marshal.FreeHGlobal(pInput); } if (pOutput != IntPtr.Zero) { Marshal.FreeHGlobal(pOutput); } NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); } return(accessRules); }
/// <summary> /// Exports the security configuration for an application identified by a file or url. /// </summary> /// <param name="filePath">The file path.</param> /// <returns>The security configuration.</returns> public SecuredApplication ReadConfiguration(string filePath) { if (filePath == null) { throw new ArgumentNullException("filePath"); } string configFilePath = filePath; string exeFilePath = null; // check for valid file. if (!File.Exists(filePath)) { throw ServiceResultException.Create( StatusCodes.BadNotReadable, "Cannot find the executable or configuration file: {0}", filePath); } // find the configuration file for the executable. if (filePath.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { exeFilePath = filePath; try { FileInfo file = new FileInfo(filePath); string sectionName = file.Name; sectionName = sectionName.Substring(0, sectionName.Length - file.Extension.Length); System.Configuration.Configuration configuration = ConfigurationManager.OpenExeConfiguration(filePath); configFilePath = ApplicationConfiguration.GetFilePathFromAppConfig(sectionName); if (configFilePath == null) { configFilePath = filePath + ".config"; } } catch (Exception e) { throw ServiceResultException.Create( StatusCodes.BadNotReadable, e, "Cannot find the configuration file for the executable: {0}", filePath); } if (!File.Exists(configFilePath)) { throw ServiceResultException.Create( StatusCodes.BadNotReadable, "Cannot find the configuration file: {0}", configFilePath); } } SecuredApplication application = null; ApplicationConfiguration applicationConfiguration = null; try { XmlTextReader reader = new XmlTextReader(File.Open(configFilePath, FileMode.Open, FileAccess.Read)); try { reader.MoveToContent(); // find the SecuredApplication element in the file. if (reader.ReadToDescendant("SecuredApplication", Namespaces.OpcUaSecurity)) { DataContractSerializer serializer = new DataContractSerializer(typeof(SecuredApplication)); application = serializer.ReadObject(reader, false) as SecuredApplication; application.ConfigurationFile = configFilePath; application.ExecutableFile = exeFilePath; } // load the application configuration. else { reader.Close(); reader = new XmlTextReader(File.Open(configFilePath, FileMode.Open, FileAccess.Read)); DataContractSerializer serializer = new DataContractSerializer(typeof(ApplicationConfiguration)); applicationConfiguration = serializer.ReadObject(reader, false) as ApplicationConfiguration; } } finally { reader.Close(); } } catch (Exception e) { throw ServiceResultException.Create( StatusCodes.BadNotReadable, e, "Cannot load the configuration file: {0}", filePath); } // check if security info store on disk. if (application != null) { return(application); } application = new SecuredApplication(); // copy application info. application.ApplicationName = applicationConfiguration.ApplicationName; application.ApplicationUri = applicationConfiguration.ApplicationUri; application.ProductName = applicationConfiguration.ProductUri; application.ApplicationType = (ApplicationType)(int)applicationConfiguration.ApplicationType; application.ConfigurationFile = configFilePath; application.ExecutableFile = exeFilePath; application.ConfigurationMode = "http://opcfoundation.org/UASDK/ConfigurationTool"; application.LastExportTime = DateTime.UtcNow; // copy the security settings. if (applicationConfiguration.SecurityConfiguration != null) { application.ApplicationCertificate = SecuredApplication.ToCertificateIdentifier(applicationConfiguration.SecurityConfiguration.ApplicationCertificate); if (applicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates != null) { application.IssuerCertificateStore = SecuredApplication.ToCertificateStoreIdentifier(applicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates); if (applicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.TrustedCertificates != null) { application.IssuerCertificates = SecuredApplication.ToCertificateList(applicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.TrustedCertificates); } } if (applicationConfiguration.SecurityConfiguration.TrustedPeerCertificates != null) { application.TrustedCertificateStore = SecuredApplication.ToCertificateStoreIdentifier(applicationConfiguration.SecurityConfiguration.TrustedPeerCertificates); if (applicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.TrustedCertificates != null) { application.TrustedCertificates = SecuredApplication.ToCertificateList(applicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.TrustedCertificates); } } if (applicationConfiguration.SecurityConfiguration.RejectedCertificateStore != null) { application.RejectedCertificatesStore = SecuredApplication.ToCertificateStoreIdentifier(applicationConfiguration.SecurityConfiguration.RejectedCertificateStore); } } ServerBaseConfiguration serverConfiguration = null; if (applicationConfiguration.ServerConfiguration != null) { serverConfiguration = applicationConfiguration.ServerConfiguration; } else if (applicationConfiguration.DiscoveryServerConfiguration != null) { serverConfiguration = applicationConfiguration.DiscoveryServerConfiguration; } if (serverConfiguration != null) { application.BaseAddresses = SecuredApplication.ToListOfBaseAddresses(serverConfiguration); application.SecurityProfiles = SecuredApplication.ToListOfSecurityProfiles(serverConfiguration.SecurityPolicies); } // return exported setttings. return(application); }
/// <summary> /// Creates a new SSL certificate binding. /// </summary> public static void SetSslCertificateBinding(SslCertificateBinding binding) { if (binding == null) { throw new ArgumentNullException("binding"); } // initialize library. HttpError error = NativeMethods.HttpInitialize( new HTTPAPI_VERSION(1, 0), HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not initialize HTTP library.\r\nError={0}", error); } IntPtr pAddress = IntPtr.Zero; IntPtr pThumprint = IntPtr.Zero; IntPtr pConfigInfo = IntPtr.Zero; try { pAddress = ToIntPtr(binding.IPAddress, binding.Port); byte[] thumbprint = Utils.FromHexString(binding.Thumbprint); pThumprint = Marshal.AllocCoTaskMem(thumbprint.Length); Marshal.Copy(thumbprint, 0, pThumprint, thumbprint.Length); HTTP_SERVICE_CONFIG_SSL_SET configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET(); configSslSet.KeyDesc.pIpPort = pAddress; configSslSet.ParamDesc.pSslHash = pThumprint; configSslSet.ParamDesc.SslHashLength = thumbprint.Length; configSslSet.ParamDesc.AppId = binding.ApplicationId; configSslSet.ParamDesc.pSslCertStoreName = binding.StoreName; configSslSet.ParamDesc.DefaultCertCheckMode = binding.DefaultCertCheckMode; configSslSet.ParamDesc.DefaultFlags = binding.DefaultFlags; configSslSet.ParamDesc.DefaultRevocationFreshnessTime = binding.DefaultRevocationFreshnessTime; configSslSet.ParamDesc.DefaultRevocationUrlRetrievalTimeout = binding.DefaultRevocationUrlRetrievalTimeout; configSslSet.ParamDesc.pDefaultSslCtlIdentifier = binding.DefaultSslCtlIdentifier; configSslSet.ParamDesc.pDefaultSslCtlStoreName = binding.DefaultSslCtlStoreName; int size = Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_SET)); pConfigInfo = Marshal.AllocCoTaskMem(size); Marshal.StructureToPtr(configSslSet, pConfigInfo, false); error = NativeMethods.HttpSetServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, pConfigInfo, size, IntPtr.Zero); if (error == HttpError.ERROR_ALREADY_EXISTS) { error = NativeMethods.HttpDeleteServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, pConfigInfo, size, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not delete existing SSL certificate binding.\r\nError={0}", error); } error = NativeMethods.HttpSetServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, pConfigInfo, size, IntPtr.Zero); } if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not create SSL certificate binding.\r\nError={0}", error); } } finally { if (pConfigInfo != IntPtr.Zero) { Marshal.FreeCoTaskMem(pConfigInfo); } if (pAddress != IntPtr.Zero) { Marshal.FreeCoTaskMem(pAddress); } if (pThumprint != IntPtr.Zero) { Marshal.FreeCoTaskMem(pThumprint); } NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); } }
/// <summary> /// Deletes a new SSL certificate binding. /// </summary> public static void DeleteSslCertificateBinding(IPAddress address, ushort port) { if (address == null) { throw new ArgumentNullException("address"); } // initialize library. HttpError error = NativeMethods.HttpInitialize( new HTTPAPI_VERSION(1, 0), HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not initialize HTTP library.\r\nError={0}", error); } IntPtr pAddress = IntPtr.Zero; IntPtr pConfigInfo = IntPtr.Zero; try { pAddress = ToIntPtr(address, port); HTTP_SERVICE_CONFIG_SSL_SET configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET(); configSslSet.KeyDesc.pIpPort = pAddress; int size = Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_SET)); pConfigInfo = Marshal.AllocCoTaskMem(size); Marshal.StructureToPtr(configSslSet, pConfigInfo, false); error = NativeMethods.HttpDeleteServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, pConfigInfo, size, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not delete existing SSL certificate binding.\r\nError={0}", error); } } finally { if (pConfigInfo != IntPtr.Zero) { Marshal.FreeCoTaskMem(pConfigInfo); } if (pAddress != IntPtr.Zero) { Marshal.FreeCoTaskMem(pAddress); } NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); } }
/// <summary> /// Fetches the current SSL certificate configuration. /// </summary> public static List <SslCertificateBinding> GetSslCertificateBindings() { List <SslCertificateBinding> bindings = new List <SslCertificateBinding>(); // initialize library. HttpError error = NativeMethods.HttpInitialize( new HTTPAPI_VERSION(1, 0), HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not initialize HTTP library.\r\nError={0}", error); } // set up the iterator. HTTP_SERVICE_CONFIG_SSL_QUERY query = new HTTP_SERVICE_CONFIG_SSL_QUERY(); query.QueryDesc = HTTP_SERVICE_CONFIG_QUERY_TYPE.HttpServiceConfigQueryNext; query.KeyDesc.pIpPort = IntPtr.Zero; IntPtr pInput = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_QUERY))); NativeMethods.ZeroMemory(pInput, Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_QUERY))); IntPtr pOutput = IntPtr.Zero; try { // loop through each record. for (query.dwToken = 0; error == HttpError.NO_ERROR; query.dwToken++) { // get the size of buffer to allocate. Marshal.StructureToPtr(query, pInput, true); int requiredBufferLength = 0; error = NativeMethods.HttpQueryServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, pInput, Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_QUERY)), pOutput, requiredBufferLength, out requiredBufferLength, IntPtr.Zero); if (error == HttpError.ERROR_NO_MORE_ITEMS) { break; } if (error != HttpError.ERROR_INSUFFICIENT_BUFFER) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not read SSL configuration information.\r\nError={0}", error); } // allocate the buffer. pOutput = Marshal.AllocHGlobal(requiredBufferLength); NativeMethods.ZeroMemory(pOutput, requiredBufferLength); // get the actual data. error = NativeMethods.HttpQueryServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo, pInput, Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_QUERY)), pOutput, requiredBufferLength, out requiredBufferLength, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not read SSL configuration information.\r\nError={0}", error); } HTTP_SERVICE_CONFIG_SSL_SET sslSet = (HTTP_SERVICE_CONFIG_SSL_SET)Marshal.PtrToStructure(pOutput, typeof(HTTP_SERVICE_CONFIG_SSL_SET)); short family = Marshal.ReadInt16(sslSet.KeyDesc.pIpPort); SslCertificateBinding binding = new SslCertificateBinding(); if (family == AF_INET) { SOCKADDR_IN inet = (SOCKADDR_IN)Marshal.PtrToStructure(sslSet.KeyDesc.pIpPort, typeof(SOCKADDR_IN)); binding.IPAddress = new IPAddress(inet.addr); binding.Port = inet.port; } if (family == AF_INET6) { SOCKADDR_IN6 inet = (SOCKADDR_IN6)Marshal.PtrToStructure(sslSet.KeyDesc.pIpPort, typeof(SOCKADDR_IN6)); binding.IPAddress = new IPAddress(inet.addr, inet.scopeID); binding.Port = inet.port; } binding.Port = (ushort)(((binding.Port & 0xFF00) >> 8) | ((binding.Port & 0x00FF) << 8)); byte[] bytes = new byte[sslSet.ParamDesc.SslHashLength]; Marshal.Copy(sslSet.ParamDesc.pSslHash, bytes, 0, bytes.Length); binding.Thumbprint = Utils.ToHexString(bytes); binding.ApplicationId = sslSet.ParamDesc.AppId; binding.StoreName = sslSet.ParamDesc.pSslCertStoreName; binding.DefaultCertCheckMode = sslSet.ParamDesc.DefaultCertCheckMode; binding.DefaultRevocationFreshnessTime = sslSet.ParamDesc.DefaultRevocationFreshnessTime; binding.DefaultRevocationUrlRetrievalTimeout = sslSet.ParamDesc.DefaultRevocationUrlRetrievalTimeout; binding.DefaultSslCtlIdentifier = sslSet.ParamDesc.pDefaultSslCtlIdentifier; binding.DefaultSslCtlStoreName = sslSet.ParamDesc.pDefaultSslCtlStoreName; binding.DefaultFlags = sslSet.ParamDesc.DefaultFlags; bindings.Add(binding); Marshal.FreeHGlobal(pOutput); pOutput = IntPtr.Zero; } } finally { if (pInput != IntPtr.Zero) { Marshal.DestroyStructure(pInput, typeof(HTTP_SERVICE_CONFIG_SSL_QUERY)); Marshal.FreeHGlobal(pInput); } if (pOutput != IntPtr.Zero) { Marshal.FreeHGlobal(pOutput); } NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); } return(bindings); }
/// <summary> /// Validates the identity token supplied by the client. /// </summary> private UserIdentityToken ValidateUserIdentityToken( ExtensionObject identityToken, SignatureData userTokenSignature, out UserTokenPolicy policy) { policy = null; // check for empty token. if (identityToken == null || identityToken.Body == null) { // not changing the token if already activated. if (m_activated) { return(null); } // check if an anonymous login is permitted. if (m_endpoint.UserIdentityTokens != null && m_endpoint.UserIdentityTokens.Count > 0) { bool found = false; for (int ii = 0; ii < m_endpoint.UserIdentityTokens.Count; ii++) { if (m_endpoint.UserIdentityTokens[ii].TokenType == UserTokenType.Anonymous) { found = true; policy = m_endpoint.UserIdentityTokens[ii]; break; } } if (!found) { throw ServiceResultException.Create(StatusCodes.BadUserAccessDenied, "Anonymous user token policy not supported."); } } // create an anonymous token to use for subsequent validation. AnonymousIdentityToken anonymousToken = new AnonymousIdentityToken(); anonymousToken.PolicyId = policy.PolicyId; return(anonymousToken); } UserIdentityToken token = null; // check for unrecognized token. if (!typeof(UserIdentityToken).IsInstanceOfType(identityToken.Body)) { //handle the use case when the UserIdentityToken is binary encoded over xml message encoding if (identityToken.Encoding == ExtensionObjectEncoding.Binary && typeof(byte[]).IsInstanceOfType(identityToken.Body)) { UserIdentityToken newToken = BaseVariableState.DecodeExtensionObject(null, typeof(UserIdentityToken), identityToken, false) as UserIdentityToken; if (newToken == null) { throw ServiceResultException.Create(StatusCodes.BadUserAccessDenied, "Invalid user identity token provided."); } policy = m_endpoint.FindUserTokenPolicy(newToken.PolicyId); if (policy == null) { throw ServiceResultException.Create(StatusCodes.BadUserAccessDenied, "User token policy not supported.", "Opc.Ua.Server.Session.ValidateUserIdentityToken"); } switch (policy.TokenType) { case UserTokenType.Anonymous: token = BaseVariableState.DecodeExtensionObject(null, typeof(AnonymousIdentityToken), identityToken, true) as AnonymousIdentityToken; break; case UserTokenType.UserName: token = BaseVariableState.DecodeExtensionObject(null, typeof(UserNameIdentityToken), identityToken, true) as UserNameIdentityToken; break; case UserTokenType.Certificate: token = BaseVariableState.DecodeExtensionObject(null, typeof(X509IdentityToken), identityToken, true) as X509IdentityToken; break; case UserTokenType.IssuedToken: token = BaseVariableState.DecodeExtensionObject(null, typeof(IssuedIdentityToken), identityToken, true) as IssuedIdentityToken; break; default: throw ServiceResultException.Create(StatusCodes.BadUserAccessDenied, "Invalid user identity token provided."); } } else { throw ServiceResultException.Create(StatusCodes.BadUserAccessDenied, "Invalid user identity token provided."); } } else { // get the token. token = (UserIdentityToken)identityToken.Body; } // find the user token policy. policy = m_endpoint.FindUserTokenPolicy(token.PolicyId); if (policy == null) { throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid, "User token policy not supported."); } // determine the security policy uri. string securityPolicyUri = policy.SecurityPolicyUri; if (String.IsNullOrEmpty(securityPolicyUri)) { securityPolicyUri = m_endpoint.SecurityPolicyUri; } if (ServerBase.RequireEncryption(m_endpoint)) { // decrypt the token. if (m_serverCertificate == null) { m_serverCertificate = CertificateFactory.Create(m_endpoint.ServerCertificate, true); // check for valid certificate. if (m_serverCertificate == null) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate cannot be found."); } } try { token.Decrypt(m_serverCertificate, m_serverNonce, securityPolicyUri); } catch (Exception e) { if (e is ServiceResultException) { throw; } throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid, e, "Could not decrypt identity token."); } // verify the signature. if (securityPolicyUri != SecurityPolicies.None) { byte[] dataToSign = Utils.Append(m_serverCertificate.RawData, m_serverNonce); if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri)) { throw new ServiceResultException(StatusCodes.BadUserSignatureInvalid, "Invalid user signature!"); } } } // validate user identity token. return(token); }
/// <summary> /// Display the exception in the dialog. /// </summary> public void ShowDialog(string caption, Exception e) { Text.Text = caption; StringBuilder buffer = new StringBuilder(); buffer.Append("<html><body style='margin:0'>"); while (e != null) { string message = e.Message; ServiceResultException exception = e as ServiceResultException; if (exception != null) { message = exception.ToLongString(); } message = ReplaceSpecialCharacters(message); if (exception != null) { buffer.Append("<p>"); buffer.Append("<font style='font:9pt/12pt verdana;color:black'>"); buffer.Append(message); buffer.Append("</font>"); buffer.Append("</p>"); } else { buffer.Append("<font style='font:9pt/12pt verdana;color:red'><b>"); buffer.Append(message); buffer.Append("</b></font><br>"); } message = e.StackTrace; if (!String.IsNullOrEmpty(message)) { message = ReplaceSpecialCharacters(message); buffer.Append("<p>"); buffer.Append("<font style='font:9pt/12pt verdana;color:black'>"); buffer.Append(message); buffer.Append("</font>"); buffer.Append("</p>"); } e = e.InnerException; } buffer.Append("</body></html>"); ExceptionBrowser.NavigateToString(buffer.ToString()); Popup myPopup = new Popup(); myPopup.Child = this; myPopup.IsOpen = true; }
private async Task ReceiveResponsesAsync(CancellationToken token = default(CancellationToken)) { while (!token.IsCancellationRequested) { try { var response = await this.ReceiveResponseAsync(token).ConfigureAwait(false); if (response == null) { // Null response indicates socket closed. This is expected when closing secure channel. if (this.State == CommunicationState.Closing) { return; } throw new ServiceResultException(StatusCodes.BadServerNotConnected); } var header = response.ResponseHeader; TaskCompletionSource<IServiceResponse> tcs; if (this.pendingCompletions.TryRemove(header.RequestHandle, out tcs)) { if (StatusCode.IsBad(header.ServiceResult)) { var ex = new ServiceResultException(new ServiceResult(header.ServiceResult, header.ServiceDiagnostics, header.StringTable)); Log.Warn($"Received {response.GetType().Name} Handle: {response.ResponseHeader.RequestHandle} Code: {header.ServiceResult} - {ex.Message}"); tcs.TrySetException(ex); } else { Log.Trace($"Received {response.GetType().Name} Handle: {response.ResponseHeader.RequestHandle}"); tcs.TrySetResult(response); } } // check if time to renew token if (this.State == CommunicationState.Opened && DateTime.UtcNow > this.tokenRenewalTime) { this.tokenRenewalTime = this.tokenRenewalTime.AddMilliseconds(60000); var task = Task.Run(() => this.OnRenewAsync(token)); } } catch (Exception ex) { if (!token.IsCancellationRequested) { await this.FaultAsync(ex).ConfigureAwait(false); await this.AbortAsync().ConfigureAwait(false); } } } }
/// <summary> /// Sets the application access rules for the specified URL. /// </summary> public static void SetAccessRules(string url, IList <HttpAccessRule> rules, bool replaceExisting) { HttpError error = NativeMethods.HttpInitialize( new HTTPAPI_VERSION(1, 0), HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not initialize HTTP library.\r\nError={0}", error); } // fetch existing rules if not replacing them. if (!replaceExisting) { IList <HttpAccessRule> existingRules = GetAccessRules(url); if (existingRules.Count > 0) { List <HttpAccessRule> mergedRules = new List <HttpAccessRule>(existingRules); mergedRules.AddRange(rules); rules = mergedRules; } } HTTP_SERVICE_CONFIG_URLACL_SET update = new HTTP_SERVICE_CONFIG_URLACL_SET(); update.KeyDesc.pUrlPrefix = url; update.ParamDesc.pStringSecurityDescriptor = FormatSddl(rules); IntPtr pStruct = IntPtr.Zero; int updateSize = Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_URLACL_SET)); try { pStruct = Marshal.AllocHGlobal(updateSize); NativeMethods.ZeroMemory(pStruct, updateSize); Marshal.StructureToPtr(update, pStruct, false); error = NativeMethods.HttpDeleteServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo, pStruct, updateSize, IntPtr.Zero); if (error != HttpError.ERROR_FILE_NOT_FOUND && error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not delete existing access rules for HTTP url.\r\nError={1}, Url={0}", url, error); } if (rules.Count > 0) { error = NativeMethods.HttpSetServiceConfiguration( IntPtr.Zero, HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo, pStruct, updateSize, IntPtr.Zero); if (error != HttpError.NO_ERROR) { throw ServiceResultException.Create( StatusCodes.BadUnexpectedError, "Could not set the access rules for HTTP url.\r\nError={1}, Url={0}", url, error); } } } finally { if (pStruct != IntPtr.Zero) { Marshal.DestroyStructure(pStruct, typeof(HTTP_SERVICE_CONFIG_URLACL_SET)); Marshal.FreeHGlobal(pStruct); } NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero); } }
/// <summary> /// Translates an exception. /// </summary> /// <param name="context">The context.</param> /// <param name="e">The ServiceResultException e.</param> /// <returns>Returns an exception thrown when a UA defined error occurs, the return type is <seealso cref="ServiceResultException"/>.</returns> protected virtual ServiceResultException TranslateException(OperationContext context, ServiceResultException e) { IList<string> preferredLocales = null; if (context != null && context.Session != null) { preferredLocales = context.Session.PreferredLocales; } return TranslateException(context.DiagnosticsMask, preferredLocales, e); }
/// <summary> /// Activates an existing session /// </summary> public virtual bool ActivateSession( OperationContext context, NodeId authenticationToken, SignatureData clientSignature, List <SoftwareCertificate> clientSoftwareCertificates, ExtensionObject userIdentityToken, SignatureData userTokenSignature, StringCollection localeIds, out byte[] serverNonce) { serverNonce = null; Session session = null; UserIdentityToken newIdentity = null; UserTokenPolicy userTokenPolicy = null; lock (m_lock) { // find session. if (!m_sessions.TryGetValue(authenticationToken, out session)) { throw new ServiceResultException(StatusCodes.BadSessionClosed); } // create new server nonce. serverNonce = new byte[m_minNonceLength]; IBuffer buffer = CryptographicBuffer.GenerateRandom((uint)m_minNonceLength); CryptographicBuffer.CopyToByteArray(buffer, out serverNonce); // validate before activation. session.ValidateBeforeActivate( context, clientSignature, clientSoftwareCertificates, userIdentityToken, userTokenSignature, localeIds, serverNonce, out newIdentity, out userTokenPolicy); } IUserIdentity identity = null; IUserIdentity effectiveIdentity = null; ServiceResult error = null; try { // check if the application has a callback which validates the identity tokens. lock (m_eventLock) { if (m_ImpersonateUser != null) { ImpersonateEventArgs args = new ImpersonateEventArgs(newIdentity, userTokenPolicy); m_ImpersonateUser(session, args); if (ServiceResult.IsBad(args.IdentityValidationError)) { error = args.IdentityValidationError; } else { identity = args.Identity; effectiveIdentity = args.EffectiveIdentity; } } } // parse the token manually if the identity is not provided. if (identity == null) { identity = new UserIdentity(newIdentity); } // use the identity as the effectiveIdentity if not provided. if (effectiveIdentity == null) { effectiveIdentity = identity; } } catch (Exception e) { if (e is ServiceResultException) { throw; } throw ServiceResultException.Create( StatusCodes.BadIdentityTokenInvalid, e, "Could not validate user identity token: {0}", newIdentity); } // check for validation error. if (ServiceResult.IsBad(error)) { throw new ServiceResultException(error); } // activate session. bool contextChanged = session.Activate( context, clientSoftwareCertificates, newIdentity, identity, effectiveIdentity, localeIds, serverNonce); // raise session related event. if (contextChanged) { RaiseSessionEvent(session, SessionEventReason.Activated); } // indicates that the identity context for the session has changed. return(contextChanged); }
/// <summary> /// Reconnects to the server. /// </summary> private bool DoReconnect() { // try a reconnect. if (!m_reconnectFailed) { try { m_session.Reconnect(); // monitored items should start updating on their own. return(true); } catch (Exception exception) { bool recreateNow = false; // recreate the session if it has been closed or the nonce is wrong. ServiceResultException sre = exception as ServiceResultException; if (sre != null) { switch (sre.StatusCode) { case StatusCodes.BadSessionClosed: case StatusCodes.BadApplicationSignatureInvalid: { recreateNow = true; break; } default: { Utils.Trace((int)Utils.TraceMasks.Error, "Unexpected RECONNECT error code. {0}", sre.StatusCode); break; } } } m_reconnectFailed = true; // try a reconnect again after a delay. if (!recreateNow) { Utils.Trace("Reconnect failed. {0}", exception.Message); return(false); } } } // re-create the session. try { Session session = Session.Recreate(m_session); m_session.Close(); m_session = session; return(true); } catch (Exception exception) { Utils.Trace("Unexpected re-creating a Session with the UA Server. {0}", exception.Message); return(false); } }
/// <summary> /// Creates a session with the endpoint. /// </summary> public async Task <Session> Connect(ConfiguredEndpoint endpoint) { if (endpoint == null) { throw new ArgumentNullException("endpoint"); } // check if the endpoint needs to be updated. if (endpoint.UpdateBeforeConnect) { ConfiguredServerDlg configurationDialog = new ConfiguredServerDlg(); endpoint = await configurationDialog.ShowDialog(endpoint, m_configuration); } if (endpoint == null) { return(null); } m_endpoint = endpoint; // copy the message context. m_messageContext = m_configuration.CreateMessageContext(); X509Certificate2 clientCertificate = null; if (endpoint.Description.SecurityPolicyUri != SecurityPolicies.None) { if (m_configuration.SecurityConfiguration.ApplicationCertificate == null) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate must be specified."); } clientCertificate = await m_configuration.SecurityConfiguration.ApplicationCertificate.Find(true); if (clientCertificate == null) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate cannot be found."); } } // create the channel. ITransportChannel channel = SessionChannel.Create( m_configuration, endpoint.Description, endpoint.Configuration, clientCertificate, m_messageContext); try { // create the session. Session session = new Session(channel, m_configuration, endpoint, null); session.ReturnDiagnostics = DiagnosticsMasks.All; SessionOpenDlg sessiondlg = new SessionOpenDlg(); session = await sessiondlg.ShowDialog(session, PreferredLocales); if (session != null) { // session now owns the channel. channel = null; // add session to tree. AddNode(session); return(session); } } finally { // ensure the channel is closed on error. if (channel != null) { channel.Close(); channel = null; } } return(null); }
protected ArraySegment <byte> ReadSymmetricMessage( ArraySegment <byte> buffer, bool isRequest, out ChannelToken token, out uint requestId, out uint sequenceNumber) { BinaryDecoder decoder = new BinaryDecoder(buffer.Array, buffer.Offset, buffer.Count, Quotas.MessageContext); uint messageType = decoder.ReadUInt32(null); uint messageSize = decoder.ReadUInt32(null); uint channelId = decoder.ReadUInt32(null); uint tokenId = decoder.ReadUInt32(null); // ensure the channel is valid. if (channelId != ChannelId) { throw ServiceResultException.Create( StatusCodes.BadTcpSecureChannelUnknown, "SecureChannelId is not known. ChanneId={0}, CurrentChannelId={1}", channelId, ChannelId); } // check for a message secured with the new token. if (RenewedToken != null && RenewedToken.TokenId == tokenId) { ActivateToken(RenewedToken); } // check if activation of the new token should be forced. if (RenewedToken != null && CurrentToken.ActivationRequired) { ActivateToken(RenewedToken); Utils.Trace("Token #{0} activated forced.", CurrentToken.TokenId); } // check for valid token. ChannelToken currentToken = CurrentToken; if (currentToken == null) { throw new ServiceResultException(StatusCodes.BadSecureChannelClosed); } // find the token. if (currentToken.TokenId != tokenId && PreviousToken != null && PreviousToken.TokenId != tokenId) { throw ServiceResultException.Create( StatusCodes.BadTcpSecureChannelUnknown, "TokenId is not known. ChanneId={0}, TokenId={1}, CurrentTokenId={2}, PreviousTokenId={3}", channelId, tokenId, currentToken.TokenId, (PreviousToken != null) ? (int)PreviousToken.TokenId : -1); } token = currentToken; // check for a message secured with the token before it expired. if (PreviousToken != null && PreviousToken.TokenId == tokenId) { token = PreviousToken; } // check if token has expired. if (token.Expired) { throw ServiceResultException.Create(StatusCodes.BadTcpSecureChannelUnknown, "Token #{0} has expired. Lifetime={1:HH:mm:ss.fff}", token.TokenId, token.CreatedAt); } int headerSize = decoder.Position; if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { // decrypt the message. Decrypt(token, new ArraySegment <byte>(buffer.Array, buffer.Offset + headerSize, buffer.Count - headerSize), isRequest); } if (SecurityMode != MessageSecurityMode.None) { // extract signature. byte[] signature = new byte[SymmetricSignatureSize]; for (int ii = 0; ii < SymmetricSignatureSize; ii++) { signature[ii] = buffer.Array[buffer.Offset + buffer.Count - SymmetricSignatureSize + ii]; } // verify the signature. if (!Verify(token, signature, new ArraySegment <byte>(buffer.Array, buffer.Offset, buffer.Count - SymmetricSignatureSize), isRequest)) { Utils.Trace("Could not verify signature on message."); throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Could not verify the signature on the message."); } } int paddingCount = 0; if (SecurityMode == MessageSecurityMode.SignAndEncrypt) { // verify padding. int paddingStart = buffer.Offset + buffer.Count - SymmetricSignatureSize - 1; paddingCount = buffer.Array[paddingStart]; for (int ii = paddingStart - paddingCount; ii < paddingStart; ii++) { if (buffer.Array[ii] != paddingCount) { throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Could not verify the padding in the message."); } } // add byte for size. paddingCount++; } // extract request id and sequence number. sequenceNumber = decoder.ReadUInt32(null); requestId = decoder.ReadUInt32(null); // return an the data contained in the message. int startOfBody = buffer.Offset + TcpMessageLimits.SymmetricHeaderSize + TcpMessageLimits.SequenceHeaderSize; int sizeOfBody = buffer.Count - TcpMessageLimits.SymmetricHeaderSize - TcpMessageLimits.SequenceHeaderSize - paddingCount - SymmetricSignatureSize; return(new ArraySegment <byte>(buffer.Array, startOfBody, sizeOfBody)); }
/// <summary> /// Deletes the recent history. /// </summary> private void DeleteHistory(NodeId areaId, List <VariantCollection> events, FilterDeclaration filter) { // find the event id. int index = 0; foreach (FilterDeclarationField field in filter.Fields) { if (field.InstanceDeclaration.BrowseName == Opc.Ua.BrowseNames.EventId) { break; } index++; } // can't delete events if no event id. if (index >= filter.Fields.Count) { throw ServiceResultException.Create(StatusCodes.BadEventIdUnknown, "Cannot delete events if EventId was not selected."); } // build list of nodes to delete. DeleteEventDetails details = new DeleteEventDetails(); details.NodeId = areaId; foreach (VariantCollection e in events) { byte[] eventId = null; if (e.Count > index) { eventId = e[index].Value as byte[]; } details.EventIds.Add(eventId); } // delete the events. ExtensionObjectCollection nodesToUpdate = new ExtensionObjectCollection(); nodesToUpdate.Add(new ExtensionObject(details)); HistoryUpdateResultCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; m_session.HistoryUpdate( null, nodesToUpdate, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToUpdate); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToUpdate); if (StatusCode.IsBad(results[0].StatusCode)) { throw new ServiceResultException(results[0].StatusCode); } // check for item level errors. if (results[0].OperationResults.Count > 0) { int count = 0; for (int ii = 0; ii < results[0].OperationResults.Count; ii++) { if (StatusCode.IsBad(results[0].OperationResults[ii])) { count++; } } // raise an error. if (count > 0) { throw ServiceResultException.Create( StatusCodes.BadEventIdUnknown, "Error deleting events. Only {0} of {1} deletes succeeded.", events.Count - count, events.Count); } } }
/// <summary> /// Writes the contents of an Variant to the stream. /// </summary> /// <param name="value"></param> /// <param name="valueRank"></param> /// <param name="builtInType"></param> private void WriteVariantContents(object value, int valueRank, BuiltInType builtInType) { // write scalar. if (valueRank < 0) { switch (builtInType) { case BuiltInType.Null: WriteNull(null); return; case BuiltInType.Boolean: WriteBoolean(null, (bool)value); return; case BuiltInType.SByte: WriteSByte(null, (sbyte)value); return; case BuiltInType.Byte: WriteByte(null, (byte)value); return; case BuiltInType.Int16: WriteInt16(null, (short)value); return; case BuiltInType.UInt16: WriteUInt16(null, (ushort)value); return; case BuiltInType.Int32: WriteInt32(null, (int)value); return; case BuiltInType.UInt32: WriteUInt32(null, (uint)value); return; case BuiltInType.Int64: WriteInt64(null, (long)value); return; case BuiltInType.UInt64: WriteUInt64(null, (ulong)value); return; case BuiltInType.Float: WriteFloat(null, (float)value); return; case BuiltInType.Double: WriteDouble(null, (double)value); return; case BuiltInType.String: WriteString(null, (string)value); return; case BuiltInType.DateTime: WriteDateTime(null, (DateTime)value); return; case BuiltInType.Guid: WriteGuid(null, (Uuid)value); return; case BuiltInType.ByteString: WriteByteString(null, (byte[])value); return; case BuiltInType.XmlElement: WriteXmlElement(null, (XmlElement)value); return; case BuiltInType.NodeId: WriteNodeId(null, (NodeId)value); return; case BuiltInType.ExpandedNodeId: WriteExpandedNodeId(null, (ExpandedNodeId)value); return; case BuiltInType.StatusCode: WriteStatusCode(null, (StatusCode)value); return; case BuiltInType.QualifiedName: WriteQualifiedName(null, (QualifiedName)value); return; case BuiltInType.LocalizedText: WriteLocalizedText(null, (LocalizedText)value); return; case BuiltInType.ExtensionObject: WriteExtensionObject(null, (ExtensionObject)value); return; case BuiltInType.DataValue: WriteDataValue(null, (DataValue)value); return; case BuiltInType.Enumeration: WriteInt32(null, (int)value); return; case BuiltInType.Number: case BuiltInType.Integer: case BuiltInType.UInteger: case BuiltInType.Variant: throw ServiceResultException.Create(StatusCodes.BadEncodingError, "Unexpected type encountered while encoding variant " + value.GetType()); } } // write array. else if (valueRank <= 1) { switch (builtInType) { case BuiltInType.Null: WriteNull(null); return; case BuiltInType.Boolean: WriteBooleanArray(null, (bool[])value); return; case BuiltInType.SByte: WriteSByteArray(null, (sbyte[])value); return; case BuiltInType.Byte: WriteByteArray(null, (byte[])value); return; case BuiltInType.Int16: WriteInt16Array(null, (short[])value); return; case BuiltInType.UInt16: WriteUInt16Array(null, (ushort[])value); return; case BuiltInType.Int32: WriteInt32Array(null, (int[])value); return; case BuiltInType.UInt32: WriteUInt32Array(null, (uint[])value); return; case BuiltInType.Int64: WriteInt64Array(null, (long[])value); return; case BuiltInType.UInt64: WriteUInt64Array(null, (ulong[])value); return; case BuiltInType.Float: WriteFloatArray(null, (float[])value); return; case BuiltInType.Double: WriteDoubleArray(null, (double[])value); return; case BuiltInType.String: WriteStringArray(null, (string[])value); return; case BuiltInType.DateTime: WriteDateTimeArray(null, (DateTime[])value); return; case BuiltInType.Guid: WriteGuidArray(null, (Uuid[])value); return; case BuiltInType.ByteString: WriteByteStringArray(null, (byte[][])value); return; case BuiltInType.XmlElement: WriteXmlElementArray(null, (XmlElement[])value); return; case BuiltInType.NodeId: WriteNodeIdArray(null, (NodeId[])value); return; case BuiltInType.ExpandedNodeId: WriteExpandedNodeIdArray(null, (ExpandedNodeId[])value); return; case BuiltInType.StatusCode: WriteStatusCodeArray(null, (StatusCode[])value); return; case BuiltInType.QualifiedName: WriteQualifiedNameArray(null, (QualifiedName[])value); return; case BuiltInType.LocalizedText: WriteLocalizedTextArray(null, (LocalizedText[])value); return; case BuiltInType.ExtensionObject: WriteExtensionObjectArray(null, (ExtensionObject[])value); return; case BuiltInType.DataValue: WriteDataValueArray(null, (DataValue[])value); return; case BuiltInType.Enumeration: var enums = value as Enum[]; var values = new string[enums.Length]; for (var index = 0; index < enums.Length; index++) { var text = enums[index].ToString(); text += "_"; text += ((int)(object)enums[index]).ToString(CultureInfo.InvariantCulture); values[index] = text; } WriteStringArray(null, values); return; case BuiltInType.Number: case BuiltInType.UInteger: case BuiltInType.Integer: case BuiltInType.Variant: if (value is Variant[] variants) { WriteVariantArray(null, variants); return; } if (value is object[] objects) { WriteObjectArray(null, objects); return; } throw ServiceResultException.Create(StatusCodes.BadEncodingError, "Unexpected type encountered while encoding an array" + $" of Variants:{value.GetType()}"); } } else { if (value == null) { WriteNull(null); return; } if (value is Matrix matrix) { var index = 0; WriteMatrix(matrix, 0, ref index, builtInType); return; } } // oops - should never happen. throw new ServiceResultException(StatusCodes.BadEncodingError, $"Type '{value.GetType().FullName}' is not allowed in an Variant."); }
/// <summary> /// Starts listening at the specified port. /// </summary> public void Start() { lock (m_lock) { // ensure a valid port. int port = m_uri.Port; if (port <= 0 || port > UInt16.MaxValue) { port = Utils.UaTcpDefaultPort; } // create IPv4 socket. try { IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, port); m_listeningSocket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); SocketAsyncEventArgs args = new SocketAsyncEventArgs(); args.Completed += OnAccept; args.UserToken = m_listeningSocket; m_listeningSocket.Bind(endpoint); m_listeningSocket.Listen(Int32.MaxValue); if (!m_listeningSocket.AcceptAsync(args)) { OnAccept(null, args); } } catch (Exception ex) { // no IPv4 support. m_listeningSocket = null; Utils.Trace("failed to create IPv4 listening socket: " + ex.Message); } // create IPv6 socket try { IPEndPoint endpointIPv6 = new IPEndPoint(IPAddress.IPv6Any, port); m_listeningSocketIPv6 = new Socket(endpointIPv6.AddressFamily, SocketType.Stream, ProtocolType.Tcp); SocketAsyncEventArgs args = new SocketAsyncEventArgs(); args.Completed += OnAccept; args.UserToken = m_listeningSocketIPv6; m_listeningSocketIPv6.Bind(endpointIPv6); m_listeningSocketIPv6.Listen(Int32.MaxValue); if (!m_listeningSocketIPv6.AcceptAsync(args)) { OnAccept(null, args); } } catch (Exception ex) { // no IPv6 support m_listeningSocketIPv6 = null; Utils.Trace("failed to create IPv6 listening socket: " + ex.Message); } if (m_listeningSocketIPv6 == null && m_listeningSocket == null) { throw ServiceResultException.Create( StatusCodes.BadNoCommunication, "Failed to establish tcp listener sockets for Ipv4 and IPv6.\r\n"); } } }
/// <summary> /// Activates an existing session /// </summary> public virtual bool ActivateSession( OperationContext context, NodeId authenticationToken, SignatureData clientSignature, List <SoftwareCertificate> clientSoftwareCertificates, ExtensionObject userIdentityToken, SignatureData userTokenSignature, StringCollection localeIds, out byte[] serverNonce) { serverNonce = null; Session session = null; UserIdentityToken newIdentity = null; UserTokenPolicy userTokenPolicy = null; lock (m_lock) { // find session. if (!m_sessions.TryGetValue(authenticationToken, out session)) { throw new ServiceResultException(StatusCodes.BadSessionIdInvalid); } // check if session timeout has expired. if (session.HasExpired) { // raise audit event for session closed because of timeout m_server.ReportAuditCloseSessionEvent(null, session, "Session/Timeout"); m_server.CloseSession(null, session.Id, false); throw new ServiceResultException(StatusCodes.BadSessionClosed); } // create new server nonce. serverNonce = Utils.Nonce.CreateNonce((uint)m_minNonceLength); // validate before activation. session.ValidateBeforeActivate( context, clientSignature, clientSoftwareCertificates, userIdentityToken, userTokenSignature, localeIds, serverNonce, out newIdentity, out userTokenPolicy); } IUserIdentity identity = null; IUserIdentity effectiveIdentity = null; ServiceResult error = null; try { // check if the application has a callback which validates the identity tokens. lock (m_eventLock) { if (m_impersonateUser != null) { ImpersonateEventArgs args = new ImpersonateEventArgs(newIdentity, userTokenPolicy, context.ChannelContext.EndpointDescription); m_impersonateUser(session, args); if (ServiceResult.IsBad(args.IdentityValidationError)) { error = args.IdentityValidationError; } else { identity = args.Identity; effectiveIdentity = args.EffectiveIdentity; } } } // parse the token manually if the identity is not provided. if (identity == null) { identity = newIdentity != null ? new UserIdentity(newIdentity) : new UserIdentity(); } // use the identity as the effectiveIdentity if not provided. if (effectiveIdentity == null) { effectiveIdentity = identity; } } catch (Exception e) { if (e is ServiceResultException) { throw; } throw ServiceResultException.Create( StatusCodes.BadIdentityTokenInvalid, e, "Could not validate user identity token: {0}", newIdentity); } // check for validation error. if (ServiceResult.IsBad(error)) { throw new ServiceResultException(error); } // activate session. bool contextChanged = session.Activate( context, clientSoftwareCertificates, newIdentity, identity, effectiveIdentity, localeIds, serverNonce); // raise session related event. if (contextChanged) { RaiseSessionEvent(session, SessionEventReason.Activated); } // indicates that the identity context for the session has changed. return(contextChanged); }
/// <summary> /// Browses the specified node. /// </summary> public ReferenceDescriptionCollection Browse(NodeId nodeId) { if (m_session == null) { throw new ServiceResultException(StatusCodes.BadServerNotConnected, "Cannot browse if not connected to a server."); } try { m_browseInProgress = true; // construct request. BrowseDescription nodeToBrowse = new BrowseDescription(); nodeToBrowse.NodeId = nodeId; nodeToBrowse.BrowseDirection = m_browseDirection; nodeToBrowse.ReferenceTypeId = m_referenceTypeId; nodeToBrowse.IncludeSubtypes = m_includeSubtypes; nodeToBrowse.NodeClassMask = m_nodeClassMask; nodeToBrowse.ResultMask = m_resultMask; BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); nodesToBrowse.Add(nodeToBrowse); // make the call to the server. BrowseResultCollection results; DiagnosticInfoCollection diagnosticInfos; ResponseHeader responseHeader = m_session.Browse( null, m_view, m_maxReferencesReturned, nodesToBrowse, out results, out diagnosticInfos); // ensure that the server returned valid results. Session.ValidateResponse(results, nodesToBrowse); Session.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); // check if valid. if (StatusCode.IsBad(results[0].StatusCode)) { throw ServiceResultException.Create(results[0].StatusCode, 0, diagnosticInfos, responseHeader.StringTable); } // fetch initial set of references. byte[] continuationPoint = results[0].ContinuationPoint; ReferenceDescriptionCollection references = results[0].References; // process any continuation point. while (continuationPoint != null) { ReferenceDescriptionCollection additionalReferences; if (!m_continueUntilDone && m_MoreReferences != null) { BrowserEventArgs args = new BrowserEventArgs(references); m_MoreReferences(this, args); // cancel browser and return the references fetched so far. if (args.Cancel) { BrowseNext(ref continuationPoint, true); return(references); } m_continueUntilDone = args.ContinueUntilDone; } additionalReferences = BrowseNext(ref continuationPoint, false); if (additionalReferences != null && additionalReferences.Count > 0) { references.AddRange(additionalReferences); } else { Utils.Trace("Continuation point exists, but the browse results are null/empty."); break; } } // return the results. return(references); } finally { m_browseInProgress = false; } }
/// <summary> /// Creates a session with the endpoint. /// </summary> public Session Connect(ConfiguredEndpoint endpoint) { if (endpoint == null) { throw new ArgumentNullException("endpoint"); } EndpointDescriptionCollection availableEndpoints = null; // check if the endpoint needs to be updated. if (endpoint.UpdateBeforeConnect) { ConfiguredServerDlg configurationDialog = new ConfiguredServerDlg(); endpoint = configurationDialog.ShowDialog(endpoint, m_configuration); if (endpoint == null) { return(null); } availableEndpoints = configurationDialog.AvailableEnpoints; } m_endpoint = endpoint; // copy the message context. m_messageContext = m_configuration.CreateMessageContext(); X509Certificate2 clientCertificate = null; //X509Certificate2Collection clientCertificateChain = null; if (endpoint.Description.SecurityPolicyUri != SecurityPolicies.None) { if (m_configuration.SecurityConfiguration.ApplicationCertificate == null) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate must be specified."); } clientCertificate = m_configuration.SecurityConfiguration.ApplicationCertificate.Find(true); if (clientCertificate == null) { throw ServiceResultException.Create(StatusCodes.BadConfigurationError, "ApplicationCertificate cannot be found."); } //load certificate chain //clientCertificateChain = new X509Certificate2Collection(clientCertificate); //List<CertificateIdentifier> issuers = new List<CertificateIdentifier>(); //m_configuration.CertificateValidator.GetIssuers(clientCertificate, issuers); //for (int i = 0; i < issuers.Count; i++) //{ // clientCertificateChain.Add(issuers[i].Certificate); //} } // create the channel. ITransportChannel channel = SessionChannel.Create( m_configuration, endpoint.Description, endpoint.Configuration, //clientCertificateChain, clientCertificate, m_messageContext); // create the session. return(Connect(endpoint, channel, availableEndpoints)); }
/// <summary> /// Creates a session with the server. /// </summary> /// <param name="state">The state.</param> private void OnReconnectSession(object state) { // get the CLSID of the COM server. Session session = state as Session; if (session == null) { return; } // check if nothing to do. lock (m_lock) { if (!m_running || !Object.ReferenceEquals(m_session, session)) { return; } // stop the reconnect timer. if (m_reconnectTimer != null) { m_reconnectTimer.Dispose(); m_reconnectTimer = null; } } // reconnect the session. try { session.Reconnect(); lock (m_lock) { if (!m_running || !Object.ReferenceEquals(m_session, session)) { session.Dispose(); return; } } OnSessionReconected(); } catch (Exception e) { Utils.Trace("Unexpected reconnecting a Session with the UA Server. {0}", e.Message); // schedule a reconnect. lock (m_lock) { // check if session has been replaced. if (!m_running || !Object.ReferenceEquals(m_session, session)) { session.Dispose(); return; } // check if the session has been closed. ServiceResultException sre = e as ServiceResultException; if (sre == null || sre.StatusCode != StatusCodes.BadSessionClosed) { m_session = null; session.Dispose(); OnSessionRemoved(); ThreadPool.QueueUserWorkItem(OnCreateSession, null); Utils.Trace("Calling OnCreateSession NOW."); return; } // check if reconnecting is still an option. if (m_lastKeepAliveTime.AddMilliseconds(session.SessionTimeout) > DateTime.UtcNow) { m_reconnectTimer = new Timer(OnReconnectSession, session, 20000, Timeout.Infinite); Utils.Trace("Calling OnReconnectSession in 20000ms."); OnReconnectInProgress(20); return; } // give up and re-create the session. m_session = null; session.Dispose(); OnSessionRemoved(); m_reconnectTimer = new Timer(OnCreateSession, null, 20000, Timeout.Infinite); Utils.Trace("Calling OnCreateSession in 20000ms."); OnReconnectInProgress(20); } } }
/// <summary> /// Processes an OpenSecureChannel request message. /// </summary> private bool ProcessOpenSecureChannelRequest(uint messageType, ArraySegment <byte> messageChunk) { // validate the channel state. if (State != TcpChannelState.Opening && State != TcpChannelState.Open) { ForceChannelFault(StatusCodes.BadTcpMessageTypeInvalid, "Client sent an unexpected OpenSecureChannel message."); return(false); } // parse the security header. uint channelId = 0; X509Certificate2 clientCertificate = null; uint requestId = 0; uint sequenceNumber = 0; ArraySegment <byte> messageBody; try { messageBody = ReadAsymmetricMessage( messageChunk, ServerCertificate, out channelId, out clientCertificate, out requestId, out sequenceNumber); // check for replay attacks. if (!VerifySequenceNumber(sequenceNumber, "ProcessOpenSecureChannelRequest")) { throw new ServiceResultException(StatusCodes.BadSequenceNumberInvalid); } } catch (Exception e) { ServiceResultException innerException = e.InnerException as ServiceResultException; // If the certificate structre, signare and trust list checks pass, we return the other specific validation errors instead of BadSecurityChecksFailed if (innerException != null && ( innerException.StatusCode == StatusCodes.BadCertificateTimeInvalid || innerException.StatusCode == StatusCodes.BadCertificateIssuerTimeInvalid || innerException.StatusCode == StatusCodes.BadCertificateHostNameInvalid || innerException.StatusCode == StatusCodes.BadCertificateUriInvalid || innerException.StatusCode == StatusCodes.BadCertificateUseNotAllowed || innerException.StatusCode == StatusCodes.BadCertificateIssuerUseNotAllowed || innerException.StatusCode == StatusCodes.BadCertificateRevocationUnknown || innerException.StatusCode == StatusCodes.BadCertificateIssuerRevocationUnknown || innerException.StatusCode == StatusCodes.BadCertificateRevoked || innerException.StatusCode == StatusCodes.BadCertificateIssuerRevoked)) { ForceChannelFault(innerException, innerException.StatusCode, e.Message); return(false); } else { ForceChannelFault(e, StatusCodes.BadSecurityChecksFailed, "Could not verify security on OpenSecureChannel request."); return(false); } } BufferCollection chunksToProcess = null; try { bool firstCall = ClientCertificate == null; // must ensure the same certificate was used. if (ClientCertificate != null) { CompareCertificates(ClientCertificate, clientCertificate, false); } else { ClientCertificate = clientCertificate; } // check if it is necessary to wait for more chunks. if (!TcpMessageType.IsFinal(messageType)) { SaveIntermediateChunk(requestId, messageBody); return(false); } // create a new token. TcpChannelToken token = CreateToken(); token.TokenId = GetNewTokenId(); token.ServerNonce = CreateNonce(); // get the chunks to process. chunksToProcess = GetSavedChunks(requestId, messageBody); OpenSecureChannelRequest request = (OpenSecureChannelRequest)BinaryDecoder.DecodeMessage( new ArraySegmentStream(chunksToProcess), typeof(OpenSecureChannelRequest), Quotas.MessageContext); if (request == null) { throw ServiceResultException.Create(StatusCodes.BadStructureMissing, "Could not parse OpenSecureChannel request body."); } // check the security mode. if (request.SecurityMode != SecurityMode) { ReviseSecurityMode(firstCall, request.SecurityMode); } // check the client nonce. token.ClientNonce = request.ClientNonce; if (!ValidateNonce(token.ClientNonce)) { throw ServiceResultException.Create(StatusCodes.BadNonceInvalid, "Client nonce is not the correct length or not random enough."); } // choose the lifetime. int lifetime = (int)request.RequestedLifetime; if (lifetime < TcpMessageLimits.MinSecurityTokenLifeTime) { lifetime = TcpMessageLimits.MinSecurityTokenLifeTime; } if (lifetime > 0 && lifetime < token.Lifetime) { token.Lifetime = lifetime; } // check the request type. SecurityTokenRequestType requestType = request.RequestType; if (requestType == SecurityTokenRequestType.Issue && State != TcpChannelState.Opening) { throw ServiceResultException.Create(StatusCodes.BadRequestTypeInvalid, "Cannot request a new token for an open channel."); } if (requestType == SecurityTokenRequestType.Renew && State != TcpChannelState.Open) { // may be reconnecting to a dropped channel. if (State == TcpChannelState.Opening) { // tell the listener to find the channel that can process the request. m_listener.ReconnectToExistingChannel( Socket, requestId, sequenceNumber, channelId, ClientCertificate, token, request); Utils.Trace( "TCPSERVERCHANNEL ReconnectToExistingChannel Socket={0:X8}, ChannelId={1}, TokenId={2}", (Socket != null)?Socket.Handle:0, (CurrentToken != null)?CurrentToken.ChannelId:0, (CurrentToken != null)?CurrentToken.TokenId:0); // close the channel. ChannelClosed(); // nothing more to do. return(false); } throw ServiceResultException.Create(StatusCodes.BadRequestTypeInvalid, "Cannot request to rewew a token for a channel that has not been opened."); } // check the channel id. if (requestType == SecurityTokenRequestType.Renew && channelId != ChannelId) { throw ServiceResultException.Create(StatusCodes.BadTcpSecureChannelUnknown, "Do not recognize the secure channel id provided."); } // log security information. if (requestType == SecurityTokenRequestType.Issue) { Opc.Ua.Security.Audit.SecureChannelCreated( g_ImplementationString, this.m_listener.EndpointUrl.ToString(), Utils.Format("{0}", this.ChannelId), this.EndpointDescription, this.ClientCertificate, this.ServerCertificate, BinaryEncodingSupport.Required); } else { Opc.Ua.Security.Audit.SecureChannelRenewed( g_ImplementationString, Utils.Format("{0}", this.ChannelId)); } if (requestType == SecurityTokenRequestType.Renew) { SetRenewedToken(token); } else { ActivateToken(token); } State = TcpChannelState.Open; // send the response. SendOpenSecureChannelResponse(requestId, token, request); return(false); } catch (Exception e) { SendServiceFault(requestId, ServiceResult.Create(e, StatusCodes.BadTcpInternalError, "Unexpected error processing OpenSecureChannel request.")); return(false); } finally { if (chunksToProcess != null) { chunksToProcess.Release(BufferManager, "ProcessOpenSecureChannelRequest"); } } }
/// <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); encoder.WriteUInt32(null, secureChannelId); encoder.WriteString(null, securityPolicyUri); if (SecurityMode != MessageSecurityMode.None) { 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 { encoder.WriteByteString(null, null); encoder.WriteByteString(null, null); } if (encoder.Position - start > SendBufferSize) { throw ServiceResultException.Create( StatusCodes.BadInternalError, "AsymmetricSecurityHeader is {0} bytes which is too large for the send buffer size of {1} bytes.", encoder.Position - start, SendBufferSize); } }
/// <summary> /// Creates the response header. /// </summary> /// <param name="requestHeader">The object that contains description for the RequestHeader DataType.</param> /// <param name="exception">The exception used to create DiagnosticInfo assigned to the ServiceDiagnostics.</param> /// <returns>Returns a description for the ResponseHeader DataType. </returns> protected ResponseHeader CreateResponse(RequestHeader requestHeader, ServiceResultException exception) { ResponseHeader responseHeader = new ResponseHeader(); responseHeader.ServiceResult = exception.StatusCode; responseHeader.Timestamp = DateTime.UtcNow; responseHeader.RequestHandle = requestHeader.RequestHandle; StringTable stringTable = new StringTable(); responseHeader.ServiceDiagnostics = new DiagnosticInfo(exception, (DiagnosticsMasks)requestHeader.ReturnDiagnostics, true, stringTable); responseHeader.StringTable = stringTable.ToArray(); return responseHeader; }
protected void ReadAsymmetricMessageHeader( BinaryDecoder decoder, X509Certificate2 receiverCertificate, out uint secureChannelId, out X509Certificate2Collection senderCertificateChain, out string securityPolicyUri) { senderCertificateChain = null; uint messageType = decoder.ReadUInt32(null); uint messageSize = decoder.ReadUInt32(null); // decode security header. byte[] certificateData = null; byte[] thumbprintData = null; try { secureChannelId = decoder.ReadUInt32(null); securityPolicyUri = decoder.ReadString(null, TcpMessageLimits.MaxSecurityPolicyUriSize); certificateData = decoder.ReadByteString(null, TcpMessageLimits.MaxCertificateSize); thumbprintData = decoder.ReadByteString(null, TcpMessageLimits.CertificateThumbprintSize); } catch (Exception e) { throw ServiceResultException.Create( StatusCodes.BadSecurityChecksFailed, e, "The asymmetric security header could not be parsed."); } // verify sender certificate chain. if (certificateData != null && certificateData.Length > 0) { senderCertificateChain = Utils.ParseCertificateChainBlob(certificateData); try { string thumbprint = senderCertificateChain[0].Thumbprint; if (thumbprint == null) { throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, "Invalid certificate thumbprint."); } } catch (Exception e) { throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, e, "The sender's certificate could not be parsed."); } } else { if (securityPolicyUri != SecurityPolicies.None) { throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, "The sender's certificate was not specified."); } } // verify receiver thumbprint. if (thumbprintData != null && thumbprintData.Length > 0) { if (receiverCertificate.Thumbprint.ToUpperInvariant() != GetThumbprintString(thumbprintData)) { throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, "The receiver's certificate thumbprint is not valid."); } } else { if (securityPolicyUri != SecurityPolicies.None) { throw ServiceResultException.Create(StatusCodes.BadCertificateInvalid, "The receiver's certificate thumbprint was not specified."); } } }
/// <summary> /// Handles a certificate validation error. /// </summary> private bool HandleValidationError(X509Certificate2 certificate, ServiceResultException e) { StringBuilder buffer = new StringBuilder(); switch (e.StatusCode) { case StatusCodes.BadCertificateIssuerRevocationUnknown: { buffer.AppendFormat("Could not determine whether the issuing certificate was revoked."); buffer.Append("\r\n"); buffer.Append("Would you still like to accept the certificate?\r\n"); break; } case StatusCodes.BadCertificateIssuerTimeInvalid: { buffer.AppendFormat("The issuing certificate has expired or is not yet valid."); buffer.Append("\r\n"); buffer.Append("Would you still like to accept the certificate?\r\n"); break; } case StatusCodes.BadCertificateRevocationUnknown: { buffer.AppendFormat("Could not determine whether the certificate was revoked by the Certificate Authority."); buffer.Append("\r\n"); buffer.Append("Would you still like to accept the certificate?\r\n"); break; } case StatusCodes.BadCertificateTimeInvalid: { buffer.AppendFormat("The certificate has expired or is not yet valid."); buffer.Append("\r\n"); buffer.Append("Would you still like to accept the certificate?\r\n"); buffer.Append("\r\n"); buffer.Append("Certificate = "); buffer.Append(certificate.Subject); buffer.Append("\r\n"); buffer.Append("Valid From = "); buffer.Append(certificate.NotBefore.ToLocalTime()); buffer.Append("\r\n"); buffer.Append("Valid To = "); buffer.Append(certificate.NotBefore.ToLocalTime()); break; } case StatusCodes.BadCertificateUntrusted: { if (Utils.CompareDistinguishedName(certificate.Issuer, certificate.Subject)) { return true; } buffer.Append("This certificates was issued by an unknown Certificate Authority."); buffer.Append("This means it could have been altered and there is no way to detect changes."); buffer.Append("You should only accept it if you are absolutely certain the certificate has come via a secure channel from a legimate source."); buffer.Append("\r\n"); buffer.Append("\r\n"); buffer.Append("Would you still like to accept the certificate?\r\n"); buffer.Append("\r\n"); buffer.Append("Certificate = "); buffer.Append(certificate.Subject); buffer.Append("\r\n"); buffer.Append("Certificate Authority = "); buffer.Append(certificate.Issuer); break; } default: { buffer.Append("An error that cannot be ignored occurred during validation.\r\n"); buffer.Append("\r\n"); buffer.Append("Certificate = "); buffer.Append(certificate.Subject); buffer.Append("\r\n"); buffer.Append("ErrorCode = "); buffer.Append(StatusCodes.GetBrowseName(e.StatusCode)); buffer.Append("\r\n"); buffer.Append("Message = "); buffer.Append(e.Message); new YesNoDlg().ShowDialog(buffer.ToString(), "Certificate Validation Error"); return false; } } DialogResult result = new YesNoDlg().ShowDialog(buffer.ToString(), "Certificate Validation Error"); if (result != DialogResult.Yes) { return false; } return true; }
/// <summary> /// Processes an OpenSecureChannel request message. /// </summary> protected ArraySegment <byte> ReadAsymmetricMessage( ArraySegment <byte> buffer, X509Certificate2 receiverCertificate, out uint channelId, out X509Certificate2 senderCertificate, out uint requestId, out uint sequenceNumber) { 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 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) { 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. if (!m_uninitialized) { if (securityPolicyUri != m_securityPolicyUri) { throw ServiceResultException.Create(StatusCodes.BadSecurityPolicyRejected, "Cannot change the security policy after creating the channnel."); } } else { // find a matching endpoint description. if (m_endpoints != null) { foreach (EndpointDescription endpoint in m_endpoints) { // There may be multiple endpoints with the same securityPolicyUri. // Just choose the first one that matches. This choice will be re-examined // When the OpenSecureChannel request body is processed. if (endpoint.SecurityPolicyUri == securityPolicyUri || (securityPolicyUri == SecurityPolicies.None && endpoint.SecurityMode == MessageSecurityMode.None)) { m_securityMode = endpoint.SecurityMode; m_securityPolicyUri = securityPolicyUri; m_discoveryOnly = false; m_uninitialized = false; m_selectedEndpoint = endpoint; // recalculate the key sizes. CalculateSymmetricKeySizes(); break; } } } // allow a discovery only channel with no security if policy not suppported if (m_uninitialized) { if (securityPolicyUri != SecurityPolicies.None) { throw ServiceResultException.Create(StatusCodes.BadSecurityPolicyRejected, "The security policy is not supported."); } m_securityMode = MessageSecurityMode.None; m_securityPolicyUri = SecurityPolicies.None; m_discoveryOnly = true; m_uninitialized = false; m_selectedEndpoint = null; } } int headerSize = decoder.Position; // decrypt the body. ArraySegment <byte> plainText = Decrypt( new ArraySegment <byte>(buffer.Array, buffer.Offset + headerSize, buffer.Count - headerSize), new ArraySegment <byte>(buffer.Array, buffer.Offset, headerSize), receiverCertificate); // extract signature. int signatureSize = GetAsymmetricSignatureSize(senderCertificate); byte[] signature = new byte[signatureSize]; for (int ii = 0; ii < signatureSize; ii++) { signature[ii] = plainText.Array[plainText.Offset + plainText.Count - signatureSize + ii]; } // verify the signature. ArraySegment <byte> dataToVerify = new ArraySegment <byte>(plainText.Array, plainText.Offset, plainText.Count - signatureSize); if (!Verify(dataToVerify, signature, senderCertificate)) { Utils.Trace("Could not verify signature on message."); throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Could not verify the signature on the message."); } // verify padding. int paddingCount = 0; if (SecurityMode != MessageSecurityMode.None) { int paddingEnd = -1; if (CertificateFactory.GetRSAPublicKeySize(receiverCertificate) > TcpMessageLimits.KeySizeExtraPadding) { paddingEnd = plainText.Offset + plainText.Count - signatureSize - 1; paddingCount = plainText.Array[paddingEnd - 1] + plainText.Array[paddingEnd] * 256; //parse until paddingStart-1; the last one is actually the extrapaddingsize for (int ii = paddingEnd - paddingCount; ii < paddingEnd; ii++) { if (plainText.Array[ii] != plainText.Array[paddingEnd - 1]) { throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Could not verify the padding in the message."); } } } else { paddingEnd = plainText.Offset + plainText.Count - signatureSize - 1; paddingCount = plainText.Array[paddingEnd]; for (int ii = paddingEnd - paddingCount; ii < paddingEnd; ii++) { if (plainText.Array[ii] != plainText.Array[paddingEnd]) { throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, "Could not verify the padding in the message."); } } } paddingCount++; } // decode message. decoder = new BinaryDecoder( plainText.Array, plainText.Offset + headerSize, plainText.Count - headerSize, Quotas.MessageContext); sequenceNumber = decoder.ReadUInt32(null); requestId = decoder.ReadUInt32(null); headerSize += decoder.Position; decoder.Close(); Utils.Trace("Security Policy: {0}", SecurityPolicyUri); Utils.Trace("Sender Certificate: {0}", (senderCertificate != null) ? senderCertificate.Subject : "(none)"); // return the body. return(new ArraySegment <byte>( plainText.Array, plainText.Offset + headerSize, plainText.Count - headerSize - signatureSize - paddingCount)); }
/// <summary> /// Translates an exception. /// </summary> /// <param name="diagnosticsMasks">The fields to return.</param> /// <param name="preferredLocales">The preferred locales.</param> /// <param name="e">The ServiceResultException e.</param> /// <returns>Returns an exception thrown when a UA defined error occurs, the return type is <seealso cref="ServiceResultException"/>.</returns> protected virtual ServiceResultException TranslateException(DiagnosticsMasks diagnosticsMasks, IList<string> preferredLocales, ServiceResultException e) { if (e == null) { return null; } // check if inner result required. ServiceResult innerResult = null; if ((diagnosticsMasks & (DiagnosticsMasks.ServiceInnerDiagnostics | DiagnosticsMasks.ServiceInnerStatusCode)) != 0) { innerResult = e.InnerResult; } // check if translated text required. LocalizedText translatedText = null; if ((diagnosticsMasks & DiagnosticsMasks.ServiceLocalizedText) != 0) { translatedText = e.LocalizedText; } // create new result object. ServiceResult result = new ServiceResult( e.StatusCode, e.SymbolicId, e.NamespaceUri, translatedText, e.AdditionalInfo, innerResult); // translate result. result = m_serverInternal.ResourceManager.Translate(preferredLocales, result); return new ServiceResultException(result); }
/// <summary> /// Updates the security configuration for an application identified by a file or url. /// </summary> /// <param name="filePath">The file path.</param> /// <param name="configuration">The configuration.</param> public void WriteConfiguration(string filePath, SecuredApplication configuration) { if (configuration == null) { throw new ArgumentNullException("configuration"); } // check for valid file. if (String.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { throw ServiceResultException.Create( StatusCodes.BadNotReadable, "Cannot find the configuration file: {0}", configuration.ConfigurationFile); } // load from file. XmlDocument document = new XmlDocument(); document.Load(filePath); XmlElement element = Find(document.DocumentElement, "SecuredApplication", Namespaces.OpcUaSecurity); // update secured application. if (element != null) { configuration.LastExportTime = DateTime.UtcNow; element.InnerXml = SetObject(typeof(SecuredApplication), configuration); } // update application configuration. else { UpdateDocument(document.DocumentElement, configuration); } try { // update configuration file. Stream ostrm = File.Open(filePath, FileMode.Create, FileAccess.Write); XmlTextWriter writer = new XmlTextWriter(ostrm, System.Text.Encoding.UTF8); writer.Formatting = Formatting.Indented; try { document.Save(writer); } finally { writer.Close(); } } catch (Exception e) { throw ServiceResultException.Create( StatusCodes.BadNotWritable, e, "Cannot update the configuration file: {0}", configuration.ConfigurationFile); } }