UNPKG

node-opcua-server

Version:

pure nodejs OPCUA SDK - module server

1,004 lines (1,003 loc) 135 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OPCUAServer = exports.RegisterServerMethod = void 0; exports.filterDiagnosticInfo = filterDiagnosticInfo; /* eslint-disable complexity */ /** * @module node-opcua-server */ const crypto_1 = require("crypto"); const util_1 = require("util"); const async_1 = __importDefault(require("async")); const chalk_1 = __importDefault(require("chalk")); const node_opcua_hostname_1 = require("node-opcua-hostname"); const node_opcua_assert_1 = require("node-opcua-assert"); const node_opcua_utils_1 = require("node-opcua-utils"); const node_opcua_address_space_1 = require("node-opcua-address-space"); const node_opcua_certificate_manager_1 = require("node-opcua-certificate-manager"); const node_opcua_common_1 = require("node-opcua-common"); const web_1 = require("node-opcua-crypto/web"); const node_opcua_data_model_1 = require("node-opcua-data-model"); const node_opcua_data_value_1 = require("node-opcua-data-value"); const node_opcua_debug_1 = require("node-opcua-debug"); const node_opcua_object_registry_1 = require("node-opcua-object-registry"); const node_opcua_secure_channel_1 = require("node-opcua-secure-channel"); const node_opcua_service_browse_1 = require("node-opcua-service-browse"); const node_opcua_service_call_1 = require("node-opcua-service-call"); const node_opcua_service_endpoints_1 = require("node-opcua-service-endpoints"); const node_opcua_service_history_1 = require("node-opcua-service-history"); const node_opcua_service_node_management_1 = require("node-opcua-service-node-management"); const node_opcua_service_query_1 = require("node-opcua-service-query"); const node_opcua_service_read_1 = require("node-opcua-service-read"); const node_opcua_service_register_node_1 = require("node-opcua-service-register-node"); const node_opcua_service_session_1 = require("node-opcua-service-session"); const node_opcua_service_subscription_1 = require("node-opcua-service-subscription"); const node_opcua_service_translate_browse_path_1 = require("node-opcua-service-translate-browse-path"); const node_opcua_service_write_1 = require("node-opcua-service-write"); const node_opcua_status_code_1 = require("node-opcua-status-code"); const node_opcua_types_1 = require("node-opcua-types"); const node_opcua_variant_1 = require("node-opcua-variant"); const node_opcua_variant_2 = require("node-opcua-variant"); const node_opcua_utils_2 = require("node-opcua-utils"); const base_server_1 = require("./base_server"); const factory_1 = require("./factory"); const monitored_item_1 = require("./monitored_item"); const register_server_manager_1 = require("./register_server_manager"); const register_server_manager_hidden_1 = require("./register_server_manager_hidden"); const register_server_manager_mdns_only_1 = require("./register_server_manager_mdns_only"); const server_end_point_1 = require("./server_end_point"); const server_engine_1 = require("./server_engine"); const user_manager_1 = require("./user_manager"); const user_manager_ua_1 = require("./user_manager_ua"); function isSubscriptionIdInvalid(subscriptionId) { return subscriptionId < 0 || subscriptionId >= 0xffffffff; } // tslint:disable-next-line:no-var-requires const thenify_ex_1 = require("thenify-ex"); // tslint:disable-next-line:no-var-requires const package_info = require("../package.json"); const debugLog = (0, node_opcua_debug_1.make_debugLog)(__filename); const errorLog = (0, node_opcua_debug_1.make_errorLog)(__filename); const warningLog = (0, node_opcua_debug_1.make_warningLog)(__filename); const default_maxConnectionsPerEndpoint = 10; function g_sendError(channel, message, ResponseClass, statusCode) { const response = new node_opcua_types_1.ServiceFault({ responseHeader: { serviceResult: statusCode } }); return channel.send_response("MSG", response, message); } const default_build_info = { manufacturerName: "NodeOPCUA : MIT Licence ( see http://node-opcua.github.io/)", productName: "NodeOPCUA-Server", productUri: null, // << should be same as default_server_info.productUri? softwareVersion: package_info.version, buildNumber: "0", buildDate: new Date(2020, 1, 1) // xx buildDate: fs.statSync(package_json_file).mtime }; const minSessionTimeout = 100; // 100 milliseconds const defaultSessionTimeout = 1000 * 30; // 30 seconds const maxSessionTimeout = 1000 * 60 * 50; // 50 minutes let unnamed_session_count = 0; function _adjust_session_timeout(sessionTimeout) { let revisedSessionTimeout = sessionTimeout || defaultSessionTimeout; revisedSessionTimeout = Math.min(revisedSessionTimeout, maxSessionTimeout); revisedSessionTimeout = Math.max(revisedSessionTimeout, minSessionTimeout); return revisedSessionTimeout; } function channel_has_session(channel, session) { if (session.channel === channel) { (0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(channel.sessionTokens, session.authenticationToken.toString())); return true; } return false; } function moveSessionToChannel(session, channel) { debugLog("moveSessionToChannel sessionId", session.nodeId, " channelId=", channel.channelId); if (session.publishEngine) { session.publishEngine.cancelPendingPublishRequestBeforeChannelChange(); } session._detach_channel(); session._attach_channel(channel); (0, node_opcua_assert_1.assert)(session.channel.channelId === channel.channelId); } async function _attempt_to_close_some_old_unactivated_session(server) { const session = server.engine.getOldestInactiveSession(); if (session) { await server.engine.closeSession(session.authenticationToken, false, "Forcing"); } } function getRequiredEndpointInfo(endpoint) { (0, node_opcua_assert_1.assert)(endpoint instanceof node_opcua_types_1.EndpointDescription); // https://reference.opcfoundation.org/v104/Core/docs/Part4/5.6.2/ // https://reference.opcfoundation.org/v105/Core/docs/Part4/5.6.2/ const e = new node_opcua_types_1.EndpointDescription({ endpointUrl: endpoint.endpointUrl, securityLevel: endpoint.securityLevel, securityMode: endpoint.securityMode, securityPolicyUri: endpoint.securityPolicyUri, server: { applicationUri: endpoint.server.applicationUri, applicationType: endpoint.server.applicationType, applicationName: endpoint.server.applicationName, productUri: endpoint.server.productUri }, transportProfileUri: endpoint.transportProfileUri, userIdentityTokens: endpoint.userIdentityTokens }); // reduce even further by explicitly setting unwanted members to null e.server.applicationName = null; // xx e.server.applicationType = null as any; e.server.gatewayServerUri = null; e.server.discoveryProfileUri = null; e.server.discoveryUrls = null; e.serverCertificate = null; return e; } // serverUri String This value is only specified if the EndpointDescription has a gatewayServerUri. // This value is the applicationUri from the EndpointDescription which is the applicationUri for the // underlying Server. The type EndpointDescription is defined in 7.10. function _serverEndpointsForCreateSessionResponse(server, endpointUrl, serverUri) { serverUri = null; // unused then // https://reference.opcfoundation.org/v104/Core/docs/Part4/5.6.2/ // https://reference.opcfoundation.org/v105/Core/docs/Part4/5.6.2/ return server ._get_endpoints(endpointUrl) .filter((e) => !e.restricted) // remove restricted endpoints .filter((e) => (0, node_opcua_utils_2.matchUri)(e.endpointUrl, endpointUrl)) .map(getRequiredEndpointInfo); } function adjustSecurityPolicy(channel, userTokenPolicy_securityPolicyUri) { // check that userIdentityToken let securityPolicy = (0, node_opcua_secure_channel_1.fromURI)(userTokenPolicy_securityPolicyUri); // if the security policy is not specified we use the session security policy if (securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.Invalid) { securityPolicy = (0, node_opcua_secure_channel_1.fromURI)(channel.securityPolicy); (0, node_opcua_assert_1.assert)(securityPolicy !== node_opcua_secure_channel_1.SecurityPolicy.Invalid); } return securityPolicy; } function findUserTokenByPolicy(endpoint_description, userTokenType, policyId) { (0, node_opcua_assert_1.assert)(endpoint_description instanceof node_opcua_types_1.EndpointDescription); const r = endpoint_description.userIdentityTokens.filter((userIdentity) => userIdentity.tokenType === userTokenType && (!policyId || userIdentity.policyId === policyId)); return r.length === 0 ? null : r[0]; } function findUserTokenPolicy(endpoint_description, userTokenType) { (0, node_opcua_assert_1.assert)(endpoint_description instanceof node_opcua_types_1.EndpointDescription); const r = endpoint_description.userIdentityTokens.filter((userIdentity) => { (0, node_opcua_assert_1.assert)(userIdentity.tokenType !== undefined); return userIdentity.tokenType === userTokenType; }); return r.length === 0 ? null : r[0]; } function createAnonymousIdentityToken(endpoint_desc) { (0, node_opcua_assert_1.assert)(endpoint_desc instanceof node_opcua_types_1.EndpointDescription); const userTokenPolicy = findUserTokenPolicy(endpoint_desc, node_opcua_service_endpoints_1.UserTokenType.Anonymous); if (!userTokenPolicy) { throw new Error("Cannot find ANONYMOUS user token policy in end point description"); } return new node_opcua_service_session_1.AnonymousIdentityToken({ policyId: userTokenPolicy.policyId }); } function sameIdentityToken(token1, token2) { if (token1 instanceof node_opcua_service_session_1.UserNameIdentityToken) { if (!(token2 instanceof node_opcua_service_session_1.UserNameIdentityToken)) { return false; } if (token1.userName !== token2.userName) { return false; } if (token1.password.toString("hex") !== token2.password.toString("hex")) { // note pasword hash may be different from two request and cannot be verified at this stage // we assume that we have a valid password // NOT CALLING return false; } return true; } else if (token1 instanceof node_opcua_service_session_1.AnonymousIdentityToken) { if (!(token2 instanceof node_opcua_service_session_1.AnonymousIdentityToken)) { return false; } if (token1.policyId !== token2.policyId) { return false; } return true; } (0, node_opcua_assert_1.assert)(false, " Not implemented yet"); return false; } function getTokenType(userIdentityToken) { if (userIdentityToken instanceof node_opcua_service_session_1.AnonymousIdentityToken) { return node_opcua_service_endpoints_1.UserTokenType.Anonymous; } else if (userIdentityToken instanceof node_opcua_service_session_1.UserNameIdentityToken) { return node_opcua_service_endpoints_1.UserTokenType.UserName; } else if (userIdentityToken instanceof node_opcua_types_1.IssuedIdentityToken) { return node_opcua_service_endpoints_1.UserTokenType.IssuedToken; } else if (userIdentityToken instanceof node_opcua_service_session_1.X509IdentityToken) { return node_opcua_service_endpoints_1.UserTokenType.Certificate; } return node_opcua_service_endpoints_1.UserTokenType.Invalid; } function thumbprint(certificate) { return certificate ? certificate.toString("base64") : ""; } /*=== private * * perform the read operation on a given node for a monitored item. * this method DOES NOT apply to Variable Values attribute * * @param self * @param oldValue * @param node * @param itemToMonitor * @private */ function monitoredItem_read_and_record_value(self, context, oldValue, node, itemToMonitor, callback) { (0, node_opcua_assert_1.assert)(self instanceof monitored_item_1.MonitoredItem); (0, node_opcua_assert_1.assert)(oldValue instanceof node_opcua_data_value_1.DataValue); (0, node_opcua_assert_1.assert)(itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value); const dataValue = node.readAttribute(context, itemToMonitor.attributeId, itemToMonitor.indexRange, itemToMonitor.dataEncoding); callback(null, dataValue); } /*== private * this method applies to Variable Values attribute * @private */ function monitoredItem_read_and_record_value_async(self, context, oldValue, node, itemToMonitor, callback) { (0, node_opcua_assert_1.assert)(context instanceof node_opcua_address_space_1.SessionContext); (0, node_opcua_assert_1.assert)(itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value); (0, node_opcua_assert_1.assert)(self instanceof monitored_item_1.MonitoredItem); (0, node_opcua_assert_1.assert)(oldValue instanceof node_opcua_data_value_1.DataValue); // do it asynchronously ( this is only valid for value attributes ) (0, node_opcua_assert_1.assert)(itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value); node.readValueAsync(context, (err, dataValue) => { callback(err, dataValue); }); } function build_scanning_node_function(addressSpace, itemToMonitor) { (0, node_opcua_assert_1.assert)(itemToMonitor instanceof node_opcua_service_read_1.ReadValueId); const node = addressSpace.findNode(itemToMonitor.nodeId); /* istanbul ignore next */ if (!node) { errorLog(" INVALID NODE ID , ", itemToMonitor.nodeId.toString()); (0, node_opcua_debug_1.dump)(itemToMonitor); return (_sessionContext, _oldData, callback) => { callback(null, new node_opcua_data_value_1.DataValue({ statusCode: node_opcua_status_code_1.StatusCodes.BadNodeIdUnknown, value: { dataType: node_opcua_variant_1.DataType.Null, value: 0 } })); }; } ///// !!monitoredItem.setNode(node); if (itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value) { const monitoredItem_read_and_record_value_func = itemToMonitor.attributeId === node_opcua_data_model_1.AttributeIds.Value && typeof node.readValueAsync === "function" ? monitoredItem_read_and_record_value_async : monitoredItem_read_and_record_value; return function func(sessionContext, oldDataValue, callback) { (0, node_opcua_assert_1.assert)(this instanceof monitored_item_1.MonitoredItem); (0, node_opcua_assert_1.assert)(oldDataValue instanceof node_opcua_data_value_1.DataValue); (0, node_opcua_assert_1.assert)(typeof callback === "function"); monitoredItem_read_and_record_value_func(this, sessionContext, oldDataValue, node, itemToMonitor, callback); }; } else { // Attributes, other than the Value Attribute, are only monitored for a change in value. // The filter is not used for these Attributes. Any change in value for these Attributes // causes a Notification to be generated. // only record value when it has changed return function func(sessionContext, oldDataValue, callback) { (0, node_opcua_assert_1.assert)(this instanceof monitored_item_1.MonitoredItem); (0, node_opcua_assert_1.assert)(oldDataValue instanceof node_opcua_data_value_1.DataValue); (0, node_opcua_assert_1.assert)(typeof callback === "function"); const newDataValue = node.readAttribute(sessionContext, itemToMonitor.attributeId); callback(null, newDataValue); }; } } function prepareMonitoredItem(context, addressSpace, monitoredItem) { const itemToMonitor = monitoredItem.itemToMonitor; const readNodeFunc = build_scanning_node_function(addressSpace, itemToMonitor); monitoredItem.samplingFunc = readNodeFunc; } function isMonitoringModeValid(monitoringMode) { (0, node_opcua_assert_1.assert)(node_opcua_types_1.MonitoringMode.Invalid !== undefined); return monitoringMode !== node_opcua_types_1.MonitoringMode.Invalid && monitoringMode <= node_opcua_types_1.MonitoringMode.Reporting; } function _installRegisterServerManager(self) { (0, node_opcua_assert_1.assert)(self instanceof OPCUAServer); (0, node_opcua_assert_1.assert)(!self.registerServerManager); /* istanbul ignore next */ if (!self.registerServerMethod) { throw new Error("Internal Error"); } switch (self.registerServerMethod) { case RegisterServerMethod.HIDDEN: self.registerServerManager = new register_server_manager_hidden_1.RegisterServerManagerHidden({ server: self }); break; case RegisterServerMethod.MDNS: self.registerServerManager = new register_server_manager_mdns_only_1.RegisterServerManagerMDNSONLY({ server: self }); break; case RegisterServerMethod.LDS: self.registerServerManager = new register_server_manager_1.RegisterServerManager({ discoveryServerEndpointUrl: self.discoveryServerEndpointUrl, server: self }); break; /* istanbul ignore next */ default: throw new Error("Invalid switch"); } self.registerServerManager.on("serverRegistrationPending", () => { /** * emitted when the server is trying to registered the LDS * but when the connection to the lds has failed * serverRegistrationPending is sent when the backoff signal of the * connection process is raised * @event serverRegistrationPending */ debugLog("serverRegistrationPending"); self.emit("serverRegistrationPending"); }); self.registerServerManager.on("serverRegistered", () => { /** * emitted when the server is successfully registered to the LDS * @event serverRegistered */ debugLog("serverRegistered"); self.emit("serverRegistered"); }); self.registerServerManager.on("serverRegistrationRenewed", () => { /** * emitted when the server has successfully renewed its registration to the LDS * @event serverRegistrationRenewed */ debugLog("serverRegistrationRenewed"); self.emit("serverRegistrationRenewed"); }); self.registerServerManager.on("serverUnregistered", () => { debugLog("serverUnregistered"); /** * emitted when the server is successfully unregistered to the LDS * ( for instance during shutdown) * @event serverUnregistered */ self.emit("serverUnregistered"); }); } function validate_applicationUri(channel, request) { const applicationUri = request.clientDescription.applicationUri; const clientCertificate = request.clientCertificate; // if session is insecure there is no need to check certificate information if (channel.securityMode === node_opcua_secure_channel_1.MessageSecurityMode.None) { return true; // assume correct } if (!clientCertificate || clientCertificate.length === 0) { return true; // can't check } const e = (0, web_1.exploreCertificate)(clientCertificate); const uniformResourceIdentifier = e.tbsCertificate.extensions.subjectAltName?.uniformResourceIdentifier ?? null; const applicationUriFromCert = uniformResourceIdentifier && uniformResourceIdentifier.length > 0 ? uniformResourceIdentifier[0] : null; /* istanbul ignore next */ if (applicationUriFromCert !== applicationUri) { errorLog("BadCertificateUriInvalid!"); errorLog("applicationUri = ", applicationUri); errorLog("applicationUriFromCert = ", applicationUriFromCert); } return applicationUriFromCert === applicationUri; } function validate_security_endpoint(server, request, channel) { debugLog("validate_security_endpoint = ", request.endpointUrl); let endpoints = server._get_endpoints(request.endpointUrl); // endpointUrl String The network address that the Client used to access the Session Endpoint. // The HostName portion of the URL should be one of the HostNames for the application that are // specified in the Server’s ApplicationInstanceCertificate (see 7.2). The Server shall raise an // AuditUrlMismatchEventType event if the URL does not match the Server’s HostNames. // AuditUrlMismatchEventType event type is defined in Part 5. // The Server uses this information for diagnostics and to determine the set of // EndpointDescriptions to return in the response. // ToDo: check endpointUrl validity and emit an AuditUrlMismatchEventType event if not // sometime endpoints have a extra leading "/" that can be ignored // don't be too harsh. if (endpoints.length === 0 && request.endpointUrl?.endsWith("/")) { endpoints = server._get_endpoints(request.endpointUrl.slice(0, -1)); } if (endpoints.length === 0) { // we have a UrlMismatch here const ua_server = server.engine.addressSpace.rootFolder.objects.server; if (!request.endpointUrl?.match(/localhost/i) || OPCUAServer.requestExactEndpointUrl) { warningLog("Cannot find suitable endpoints in available endpoints. endpointUri =", request.endpointUrl); } ua_server.raiseEvent("AuditUrlMismatchEventType", { endpointUrl: { dataType: node_opcua_variant_1.DataType.String, value: request.endpointUrl } }); if (OPCUAServer.requestExactEndpointUrl) { return { errCode: node_opcua_status_code_1.StatusCodes.BadServiceUnsupported }; } else { endpoints = server._get_endpoints(null); } } // ignore restricted endpoints endpoints = endpoints.filter((e) => !e.restricted); const endpoints_matching_security_mode = endpoints.filter((e) => { return e.securityMode === channel.securityMode; }); if (endpoints_matching_security_mode.length === 0) { return { errCode: node_opcua_status_code_1.StatusCodes.BadSecurityModeRejected }; } const endpoints_matching_security_policy = endpoints_matching_security_mode.filter((e) => { return e.securityPolicyUri === channel.securityPolicy; }); if (endpoints_matching_security_policy.length === 0) { return { errCode: node_opcua_status_code_1.StatusCodes.BadSecurityPolicyRejected }; } if (endpoints_matching_security_policy.length !== 1) { debugLog("endpoints_matching_security_policy= ", endpoints_matching_security_policy.length); } return { errCode: node_opcua_status_code_1.StatusCodes.Good, endpoint: endpoints_matching_security_policy[0] }; } function filterDiagnosticInfo(returnDiagnostics, response) { if (node_opcua_data_model_1.RESPONSE_DIAGNOSTICS_MASK_ALL & returnDiagnostics) { response.responseHeader.serviceDiagnostics = (0, node_opcua_data_model_1.filterDiagnosticServiceLevel)(returnDiagnostics, response.responseHeader.serviceDiagnostics); if (response.diagnosticInfos && response.diagnosticInfos.length > 0) { response.diagnosticInfos = response.diagnosticInfos.map((d) => (0, node_opcua_data_model_1.filterDiagnosticOperationLevel)(returnDiagnostics, d)); } else { response.diagnosticInfos = []; } if (response.results) { for (const entry of response.results) { if (entry.inputArgumentDiagnosticInfos && entry.inputArgumentDiagnosticInfos.length > 0) { entry.inputArgumentDiagnosticInfos = entry.inputArgumentDiagnosticInfos.map((d) => (0, node_opcua_data_model_1.filterDiagnosticOperationLevel)(returnDiagnostics, d)); } else { entry.inputArgumentDiagnosticInfos = []; } } } } } var RegisterServerMethod; (function (RegisterServerMethod) { RegisterServerMethod[RegisterServerMethod["HIDDEN"] = 1] = "HIDDEN"; RegisterServerMethod[RegisterServerMethod["MDNS"] = 2] = "MDNS"; RegisterServerMethod[RegisterServerMethod["LDS"] = 3] = "LDS"; // the server registers itself to the LDS or LDS-ME (Local Discovery Server) })(RegisterServerMethod || (exports.RegisterServerMethod = RegisterServerMethod = {})); const g_requestExactEndpointUrl = !!process.env.NODEOPCUA_SERVER_REQUEST_EXACT_ENDPOINT_URL; /** * */ class OPCUAServer extends base_server_1.OPCUABaseServer { /** * total number of bytes written by the server since startup */ get bytesWritten() { return this.endpoints.reduce((accumulated, endpoint) => { return accumulated + endpoint.bytesWritten; }, 0); } /** * total number of bytes read by the server since startup */ get bytesRead() { return this.endpoints.reduce((accumulated, endpoint) => { return accumulated + endpoint.bytesRead; }, 0); } /** * Number of transactions processed by the server since startup */ get transactionsCount() { return this.endpoints.reduce((accumulated, endpoint) => { return accumulated + endpoint.transactionsCount; }, 0); } /** * The server build info */ get buildInfo() { return this.engine.buildInfo; } /** * the number of connected channel on all existing end points */ get currentChannelCount() { // TODO : move to base return this.endpoints.reduce((currentValue, endPoint) => { return currentValue + endPoint.currentChannelCount; }, 0); } /** * The number of active subscriptions from all sessions */ get currentSubscriptionCount() { return this.engine ? this.engine.currentSubscriptionCount : 0; } /** * the number of session activation requests that have been rejected */ get rejectedSessionCount() { return this.engine ? this.engine.rejectedSessionCount : 0; } /** * the number of request that have been rejected */ get rejectedRequestsCount() { return this.engine ? this.engine.rejectedRequestsCount : 0; } /** * the number of sessions that have been aborted */ get sessionAbortCount() { return this.engine ? this.engine.sessionAbortCount : 0; } /** * the publishing interval count */ get publishingIntervalCount() { return this.engine ? this.engine.publishingIntervalCount : 0; } /** * the number of sessions currently active */ get currentSessionCount() { return this.engine ? this.engine.currentSessionCount : 0; } /** * true if the server has been initialized * */ get initialized() { return this.engine && this.engine.addressSpace !== null; } /** * is the server auditing ? */ get isAuditing() { return this.engine ? this.engine.isAuditing : false; } /** * the maximum number of concurrent sessions allowed on the server */ get maxAllowedSessionNumber() { return this.engine.serverCapabilities.maxSessions; } constructor(options) { super(options); /** * false if anonymous connection are not allowed */ this.allowAnonymous = false; this.allowAnonymous = false; options = options || {}; this.options = options; if (options.maxAllowedSessionNumber !== undefined) { warningLog("[NODE-OPCUA-W21] maxAllowedSessionNumber property is now deprecated , please use serverCapabilities.maxSessions instead"); options.serverCapabilities = options.serverCapabilities || {}; options.serverCapabilities.maxSessions = options.maxAllowedSessionNumber; } // adjust securityPolicies if any if (options.securityPolicies) { options.securityPolicies = options.securityPolicies.map(node_opcua_secure_channel_1.coerceSecurityPolicy); } /** * @property maxConnectionsPerEndpoint */ this.maxConnectionsPerEndpoint = options.maxConnectionsPerEndpoint || default_maxConnectionsPerEndpoint; // build Info const buildInfo = { ...default_build_info, ...options.buildInfo }; // repair product name buildInfo.productUri = buildInfo.productUri || this.serverInfo.productUri; this.serverInfo.productUri = this.serverInfo.productUri || buildInfo.productUri; this.userManager = (0, user_manager_1.makeUserManager)(options.userManager); options.allowAnonymous = options.allowAnonymous === undefined ? true : !!options.allowAnonymous; /** * @property allowAnonymous */ this.allowAnonymous = options.allowAnonymous; this.discoveryServerEndpointUrl = options.discoveryServerEndpointUrl || "opc.tcp://%FQDN%:4840"; (0, node_opcua_assert_1.assert)(typeof this.discoveryServerEndpointUrl === "string"); this.serverInfo.applicationType = options.serverInfo?.applicationType === undefined ? node_opcua_service_endpoints_1.ApplicationType.Server : options.serverInfo.applicationType; this.capabilitiesForMDNS = options.capabilitiesForMDNS || ["NA"]; this.registerServerMethod = options.registerServerMethod || RegisterServerMethod.HIDDEN; _installRegisterServerManager(this); if (!options.userCertificateManager) { this.userCertificateManager = (0, node_opcua_certificate_manager_1.getDefaultCertificateManager)("UserPKI"); } else { this.userCertificateManager = options.userCertificateManager; } // note: we need to delay initialization of endpoint as certain resources // such as %FQDN% might not be ready yet at this stage this._delayInit = async () => { /* istanbul ignore next */ if (!options) { throw new Error("Internal Error"); } // to check => this.serverInfo.applicationName = this.serverInfo.productName || buildInfo.productName; // note: applicationUri is handled in a special way this.engine = new server_engine_1.ServerEngine({ applicationUri: () => this.serverInfo.applicationUri, buildInfo, isAuditing: options.isAuditing, serverCapabilities: options.serverCapabilities, serverConfiguration: { serverCapabilities: () => { return this.capabilitiesForMDNS || ["NA"]; }, supportedPrivateKeyFormat: ["PEM"], applicationType: () => this.serverInfo.applicationType, applicationUri: () => this.serverInfo.applicationUri || "", productUri: () => this.serverInfo.productUri || "", // hasSecureElement: () => false, multicastDnsEnabled: () => this.registerServerMethod === RegisterServerMethod.MDNS } }); this.objectFactory = new factory_1.Factory(this.engine); const endpointDefinitions = [ ...(options.endpoints || []), ...(options.alternateEndpoints || []) ]; const hostname = (0, node_opcua_hostname_1.getFullyQualifiedDomainName)(); endpointDefinitions.forEach((endpointDefinition) => { endpointDefinition.port = endpointDefinition.port === undefined ? 26543 : endpointDefinition.port; endpointDefinition.hostname = endpointDefinition.hostname || hostname; }); if (!options.endpoints) { endpointDefinitions.push({ port: options.port === undefined ? 26543 : options.port, hostname: options.hostname || hostname, host: options.host, allowAnonymous: options.allowAnonymous, alternateHostname: options.alternateHostname, disableDiscovery: options.disableDiscovery, securityModes: options.securityModes, securityPolicies: options.securityPolicies }); } // todo should self.serverInfo.productUri match self.engine.buildInfo.productUri ? for (const endpointOptions of endpointDefinitions) { const endPoint = this.createEndpointDescriptions(options, endpointOptions); this.endpoints.push(endPoint); endPoint.on("message", (message, channel) => { this.on_request(message, channel); }); // endPoint.on("error", (err: Error) => { // errorLog("OPCUAServer endpoint error", err); // // set serverState to ServerState.Failed; // this.engine.setServerState(ServerState.Failed); // this.shutdown(() => { // /* empty */ // }); // }); } }; } initialize(...args) { const done = args[0]; (0, node_opcua_assert_1.assert)(!this.initialized, "server is already initialized"); // already initialized ? this._preInitTask.push(async () => { /* istanbul ignore else */ if (this._delayInit) { await this._delayInit(); this._delayInit = undefined; } }); this.performPreInitialization() .then(() => { OPCUAServer.registry.register(this); this.engine.initialize(this.options, () => { (0, user_manager_ua_1.bindRoleSet)(this.userManager, this.engine.addressSpace); setImmediate(() => { this.emit("post_initialize"); done(); }); }); }) .catch((err) => { done(err); }); } start(...args) { const done = args[0]; const tasks = []; tasks.push((0, util_1.callbackify)(node_opcua_hostname_1.extractFullyQualifiedDomainName)); if (!this.initialized) { tasks.push((callback) => { this.initialize(callback); }); } tasks.push((callback) => { super.start((err) => { if (err) { this.shutdown((/*err2*/ err2) => { callback(err); }); } else { // we start the registration process asynchronously // as we want to make server immediately available this.registerServerManager.start(() => { /* empty */ }); setImmediate(callback); } }); }); async_1.default.series(tasks, done); } shutdown(...args) { const timeout = args.length === 1 ? OPCUAServer.defaultShutdownTimeout : args[0]; const callback = (args.length === 1 ? args[0] : args[1]); (0, node_opcua_assert_1.assert)(typeof callback === "function"); debugLog("OPCUAServer#shutdown (timeout = ", timeout, ")"); /* istanbul ignore next */ if (!this.engine) { return callback(); } (0, node_opcua_assert_1.assert)(this.engine); if (!this.engine.isStarted()) { // server may have been shot down already , or may have fail to start !! const err = new Error("OPCUAServer#shutdown failure ! server doesn't seems to be started yet"); return callback(err); } this.userCertificateManager.dispose(); this.engine.setServerState(node_opcua_common_1.ServerState.Shutdown); const shutdownTime = new Date(Date.now() + timeout); this.engine.setShutdownTime(shutdownTime); debugLog("OPCUAServer is now un-registering itself from the discovery server " + this.buildInfo); this.registerServerManager.stop((err) => { debugLog("OPCUAServer unregistered from discovery server", err); setTimeout(async () => { await this.engine.shutdown(); debugLog("OPCUAServer#shutdown: started"); base_server_1.OPCUABaseServer.prototype.shutdown.call(this, (err1) => { debugLog("OPCUAServer#shutdown: completed"); this.dispose(); callback(err1); }); }, timeout); }); } dispose() { for (const endpoint of this.endpoints) { endpoint.dispose(); } this.endpoints = []; this.removeAllListeners(); if (this.registerServerManager) { this.registerServerManager.dispose(); this.registerServerManager = undefined; } OPCUAServer.registry.unregister(this); /* istanbul ignore next */ if (this.engine) { this.engine.dispose(); } } raiseEvent(eventType, options) { /* istanbul ignore next */ if (!this.engine.addressSpace) { errorLog("addressSpace missing"); return; } const server = this.engine.addressSpace.findNode("Server"); /* istanbul ignore next */ if (!server) { // xx throw new Error("OPCUAServer#raiseEvent : cannot find Server object"); return; } let eventTypeNode = eventType; if (typeof eventType === "string") { eventTypeNode = this.engine.addressSpace.findEventType(eventType); if (eventTypeNode) { return server.raiseEvent(eventTypeNode, options); } else { console.warn(" cannot find event type ", eventType); } } else { return server.raiseEvent(eventTypeNode, options); } } /** * create and register a new session * @private */ createSession(options) { /* istanbul ignore next */ if (!this.engine) { throw new Error("Internal Error"); } return this.engine.createSession(options); } /** * retrieve a session by authentication token * @private */ getSession(authenticationToken, activeOnly) { return this.engine ? this.engine.getSession(authenticationToken, activeOnly) : null; } /** * * @param channel * @param clientCertificate * @param clientNonce * @private */ computeServerSignature(channel, clientCertificate, clientNonce) { return (0, node_opcua_secure_channel_1.computeSignature)(clientCertificate, clientNonce, this.getPrivateKey(), channel.securityPolicy); } /** * * @param session * @param channel * @param clientSignature */ verifyClientSignature(session, channel, clientSignature) { const clientCertificate = channel.clientCertificate; const securityPolicy = channel.securityPolicy; const serverCertificate = this.getCertificate(); const result = (0, node_opcua_secure_channel_1.verifySignature)(serverCertificate, session.nonce, clientSignature, clientCertificate, securityPolicy); return result; } isValidUserNameIdentityToken(channel, session, userTokenPolicy, userIdentityToken, userTokenSignature, callback) { (0, node_opcua_assert_1.assert)(userIdentityToken instanceof node_opcua_service_session_1.UserNameIdentityToken); const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri); if (securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) { return callback(null, node_opcua_status_code_1.StatusCodes.Good); } const cryptoFactory = (0, node_opcua_secure_channel_1.getCryptoFactory)(securityPolicy); /* istanbul ignore next */ if (!cryptoFactory) { return callback(null, node_opcua_status_code_1.StatusCodes.BadSecurityPolicyRejected); } /* istanbul ignore next */ if (userIdentityToken.encryptionAlgorithm !== cryptoFactory.asymmetricEncryptionAlgorithm) { errorLog("invalid encryptionAlgorithm"); errorLog("userTokenPolicy", userTokenPolicy.toString()); errorLog("userTokenPolicy", userIdentityToken.toString()); return callback(null, node_opcua_status_code_1.StatusCodes.BadIdentityTokenInvalid); } const userName = userIdentityToken.userName; const password = userIdentityToken.password; if (!userName || !password) { return callback(null, node_opcua_status_code_1.StatusCodes.BadIdentityTokenInvalid); } return callback(null, node_opcua_status_code_1.StatusCodes.Good); } isValidX509IdentityToken(channel, session, userTokenPolicy, userIdentityToken, userTokenSignature, callback) { (0, node_opcua_assert_1.assert)(userIdentityToken instanceof node_opcua_service_session_1.X509IdentityToken); (0, node_opcua_assert_1.assert)(typeof callback === "function"); const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri); const cryptoFactory = (0, node_opcua_secure_channel_1.getCryptoFactory)(securityPolicy); /* istanbul ignore next */ if (!cryptoFactory) { return callback(null, node_opcua_status_code_1.StatusCodes.BadSecurityPolicyRejected); } if (!userTokenSignature || !userTokenSignature.signature) { this.raiseEvent("AuditCreateSessionEventType", {}); return callback(null, node_opcua_status_code_1.StatusCodes.BadUserSignatureInvalid); } if (userIdentityToken.policyId !== userTokenPolicy.policyId) { errorLog("invalid encryptionAlgorithm"); errorLog("userTokenPolicy", userTokenPolicy.toString()); errorLog("userTokenPolicy", userIdentityToken.toString()); return callback(null, node_opcua_status_code_1.StatusCodes.BadSecurityPolicyRejected); } const certificate = userIdentityToken.certificateData; /* as Certificate*/ const nonce = session.nonce; const serverCertificate = this.getCertificate(); (0, node_opcua_assert_1.assert)(serverCertificate instanceof Buffer); (0, node_opcua_assert_1.assert)(certificate instanceof Buffer, "expecting certificate to be a Buffer"); (0, node_opcua_assert_1.assert)(nonce instanceof Buffer, "expecting nonce to be a Buffer"); (0, node_opcua_assert_1.assert)(userTokenSignature.signature instanceof Buffer, "expecting userTokenSignature to be a Buffer"); // verify proof of possession by checking certificate signature & server nonce correctness if (!(0, node_opcua_secure_channel_1.verifySignature)(serverCertificate, nonce, userTokenSignature, certificate, securityPolicy)) { return callback(null, node_opcua_status_code_1.StatusCodes.BadUserSignatureInvalid); } // verify if certificate is Valid this.userCertificateManager.checkCertificate(certificate, (err, certificateStatus) => { /* istanbul ignore next */ if (err) { return callback(err); } if (this.isAuditing) { switch (certificateStatus) { case node_opcua_status_code_1.StatusCodes.Good: break; case node_opcua_status_code_1.StatusCodes.BadCertificateUntrusted: this.raiseEvent("AuditCertificateUntrustedEventType", { certificate: { dataType: node_opcua_variant_1.DataType.ByteString, value: certificate }, sourceName: { dataType: node_opcua_variant_1.DataType.String, value: "Security/Certificate" } }); break; case node_opcua_status_code_1.StatusCodes.BadCertificateTimeInvalid: case node_opcua_status_code_1.StatusCodes.BadCertificateIssuerTimeInvalid: this.raiseEvent("AuditCertificateExpiredEventType", { certificate: { dataType: node_opcua_variant_1.DataType.ByteString, value: certificate }, sourceName: { dataType: node_opcua_variant_1.DataType.String, value: "Security/Certificate" } }); break; case node_opcua_status_code_1.StatusCodes.BadCertificateRevoked: case node_opcua_status_code_1.StatusCodes.BadCertificateRevocationUnknown: case node_opcua_status_code_1.StatusCodes.BadCertificateIssuerRevocationUnknown: this.raiseEvent("AuditCertificateRevokedEventType", { certificate: { dataType: node_opcua_variant_1.DataType.ByteString, value: certificate }, sourceName: { dataType: node_opcua_variant_1.DataType.String, value: "Security/Certificate" } }); break; case node_opcua_status_code_1.StatusCodes.BadCertificateIssuerUseNotAllowed: case node_opcua_status_code_1.StatusCodes.BadCertificateUseNotAllowed: case node_opcua_status_code_1.StatusCodes.BadSecurityChecksFailed: this.raiseEvent("AuditCertificateMismatchEventType", { certificate: { dataType: node_opcua_variant_1.DataType.ByteString, value: certificate }, sourceName: { dataType: node_opcua_variant_1.DataType.String, value: "Security/Certificate" } }); break; } } if (certificateStatus && (node_opcua_status_code_1.StatusCodes.BadCertificateUntrusted.equals(certificateStatus) || node_opcua_status_code_1.StatusCodes.BadCertificateTimeInvalid.equals(certificateStatus) || node_opcua_status_code_1.StatusCodes.BadCertificateIssuerTimeInvalid.equals(certificateStatus) || node_opcua_status_code_1.StatusCodes.BadCertificateIssuerUseNotAllowed.equals(certificateStatus) || node_opcua_status_code_1.StatusCodes.BadCertificateIssuerRevocationUnknown.equals(certificateStatus) || node_opcua_status_code_1.StatusCodes.BadCertificateRevocationUnknown.equals(certificateStatus) || node_opcua_status_code_1.StatusCodes.BadCertificateRevoked.equals(certificateStatus) || node_opcua_status_code_1.StatusCodes.BadCertificateUseNotAllowed.equals(certificateStatus) || node_opcua_status_code_1.StatusCodes.BadSecurityChecksFailed.equals(certificateStatus) || !node_opcua_status_code_1.StatusCodes.Good.equals(certificateStatus))) { debugLog("isValidX509IdentityToken => certificateStatus = ", certificateStatus?.toString()); return callback(null, node_opcua_status_code_1.StatusCodes.BadIdentityTokenRejected); } if (node_opcua_status_code_1.StatusCodes.Good !== certificateStatus) { (0, node_opcua_assert_1.assert)(certificateStatus instanceof node_opcua_status_code_1.StatusCode); return callback(null, certificateStatus); // return callback(null, StatusCodes.BadIdentityTokenInvalid); } // verify if certificate is truster or rejected // todo: StatusCodes.BadCertificateUntrusted // store untrusted certificate to rejected folder // todo: return callback(null, node_opcua_status_code_1.StatusCodes.Good); }); } /** * @internal */ userNameIdentityTokenAuthenticateUser(channel, session, userTokenPolicy, userIdentityToken, callback) { (0, node_opcua_assert_1.assert)(userIdentityToken instanceof node_opcua_service_session_1.UserNameIdentityToken); // assert(this.isValidUserNameIdentityToken(channel, session, userTokenPolicy, userIdentityToken)); const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri); const userName = userIdentityToken.userName; let password = userIdentityToken.password; // decrypt password if necessary if (securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) { // not good, password was sent in clear text ... password = password.toString(); } else { const serverPrivateKey = this.getPrivateKey(); const serverNonce = session.nonce; (0, node_opcua_assert_1.assert)(serverNonce instanceof Buffer); const cryptoFactory = (0, node_opcua_secure_channel_1.getCryptoFactory)(securityPolicy); /* istanbul ignore next */ if (!cryptoFactory) { return callback(new Error(" Unsupported security Policy")); } const buff = cryptoFactory.asymmetricDecrypt(password, serverPrivateKey);