"use strict";
/**
* @module opcua.miscellaneous
*/
const Enum = require("node-opcua-enum");
const assert = require("node-opcua-assert").assert;
const _ = require("underscore");
/**
* @class SecurityPolicy
* @static
*
* OPCUA Spec Release 1.02 page 15 OPC Unified Architecture, Part 7
*
* @property Basic128Rsa15 Security Basic 128Rsa15
* -----------------------
* A suite of algorithms that uses RSA15 as
* Key-Wrap-algorithm and 128-Bit for encryption algorithms.
* -> SymmetricSignatureAlgorithm - HmacSha1 -(http://www.w3.org/2000/09/xmldsig#hmac-sha1).
* -> SymmetricEncryptionAlgorithm - Aes128 -(http://www.w3.org/2001/04/xmlenc#aes128-cbc).
* -> AsymmetricSignatureAlgorithm - RsaSha1 -(http://www.w3.org/2000/09/xmldsig#rsa-sha1).
* -> AsymmetricKeyWrapAlgorithm - KwRsa15 -(http://www.w3.org/2001/04/xmlenc#rsa-1_5).
* -> AsymmetricEncryptionAlgorithm - Rsa15 -(http://www.w3.org/2001/04/xmlenc#rsa-1_5).
* -> KeyDerivationAlgorithm - PSha1 -(http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha1).
* -> DerivedSignatureKeyLength - 128
* -> MinAsymmetricKeyLength - 1024
* -> MaxAsymmetricKeyLength - 2048
* -> CertificateSignatureAlgorithm - Sha1
*
* @property Basic256 Security Basic 256:
* -------------------
* A suite of algorithms that are for 256-Bit encryption, algorithms include:
* -> SymmetricSignatureAlgorithm - HmacSha1 -(http://www.w3.org/2000/09/xmldsig#hmac-sha1).
* -> SymmetricEncryptionAlgorithm - Aes256 -(http://www.w3.org/2001/04/xmlenc#aes256-cbc).
* -> AsymmetricSignatureAlgorithm - RsaSha1 -(http://www.w3.org/2000/09/xmldsig#rsa-sha1).
* -> AsymmetricKeyWrapAlgorithm - KwRsaOaep-(http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p).
* -> AsymmetricEncryptionAlgorithm - RsaOaep -(http://www.w3.org/2001/04/xmlenc#rsa-oaep).
* -> KeyDerivationAlgorithm - PSha1 -(http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha1).
* -> DerivedSignatureKeyLength - 192.
* -> MinAsymmetricKeyLength - 1024
* -> MaxAsymmetricKeyLength - 2048
* -> CertificateSignatureAlgorithm - Sha1
*
* @property Basic256 Security Basic 256 Sha256
* --------------------------------------------
* A suite of algorithms that are for 256-Bit encryption, algorithms include.
* -> SymmetricSignatureAlgorithm - Hmac_Sha256 -(http://www.w3.org/2000/09/xmldsig#hmac-sha256).
* -> SymmetricEncryptionAlgorithm - Aes256_CBC -(http://www.w3.org/2001/04/xmlenc#aes256-cbc).
* -> AsymmetricSignatureAlgorithm - Rsa_Sha256 -(http://www.w3.org/2001/04/xmldsig-more#rsa-sha256).
* -> AsymmetricKeyWrapAlgorithm - KwRsaOaep -(http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p).
* -> AsymmetricEncryptionAlgorithm - Rsa_Oaep -(http://www.w3.org/2001/04/xmlenc#rsa-oaep).
* -> KeyDerivationAlgorithm - PSHA256 -(http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512/dk/p_sha256).
* -> DerivedSignatureKeyLength - 256
* -> MinAsymmetricKeyLength - 2048
* -> MaxAsymmetricKeyLength - 4096
* -> CertificateSignatureAlgorithm - Sha256
*
* Support for this security profile may require support for a second application instance certificate, with a larger
* keysize. Applications shall support multiple Application Instance Certificates if required by supported Security
* Polices and use the certificate that is required for a given security endpoint.
*
*
*/
const SecurityPolicy = new Enum({
Invalid: "invalid",
None: "http://opcfoundation.org/UA/SecurityPolicy#None",
Basic128: "http://opcfoundation.org/UA/SecurityPolicy#Basic128",
Basic128Rsa15: "http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15",
Basic192: "http://opcfoundation.org/UA/SecurityPolicy#Basic192",
Basic192Rsa15: "http://opcfoundation.org/UA/SecurityPolicy#Basic192Rsa15",
Basic256: "http://opcfoundation.org/UA/SecurityPolicy#Basic256",
Basic256Rsa15: "http://opcfoundation.org/UA/SecurityPolicy#Basic256Rsa15",
Basic256Sha256: "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"
});
exports.fromURI = function (uri) {
// istanbul ignore next
if (typeof uri !== "string") {
return SecurityPolicy.Invalid;
}
const a = uri.split("#");
// istanbul ignore next
if (a.length < 2) {
return SecurityPolicy.Invalid;
}
const v = SecurityPolicy[a[1]];
return v || SecurityPolicy.Invalid;
};
exports.toURI = function (value) {
const securityPolicy = SecurityPolicy.get(value) || SecurityPolicy.Invalid;
if (securityPolicy === SecurityPolicy.Invalid) {
throw new Error("trying to convert an invalid Security Policy into a URI: " + value);
}
return securityPolicy.value;
};
exports.SecurityPolicy = SecurityPolicy;
const crypto_utils = require("node-opcua-crypto");
// --------------------
function RSAPKCS1V15_Decrypt(buffer, privateKey) {
const block_size = crypto_utils.rsa_length(privateKey);
return crypto_utils.privateDecrypt_long(buffer, privateKey, block_size, crypto_utils.RSA_PKCS1_PADDING);
}
function RSAOAEP_Decrypt(buffer, privateKey) {
const block_size = crypto_utils.rsa_length(privateKey);
return crypto_utils.privateDecrypt_long(buffer, privateKey, block_size, crypto_utils.RSA_PKCS1_OAEP_PADDING);
}
// --------------------
function asymmetricVerifyChunk(chunk, certificate) {
const crypto_factory = this;
assert(chunk instanceof Buffer);
assert(certificate instanceof Buffer);
// let's get the signatureLength by checking the size
// of the certificate's public key
const cert = crypto_utils.exploreCertificateInfo(certificate);
const signatureLength = cert.publicKeyLength; // 1024 bits = 128Bytes or 2048=256Bytes
const block_to_verify = chunk.slice(0, chunk.length - signatureLength);
const signature = chunk.slice(chunk.length - signatureLength);
return crypto_factory.asymmetricVerify(block_to_verify, signature, certificate);
}
function RSAPKCS1V15SHA1_Verify(buffer, signature, certificate) {
assert(certificate instanceof Buffer);
assert(signature instanceof Buffer);
const options = {
algorithm: "RSA-SHA1",
publicKey: crypto_utils.toPem(certificate, "CERTIFICATE")
};
return crypto_utils.verifyMessageChunkSignature(buffer, signature, options);
}
const RSAPKCS1OAEPSHA1_Verify = RSAPKCS1V15SHA1_Verify;
function RSAPKCS1OAEPSHA256_Verify(buffer, signature, certificate) {
const options = {
algorithm: "RSA-SHA256",
publicKey: crypto_utils.toPem(certificate, "CERTIFICATE")
};
return crypto_utils.verifyMessageChunkSignature(buffer, signature, options);
}
function RSAPKCS1V15SHA1_Sign(buffer, privateKey) {
if (privateKey instanceof Buffer) {
privateKey = crypto_utils.toPem(privateKey, "RSA PRIVATE KEY");
}
const params = {
signatureLength: crypto_utils.rsa_length(privateKey),
algorithm: "RSA-SHA1",
privateKey: privateKey
};
return crypto_utils.makeMessageChunkSignature(buffer, params);
}
function RSAPKCS1V15SHA256_Sign(buffer, privateKey) {
if (privateKey instanceof Buffer) {
privateKey = crypto_utils.toPem(privateKey, "RSA PRIVATE KEY");
}
const params = {
signatureLength: crypto_utils.rsa_length(privateKey),
algorithm: "RSA-SHA256",
privateKey: privateKey
};
return crypto_utils.makeMessageChunkSignature(buffer, params);
}
const RSAPKCS1OAEPSHA1_Sign = RSAPKCS1V15SHA1_Sign;
function RSAPKCS1V15_Encrypt(buffer, publicKey) {
const key_length = crypto_utils.rsa_length(publicKey);
return crypto_utils.publicEncrypt_long(buffer, publicKey, key_length, 11, crypto_utils.RSA_PKCS1_PADDING);
}
function RSAOAEP_Encrypt(buffer, publicKey) {
const key_length = crypto_utils.rsa_length(publicKey);
return crypto_utils.publicEncrypt_long(buffer, publicKey, key_length, 42, crypto_utils.RSA_PKCS1_OAEP_PADDING);
}
function compute_derived_keys(serverNonce, clientNonce) {
const self = this;
// calculate derived keys
const derivedKeys = {
derivedClientKeys: null,
derivedServerKeys: null,
algorithm: null
};
if (clientNonce && serverNonce) {
const options = {
signingKeyLength: self.derivedSignatureKeyLength,
encryptingKeyLength: self.derivedEncryptionKeyLength,
encryptingBlockSize: self.encryptingBlockSize,
signatureLength: self.signatureLength,
algorithm: self.symmetricEncryptionAlgorithm,
sha1or256: self.sha1or256
};
derivedKeys.derivedClientKeys = crypto_utils.computeDerivedKeys(serverNonce, clientNonce, options);
derivedKeys.derivedServerKeys = crypto_utils.computeDerivedKeys(clientNonce, serverNonce, options);
}
return derivedKeys;
}
exports.compute_derived_keys = compute_derived_keys;
const _Basic128Rsa15 = {
securityPolicy: SecurityPolicy.Basic128Rsa15,
symmetricKeyLength: 16,
derivedEncryptionKeyLength: 16,
derivedSignatureKeyLength: 16,
encryptingBlockSize: 16,
signatureLength: 20,
minimumAsymmetricKeyLength: 128,
maximumAsymmetricKeyLength: 512,
/* asymmetric signature algorithm */
asymmetricVerifyChunk: asymmetricVerifyChunk,
asymmetricSign: RSAPKCS1V15SHA1_Sign,
asymmetricVerify: RSAPKCS1V15SHA1_Verify,
asymmetricSignatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
/* asymmetric encryption algorithm */
asymmetricEncrypt: RSAPKCS1V15_Encrypt,
asymmetricDecrypt: RSAPKCS1V15_Decrypt,
asymmetricEncryptionAlgorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
blockPaddingSize: 11,
symmetricEncryptionAlgorithm: "aes-128-cbc",
sha1or256: "SHA1",
compute_derived_keys: compute_derived_keys
};
const _Basic256 = {
securityPolicy: SecurityPolicy.Basic256,
symmetricKeyLength: 32,
derivedEncryptionKeyLength: 32,
derivedSignatureKeyLength: 24,
encryptingBlockSize: 16,
signatureLength: 20,
minimumAsymmetricKeyLength: 128,
maximumAsymmetricKeyLength: 512,
asymmetricVerifyChunk: asymmetricVerifyChunk,
asymmetricSign: RSAPKCS1OAEPSHA1_Sign,
asymmetricVerify: RSAPKCS1OAEPSHA1_Verify,
asymmetricSignatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
/* asymmetric encryption algorithm */
asymmetricEncrypt: RSAOAEP_Encrypt,
asymmetricDecrypt: RSAOAEP_Decrypt,
asymmetricEncryptionAlgorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep",
blockPaddingSize: 42,
// "aes-256-cbc"
symmetricEncryptionAlgorithm: "aes-256-cbc",
sha1or256: "SHA1",
compute_derived_keys: compute_derived_keys
};
const _Basic256Sha256 = {
securityPolicy: SecurityPolicy.Basic256Sha256,
symmetricKeyLength: 32,
derivedEncryptionKeyLength: 32,
derivedSignatureKeyLength: 32,
encryptingBlockSize: 16,
signatureLength: 32,
minimumAsymmetricKeyLength: 2048,
maximumAsymmetricKeyLength: 4096,
asymmetricVerifyChunk: asymmetricVerifyChunk,
asymmetricSign: RSAPKCS1V15SHA256_Sign,
asymmetricVerify: RSAPKCS1OAEPSHA256_Verify,
asymmetricSignatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
/* asymmetric encryption algorithm */
asymmetricEncrypt: RSAOAEP_Encrypt,
asymmetricDecrypt: RSAOAEP_Decrypt,
asymmetricEncryptionAlgorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep",
blockPaddingSize: 42,
// "aes-256-cbc"
symmetricEncryptionAlgorithm: "aes-256-cbc",
sha1or256: "SHA256",
compute_derived_keys: compute_derived_keys
};
function getCryptoFactory(securityPolicy) {
assert(typeof securityPolicy.key === "string");
switch (securityPolicy.key) {
case SecurityPolicy.None.key:
return null;
case SecurityPolicy.Basic128Rsa15.key:
return _Basic128Rsa15;
case SecurityPolicy.Basic256.key:
return _Basic256;
case SecurityPolicy.Basic256Sha256.key:
return _Basic256Sha256;
default:
return null;
}
}
exports.getCryptoFactory = getCryptoFactory;
const MessageSecurityMode = require("node-opcua-service-secure-channel").MessageSecurityMode;
const SignatureData = require("node-opcua-service-secure-channel").SignatureData;
function computeSignature(senderCertificate, senderNonce, receiverPrivatekey, securityPolicy) {
if (!senderNonce || !senderCertificate) {
return null;
}
const crypto_factory = getCryptoFactory(securityPolicy);
if (!crypto_factory) {
return null;
}
// This parameter is calculated by appending the clientNonce to the clientCertificate
const buffer = Buffer.concat([senderCertificate, senderNonce]);
// ... and signing the resulting sequence of bytes.
const signature = crypto_factory.asymmetricSign(buffer, receiverPrivatekey);
return new SignatureData({
// This is a signature generated with the private key associated with a Certificate
signature: signature,
// A string containing the URI of the algorithm.
// The URI string values are defined as part of the security profiles specified in Part 7.
// (The SignatureAlgorithm shall be the AsymmetricSignatureAlgorithm specified in the
// SecurityPolicy for the Endpoint)
algorithm: crypto_factory.asymmetricSignatureAlgorithm // "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
});
}
exports.computeSignature = computeSignature;
function verifySignature(receiverCertificate, receiverNonce, signature, senderCertificate, securityPolicy) {
if (securityPolicy === SecurityPolicy.None) {
return true;
}
const crypto_factory = getCryptoFactory(securityPolicy);
if (!crypto_factory) {
return false;
}
assert(receiverNonce instanceof Buffer);
assert(receiverCertificate instanceof Buffer);
assert(signature instanceof SignatureData);
assert(senderCertificate instanceof Buffer);
if (!(signature.signature instanceof Buffer)) {
// no signature provided
return false;
}
assert(signature.signature instanceof Buffer);
// This parameter is calculated by appending the clientNonce to the clientCertificate
const buffer = Buffer.concat([receiverCertificate, receiverNonce]);
return crypto_factory.asymmetricVerify(buffer, signature.signature, senderCertificate);
}
exports.verifySignature = verifySignature;
function getOptionsForSymmetricSignAndEncrypt(securityMode, derivedKeys) {
assert(derivedKeys.hasOwnProperty("signatureLength"));
assert(securityMode !== MessageSecurityMode.NONE && securityMode !== MessageSecurityMode.INVALID);
let options = {
signatureLength: derivedKeys.signatureLength,
signingFunc: function (chunk) {
return crypto_utils.makeMessageChunkSignatureWithDerivedKeys(chunk, derivedKeys);
}
};
if (securityMode === MessageSecurityMode.SIGNANDENCRYPT) {
options = _.extend(options, {
plainBlockSize: derivedKeys.encryptingBlockSize,
cipherBlockSize: derivedKeys.encryptingBlockSize,
encrypt_buffer: function (chunk) {
return crypto_utils.encryptBufferWithDerivedKeys(chunk, derivedKeys);
}
});
}
return options;
}
exports.getOptionsForSymmetricSignAndEncrypt = getOptionsForSymmetricSignAndEncrypt;