UNPKG

@smartdcc/gbcs-parser

Version:
197 lines 7.68 kB
"use strict"; /* * * Original copyright holders for the GBCS message parser tool: * * Copyright (c) 2019 Andre B. Oliveira * 2019 Enrique Giraldo * 2019 Cristóbal Borrero * * Copyright for subsequent changes, including porting to NodeJS, * updating for TypeScript and refactor to support unit testing: * * Copyright (c) 2022 Smart DCC Limited * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.gcm = gcm; exports.ungcm = ungcm; exports.decryptPayloadWithKey = decryptPayloadWithKey; exports.deriveKeyFromPair = deriveKeyFromPair; exports.decryptGbcsData = decryptGbcsData; exports.signGroupingHeader = signGroupingHeader; const console_1 = require("console"); const crypto_1 = require("crypto"); const parser_1 = require("./parser"); const util_1 = require("./util"); /** * standard gcm for use with GBCS - sets the cipher size and fixes the iv * * @param cipherInfo - originator/target/counter from grouping header * @param plainText - text to encrypt - set as empty buffer if none * @param aad - additional auth data - set as empty buffer if none * @param aesKey - output from createSecretKey * @param authTagLength - tag length in bytes - default is 12 * @returns */ function gcm(cipherInfo, plainText, aad, aesKey, authTagLength) { const iv = new Uint8Array(12); iv.set(cipherInfo.origSysTitle, 0); iv.set([0, 0, 0, 0], 8); const cipher = (0, crypto_1.createCipheriv)('aes-128-gcm', aesKey, iv, { authTagLength: authTagLength ?? 12, }); cipher.setAAD(aad); const cipherText = cipher.update(plainText); cipher.final(); const tag = cipher.getAuthTag(); return { cipherText, tag }; } /** * standard gcm decrypt for use with GBCS - sets the cipher size and fixes the iv * * @param cipherInfo - originator/target/counter from grouping header * @param cipherText - text to decrypt - set as empty buffer if none * @param aad - additional auth data - set as empty buffer if none * @param aesKey - output from createSecretKey * @param tag - auth tag - default is 12 * @returns plainText or throws error in case of auth fail */ function ungcm(cipherInfo, cipherText, aad, aesKey, tag) { const iv = new Uint8Array(12); iv.set(cipherInfo.origSysTitle, 0); iv.set([0, 0, 0, 0], 8); const decipher = (0, crypto_1.createDecipheriv)('aes-128-gcm', aesKey, iv); decipher.setAAD(aad); decipher.setAuthTag(tag); const plainText = decipher.update(cipherText); decipher.final(); return plainText; } function decryptPayloadWithKey(cipherInfo, ciphertextTag, /* Uint8Array(16) */ aesKey, doneCb) { const iv = new Uint8Array(12); iv.set(cipherInfo.origSysTitle, 0); iv.set([0, 0, 0, 0], 8); const decipher = (0, crypto_1.createDecipheriv)('aes-128-gcm', aesKey, iv); decipher.setAAD(new Uint8Array([0x31])); decipher.setAuthTag(ciphertextTag.subarray(-12)); const plaintext = decipher.update(ciphertextTag.subarray(0, -12)); decipher.final(); const yy = { input: new util_1.Uint8ArrayWrapper(new Uint8Array(plaintext)), index: 0, end: plaintext.byteLength, }; doneCb(yy); } /** * performs kdf as described in section 4 of GBCS * * @param privkey * @param pubkey * @param cipherInfo * @param mode tweaks the otherInfo field, if omitted "encryption" * @returns */ function deriveKeyFromPair(privkey, pubkey, cipherInfo, mode) { if (typeof privkey === 'string') { privkey = (0, crypto_1.createPrivateKey)({ key: privkey, format: 'pem' }); } if (typeof pubkey === 'string') { pubkey = (0, crypto_1.createPublicKey)({ key: pubkey, format: 'pem' }); } (0, console_1.assert)(privkey.asymmetricKeyType === 'ec', 'expected ec private key'); (0, console_1.assert)(pubkey.asymmetricKeyType === 'ec', 'expected ec public key'); const privEcKey = privkey .export({ type: 'sec1', format: 'der' }) .subarray(7, 7 + 32); const pubEcKey = pubkey.export({ type: 'spki', format: 'der' }).subarray(-65); const ecdh = (0, crypto_1.createECDH)('prime256v1'); ecdh.setPrivateKey(privEcKey); const secret = ecdh.computeSecret(pubEcKey); const otherInfo = new Uint8Array(33); otherInfo.set([0x60, 0x85, 0x74, 0x06, 0x08, 0x03, 0x00], 0); // algorithm-id otherInfo.set(cipherInfo.origSysTitle, 7); otherInfo.set([0x09], 7 + 8); switch (mode) { case 'command': otherInfo.set([0x01], 7 + 8 + 1); break; case 'response': otherInfo.set([0x02], 7 + 8 + 1); break; case 'alert': otherInfo.set([0x03], 7 + 8 + 1); break; case 'encryption': otherInfo.set([0x04], 7 + 8 + 1); break; } otherInfo.set(mode === 'encryption' ? (cipherInfo.supplimentryOriginatorCounter ?? cipherInfo.origCounter) : cipherInfo.origCounter, 7 + 8 + 2); otherInfo.set(mode === 'encryption' ? (cipherInfo.supplimentryRemotePartyId ?? cipherInfo.recipSysTitle) : cipherInfo.recipSysTitle, 7 + 8 + 2 + 8); const sha256 = (0, crypto_1.createHash)('sha256'); sha256.update(new Uint8Array([0, 0, 0, 1])); sha256.update(secret); sha256.update(otherInfo); const aesKey = sha256.digest().subarray(0, 16); return (0, crypto_1.createSecretKey)(aesKey); } function decryptGbcsData(ctx, ciphertextAndTag, okCallback) { ctx.decryptionList.push(function (cipherInfo, aesKey) { decryptPayloadWithKey(cipherInfo, ciphertextAndTag, aesKey, okCallback); }); } /** * Sign the output of transform, result is a base64 encoded string. * * @param originatorId originator id used to lookup key * @param payload base64 encoded gbcs message * @param keyStore * @ */ async function signGroupingHeader(originatorId, payload, keyStore) { const signersKey = await keyStore(originatorId, 'DS', { privateKey: true }); let tbs = Buffer.from(payload, 'base64'); if (tbs.length === 0 || tbs[0] !== 0xdf) { throw new Error('not general signing apdu'); } /* * From green book. should also remove last byte as it will be replaced with * signature but Boxed < 1.4.1 does not currently follow specification. To * support this, first parse the message to determine if the signature is * present. */ const message = await (0, parser_1.parseGbcsMessage)(payload, keyStore); if ('Signature' in message) { if (message['Signature'].children['Signature Length']?.hex !== '00') { throw new Error('already signed'); } tbs = tbs.subarray(0, -1); } const signature = (0, crypto_1.sign)('SHA256', tbs.subarray(1), { key: signersKey, dsaEncoding: 'ieee-p1363', }); if (signature.length !== 64) { throw new Error('unexpected signature length'); } return Buffer.concat([tbs, Buffer.from([64]), signature]).toString('base64'); } //# sourceMappingURL=crypto.js.map