APIs

Show:
"use strict";
/**
 * @module opcua.server
 */

var _ = require("underscore");
var assert = require("node-opcua-assert");
var util = require("util");
var EventEmitter = require("events").EventEmitter;


var crypto_utils = require("node-opcua-crypto").crypto_utils;

var MessageBuilder = require("../message_builder").MessageBuilder;
var MessageChunker = require("../message_chunker").MessageChunker;

var securityPolicy_m =require("../security_policy");
var SecurityPolicy = securityPolicy_m.SecurityPolicy;


var ServerTCP_transport = require("node-opcua-transport").ServerTCP_transport;

var StatusCode = require("node-opcua-status-code").StatusCode;
var StatusCodes = require("node-opcua-status-code").StatusCodes;

var secure_channel_service = require("../services");
var AsymmetricAlgorithmSecurityHeader = secure_channel_service.AsymmetricAlgorithmSecurityHeader;
var MessageSecurityMode = require("node-opcua-service-secure-channel").MessageSecurityMode;
var ChannelSecurityToken = secure_channel_service.ChannelSecurityToken;
var ServiceFault = secure_channel_service.ServiceFault;
var OpenSecureChannelRequest = secure_channel_service.OpenSecureChannelRequest;
var OpenSecureChannelResponse = secure_channel_service.OpenSecureChannelResponse;
var SecurityTokenRequestType = secure_channel_service.SecurityTokenRequestType;



var split_der = require("node-opcua-crypto").crypto_explore_certificate.split_der;



assert(MessageSecurityMode);
assert(ChannelSecurityToken);
assert(OpenSecureChannelRequest);
assert(OpenSecureChannelResponse);
assert(SecurityTokenRequestType);
assert(ServiceFault);

var do_trace_message = process.env.DEBUG && (process.env.DEBUG.indexOf("TRACE")) >= 0;


var crypto = require("crypto");
var analyze_object_binary_encoding = require("node-opcua-packet-analyzer").analyze_object_binary_encoding;

var debugLog = require("node-opcua-debug").make_debugLog(__filename);
var doDebug = require("node-opcua-debug").checkDebugFlag(__filename);

var last_channel_id = 0;
function getNextChannelId() {
    last_channel_id += 1;
    return last_channel_id;
}

var get_clock_tick = require("node-opcua-utils").get_clock_tick;

/**
 * @class ServerSecureChannelLayer
 * @extends EventEmitter
 * @uses MessageBuilder
 * @uses MessageChunker
 * @constructor
 * @param options
 * @param options.parent {OPCUAServerEndPoint} parent
 * @param [options.timeout = 10000] {Number} timeout in milliseconds
 * @param [options.defaultSecureTokenLifetime = 30000] defaultSecureTokenLifetime
 * @param [options.objectFactory] an factory that provides a method createObjectId(id) for the message builder
 */
function ServerSecureChannelLayer(options) {

    options = options || {};

    var self = this;

    self.parent = options.parent;

    self.protocolVersion = 0;

    self.lastTokenId = 0;

    self.timeout = options.timeout || 10000; // connection timeout

    self.defaultSecureTokenLifetime = options.defaultSecureTokenLifetime || 600000;

    // uninitialized securityToken
    self.securityToken = {secureChannelId: 0, tokenId: 0};

    self.serverNonce = null; // will be created when needed


    options.objectFactory = options.objectFactory || require("node-opcua-factory");
    assert(_.isObject(options.objectFactory));

    self.messageBuilder = new MessageBuilder({objectFactory: options.objectFactory});

    self.messageBuilder.privateKey = self.getPrivateKey();

    //disabled self.messageBuilder.on("chunk", function (chunk) {});

    //disabled self.messageBuilder.on("full_message_body", function (full_message_body) { });

    self.messageBuilder.on("error", function (err) {

        // istanbul ignore next
        if (doDebug) {
            debugLog("xxxxx error ".red,err.message.yellow,err.stack);
            debugLog("xxxxx Server is now closing socket, without further notice".red);
        }
        // close socket immediately
        self.close(undefined);
    });


    // at first use a anonymous connection
    self.securityHeader = new AsymmetricAlgorithmSecurityHeader({
        securityPolicyUri: "http://opcfoundation.org/UA/SecurityPolicy#None",
        senderCertificate: null,
        receiverCertificateThumbprint: null
    });

    self.messageChunker = new MessageChunker({
        securityHeader: self.securityHeader // for OPN
    });

    self.secureChannelId = getNextChannelId();

    self._tick0 = 0;

    self.securityMode = MessageSecurityMode.INVALID;

    self.timeoutId = 0;

    self._transactionsCount = 0;

    self.sessionTokens = {};
}

util.inherits(ServerSecureChannelLayer, EventEmitter);

var ObjectRegistry = require("node-opcua-object-registry").ObjectRegistry;
ServerSecureChannelLayer.registry = new ObjectRegistry();
/**
 * the endpoint associated with this secure channel
 * @property endpoints
 * @type {OPCUAServerEndPoint}
 *
 */
ServerSecureChannelLayer.prototype.__defineGetter__("endpoints", function () {
    return this.parent;
});

ServerSecureChannelLayer.prototype.setSecurity = function (securityMode, securityPolicy) {
    var self = this;
    // TODO verify that the endpoint really supports this mode

    self.messageBuilder.setSecurity(securityMode, securityPolicy);
};

/**
 * @method getCertificate
 * @return {Buffer} the X509 DER form certificate
 */
ServerSecureChannelLayer.prototype.getCertificateChain = function () {
    assert(this.parent, "expecting a valid parent");
    return this.parent.getCertificateChain();
};

/**
 * @method getCertificate
 * @return {Buffer} the X509 DER form certificate
 */
ServerSecureChannelLayer.prototype.getCertificate = function () {
    assert(this.parent, "expecting a valid parent");
    return this.parent.getCertificate();
};

ServerSecureChannelLayer.prototype.getSignatureLength = function () {

    var self = this;
    var chain = self.getCertificateChain();
    var s = split_der(chain)[0];
    var cert = crypto_utils.exploreCertificate(s);
    return cert.publicKeyLength; // 1024 bits = 128Bytes or 2048=256Bytes
};

/**
 * @method getPrivateKey
 * @return {Buffer} the privateKey
 */
ServerSecureChannelLayer.prototype.getPrivateKey = function () {
    return this.parent ? this.parent._privateKey : null;
};

ServerSecureChannelLayer.prototype.__defineGetter__("securityTokenCount", function () {
    assert(_.isNumber(this.lastTokenId));
    return this.lastTokenId;
});


function _stop_security_token_watch_dog() {

    /* jshint validthis: true */
    var self = this;

    if (self._securityTokenTimeout) {
        clearTimeout(self._securityTokenTimeout);
        self._securityTokenTimeout = null;
    }
}

function _start_security_token_watch_dog() {

    /* jshint validthis: true */
    var self = this;

    // install securityToken timeout watchdog
    self._securityTokenTimeout = setTimeout(function () {
        console.log(" Security token has really expired and shall be discarded !!!!");
        console.log(" Server will now refuse message with token ", self.securityToken.tokenId);
        self._securityTokenTimeout = null;
    }, self.securityToken.revisedLifeTime * 120 / 100);

}

ServerSecureChannelLayer.prototype._add_new_security_token = function () {

    // The  Server  has  to accept requests secured with the old SecurityToken  until that  SecurityToken  expires
    // or until it receives a  Message  from the  Client  secured with the new  SecurityToken.
    var self = this;

    _stop_security_token_watch_dog.call(self);
    self.lastTokenId += 1;

    var securityToken = new ChannelSecurityToken({
        secureChannelId: self.secureChannelId,
        tokenId: self.lastTokenId, // todo ?
        createdAt: new Date(), // now
        revisedLifeTime: self.revisedLifeTime
    });

    assert(!securityToken.expired);
    assert(_.isFinite(securityToken.revisedLifeTime));

    self.securityToken = securityToken;

    debugLog("SecurityToken", securityToken.tokenId);

    _start_security_token_watch_dog.call(self);
};

function _prepare_security_token(openSecureChannelRequest) {

    /* jshint validthis: true */
    var self = this;
    assert(openSecureChannelRequest instanceof OpenSecureChannelRequest);

    delete self.securityToken;

    if (openSecureChannelRequest.requestType === SecurityTokenRequestType.RENEW) {

        _stop_security_token_watch_dog.call(self);

    } else if (openSecureChannelRequest.requestType === SecurityTokenRequestType.ISSUE) {
        // TODO
    } else {
        // Invalid requestType
    }

    self._add_new_security_token();
}

function _set_lifetime(requestedLifetime) {

    /* jshint validthis: true */
    var self = this;

    assert(_.isFinite(requestedLifetime));


    // revised lifetime
    self.revisedLifeTime = requestedLifetime;
    if (self.revisedLifeTime === 0) {
        self.revisedLifeTime = self.defaultSecureTokenLifetime;
    } else {
        self.revisedLifeTime = Math.min(self.defaultSecureTokenLifetime, self.revisedLifeTime);
    }

}

function _stop_open_channel_watch_dog() {

    /* jshint validthis: true */
    var self = this;

    if (self.timeoutId) {
        clearTimeout(self.timeoutId);
        self.timeoutId = null;
    }
}


ServerSecureChannelLayer.prototype._cleanup_pending_timers = function () {

    var self = this;

    // there is no need for the security token expiration event to trigger anymore
    _stop_security_token_watch_dog.call(self);

    _stop_open_channel_watch_dog.call(self);
};

/**
 * @method init
 * @async
 * @param socket {Socket}
 * @param callback {Function}
 */
ServerSecureChannelLayer.prototype.init = function (socket, callback) {

    var self = this;

    self.transport = new ServerTCP_transport();
    self.transport.timeout = self.timeout;

    self.transport.init(socket, function (err) {
        if (err) {
            callback(err);
        } else {
            // bind low level TCP transport to messageBuilder
            self.transport.on("message", function (message_chunk) {
                assert(self.messageBuilder);
                self.messageBuilder.feed(message_chunk);
            });
            debugLog("ServerSecureChannelLayer : Transport layer has been initialized ");
            debugLog("... now waiting for OpenSecureChannelRequest...");

            ServerSecureChannelLayer.registry.register(self);

            _wait_for_open_secure_channel_request.call(self, callback, self.timeout);
        }
    });

    // detect transport closure
    self._transport_socket_close_listener = function(err) {
       self._abort();
    };
    self.transport.on("socket_closed",self._transport_socket_close_listener);


};

ServerSecureChannelLayer.prototype._rememberClientAddressAndPort = function() {
    if (this.transport._socket) {
        this._remoteAddress = this.transport._socket.remoteAddress;
        this._remotePort = this.transport._socket.remotePort;
    }
};

ServerSecureChannelLayer.prototype.__defineGetter__("remoteAddress",function() {
    return  this._remoteAddress;
});

ServerSecureChannelLayer.prototype.__defineGetter__("remotePort",function() {
    return  this._remotePort;
});


function _cancel_wait_for_open_secure_channel_request_timeout() {

    /* jshint validthis: true */
    var self = this;

    assert(self);

    _stop_open_channel_watch_dog.call(self);
    //xx assert(self.timeoutId);
    //xx // suspend timeout handler
    //xx clearTimeout(self.timeoutId);
    //xx self.timeoutId = null;
}

function _install_wait_for_open_secure_channel_request_timeout(callback, timeout) {

    /* jshint validthis: true */
    var self = this;

    assert(_.isFinite(timeout));
    assert(_.isFunction(callback));
    assert(self);

    self.timeoutId = setTimeout(function () {

        self.timeoutId = null;
        var err = new Error("Timeout waiting for OpenChannelRequest (timeout was " + timeout + " ms)");
        debugLog(err.message);
        self.close(function () {
            callback(err);
        });
    }, timeout);

}

function _on_initial_open_secure_channel_request(callback, request, msgType, requestId, secureChannelId) {

    /* istanbul ignore next */
    if (do_trace_message) {
        dump_request(request,requestId,secureChannelId);
    }

    assert(_.isFunction(callback));
    /* jshint validthis: true */
    var self = this;

    assert(self);

    // check that the request is a OpenSecureChannelRequest
    /* istanbul ignore next */
    if (doDebug) {
        debugLog(self.messageBuilder.sequenceHeader.toString());
        debugLog(self.messageBuilder.securityHeader.toString());
        //xx analyze_object_binary_encoding(request);
    }

    _cancel_wait_for_open_secure_channel_request_timeout.call(self);

    requestId = self.messageBuilder.sequenceHeader.requestId;
    assert(requestId > 0);

    var message = {
        request: request,
        securityHeader: self.messageBuilder.securityHeader,
        requestId: requestId
    };
    assert(message.requestId === requestId);

    self.clientSecurityHeader = message.securityHeader;

    _on_initial_OpenSecureChannelRequest.call(self, message, callback);

}

function _wait_for_open_secure_channel_request(callback, timeout) {

    /* jshint validthis: true */
    var self = this;
    _install_wait_for_open_secure_channel_request_timeout.call(self, callback, timeout);
    self.messageBuilder.once("message", _on_initial_open_secure_channel_request.bind(self, callback));
}

function _send_chunk(callback, messageChunk) {

    /* jshint validthis: true */
    var self = this;

    if (messageChunk) {
        self.transport.write(messageChunk);
    } else {
        // record tick 3 : transaction completed.
        self._tick3 = get_clock_tick();

        if (callback) {
            setImmediate(callback);
        }

        self._record_transaction_statistics();

        /* istanbul ignore next */
        if (doDebug) {
            // dump some statistics about transaction ( time and sizes )
            self._dump_transaction_statistics();
        }

        self.emit("transaction_done");

    }
}


ServerSecureChannelLayer.prototype._get_security_options_for_OPN = function () {

    var self = this;
    var cryptoFactory = self.messageBuilder.cryptoFactory;
    var options = {};
    // install sign & sign-encrypt behavior
    if (self.securityMode === MessageSecurityMode.SIGN || self.securityMode === MessageSecurityMode.SIGNANDENCRYPT) {

        assert(cryptoFactory, "ServerSecureChannelLayer must have a crypto strategy");

        options.signatureLength = self.getSignatureLength();

        options.signingFunc = function (chunk) {
            var signed = cryptoFactory.asymmetricSign(chunk, self.getPrivateKey());
            assert(signed.length === options.signatureLength);
            return signed;
        };

        assert(self.receiverPublicKeyLength >= 0);
        options.plainBlockSize = self.receiverPublicKeyLength - cryptoFactory.blockPaddingSize;
        options.cipherBlockSize = self.receiverPublicKeyLength;

        options.encrypt_buffer = function (chunk) {
            return cryptoFactory.asymmetricEncrypt(chunk, self.receiverPublicKey);
        };
    }
    return options;
};

ServerSecureChannelLayer.prototype._get_security_options_for_MSG = function () {

    var self = this;
    if (self.securityMode === MessageSecurityMode.NONE) {
        return null;
    }
    var cryptoFactory = self.messageBuilder.cryptoFactory;

    /* istanbul ignore next */
    if (!cryptoFactory) {
        return null;
    }

    assert(cryptoFactory, "ServerSecureChannelLayer must have a crypto strategy");
    assert(self.derivedKeys.derivedServerKeys);
    var derivedServerKeys = self.derivedKeys.derivedServerKeys;
    return securityPolicy_m.getOptionsForSymmetricSignAndEncrypt(self.securityMode, derivedServerKeys);
};


/**
 * @method send_response
 * @async
 * @param msgType
 * @param response
 * @param message
 * @param  {Function} [callback] an optional callback function
 */
ServerSecureChannelLayer.prototype.send_response = function (msgType, response, message, callback) {

    var request = message.request;
    var requestId = message.requestId;
    var self = this;

    // istanbul ignore next
    if (doDebug) {
        assert(response._schema);
        assert(request._schema);
        assert(requestId && requestId > 0);
        // verify that response for a given requestId is only sent once.
        if (!self.__verifId) {
            self.__verifId = {};
        }
        assert(!self.__verifId[requestId]," response for requestId has already been sent !! - Internal Error");
        self.__verifId[requestId] = requestId;
    }

    self.msgType = msgType;

    // record tick : send response received.
    self._tick2 = get_clock_tick();

    assert(self.securityToken);

    var options = {
        requestId: requestId,
        secureChannelId: self.securityToken.secureChannelId,
        tokenId: self.securityToken.tokenId,

        chunkSize: self.transport.receiveBufferSize

    };

    var security_options = (msgType === "OPN") ? self._get_security_options_for_OPN() : self._get_security_options_for_MSG();
    options = _.extend(options, security_options);

    //xx assert(_.isFinite(request.requestHeader.requestHandle));

    response.responseHeader.requestHandle = request.requestHeader.requestHandle;

    /* istanbul ignore next */
    if (0 && doDebug) {
        console.log(" options ", options);
        analyze_object_binary_encoding(response);
    }

    //xx console.log(" sending request ".bgWhite.red,requestId,message.request.constructor.name);

    /* istanbul ignore next */
    if (do_trace_message) {
        console.log("xxxx   >>>> ---------------------------------------- ".cyan.bold, response._schema.name.green.bold,requestId);
        console.log(response.toString());
        console.log("xxxx   >>>> ----------------------------------------|\n".cyan.bold);
    }

    if (self._on_response) {
        self._on_response(msgType, response, message);
    }

    self._transactionsCount += 1;
    self.messageChunker.chunkSecureMessage(msgType, options, response, _send_chunk.bind(self, callback));
};


/**
 *
 * send a ServiceFault response
 * @method send_error_and_abort
 * @async
 * @param statusCode  {StatusCode} the status code
 * @param description {String}
 * @param message     {String}
 * @param callback    {Function}
 */
ServerSecureChannelLayer.prototype.send_error_and_abort = function (statusCode, description, message, callback) {

    var self = this;

    assert(statusCode instanceof StatusCode);
    assert(message.request._schema);
    assert(message.requestId && message.requestId > 0);
    assert(_.isFunction(callback));

    var response = new ServiceFault({
        responseHeader: {serviceResult: statusCode}
    });

    response.description = description;
    self.send_response("MSG", response, message, function () {
        self.close(callback);
    });

};


/**
 * _process_certificates extracts client public keys from client certificate
 *  and store them in self.receiverPublicKey and self.receiverCertificate
 *  it also caches self.receiverPublicKeyLength.
 *
 *  so they can be used by security channel.
 *
 * @method _process_certificates
 * @param message the message coming from the client
 * @param callback
 * @private
 * @async
 */
ServerSecureChannelLayer.prototype._process_certificates = function (message, callback) {

    var self = this;

    self.receiverPublicKey = null;
    self.receiverPublicKeyLength = 0;
    self.receiverCertificate = message.securityHeader ? message.securityHeader.senderCertificate : null;

    // ignore receiverCertificate that have a zero length
    /* istanbul ignore next */
    if (self.receiverCertificate && self.receiverCertificate.length === 0) {
        self.receiverCertificate = null;
    }

    if (self.receiverCertificate) {
        // extract public key
        crypto_utils.extractPublicKeyFromCertificate(self.receiverCertificate, function (err, key) {
            if (!err) {
                self.receiverPublicKey = key;
                self.receiverPublicKeyLength = crypto_utils.rsa_length(key);
            }
            callback(err);
        });
    } else {
        self.receiverPublicKey = null;
        callback();
    }
};

/**
 * @method _prepare_security_header
 * @param request
 * @param message
 * @return {AsymmetricAlgorithmSecurityHeader}
 * @private
 */
function _prepare_security_header(request, message) {

    /* jshint validthis: true */
    var self = this;
    var securityHeader = null;
    // senderCertificate:
    //    The X509v3 certificate assigned to the sending application instance.
    //    This is a DER encoded blob.
    //    This indicates what private key was used to sign the MessageChunk.
    //    This field shall be null if the message is not signed.
    // receiverCertificateThumbprint:
    //    The thumbprint of the X509v3 certificate assigned to the receiving application
    //    The thumbprint is the SHA1 digest of the DER encoded form of the certificate.
    //    This indicates what public key was used to encrypt the MessageChunk
    //   This field shall be null if the message is not encrypted.
    switch (request.securityMode.value) {
        case MessageSecurityMode.NONE.value:
            assert(!message.securityHeader || message.securityHeader.securityPolicyUri === "http://opcfoundation.org/UA/SecurityPolicy#None");
            securityHeader = new AsymmetricAlgorithmSecurityHeader({
                securityPolicyUri: "http://opcfoundation.org/UA/SecurityPolicy#None",
                senderCertificate: null, // message not signed
                receiverCertificateThumbprint: null // message not encrypted
            });

            break;
        case MessageSecurityMode.SIGN.value:
        case MessageSecurityMode.SIGNANDENCRYPT.value:

            // get the thumbprint of the client certificate
            var thumbprint = self.receiverCertificate ? crypto_utils.makeSHA1Thumbprint(self.receiverCertificate) : null;

            securityHeader = new AsymmetricAlgorithmSecurityHeader({
                securityPolicyUri: self.clientSecurityHeader.securityPolicyUri,
                senderCertificate: self.getCertificateChain(), // certificate of the private key used to sign the message
                receiverCertificateThumbprint: thumbprint // message not encrypted (????)
            });
            break;
    }
    return securityHeader;
}


function _handle_OpenSecureChannelRequest(message, callback) {

    /* jshint validthis: true */
    var self = this;

    var request = message.request;
    var requestId = message.requestId;
    assert(request._schema.name === "OpenSecureChannelRequest");
    assert(requestId && requestId > 0);

    self.clientNonce = request.clientNonce;

    _set_lifetime.call(self, request.requestedLifetime);

    _prepare_security_token.call(self, request);


    var serviceResult = StatusCodes.Good;

    var cryptoFactory = self.messageBuilder.cryptoFactory;
    if (cryptoFactory) {

        // serverNonce: A random number that shall not be used in any other request. A new
        //    serverNonce shall be generated for each time a SecureChannel is renewed.
        //    This parameter shall have a length equal to key size used for the symmetric
        //    encryption algorithm that is identified by the securityPolicyUri.
        self.serverNonce = crypto.randomBytes(cryptoFactory.symmetricKeyLength);

        if (self.clientNonce.length !== self.serverNonce.length) {

            console.log("warning client Nonce length doesn't match server nonce length".red,self.clientNonce.length ," !== ",self.serverNonce.length);
            // what can we do
            // - just ignore it ?
            // - or adapt serverNonce length to clientNonce Length ?
            //xx self.serverNonce = crypto.randomBytes(self.clientNonce.length);
            // - or adapt clientNonce length to serverNonce Length ?
            //xx self.clientNonce = self.clientNonce.slice(0,self.serverNonce.length);
            //
            // - or abort connection ? << LET BE SAFE AND CHOOSE THIS ONE !
            serviceResult = StatusCodes.BadSecurityModeRejected; // ToDo check code

        }
        // expose derivedKey to use for symmetric sign&encrypt
        // to help us decrypting and verifying messages received from client
        self.derivedKeys = cryptoFactory.compute_derived_keys(this.serverNonce, this.clientNonce);
    }

    var derivedClientKeys = this.derivedKeys ? this.derivedKeys.derivedClientKeys : null;
    this.messageBuilder.pushNewToken(this.securityToken, derivedClientKeys);

    // let prepare self.securityHeader;
    self.securityHeader = _prepare_security_header.call(self, request, message);
    assert(self.securityHeader);

    var derivedServerKeys = self.derivedKeys ? self.derivedKeys.derivedServerKeys : null;

    self.messageChunker.update({

        // for OPN
        securityHeader: self.securityHeader,

        // derived keys for symmetric encryption of standard MSG
        // to sign and encrypt MSG sent to client
        derivedKeys: derivedServerKeys
    });

    var response = new OpenSecureChannelResponse({
        responseHeader: {
            serviceResult: serviceResult
        },
        serverProtocolVersion: self.protocolVersion,
        securityToken: self.securityToken,
        serverNonce: self.serverNonce
    });

    // get the clientCertificate from message securityHeader
    // for convenience
    self.clientCertificate = message.securityHeader ? message.securityHeader.senderCertificate : null;

    var description;

    // If the SecurityMode is not None then the Server shall verify that a SenderCertificate and a
    // ReceiverCertificateThumbprint were specified in the SecurityHeader.
    if (self.securityMode.value !== MessageSecurityMode.NONE.value) {

        if (!_check_receiverCertificateThumbprint.call(self, self.clientSecurityHeader)) {
            description = "Server#OpenSecureChannelRequest : Invalid receiver certificate thumbprint : the thumbprint doesn't match server certificate !";
            console.log(description.cyan);
            response.responseHeader.serviceResult = StatusCodes.BadCertificateInvalid;
        }
    }

    if (self.clientCertificate) {
        var certificate_status = _check_certificate_validity(self.clientCertificate);
        if (StatusCodes.Good !== certificate_status) {

            description = "Sender Certificate Error";
            console.log(description.cyan, certificate_status.toString().bgRed.yellow);
            // OPCUA specification v1.02 part 6 page 42 $6.7.4
            // If an error occurs after the  Server  has verified  Message  security  it  shall  return a  ServiceFault  instead
            // of a OpenSecureChannel  response. The  ServiceFault  Message  is described in  Part  4,   7.28.
            response = new ServiceFault({responseHeader: {serviceResult: certificate_status}});
        }
    }

    self.send_response("OPN", response, message, function (/*err*/) {
        if (response.responseHeader.serviceResult !== StatusCodes.Good) {
            self.close();
        }
        callback(null);

    });

}



ServerSecureChannelLayer.prototype._abort = function () {

    var self = this;
    if (self._abort_has_been_called) {
        return;
    }

    ServerSecureChannelLayer.registry.unregister(self);

    self._abort_has_been_called = true;

    self._cleanup_pending_timers();
    /**
     * notify the observers that the SecureChannel has aborted.
     * the reason could be :
     *   - a CloseSecureChannelRequest has been received.
     *   - a invalid message has been received
     * the event is sent after the underlying transport layer has been closed.
     *
     * @event abort
     */
    self.emit("abort");

};

/**
 * Abruptly close a Server SecureChannel ,by terminating the underlying transport.
 *
 *
 * @method close
 * @async
 * @param callback {Function}
 */
ServerSecureChannelLayer.prototype.close = function (callback) {

    var self = this;
    debugLog("ServerSecureChannelLayer#close");
    // close socket
    self.transport.disconnect(function () {
        self._abort();
        if (_.isFunction(callback)) {
            callback();
        }
    });
};

ServerSecureChannelLayer.prototype._record_transaction_statistics = function () {

    var self = this;
    self._bytesRead_before   = self._bytesRead_before || 0;
    self._byesWritten_before = self._byesWritten_before || 0;

    self.last_transaction_stats = {
        bytesRead:       self.bytesRead - self._bytesRead_before,
        bytesWritten:    self.bytesWritten - self._bytesWritten_before,
        lap_reception:   self._tick1 - self._tick0,
        lap_processing:  self._tick2 - self._tick1,
        lap_emission:    self._tick3 - self._tick2,
        //last_transaction_time: Date.now()
    };

    // final operation in statistics
    self._bytesRead_before = self.bytesRead;
    self._bytesWritten_before = self.bytesWritten;

};

ServerSecureChannelLayer.prototype._dump_transaction_statistics = function () {
    var self = this;
    console.log("                Bytes Read : ", self.last_transaction_stats.bytesRead);
    console.log("             Bytes Written : ", self.last_transaction_stats.bytesWritten);
    console.log("   time to receive request : ", self.last_transaction_stats.lap_reception / 1000, " sec");
    console.log("   time to process request : ", self.last_transaction_stats.lap_processing / 1000, " sec");
    console.log("   time to send response   : ", self.last_transaction_stats.lap_emission / 1000, " sec");
};


ServerSecureChannelLayer.prototype.has_endpoint_for_security_mode_and_policy = function (securityMode, securityPolicy) {
    var self = this;
    if (!self.endpoints) {
        return true;
    }
    var endpoint_desc = self.endpoints.getEndpointDescription(securityMode, securityPolicy);
    return (endpoint_desc !== null);
};

// istanbul ignore next
function dump_request(request,requestId,secureChannelId) {
    console.log("xxxx   <<<< ---------------------------------------- ".cyan, request._schema.name.yellow, "requestId", requestId, "secureChannelId=", secureChannelId);
    console.log(request.toString());
    console.log("xxxx   <<<< ---------------------------------------- \n".cyan);
}

var _on_common_message = function (request, msgType, requestId, secureChannelId) {
    var self = this;

    /* istanbul ignore next */
    if (do_trace_message) {
        dump_request(request,requestId,secureChannelId);
    }

    requestId = self.messageBuilder.sequenceHeader.requestId;

    var message = {
        request: request,
        requestId: requestId,
        channel: self
    };

    if (msgType === "CLO" && request._schema.name === "CloseSecureChannelRequest") {

        self.close();

    } else if (msgType === "OPN" && request._schema.name === "OpenSecureChannelRequest") {
        // intercept client request to renew security Token
        _handle_OpenSecureChannelRequest.call(self, message, function () {
        });
    } else {

        if (request._schema.name === "CloseSecureChannelRequest") {
            console.log("WARNING : RECEIVED a CloseSecureChannelRequest with MSGTYPE=" + msgType);
            self.close();

        } else {

            // record tick 1 : after message has been received, before message processing
            self._tick1 = get_clock_tick();

            /**
             * notify the observer that a OPCUA message has been received.
             * It is up to one observer to call send_response or send_error_and_abort to complete
             * the transaction.
             *
             * @event message
             * @param message
             */
            self.emit("message", message);
        }
    }
};

/**
 * @method _check_receiverCertificateThumbprint
 * verify that the receiverCertificateThumbprint send by the client
 * matching the CertificateThumbPrint of the server
 * @param clientSecurityHeader
 * @return {boolean}
 * @private
 */
function _check_receiverCertificateThumbprint(clientSecurityHeader) {
    /* jshint validthis: true */
    var self = this;
    if (clientSecurityHeader.receiverCertificateThumbprint) {
        // check if the receiverCertificateThumbprint is my certificate thumbprint
        var serverCertificateChain  = self.getCertificateChain();
        //xx var serverCertificate = split_der(serverCertificateChain)[0];
        var myCertificateThumbPrint = crypto_utils.makeSHA1Thumbprint(serverCertificateChain);
        //xx console.log("xxxx     my certificate thumbprint",myCertificateThumbPrint.toString("hex") );
        //xx console.log("xxxx receiverCertificateThumbprint",securityHeader.receiverCertificateThumbprint.toString("hex") );
        return myCertificateThumbPrint.toString("hex") === clientSecurityHeader.receiverCertificateThumbprint.toString("hex");
    }
    return true;
}


// Bad_CertificateHostNameInvalid            The HostName used to connect to a Server does not match a HostName in the
//                                           Certificate.
// Bad_CertificateIssuerRevocationUnknown    It was not possible to determine if the Issuer Certificate has been revoked.
// Bad_CertificateIssuerUseNotAllowed        The Issuer Certificate may not be used for the requested operation.
// Bad_CertificateIssuerTimeInvalid          An Issuer Certificate has expired or is not yet valid.
// Bad_CertificateIssuerRevoked              The Issuer Certificate has been revoked.
// Bad_CertificateInvalid                    The certificate provided as a parameter is not valid.
// Bad_CertificateRevocationUnknown          It was not possible to determine if the Certificate has been revoked.
// Bad_CertificateRevoked                    The Certificate has been revoked.
// Bad_CertificateTimeInvalid                The Certificate has expired or is not yet valid.
// Bad_CertificateUriInvalid                 The URI specified in the ApplicationDescription does not match the URI in the Certificate.
// Bad_CertificateUntrusted                  The Certificate is not trusted.
// Bad_CertificateUseNotAllowed              The Certificate may not be used for the requested operation.

// also see OPCUA 1.02 part 4 :
//  - page 95  6.1.3 Determining if a Certificate is Trusted
// -  page 100 6.2.3 Validating a Software Certificate
//
function _check_certificate_validity(certificate) {

    // Is the  signature on the SoftwareCertificate valid .?
    if (!certificate) {
        // missing certificate
        return StatusCodes.BadSecurityChecksFailed;
    }

    //-- var split_der = require("node-opcua-crypto").crypto_explore_certificate.split_der;
    //-- var chain = split_der(securityHeader.senderCertificate);
    //-- //xx console.log("xxx NB CERTIFICATE IN CHAIN = ".red,chain.length);

    // Has SoftwareCertificate passed its issue date and has it not expired ?
    // check dates
    var cert = crypto_utils.exploreCertificate(certificate);

    var now = new Date();

    if (cert.notBefore.getTime() > now.getTime()) {
        // certificate is not active yet
        console.log(" Sender certificate is invalid : certificate is not active yet !".red + "  not before date =" + cert.notBefore);
        return StatusCodes.BadCertificateTimeInvalid;
    }
    if (cert.notAfter.getTime() <= now.getTime()) {
        // certificate is obsolete
        console.log(" Sender certificate is invalid : certificate has expired !".red + " not after date =" + cert.notAfter);
        return StatusCodes.BadCertificateTimeInvalid;
    }

    // Has SoftwareCertificate has  been revoked by the issuer ?
    // TODO: check if certificate is revoked or not ...
    // StatusCodes.BadCertificateRevoked

    // is issuer Certificate  valid and has not been revoked by the CA that issued it. ?
    // TODO : check validity of issuer certificate
    // StatusCodes.BadCertificateIssuerRevoked

    //does the URI specified in the ApplicationDescription  match the URI in the Certificate ?
    // TODO : check ApplicationDescription of issuer certificate
    // return StatusCodes.BadCertificateUriInvalid

    return StatusCodes.Good;
}

// Bad_RequestTypeInvalid     The security token request type is not valid.
// Bad_SecurityModeRejected   The security mode does not meet the requirements set by the Server.
// Bad_SecurityPolicyRejected The security policy does not meet the requirements set by the Server.
// Bad_SecureChannelIdInvalid
// Bad_NonceInvalid

function isValidSecurityPolicy(securityPolicy) {
    switch (securityPolicy.value) {
        case SecurityPolicy.None.value:
        case SecurityPolicy.Basic128Rsa15.value:
        case SecurityPolicy.Basic256.value:
        case SecurityPolicy.Basic256Sha256.value:
            return StatusCodes.Good;
        default:
            return StatusCodes.BadSecurityPolicyRejected;
    }
}

function _send_error(statusCode, description, message, callback) {
    /* jshint validthis: true */
    var self = this;

    // turn of security mode as we haven't manage to set it to
    self.securityMode = MessageSecurityMode.NONE;

    // unexpected message type ! let close the channel
    var err = new Error(description);
    self.send_error_and_abort(statusCode, description, message, function () {
        callback(err); // OK
    });

}
function _on_initial_OpenSecureChannelRequest(message, callback) {

    assert(_.isFunction(callback));

    /* jshint validthis: true */
    var self = this;

    var request = message.request;
    var requestId = message.requestId;

    assert(requestId > 0);
    assert(_.isFinite(request.requestHeader.requestHandle));
    var description;

    // expecting a OpenChannelRequest as first communication message
    if (!(request instanceof OpenSecureChannelRequest)) {
        description = "Expecting OpenSecureChannelRequest";
        console.log("ERROR".red, "BadCommunicationError: expecting a OpenChannelRequest as first communication message");
        return _send_error.call(this, StatusCodes.BadCommunicationError, description, message, callback);
    }

    var securityPolicy = securityPolicy_m.fromURI(message.securityHeader.securityPolicyUri);

    // check security header
    var check_security_policy = isValidSecurityPolicy(securityPolicy);
    if (check_security_policy !== StatusCodes.Good) {
        description = " Unsupported securityPolicyUri " + self.messageBuilder.securityHeader.securityPolicyUri;
        return _send_error.call(this, check_security_policy, description, message, callback);
    }

    assert(request.securityMode);
    self.securityMode = request.securityMode;
    self.messageBuilder.securityMode = self.securityMode;

    var has_endpoint = self.has_endpoint_for_security_mode_and_policy(self.securityMode, securityPolicy);

    if (!has_endpoint) {
        // there is no
        description = " This server doesn't not support  " + securityPolicy.toString() + " " + self.securityMode.toString();
        return _send_error.call(self, StatusCodes.BadSecurityPolicyRejected, description, message, callback);
    }

    self.endpoint = self.endpoints && self.endpoints.getEndpointDescription(self.securityMode, securityPolicy);


    self.messageBuilder
        .on("message", _on_common_message.bind(self))
        .on("start_chunk", function () {
            //record tick 0: when the first chunk is received
            self._tick0 = get_clock_tick();

        });

    // handle initial OpenSecureChannelRequest
    self._process_certificates(message, function () {
        _handle_OpenSecureChannelRequest.call(self, message, callback);
    });

}


/**
 * the number of bytes read so far by this channel
 * @property bytesRead
 * @type {Number}
 */
ServerSecureChannelLayer.prototype.__defineGetter__("bytesRead", function () {
    var self = this;
    return self.transport ? self.transport.bytesRead : 0;
});

/**
 * the number of bytes written so far by this channel
 * @property bytesWritten
 * @type {Number}
 */
ServerSecureChannelLayer.prototype.__defineGetter__("bytesWritten", function () {
    var self = this;
    return self.transport ? self.transport.bytesWritten : 0;
});

ServerSecureChannelLayer.prototype.__defineGetter__("transactionsCount", function () {
    var self = this;
    return self._transactionsCount;
});

/**
 * true when the secure channel has been opened successfully
 * @property isOpened
 * @type {Boolean}
 *
 */
ServerSecureChannelLayer.prototype.__defineGetter__("isOpened",function() {
    var self = this;
    return self.clientCertificate;
});

/**
 * true when the secure channel is assigned to a active session
 * @property hasSession
 * @type {Boolean}
 */
ServerSecureChannelLayer.prototype.__defineGetter__("hasSession",function() {
    var self = this;
    return Object.keys(self.sessionTokens).length>0;
});

/**
 * The unique hash key to identify this secure channel
 * @property hashKey
 * @type {String}
 */
ServerSecureChannelLayer.prototype.__defineGetter__("hashKey",function() {
    var self = this;
    return self.securityToken.secureChannelId.toString();
});


exports.ServerSecureChannelLayer = ServerSecureChannelLayer;