
- Use PascalCase for classes, with uppercase acronyms. - Use camelCase for function and variables. First word/acronym is always lowercase, otherwise acronyms are uppercase. Also, make the packet classes' `tag` properties `static`.
98 lines
3.2 KiB
JavaScript
98 lines
3.2 KiB
JavaScript
/**
|
||
* @fileoverview This module implements AES-CMAC on top of
|
||
* native AES-CBC using either the WebCrypto API or Node.js' crypto API.
|
||
* @module crypto/cmac
|
||
* @private
|
||
*/
|
||
|
||
import { AES_CBC } from '@openpgp/asmcrypto.js/dist_es8/aes/cbc';
|
||
import util from '../util';
|
||
|
||
const webCrypto = util.getWebCrypto();
|
||
const nodeCrypto = util.getNodeCrypto();
|
||
const Buffer = util.getNodeBuffer();
|
||
|
||
|
||
/**
|
||
* This implementation of CMAC is based on the description of OMAC in
|
||
* http://web.cs.ucdavis.edu/~rogaway/papers/eax.pdf. As per that
|
||
* document:
|
||
*
|
||
* We have made a small modification to the OMAC algorithm as it was
|
||
* originally presented, changing one of its two constants.
|
||
* Specifically, the constant 4 at line 85 was the constant 1/2 (the
|
||
* multiplicative inverse of 2) in the original definition of OMAC [14].
|
||
* The OMAC authors indicate that they will promulgate this modification
|
||
* [15], which slightly simplifies implementations.
|
||
*/
|
||
|
||
const blockLength = 16;
|
||
|
||
|
||
/**
|
||
* xor `padding` into the end of `data`. This function implements "the
|
||
* operation xor→ [which] xors the shorter string into the end of longer
|
||
* one". Since data is always as least as long as padding, we can
|
||
* simplify the implementation.
|
||
* @param {Uint8Array} data
|
||
* @param {Uint8Array} padding
|
||
*/
|
||
function rightXORMut(data, padding) {
|
||
const offset = data.length - blockLength;
|
||
for (let i = 0; i < blockLength; i++) {
|
||
data[i + offset] ^= padding[i];
|
||
}
|
||
return data;
|
||
}
|
||
|
||
function pad(data, padding, padding2) {
|
||
// if |M| in {n, 2n, 3n, ...}
|
||
if (data.length && data.length % blockLength === 0) {
|
||
// then return M xor→ B,
|
||
return rightXORMut(data, padding);
|
||
}
|
||
// else return (M || 10^(n−1−(|M| mod n))) xor→ P
|
||
const padded = new Uint8Array(data.length + (blockLength - data.length % blockLength));
|
||
padded.set(data);
|
||
padded[data.length] = 0b10000000;
|
||
return rightXORMut(padded, padding2);
|
||
}
|
||
|
||
const zeroBlock = new Uint8Array(blockLength);
|
||
|
||
export default async function CMAC(key) {
|
||
const cbc = await CBC(key);
|
||
|
||
// L ← E_K(0^n); B ← 2L; P ← 4L
|
||
const padding = util.double(await cbc(zeroBlock));
|
||
const padding2 = util.double(padding);
|
||
|
||
return async function(data) {
|
||
// return CBC_K(pad(M; B, P))
|
||
return (await cbc(pad(data, padding, padding2))).subarray(-blockLength);
|
||
};
|
||
}
|
||
|
||
async function CBC(key) {
|
||
if (util.getWebCrypto() && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support
|
||
key = await webCrypto.importKey('raw', key, { name: 'AES-CBC', length: key.length * 8 }, false, ['encrypt']);
|
||
return async function(pt) {
|
||
const ct = await webCrypto.encrypt({ name: 'AES-CBC', iv: zeroBlock, length: blockLength * 8 }, key, pt);
|
||
return new Uint8Array(ct).subarray(0, ct.byteLength - blockLength);
|
||
};
|
||
}
|
||
if (util.getNodeCrypto()) { // Node crypto library
|
||
key = Buffer.from(key);
|
||
return async function(pt) {
|
||
pt = Buffer.from(pt);
|
||
const en = new nodeCrypto.createCipheriv('aes-' + (key.length * 8) + '-cbc', key, zeroBlock);
|
||
const ct = en.update(pt);
|
||
return new Uint8Array(ct);
|
||
};
|
||
}
|
||
// asm.js fallback
|
||
return async function(pt) {
|
||
return AES_CBC.encrypt(pt, key, false, zeroBlock);
|
||
};
|
||
}
|