node-opcua-client
Version:
pure nodejs OPCUA SDK - module client
169 lines (158 loc) • 8.6 kB
text/typescript
import { OPCUACertificateManager } from "node-opcua-certificate-manager";
import { OPCUASecureObject } from "node-opcua-common";
import {
Certificate,
exploreCertificate,
explorePrivateKey,
publicKeyAndPrivateKeyMatches
} from "node-opcua-crypto/web";
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
import { VerifyCertificateOptions } from "node-opcua-pki";
const doDebug = checkDebugFlag(__filename);
const debugLog = make_debugLog(__filename);
const errorLog = make_errorLog(__filename);
const warningLog = make_warningLog(__filename);
export function verifyIsOPCUAValidCertificate(
certificate: Certificate,
certificateFile: string,
type: "client" | "server",
applicationUri: string
): void {
const certificateInfo = exploreCertificate(certificate);
const now = new Date();
if (certificateInfo.tbsCertificate.validity.notBefore.getTime() > now.getTime()) {
// check that certificate is active
// certificate is not active yet
warningLog(
`[NODE-OPCUA-W02] The certificate is not active yet\n` +
`notBefore ${certificateInfo.tbsCertificate.validity.notBefore.toISOString()}\n` +
`certificateFile ${certificateFile}`
);
}
// check that certificate has not expired
if (certificateInfo.tbsCertificate.validity.notAfter.getTime() <= now.getTime()) {
// certificate is obsolete
warningLog(
`[NODE-OPCUA-W03] The certificate has expired\n` +
`Please regenerate a valid certificate\n` +
`notAfter = ${certificateInfo.tbsCertificate.validity.notAfter.toISOString()}\n` +
`certificateFile= ${certificateFile}`
);
} else {
const tenDays = 10 * 24 * 60 * 60 * 1000;
if (certificateInfo.tbsCertificate.validity.notAfter.getTime() <= now.getTime() + tenDays) {
// certificate is going to expired very soon
warningLog(
`[NODE-OPCUA-W05] The certificate is about to expire in less than 10 days.\n` +
`Please regenerate a valid certificate as soon as possible\n` +
`notAfter = ${certificateInfo.tbsCertificate.validity.notAfter.toISOString()}\n` +
`certificateFile= ${certificateFile}\n`
);
}
}
// check that server certificate matches Application URI
const uniformResourceIdentifier = certificateInfo?.tbsCertificate?.extensions?.subjectAltName?.uniformResourceIdentifier;
if (!uniformResourceIdentifier) {
warningLog(
`[NODE-OPCUA-W14] The certificate subjectAltName uniformResourceIdentifier is missing.\n` +
`Please regenerate a specific certificate with a uniformResourceIdentifier that matches your ${type} applicationUri\n` +
`applicationUri = ${applicationUri}\n` +
`certificateFile = ${certificateFile}\n`
);
return;
} else if (uniformResourceIdentifier[0] !== applicationUri) {
warningLog(
`[NODE-OPCUA-W06] The certificate subjectAltName does not match the ${type} applicationUri\n` +
`Please regenerate a specific certificate that matches your ${type} applicationUri\n` +
`certificate subjectAltName = ${uniformResourceIdentifier[0]}\n` +
`${type} applicationUri = ${applicationUri}\n` +
`certificateFile = ${certificateFile}\n`
);
}
const keyUsage = certificateInfo.tbsCertificate.extensions?.keyUsage;
if (!keyUsage) {
warningLog(`[NODE-OPCUA-W15] The certificate keyUsage is missing\n` + `certificateFile = ${certificateFile}`);
} else {
// spec says that certificate shall include digitalSignature, nonRepudiation, keyEncipherment and dataEncipherment.
// Other key uses are allowed.
if (!keyUsage.digitalSignature || !keyUsage.nonRepudiation || !keyUsage.keyEncipherment || !keyUsage.dataEncipherment) {
warningLog(
`[NODE-OPCUA-W16] The certificate keyUsage must include digitalSignature, nonRepudiation, keyEncipherment and dataEncipherment.\n` +
`see https://reference.opcfoundation.org/v104/Core/docs/Part6/6.2.2/\n` +
`certificateFile = ${certificateFile}`
);
warningLog(`keyUsage = ${JSON.stringify(keyUsage, null, " ")}`);
}
}
const extKeyUsage = certificateInfo.tbsCertificate.extensions?.extKeyUsage;
if (!extKeyUsage) {
warningLog(`[NODE-OPCUA-W17] The certificate extKeyUsage is missing\n` + `certificateFile = ${certificateFile}`);
} else {
// spec says that certificate shall include digitalSignature, nonRepudiation, keyEncipherment and dataEncipherment.
// Other key uses are allowed.
if (!extKeyUsage.clientAuth && !extKeyUsage.serverAuth) {
warningLog(
`[NODE-OPCUA-W18] The certificate extKeyUsage must include clientAuth and/or serverAuth.\n` +
`see https://reference.opcfoundation.org/v104/Core/docs/Part6/6.2.2/\n` +
`certificateFile = ${certificateFile}`
);
}
}
const keyLengthInBits = certificateInfo.tbsCertificate.subjectPublicKeyInfo.keyLength * 8;
if (keyLengthInBits < 1024) {
errorLog(
`[NODE-OPCUA-W19] The public key length shall be greater than or equal to 1024 bits. key length is ${keyLengthInBits}.\n` +
`see https://reference.opcfoundation.org/v104/GDS/docs/7.6.3/\n` +
`certificateFile = ${certificateFile}`
);
} else if (keyLengthInBits < 2048) {
warningLog(
`[NODE-OPCUA-W23] key lengths less than 2048 are considered insecure. key length is ${keyLengthInBits}\n` +
`see https://reference.opcfoundation.org/v104/Core/docs/Part2/6.8/\n` +
`certificateFile = ${certificateFile}`
);
}
}
export async function performCertificateSanityCheck(
secureObject: OPCUASecureObject,
serverOrClient: "server" | "client",
certificateManager: OPCUACertificateManager,
applicationUri: string
): Promise<void> {
// verify that certificate is matching private key, and inform the developer if not
const certificate = secureObject.getCertificate();
const privateKey = secureObject.getPrivateKey();
//
if (!publicKeyAndPrivateKeyMatches(certificate, privateKey)) {
errorLog("[NODE-OPCUA-E01] Configuration error : the certificate and the private key do not match !");
errorLog(" please check the configuration of the OPCUA Server");
errorLog(" privateKey= ", secureObject.privateKeyFile);
errorLog(" certificateManager.privateKey= ", certificateManager.privateKey);
errorLog(" certificateFile= ", secureObject.certificateFile);
throw new Error(
"[NODE-OPCUA-E01] Configuration error : the certificate and the private key do not match ! please fix your configuration"
);
}
// verify that the certificate provided has the right key length ( at least 2048)
const privateKeyInfo = explorePrivateKey(privateKey);
const keyLengthInBits = privateKeyInfo.modulus.length * 8;
if (keyLengthInBits <= 1024) {
warningLog(
`[NODE-OPCUA-W04] The public/private key pair uses a key length which is equal or lower than 1024 bits. ( key length was ${keyLengthInBits} )\n` +
`OPCUA version 1.04 requires that security key length are greater or equal to 2048 bits.\n` +
`The ${serverOrClient} is operating at risk. `
);
}
// verify that the certificate has a valid date and has expected extensions fields such as DNS and IP.
const status1 = await certificateManager.trustCertificate(certificate);
const options: VerifyCertificateOptions = {
acceptOutdatedCertificate: false,
acceptOutDatedIssuerCertificate: false,
acceptPendingCertificate: false,
};
const status = await certificateManager.verifyCertificate(certificate, options);
if (status !== "Good") {
warningLog("[NODE-OPCUA-W04] Warning: the certificate status is = ", status, " file = ", secureObject.certificateFile);
}
verifyIsOPCUAValidCertificate(certificate, secureObject.certificateFile, serverOrClient, applicationUri);
}