node-opcua-server
Version:
pure nodejs OPCUA SDK - module server
866 lines • 40.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OPCUAServerEndPoint = void 0;
/* eslint-disable max-statements */
/**
* @module node-opcua-server
*/
// tslint:disable:no-console
const events_1 = require("events");
const net_1 = __importDefault(require("net"));
const chalk_1 = __importDefault(require("chalk"));
const async_1 = __importDefault(require("async"));
const node_opcua_assert_1 = require("node-opcua-assert");
const web_1 = require("node-opcua-crypto/web");
const node_opcua_debug_1 = require("node-opcua-debug");
const node_opcua_hostname_1 = require("node-opcua-hostname");
const node_opcua_secure_channel_1 = require("node-opcua-secure-channel");
const node_opcua_service_endpoints_1 = require("node-opcua-service-endpoints");
const node_opcua_service_endpoints_2 = require("node-opcua-service-endpoints");
const node_opcua_service_endpoints_3 = require("node-opcua-service-endpoints");
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 doDebug = (0, node_opcua_debug_1.checkDebugFlag)(__filename);
const default_transportProfileUri = "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
function extractSocketData(socket, reason) {
const { bytesRead, bytesWritten, remoteAddress, remoteFamily, remotePort, localAddress, localPort } = socket;
const data = {
bytesRead,
bytesWritten,
localAddress,
localPort,
remoteAddress,
remoteFamily,
remotePort,
timestamp: new Date(),
reason
};
return data;
}
function extractChannelData(channel) {
const { channelId, clientCertificate, securityMode, securityPolicy, timeout, transactionsCount } = channel;
const channelData = {
channelId,
clientCertificate,
securityMode,
securityPolicy,
timeout,
transactionsCount
};
return channelData;
}
function dumpChannelInfo(channels) {
function d(s) {
return `[ status=${s.status} lastSeen=${s.clientLastContactTime.toFixed(0)}ms sessionName=${s.sessionName} timeout=${s.sessionTimeout} ]`;
}
function dumpChannel(channel) {
console.log("------------------------------------------------------");
console.log(" channelId = ", channel.channelId);
console.log(" timeout = ", channel.timeout);
console.log(" remoteAddress = ", channel.remoteAddress);
console.log(" remotePort = ", channel.remotePort);
console.log("");
console.log(" bytesWritten = ", channel.bytesWritten);
console.log(" bytesRead = ", channel.bytesRead);
console.log(" sessions = ", Object.keys(channel.sessionTokens).length);
console.log(Object.values(channel.sessionTokens).map(d).join("\n"));
const socket = channel.transport?._socket;
if (!socket) {
console.log(" SOCKET IS CLOSED");
}
}
for (const channel of channels) {
dumpChannel(channel);
}
console.log("------------------------------------------------------");
}
const emptyCertificate = Buffer.alloc(0);
const emptyPrivateKey = null;
let OPCUAServerEndPointCounter = 0;
function getUniqueName(name, collection) {
if (collection[name]) {
let counter = 0;
while (collection[name + "_" + counter.toString()]) {
counter++;
}
name = name + "_" + counter.toString();
collection[name] = 1;
return name;
}
else {
collection[name] = 1;
return name;
}
}
/**
* OPCUAServerEndPoint a Server EndPoint.
* A sever end point is listening to one port
* note:
* see OPCUA Release 1.03 part 4 page 108 7.1 ApplicationDescription
*/
class OPCUAServerEndPoint extends events_1.EventEmitter {
constructor(options) {
super();
this._started = false;
this._counter = OPCUAServerEndPointCounter++;
this._policy_deduplicator = {};
(0, node_opcua_assert_1.assert)(!Object.prototype.hasOwnProperty.call(options, "certificate"), "expecting a certificateChain instead");
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(options, "certificateChain"), "expecting a certificateChain");
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(options, "privateKey"));
this.certificateManager = options.certificateManager;
options.port = options.port || 0;
this.port = parseInt(options.port.toString(), 10);
this.host = options.host;
(0, node_opcua_assert_1.assert)(typeof this.port === "number");
this._certificateChain = options.certificateChain;
this._privateKey = options.privateKey;
this._channels = {};
this.defaultSecureTokenLifetime = options.defaultSecureTokenLifetime || 600000;
this.maxConnections = options.maxConnections || 20;
this.timeout = options.timeout || 30000;
this._server = undefined;
this._setup_server();
this._endpoints = [];
this.objectFactory = options.objectFactory;
this.bytesWrittenInOldChannels = 0;
this.bytesReadInOldChannels = 0;
this.transactionsCountOldChannels = 0;
this.securityTokenCountOldChannels = 0;
this.serverInfo = options.serverInfo;
(0, node_opcua_assert_1.assert)(this.serverInfo !== null && typeof this.serverInfo === "object");
this.transportSettings = options.transportSettings;
}
dispose() {
this._certificateChain = emptyCertificate;
this._privateKey = emptyPrivateKey;
(0, node_opcua_assert_1.assert)(Object.keys(this._channels).length === 0, "OPCUAServerEndPoint channels must have been deleted");
this._channels = {};
this.serverInfo = new node_opcua_service_endpoints_3.ApplicationDescription({});
this._endpoints = [];
(0, node_opcua_assert_1.assert)(this._endpoints.length === 0, "endpoints must have been deleted");
this._endpoints = [];
this._server = undefined;
this._listen_callback = undefined;
this.removeAllListeners();
}
toString() {
const txt = " end point" +
this._counter +
" port = " +
this.port +
" l = " +
this._endpoints.length +
" " +
(0, web_1.makeSHA1Thumbprint)(this.getCertificateChain()).toString("hex");
return txt;
}
getChannels() {
return Object.values(this._channels);
}
/**
* Returns the X509 DER form of the server certificate
*/
getCertificate() {
return (0, web_1.split_der)(this.getCertificateChain())[0];
}
/**
* Returns the X509 DER form of the server certificate
*/
getCertificateChain() {
return this._certificateChain;
}
/**
* the private key
*/
getPrivateKey() {
return this._privateKey;
}
/**
* The number of active channel on this end point.
*/
get currentChannelCount() {
return Object.keys(this._channels).length;
}
/**
*/
getEndpointDescription(securityMode, securityPolicy, endpointUrl) {
const endpoints = this.endpointDescriptions();
const arr = endpoints.filter(matching_endpoint.bind(this, securityMode, securityPolicy, endpointUrl));
if (endpointUrl && endpointUrl.length > 0 && !(arr.length === 0 || arr.length === 1)) {
errorLog("Several matching endpoints have been found : ");
for (const a of arr) {
errorLog(" ", a.endpointUrl, node_opcua_secure_channel_1.MessageSecurityMode[securityMode], securityPolicy);
}
}
return arr.length === 0 ? null : arr[0];
}
addEndpointDescription(securityMode, securityPolicy, options) {
// istanbul ignore next
if (securityMode === node_opcua_secure_channel_1.MessageSecurityMode.None && securityPolicy !== node_opcua_secure_channel_1.SecurityPolicy.None) {
throw new Error(" invalid security ");
}
// istanbul ignore next
if (securityMode !== node_opcua_secure_channel_1.MessageSecurityMode.None && securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) {
throw new Error(" invalid security ");
}
//
// resource Path is a string added at the end of the url such as "/UA/Server"
const resourcePath = (options.resourcePath || "").replace(/\\/g, "/");
(0, node_opcua_assert_1.assert)(resourcePath.length === 0 || resourcePath.charAt(0) === "/", "resourcePath should start with /");
const hostname = options.hostname || (0, node_opcua_hostname_1.getFullyQualifiedDomainName)();
const endpointUrl = `opc.tcp://${hostname}:${this.port}${resourcePath}`;
const endpoint_desc = this.getEndpointDescription(securityMode, securityPolicy, endpointUrl);
// istanbul ignore next
if (endpoint_desc) {
throw new Error(" endpoint already exist");
}
const userTokenTypes = options.userTokenTypes;
// now build endpointUrl
this._endpoints.push(_makeEndpointDescription({
collection: this._policy_deduplicator,
hostname,
server: this.serverInfo,
serverCertificateChain: this.getCertificateChain(),
securityMode,
securityPolicy,
allowUnsecurePassword: options.allowUnsecurePassword,
resourcePath: options.resourcePath,
restricted: !!options.restricted,
securityPolicies: options.securityPolicies || [],
userTokenTypes
}, this));
}
addRestrictedEndpointDescription(options) {
options = { ...options };
options.restricted = true;
return this.addEndpointDescription(node_opcua_secure_channel_1.MessageSecurityMode.None, node_opcua_secure_channel_1.SecurityPolicy.None, options);
}
addStandardEndpointDescriptions(options) {
options = options || {};
options.securityModes = options.securityModes || defaultSecurityModes;
options.securityPolicies = options.securityPolicies || defaultSecurityPolicies;
options.userTokenTypes = options.userTokenTypes || defaultUserTokenTypes;
options.allowAnonymous = options.allowAnonymous === undefined ? true : options.allowAnonymous;
// make sure we do not have anonymous
if (!options.allowAnonymous) {
options.userTokenTypes = options.userTokenTypes.filter((r) => r !== node_opcua_service_endpoints_1.UserTokenType.Anonymous);
}
const defaultHostname = options.hostname || (0, node_opcua_hostname_1.getFullyQualifiedDomainName)();
let hostnames = [defaultHostname];
options.alternateHostname = options.alternateHostname || [];
if (typeof options.alternateHostname === "string") {
options.alternateHostname = [options.alternateHostname];
}
// remove duplicates if any (uniq)
hostnames = [...new Set(hostnames.concat(options.alternateHostname))];
for (const alternateHostname of hostnames) {
const optionsE = {
hostname: alternateHostname,
securityPolicies: options.securityPolicies,
userTokenTypes: options.userTokenTypes,
allowUnsecurePassword: options.allowUnsecurePassword,
alternateHostname: options.alternateHostname,
resourcePath: options.resourcePath
};
if (options.securityModes.indexOf(node_opcua_secure_channel_1.MessageSecurityMode.None) >= 0) {
this.addEndpointDescription(node_opcua_secure_channel_1.MessageSecurityMode.None, node_opcua_secure_channel_1.SecurityPolicy.None, optionsE);
}
else {
if (!options.disableDiscovery) {
this.addRestrictedEndpointDescription(optionsE);
}
}
for (const securityMode of options.securityModes) {
if (securityMode === node_opcua_secure_channel_1.MessageSecurityMode.None) {
continue;
}
for (const securityPolicy of options.securityPolicies) {
if (securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) {
continue;
}
this.addEndpointDescription(securityMode, securityPolicy, optionsE);
}
}
}
}
/**
* returns the list of end point descriptions.
*/
endpointDescriptions() {
return this._endpoints;
}
/**
*/
listen(callback) {
(0, node_opcua_assert_1.assert)(typeof callback === "function");
(0, node_opcua_assert_1.assert)(!this._started, "OPCUAServerEndPoint is already listening");
this._listen_callback = callback;
this._server.on("error", (err) => {
debugLog(chalk_1.default.red.bold(" error") + " port = " + this.port, err);
this._started = false;
this._end_listen(err);
});
this._server.on("listening", () => {
debugLog("server is listening");
});
const listenOptions = {
port: this.port,
host: this.host
};
this._server.listen(listenOptions,
/*"::",*/ (err) => {
// 'listening' listener
debugLog(chalk_1.default.green.bold("LISTENING TO PORT "), this.port, "err ", err);
(0, node_opcua_assert_1.assert)(!err, " cannot listen to port ");
this._started = true;
if (!this.port) {
const add = this._server.address();
this.port = typeof add !== "string" ? add.port : this.port;
}
this._end_listen();
});
}
killClientSockets(callback) {
for (const channel of this.getChannels()) {
const hacked_channel = channel;
if (hacked_channel.transport && hacked_channel.transport._socket) {
// hacked_channel.transport._socket.close();
hacked_channel.transport._socket.destroy();
hacked_channel.transport._socket.emit("error", new Error("EPIPE"));
}
}
callback();
}
suspendConnection(callback) {
if (!this._started) {
return callback(new Error("Connection already suspended !!"));
}
// Stops the server from accepting new connections and keeps existing connections.
// (note from nodejs doc: This function is asynchronous, the server is finally closed
// when all connections are ended and the server emits a 'close' event.
// The optional callback will be called once the 'close' event occurs.
// Unlike that event, it will be called with an Error as its only argument
// if the server was not open when it was closed.
this._server.close(() => {
this._started = false;
debugLog("Connection has been closed !" + this.port);
});
this._started = false;
callback();
}
restoreConnection(callback) {
this.listen(callback);
}
abruptlyInterruptChannels() {
for (const channel of Object.values(this._channels)) {
channel.abruptlyInterrupt();
}
}
/**
*/
shutdown(callback) {
debugLog("OPCUAServerEndPoint#shutdown ");
if (this._started) {
// make sure we don't accept new connection any more ...
this.suspendConnection(() => {
// shutdown all opened channels ...
const _channels = Object.values(this._channels);
async_1.default.each(_channels, (channel, callback1) => {
this.shutdown_channel(channel, callback1);
}, (err) => {
/* istanbul ignore next */
if (!(Object.keys(this._channels).length === 0)) {
errorLog(" Bad !");
}
(0, node_opcua_assert_1.assert)(Object.keys(this._channels).length === 0, "channel must have unregistered themselves");
callback(err || undefined);
});
});
}
else {
callback();
}
}
/**
*/
start(callback) {
(0, node_opcua_assert_1.assert)(typeof callback === "function");
this.listen(callback);
}
get bytesWritten() {
const channels = Object.values(this._channels);
return (this.bytesWrittenInOldChannels +
channels.reduce((accumulated, channel) => {
return accumulated + channel.bytesWritten;
}, 0));
}
get bytesRead() {
const channels = Object.values(this._channels);
return (this.bytesReadInOldChannels +
channels.reduce((accumulated, channel) => {
return accumulated + channel.bytesRead;
}, 0));
}
get transactionsCount() {
const channels = Object.values(this._channels);
return (this.transactionsCountOldChannels +
channels.reduce((accumulated, channel) => {
return accumulated + channel.transactionsCount;
}, 0));
}
get securityTokenCount() {
const channels = Object.values(this._channels);
return (this.securityTokenCountOldChannels +
channels.reduce((accumulated, channel) => {
return accumulated + channel.securityTokenCount;
}, 0));
}
get activeChannelCount() {
return Object.keys(this._channels).length;
}
_dump_statistics() {
this._server.getConnections((err, count) => {
debugLog(chalk_1.default.cyan("CONCURRENT CONNECTION = "), count);
});
debugLog(chalk_1.default.cyan("MAX CONNECTIONS = "), this._server.maxConnections);
}
_setup_server() {
(0, node_opcua_assert_1.assert)(!this._server);
this._server = net_1.default.createServer({ pauseOnConnect: true }, this._on_client_connection.bind(this));
// xx console.log(" Server with max connections ", self.maxConnections);
this._server.maxConnections = this.maxConnections + 1; // plus one extra
this._listen_callback = undefined;
this._server
.on("connection", (socket) => {
// istanbul ignore next
if (doDebug) {
this._dump_statistics();
debugLog("server connected with : " + socket.remoteAddress + ":" + socket.remotePort);
}
})
.on("close", () => {
debugLog("server closed : all connections have ended");
})
.on("error", (err) => {
// this could be because the port is already in use
debugLog(chalk_1.default.red.bold("server error: "), err.message);
});
}
_on_client_connection(socket) {
// a client is attempting a connection on the socket
socket.setNoDelay(true);
debugLog("OPCUAServerEndPoint#_on_client_connection", this._started);
if (!this._started) {
debugLog(chalk_1.default.bgWhite.cyan("OPCUAServerEndPoint#_on_client_connection " +
"SERVER END POINT IS PROBABLY SHUTTING DOWN !!! - Connection is refused"));
socket.end();
return;
}
const deny_connection = () => {
console.log(chalk_1.default.bgWhite.cyan("OPCUAServerEndPoint#_on_client_connection " +
"The maximum number of connection has been reached - Connection is refused"));
const reason = "maxConnections reached (" + this.maxConnections + ")";
const socketData = extractSocketData(socket, reason);
this.emit("connectionRefused", socketData);
socket.end();
socket.destroy();
};
const establish_connection = () => {
const nbConnections = Object.keys(this._channels).length;
if (nbConnections >= this.maxConnections) {
warningLog(" nbConnections ", nbConnections, " self._server.maxConnections", this._server.maxConnections, this.maxConnections);
deny_connection();
return;
}
debugLog("OPCUAServerEndPoint._on_client_connection successful => New Channel");
const channel = new node_opcua_secure_channel_1.ServerSecureChannelLayer({
defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
// objectFactory: this.objectFactory,
parent: this,
timeout: this.timeout,
adjustTransportLimits: this.transportSettings?.adjustTransportLimits
});
debugLog("channel Timeout = >", channel.timeout);
socket.resume();
this._preregisterChannel(channel);
channel.init(socket, (err) => {
this._un_pre_registerChannel(channel);
debugLog(chalk_1.default.yellow.bold("Channel#init done"), err);
if (err) {
const reason = "openSecureChannel has Failed " + err.message;
const socketData = extractSocketData(socket, reason);
const channelData = extractChannelData(channel);
this.emit("openSecureChannelFailure", socketData, channelData);
socket.end();
socket.destroy();
}
else {
debugLog("server receiving a client connection");
this._registerChannel(channel);
}
});
channel.on("message", (message) => {
// forward
this.emit("message", message, channel, this);
});
};
// Each SecureChannel exists until it is explicitly closed or until the last token has expired and the overlap
// period has elapsed. A Server application should limit the number of SecureChannels.
// To protect against misbehaving Clients and denial of service attacks, the Server shall close the oldest
// SecureChannel that has no Session assigned before reaching the maximum number of supported SecureChannels.
this._prevent_DDOS_Attack(establish_connection, deny_connection);
}
_preregisterChannel(channel) {
// _preregisterChannel is used to keep track of channel for which
// that are in early stage of the hand shaking process.
// e.g HEL/ACK and OpenSecureChannel may not have been received yet
// as they will need to be interrupted when OPCUAServerEndPoint is closed
(0, node_opcua_assert_1.assert)(this._started, "OPCUAServerEndPoint must be started");
(0, node_opcua_assert_1.assert)(!Object.prototype.hasOwnProperty.call(this._channels, channel.hashKey), " channel already preregistered!");
const channelPriv = channel;
this._channels[channel.hashKey] = channelPriv;
channelPriv._unpreregisterChannelEvent = () => {
debugLog("Channel received an abort event during the preregistration phase");
this._un_pre_registerChannel(channel);
channel.dispose();
};
channel.on("abort", channelPriv._unpreregisterChannelEvent);
}
_un_pre_registerChannel(channel) {
if (!this._channels[channel.hashKey]) {
debugLog("Already un preregistered ?", channel.hashKey);
return;
}
delete this._channels[channel.hashKey];
const channelPriv = channel;
if (typeof channelPriv._unpreregisterChannelEvent === "function") {
channel.removeListener("abort", channelPriv._unpreregisterChannelEvent);
channelPriv._unpreregisterChannelEvent = undefined;
}
}
/**
* @private
*/
_registerChannel(channel) {
if (this._started) {
debugLog(chalk_1.default.red("_registerChannel = "), "channel.hashKey = ", channel.hashKey);
(0, node_opcua_assert_1.assert)(!this._channels[channel.hashKey]);
this._channels[channel.hashKey] = channel;
/**
* @event newChannel
* @param channel
*/
this.emit("newChannel", channel);
channel.on("abort", () => {
this._unregisterChannel(channel);
});
}
else {
debugLog("OPCUAServerEndPoint#_registerChannel called when end point is shutdown !");
debugLog(" -> channel will be forcefully terminated");
channel.close(() => {
channel.dispose();
});
}
}
/**
*/
_unregisterChannel(channel) {
debugLog("_un-registerChannel channel.hashKey", channel.hashKey);
if (!Object.prototype.hasOwnProperty.call(this._channels, channel.hashKey)) {
return;
}
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(this._channels, channel.hashKey), "channel is not registered");
/**
* @event closeChannel
* @param channel
*/
this.emit("closeChannel", channel);
// keep trace of statistics data from old channel for our own accumulated stats.
this.bytesWrittenInOldChannels += channel.bytesWritten;
this.bytesReadInOldChannels += channel.bytesRead;
this.transactionsCountOldChannels += channel.transactionsCount;
delete this._channels[channel.hashKey];
// istanbul ignore next
if (doDebug) {
this._dump_statistics();
debugLog("un-registering channel - Count = ", this.currentChannelCount);
}
/// channel.dispose();
}
_end_listen(err) {
if (!this._listen_callback)
return;
(0, node_opcua_assert_1.assert)(typeof this._listen_callback === "function");
this._listen_callback(err);
this._listen_callback = undefined;
}
/**
* shutdown_channel
* @param channel
* @param inner_callback
*/
shutdown_channel(channel, inner_callback) {
(0, node_opcua_assert_1.assert)(typeof inner_callback === "function");
channel.once("close", () => {
// xx console.log(" ON CLOSED !!!!");
});
channel.close(() => {
this._unregisterChannel(channel);
setImmediate(inner_callback);
});
}
/**
* @private
*/
_prevent_DDOS_Attack(establish_connection, deny_connection) {
const nbConnections = this.activeChannelCount;
if (nbConnections >= this.maxConnections) {
// istanbul ignore next
errorLog(chalk_1.default.bgRed.white("PREVENTING DDOS ATTACK => maxConnection =" + this.maxConnections));
const unused_channels = this.getChannels().filter((channel1) => {
return !channel1.hasSession;
});
if (unused_channels.length === 0) {
doDebug && console.log(this.getChannels()
.map(({ status, isOpened, hasSession }) => `${status} ${isOpened} ${hasSession}\n`)
.join(" "));
// all channels are in used , we cannot get any
errorLog(`All channels are in used ! we cannot cancel any ${this.getChannels().length}`);
// istanbul ignore next
if (doDebug) {
console.log(" - all channels are used !!!!");
false && dumpChannelInfo(this.getChannels());
}
setTimeout(deny_connection, 1000);
return;
}
// istanbul ignore next
if (doDebug) {
console.log(" - Unused channels that can be clobbered", unused_channels.map((channel1) => channel1.hashKey).join(" "));
}
const channel = unused_channels[0];
errorLog(`${unused_channels.length} : Forcefully closing oldest channel that have no session: ${channel.hashKey}`);
channel.close(() => {
// istanbul ignore next
if (doDebug) {
console.log(" _ Unused channel has been closed ", channel.hashKey);
}
this._unregisterChannel(channel);
establish_connection();
});
}
else {
setImmediate(establish_connection);
}
}
}
exports.OPCUAServerEndPoint = OPCUAServerEndPoint;
function estimateSecurityLevel(securityMode, securityPolicy) {
if (securityMode === node_opcua_secure_channel_1.MessageSecurityMode.None) {
return 1;
}
let offset = 100;
if (securityMode === node_opcua_secure_channel_1.MessageSecurityMode.SignAndEncrypt) {
offset = 200;
}
switch (securityPolicy) {
case node_opcua_secure_channel_1.SecurityPolicy.Basic128:
case node_opcua_secure_channel_1.SecurityPolicy.Basic128Rsa15:
case node_opcua_secure_channel_1.SecurityPolicy.Basic192:
return 2; // deprecated => low
case node_opcua_secure_channel_1.SecurityPolicy.Basic192Rsa15:
return 3; // deprecated => low
case node_opcua_secure_channel_1.SecurityPolicy.Basic256:
return 4; // deprecated => low
case node_opcua_secure_channel_1.SecurityPolicy.Basic256Rsa15:
return 4 + offset;
case node_opcua_secure_channel_1.SecurityPolicy.Aes128_Sha256_RsaOaep:
return 5 + offset;
case node_opcua_secure_channel_1.SecurityPolicy.Basic256Sha256:
return 6 + offset;
case node_opcua_secure_channel_1.SecurityPolicy.Aes256_Sha256_RsaPss:
return 7 + offset;
default:
case node_opcua_secure_channel_1.SecurityPolicy.None:
return 1;
}
}
/**
* @private
*/
function _makeEndpointDescription(options, parent) {
(0, node_opcua_assert_1.assert)(Object.prototype.hasOwnProperty.call(options, "serverCertificateChain"));
(0, node_opcua_assert_1.assert)(!Object.prototype.hasOwnProperty.call(options, "serverCertificate"));
(0, node_opcua_assert_1.assert)(!!options.securityMode); // s.MessageSecurityMode
(0, node_opcua_assert_1.assert)(!!options.securityPolicy);
(0, node_opcua_assert_1.assert)(options.server !== null && typeof options.server === "object");
(0, node_opcua_assert_1.assert)(!!options.hostname && typeof options.hostname === "string");
(0, node_opcua_assert_1.assert)(typeof options.restricted === "boolean");
const u = (n) => getUniqueName(n, options.collection);
options.securityLevel =
options.securityLevel === undefined
? estimateSecurityLevel(options.securityMode, options.securityPolicy)
: options.securityLevel;
(0, node_opcua_assert_1.assert)(isFinite(options.securityLevel), "expecting a valid securityLevel");
const securityPolicyUri = (0, node_opcua_secure_channel_1.toURI)(options.securityPolicy);
const userIdentityTokens = [];
const registerIdentity2 = (tokenType, securityPolicy, name) => {
return registerIdentity({
policyId: u(name),
tokenType,
issuedTokenType: null,
issuerEndpointUrl: null,
securityPolicyUri: securityPolicy
});
};
const registerIdentity = (r) => {
const tokenType = r.tokenType === undefined ? node_opcua_service_endpoints_1.UserTokenType.Invalid : r.tokenType;
const securityPolicy = (r.securityPolicyUri || "");
if (!securityPolicy && options.userTokenTypes.indexOf(tokenType) >= 0) {
userIdentityTokens.push(r);
return;
}
if (options.securityPolicies.indexOf(securityPolicy) >= 0 && options.userTokenTypes.indexOf(tokenType) >= 0) {
userIdentityTokens.push(r);
}
};
if (!options.noUserIdentityTokens) {
if (options.securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) {
if (options.allowUnsecurePassword) {
registerIdentity({
policyId: u("username_unsecure"),
tokenType: node_opcua_service_endpoints_1.UserTokenType.UserName,
issuedTokenType: null,
issuerEndpointUrl: null,
securityPolicyUri: null
});
}
const onlyCertificateLessConnection = options.onlyCertificateLessConnection === undefined ? false : options.onlyCertificateLessConnection;
if (!onlyCertificateLessConnection) {
registerIdentity2(node_opcua_service_endpoints_1.UserTokenType.UserName, node_opcua_secure_channel_1.SecurityPolicy.Basic256, "username_basic256");
registerIdentity2(node_opcua_service_endpoints_1.UserTokenType.UserName, node_opcua_secure_channel_1.SecurityPolicy.Basic128Rsa15, "username_basic128Rsa15");
registerIdentity2(node_opcua_service_endpoints_1.UserTokenType.UserName, node_opcua_secure_channel_1.SecurityPolicy.Basic256Sha256, "username_basic256Sha256");
registerIdentity2(node_opcua_service_endpoints_1.UserTokenType.UserName, node_opcua_secure_channel_1.SecurityPolicy.Aes128_Sha256_RsaOaep, "username_aes128Sha256RsaOaep");
// X509
registerIdentity2(node_opcua_service_endpoints_1.UserTokenType.Certificate, node_opcua_secure_channel_1.SecurityPolicy.Basic256, "certificate_basic256");
registerIdentity2(node_opcua_service_endpoints_1.UserTokenType.Certificate, node_opcua_secure_channel_1.SecurityPolicy.Basic128Rsa15, "certificate_basic128Rsa15");
registerIdentity2(node_opcua_service_endpoints_1.UserTokenType.Certificate, node_opcua_secure_channel_1.SecurityPolicy.Basic256Sha256, "certificate_basic256Sha256");
registerIdentity2(node_opcua_service_endpoints_1.UserTokenType.Certificate, node_opcua_secure_channel_1.SecurityPolicy.Aes128_Sha256_RsaOaep, "certificate_aes128Sha256RsaOaep");
}
}
else {
// note:
// when channel session security is not "None",
// userIdentityTokens can be left to null.
// in this case this mean that secure policy will be the same as connection security policy
// istanbul ignore next
if (process.env.NODEOPCUA_SERVER_EMULATE_SIEMENS) {
// However, for some reason SIEMENS plc requires that password get encrypted even though
// the secure channel is also encrypted ....
// you can set the NODEOPCUA_SERVER_EMULATE_SIEMENS env variable to simulate this behavior
const registerIdentity3 = (tokenType, securityPolicy, name) => {
const identity = {
policyId: u(name),
tokenType,
issuedTokenType: null,
issuerEndpointUrl: null,
securityPolicyUri: securityPolicy
};
userIdentityTokens.push(identity);
};
registerIdentity3(node_opcua_service_endpoints_1.UserTokenType.UserName, node_opcua_secure_channel_1.SecurityPolicy.Basic256, "username_basic256");
registerIdentity3(node_opcua_service_endpoints_1.UserTokenType.UserName, node_opcua_secure_channel_1.SecurityPolicy.Basic128Rsa15, "username_basic128Rsa15");
registerIdentity3(node_opcua_service_endpoints_1.UserTokenType.UserName, node_opcua_secure_channel_1.SecurityPolicy.Basic256Sha256, "username_basic256Sha256");
}
else {
registerIdentity({
policyId: u("usernamePassword"),
tokenType: node_opcua_service_endpoints_1.UserTokenType.UserName,
issuedTokenType: null,
issuerEndpointUrl: null,
securityPolicyUri: null
});
registerIdentity({
policyId: u("certificateX509"),
tokenType: node_opcua_service_endpoints_1.UserTokenType.Certificate,
issuedTokenType: null,
issuerEndpointUrl: null,
securityPolicyUri: null
});
}
}
registerIdentity({
policyId: u("anonymous"),
tokenType: node_opcua_service_endpoints_1.UserTokenType.Anonymous,
issuedTokenType: null,
issuerEndpointUrl: null,
securityPolicyUri: null
});
}
// return the endpoint object
const endpoint = new node_opcua_service_endpoints_2.EndpointDescription({
endpointUrl: "<to be evaluated at run time>", // options.endpointUrl,
server: undefined, // options.server,
serverCertificate: options.serverCertificateChain,
securityMode: options.securityMode,
securityPolicyUri,
userIdentityTokens,
securityLevel: options.securityLevel,
transportProfileUri: default_transportProfileUri
});
endpoint._parent = parent;
// endpointUrl is dynamic as port number may be adjusted
// when the tcp socket start listening
endpoint.__defineGetter__("endpointUrl", () => {
const port = endpoint._parent.port;
const resourcePath = options.resourcePath || "";
const hostname = options.hostname;
const endpointUrl = `opc.tcp://${hostname}:${port}${resourcePath}`;
return (0, node_opcua_hostname_1.resolveFullyQualifiedDomainName)(endpointUrl);
});
endpoint.server = options.server;
endpoint.restricted = options.restricted;
return endpoint;
}
/**
* return true if the end point matches security mode and policy
* @param endpoint
* @param securityMode
* @param securityPolicy
* @internal
*
*/
function matching_endpoint(securityMode, securityPolicy, endpointUrl, endpoint) {
(0, node_opcua_assert_1.assert)(endpoint instanceof node_opcua_service_endpoints_2.EndpointDescription);
const endpoint_securityPolicy = (0, node_opcua_secure_channel_1.fromURI)(endpoint.securityPolicyUri);
if (endpointUrl && endpoint.endpointUrl !== endpointUrl) {
return false;
}
return endpoint.securityMode === securityMode && endpoint_securityPolicy === securityPolicy;
}
const defaultSecurityModes = [node_opcua_secure_channel_1.MessageSecurityMode.None, node_opcua_secure_channel_1.MessageSecurityMode.Sign, node_opcua_secure_channel_1.MessageSecurityMode.SignAndEncrypt];
const defaultSecurityPolicies = [
// now deprecated Basic128Rs15 shall be disabled by default
// see https://profiles.opcfoundation.org/profile/1532
// SecurityPolicy.Basic128Rsa15,
// now deprecated Basic256 shall be disabled by default
// see https://profiles.opcfoundation.org/profile/2062
// SecurityPolicy.Basic256,
// xx UNUSED!! SecurityPolicy.Basic192Rsa15,
// xx UNUSED!! SecurityPolicy.Basic256Rsa15,
node_opcua_secure_channel_1.SecurityPolicy.Basic256Sha256,
node_opcua_secure_channel_1.SecurityPolicy.Aes128_Sha256_RsaOaep,
node_opcua_secure_channel_1.SecurityPolicy.Aes256_Sha256_RsaPss
];
const defaultUserTokenTypes = [
node_opcua_service_endpoints_1.UserTokenType.Anonymous,
node_opcua_service_endpoints_1.UserTokenType.UserName,
node_opcua_service_endpoints_1.UserTokenType.Certificate
// NOT USED YET : UserTokenType.IssuedToken
];
//# sourceMappingURL=server_end_point.js.map