--- a/dom/push/PushCrypto.jsm
+++ b/dom/push/PushCrypto.jsm
@@ -14,29 +14,19 @@ XPCOMUtils.defineLazyGetter(this, 'gDOMB
Services.strings.createBundle('chrome://global/locale/dom/dom.properties'));
Cu.importGlobalProperties(['crypto']);
this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray'];
var UTF8 = new TextEncoder('utf-8');
-// Legacy encryption scheme (draft-thomson-http-encryption-02).
-var AESGCM128_ENCODING = 'aesgcm128';
-var AESGCM128_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128');
-
-// New encryption scheme (draft-ietf-httpbis-encryption-encoding-01).
-var AESGCM_ENCODING = 'aesgcm';
-var AESGCM_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm');
-
-var NONCE_INFO = UTF8.encode('Content-Encoding: nonce');
-var AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus
-var P256DH_INFO = UTF8.encode('P-256\0');
var ECDH_KEY = { name: 'ECDH', namedCurve: 'P-256' };
var ECDSA_KEY = { name: 'ECDSA', namedCurve: 'P-256' };
+
// A default keyid with a name that won't conflict with a real keyid.
var DEFAULT_KEYID = '';
/** Localized error property names. */
// `Encryption` header missing or malformed.
const BAD_ENCRYPTION_HEADER = 'PushMessageBadEncryptionHeader';
// `Crypto-Key` or legacy `Encryption-Key` header missing.
@@ -113,73 +103,78 @@ function getEncryptionParams(encryptFiel
var p = encryptField.split(',', 1)[0];
if (!p) {
throw new CryptoError('Encryption header missing params',
BAD_ENCRYPTION_HEADER);
}
return p.split(';').reduce(parseHeaderFieldParams, {});
}
-function getCryptoParams(headers) {
+// Extracts the sender public key, salt, and record size from the `Crypto-Key`,
+// `Encryption-Key`, and `Encryption` headers for the aesgcm and aesgcm128
+// schemes.
+function getCryptoParamsFromHeaders(headers) {
if (!headers) {
return null;
}
var keymap;
- var padSize;
if (!headers.encoding) {
throw new CryptoError('Missing Content-Encoding header',
BAD_ENCODING_HEADER);
}
if (headers.encoding == AESGCM_ENCODING) {
// aesgcm uses the Crypto-Key header, 2 bytes for the pad length, and an
// authentication secret.
// https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-01
keymap = getEncryptionKeyParams(headers.crypto_key);
if (!keymap) {
throw new CryptoError('Missing Crypto-Key header',
BAD_CRYPTO_KEY_HEADER);
}
- padSize = 2;
} else if (headers.encoding == AESGCM128_ENCODING) {
// aesgcm128 uses Encryption-Key, 1 byte for the pad length, and no secret.
// https://tools.ietf.org/html/draft-thomson-http-encryption-02
keymap = getEncryptionKeyParams(headers.encryption_key);
if (!keymap) {
throw new CryptoError('Missing Encryption-Key header',
BAD_ENCRYPTION_KEY_HEADER);
}
- padSize = 1;
} else {
throw new CryptoError('Unsupported Content-Encoding: ' + headers.encoding,
BAD_ENCODING_HEADER);
}
var enc = getEncryptionParams(headers.encryption);
var dh = keymap[enc.keyid || DEFAULT_KEYID];
- if (!dh) {
- throw new CryptoError('Missing dh parameter', BAD_DH_PARAM);
+ var senderKey = base64URLDecode(dh);
+ if (!senderKey) {
+ throw new CryptoError('Invalid dh parameter', BAD_DH_PARAM);
}
- var salt = enc.salt;
+
+ var salt = base64URLDecode(enc.salt);
if (!salt) {
- throw new CryptoError('Missing salt parameter', BAD_SALT_PARAM);
+ throw new CryptoError('Invalid salt parameter', BAD_SALT_PARAM);
}
var rs = enc.rs ? parseInt(enc.rs, 10) : 4096;
if (isNaN(rs)) {
throw new CryptoError('rs parameter must be a number', BAD_RS_PARAM);
}
- if (rs <= padSize) {
- throw new CryptoError('rs parameter must be at least ' + padSize,
- BAD_RS_PARAM, padSize);
- }
- return {dh, salt, rs, padSize};
+ return {
+ salt: salt,
+ rs: rs,
+ senderKey: senderKey,
+ };
}
// Decodes an unpadded, base64url-encoded string.
function base64URLDecode(string) {
+ if (!string) {
+ return null;
+ }
try {
return ChromeUtils.base64URLDecode(string, {
// draft-ietf-httpbis-encryption-encoding-01 prohibits padding.
padding: 'reject',
});
} catch (ex) {}
return null;
}
@@ -256,16 +251,259 @@ function generateNonce(base, index) {
var nonce = base.slice(0, 12);
nonce = new Uint8Array(nonce);
for (var i = 0; i < 6; ++i) {
nonce[nonce.byteLength - 1 - i] ^= (index / Math.pow(256, i)) & 0xff;
}
return nonce;
}
+function encodeLength(buffer) {
+ return new Uint8Array([0, buffer.byteLength]);
+}
+
+var NONCE_INFO = UTF8.encode('Content-Encoding: nonce');
+
+class Decoder {
+ /**
+ * Creates a decoder for decrypting an incoming push message.
+ *
+ * @param {JsonWebKey} privateKey The static subscription private key.
+ * @param {BufferSource} publicKey The static subscription public key.
+ * @param {BufferSource} authenticationSecret The subscription authentication
+ * secret, or `null` if not used by the scheme.
+ * @param {Object} cryptoParams An object containing the ephemeral sender
+ * public key, salt, and record size.
+ * @param {BufferSource} ciphertext The encrypted message data.
+ */
+ constructor(privateKey, publicKey, authenticationSecret, cryptoParams,
+ ciphertext) {
+ this.privateKey = privateKey;
+ this.publicKey = publicKey;
+ this.authenticationSecret = authenticationSecret;
+ this.senderKey = cryptoParams.senderKey;
+ this.salt = cryptoParams.salt;
+ this.rs = cryptoParams.rs;
+ this.ciphertext = ciphertext;
+ }
+
+ /**
+ * Derives the decryption keys and decodes the push message.
+ *
+ * @throws {CryptoError} if decryption fails.
+ * @returns {Uint8Array} The decrypted message data.
+ */
+ async decode() {
+ if (this.ciphertext.byteLength === 0) {
+ // Zero length messages will be passed as null.
+ return null;
+ }
+ try {
+ let ikm = await this.computeSharedSecret();
+ let [gcmBits, nonce] = await this.deriveKeyAndNonce(ikm);
+ let key = await crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
+ ['decrypt']);
+
+ let r = await Promise.all(chunkArray(this.ciphertext, this.chunkSize)
+ .map((slice, index) => this.decodeChunk(slice, index, nonce, key)));
+
+ return concatArray(r);
+ } catch (error) {
+ if (error.isCryptoError) {
+ throw error;
+ }
+ // Web Crypto returns an unhelpful "operation failed for an
+ // operation-specific reason" error if decryption fails. We don't have
+ // context about what went wrong, so we throw a generic error instead.
+ throw new CryptoError('Bad encryption', BAD_CRYPTO);
+ }
+ }
+
+ /**
+ * Computes the ECDH shared secret, used as the input key material for HKDF.
+ *
+ * @throws if the static or ephemeral ECDH keys are invalid.
+ * @returns {ArrayBuffer} The shared secret.
+ */
+ async computeSharedSecret() {
+ let [appServerKey, subscriptionPrivateKey] = await Promise.all([
+ crypto.subtle.importKey('raw', this.senderKey, ECDH_KEY,
+ false, ['deriveBits']),
+ crypto.subtle.importKey('jwk', this.privateKey, ECDH_KEY,
+ false, ['deriveBits'])
+ ]);
+ return crypto.subtle.deriveBits({ name: 'ECDH', public: appServerKey },
+ subscriptionPrivateKey, 256);
+ }
+
+ /**
+ * Derives the content encryption key and nonce.
+ *
+ * @param {BufferSource} ikm The ECDH shared secret.
+ * @returns {Array} A `[gcmBits, nonce]` tuple.
+ */
+ async deriveKeyAndNonce(ikm) {
+ throw new Error('Missing `deriveKeyAndNonce` implementation');
+ }
+
+ /**
+ * Decrypts and removes padding from an encrypted record.
+ *
+ * @throws {CryptoError} if decryption fails or padding is incorrect.
+ * @param {Uint8Array} slice The encrypted record.
+ * @param {Number} index The record sequence number.
+ * @param {Uint8Array} nonce The nonce base, used to generate the IV.
+ * @param {Uint8Array} key The content encryption key.
+ * @param {Boolean} last Indicates if this is the final record.
+ * @returns {Uint8Array} The decrypted block with padding removed.
+ */
+ async decodeChunk(slice, index, nonce, key) {
+ let params = {
+ name: 'AES-GCM',
+ iv: generateNonce(nonce, index)
+ };
+ let decoded = await crypto.subtle.decrypt(params, key, slice);
+ return this.unpadChunk(new Uint8Array(decoded));
+ }
+
+ /**
+ * Removes padding from a decrypted block.
+ *
+ * @throws {CryptoError} if padding is missing or invalid.
+ * @param {Uint8Array} chunk The decrypted block with padding.
+ * @returns {Uint8Array} The block with padding removed.
+ */
+ unpadChunk(chunk, last) {
+ throw new Error('Missing `unpadChunk` implementation');
+ }
+
+ /** The record chunking size. */
+ get chunkSize() {
+ throw new Error('Missing `chunkSize` implementation');
+ }
+}
+
+class OldSchemeDecoder extends Decoder {
+ async decode() {
+ // For aesgcm and aesgcm128, the ciphertext length can't fall on a record
+ // boundary.
+ if (this.ciphertext.byteLength > 0 &&
+ this.ciphertext.byteLength % this.chunkSize === 0) {
+ throw new CryptoError('Encrypted data truncated', BAD_CRYPTO);
+ }
+ return super.decode();
+ }
+
+ /**
+ * For aesgcm, the padding length is a 16-bit unsigned big endian integer.
+ * For aesgcm128, the padding is an 8-bit integer.
+ */
+ unpadChunk(decoded) {
+ if (decoded.length < this.padSize) {
+ throw new CryptoError('Decoded array is too short!', BAD_PADDING);
+ }
+ var pad = decoded[0]
+ if (this.padSize == 2) {
+ pad = (pad << 8) | decoded[1];
+ }
+ if (pad > decoded.length - this.padSize) {
+ throw new CryptoError('Padding is wrong!', BAD_PADDING);
+ }
+ // All padded bytes must be zero except the first one.
+ for (var i = this.padSize; i < this.padSize + pad; i++) {
+ if (decoded[i] !== 0) {
+ throw new CryptoError('Padding is wrong!', BAD_PADDING);
+ }
+ }
+ return decoded.slice(pad + this.padSize);
+ }
+
+ /**
+ * aesgcm and aesgcm128 don't account for the authentication tag as part of
+ * the record size.
+ */
+ get chunkSize() {
+ return this.rs + 16;
+ }
+
+ get padSize() {
+ throw new Error('Missing `padSize` implementation');
+ }
+}
+
+/** Older encryption scheme (draft-ietf-httpbis-encryption-encoding-01). */
+
+var AESGCM_ENCODING = 'aesgcm';
+var AESGCM_KEY_INFO = UTF8.encode('Content-Encoding: aesgcm\0');
+var AESGCM_AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus
+var AESGCM_P256DH_INFO = UTF8.encode('P-256\0');
+
+class aesgcmDecoder extends OldSchemeDecoder {
+ /**
+ * Derives the aesgcm decryption key and nonce. We mix the authentication
+ * secret with the ikm using HKDF. The context string for the PRK is
+ * "Content-Encoding: auth\0". The context string for the key and nonce is
+ * "Content-Encoding: <blah>\0P-256\0" then the length and value of both the
+ * receiver key and sender key.
+ */
+ async deriveKeyAndNonce(ikm) {
+ // Since we are using an authentication secret, we need to run an extra
+ // round of HKDF with the authentication secret as salt.
+ let authKdf = new hkdf(this.authenticationSecret, ikm);
+ let prk = await authKdf.extract(AESGCM_AUTH_INFO, 32);
+ let prkKdf = new hkdf(this.salt, prk);
+ let keyInfo = concatArray([
+ AESGCM_KEY_INFO, AESGCM_P256DH_INFO,
+ encodeLength(this.publicKey), this.publicKey,
+ encodeLength(this.senderKey), this.senderKey
+ ]);
+ let nonceInfo = concatArray([
+ NONCE_INFO, new Uint8Array([0]), AESGCM_P256DH_INFO,
+ encodeLength(this.publicKey), this.publicKey,
+ encodeLength(this.senderKey), this.senderKey
+ ]);
+ return Promise.all([
+ prkKdf.extract(keyInfo, 16),
+ prkKdf.extract(nonceInfo, 12)
+ ]);
+ }
+
+ get padSize() {
+ return 2;
+ }
+}
+
+/** Oldest encryption scheme (draft-thomson-http-encryption-02). */
+
+var AESGCM128_ENCODING = 'aesgcm128';
+var AESGCM128_KEY_INFO = UTF8.encode('Content-Encoding: aesgcm128');
+
+class aesgcm128Decoder extends OldSchemeDecoder {
+ constructor(privateKey, publicKey, cryptoParams, ciphertext) {
+ super(privateKey, publicKey, null, cryptoParams, ciphertext);
+ }
+
+ /**
+ * The aesgcm128 scheme ignores the authentication secret, and uses
+ * "Content-Encoding: <blah>" for the context string. It should eventually
+ * be removed: bug 1230038.
+ */
+ deriveKeyAndNonce(ikm) {
+ let prkKdf = new hkdf(this.salt, ikm);
+ return Promise.all([
+ prkKdf.extract(AESGCM128_KEY_INFO, 16),
+ prkKdf.extract(NONCE_INFO, 12)
+ ]);
+ }
+
+ get padSize() {
+ return 1;
+ }
+}
+
this.PushCrypto = {
generateAuthenticationSecret() {
return crypto.getRandomValues(new Uint8Array(16));
},
validateAppServerKey(key) {
return crypto.subtle.importKey('raw', key, ECDSA_KEY,
@@ -280,175 +518,38 @@ this.PushCrypto = {
crypto.subtle.exportKey('raw', cryptoKey.publicKey),
crypto.subtle.exportKey('jwk', cryptoKey.privateKey)
]));
},
/**
* Decrypts a push message.
*
+ * @throws {CryptoError} if decryption fails.
* @param {JsonWebKey} privateKey The ECDH private key of the subscription
* receiving the message, in JWK form.
* @param {BufferSource} publicKey The ECDH public key of the subscription
* receiving the message, in raw form.
* @param {BufferSource} authenticationSecret The 16-byte shared
* authentication secret of the subscription receiving the message.
- * @param {Object} headers The encryption headers passed to `getCryptoParams`.
+ * @param {Object} headers The encryption headers from the push server.
* @param {BufferSource} ciphertext The encrypted message data.
- * @returns {Promise} Resolves with a `Uint8Array` containing the decrypted
- * message data. Rejects with a `CryptoError` if decryption fails.
+ * @returns {Uint8Array} The decrypted message data.
*/
- decrypt(privateKey, publicKey, authenticationSecret, headers, ciphertext) {
- return Promise.resolve().then(_ => {
- let cryptoParams = getCryptoParams(headers);
- if (!cryptoParams) {
- return null;
- }
- return this._decodeMsg(ciphertext, privateKey, publicKey,
- cryptoParams.dh, cryptoParams.salt,
- cryptoParams.rs, authenticationSecret,
- cryptoParams.padSize);
- }).catch(error => {
- if (error.isCryptoError) {
- throw error;
- }
- // Web Crypto returns an unhelpful "operation failed for an
- // operation-specific reason" error if decryption fails. We don't have
- // context about what went wrong, so we throw a generic error instead.
- throw new CryptoError('Bad encryption', BAD_CRYPTO);
- });
- },
-
- _decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey, aSalt, aRs,
- aAuthenticationSecret, aPadSize) {
-
- if (aData.byteLength === 0) {
- // Zero length messages will be passed as null.
+ async decrypt(privateKey, publicKey, authenticationSecret, headers,
+ ciphertext) {
+ // aesgcm and aesgcm128 include the salt, record size, and sender public
+ // key in the `Crypto-Key` and `Encryption` HTTP headers.
+ let cryptoParams = getCryptoParamsFromHeaders(headers);
+ if (!cryptoParams) {
return null;
}
-
- // The last chunk of data must be less than aRs, if it is not return an
- // error.
- if (aData.byteLength % (aRs + 16) === 0) {
- throw new CryptoError('Encrypted data truncated', BAD_CRYPTO);
- }
-
- let senderKey = base64URLDecode(aSenderPublicKey);
- if (!senderKey) {
- throw new CryptoError('dh parameter is not base64url-encoded',
- BAD_DH_PARAM);
- }
-
- let salt = base64URLDecode(aSalt);
- if (!salt) {
- throw new CryptoError('salt parameter is not base64url-encoded',
- BAD_SALT_PARAM);
+ let decoder;
+ if (headers.encoding == AESGCM_ENCODING) {
+ decoder = new aesgcmDecoder(privateKey, publicKey, authenticationSecret,
+ cryptoParams, ciphertext);
+ } else {
+ decoder = new aesgcm128Decoder(privateKey, publicKey, cryptoParams,
+ ciphertext);
}
-
- return Promise.all([
- crypto.subtle.importKey('raw', senderKey, ECDH_KEY,
- false, ['deriveBits']),
- crypto.subtle.importKey('jwk', aPrivateKey, ECDH_KEY,
- false, ['deriveBits'])
- ])
- .then(([appServerKey, subscriptionPrivateKey]) =>
- crypto.subtle.deriveBits({ name: 'ECDH', public: appServerKey },
- subscriptionPrivateKey, 256))
- .then(ikm => this._deriveKeyAndNonce(aPadSize,
- new Uint8Array(ikm),
- salt,
- aPublicKey,
- senderKey,
- aAuthenticationSecret))
- .then(r =>
- // AEAD_AES_128_GCM expands ciphertext to be 16 octets longer.
- Promise.all(chunkArray(aData, aRs + 16).map((slice, index) =>
- this._decodeChunk(aPadSize, slice, index, r[1], r[0]))))
- .then(r => concatArray(r));
- },
-
- _deriveKeyAndNonce(padSize, ikm, salt, receiverKey, senderKey,
- authenticationSecret) {
- var kdfPromise;
- var context;
- var encryptInfo;
- // The size of the padding determines which key derivation we use.
- //
- // 1. If the pad size is 1, we assume "aesgcm128". This scheme ignores the
- // authenticationSecret, and uses "Content-Encoding: <blah>" for the
- // context string. It should eventually be removed: bug 1230038.
- //
- // 2. If the pad size is 2, we assume "aesgcm", and mix the
- // authenticationSecret with the ikm using HKDF. The context string is:
- // "Content-Encoding: <blah>\0P-256\0" then the length and value of both the
- // receiver key and sender key.
- if (padSize == 2) {
- // Since we are using an authentication secret, we need to run an extra
- // round of HKDF with the authentication secret as salt.
- var authKdf = new hkdf(authenticationSecret, ikm);
- kdfPromise = authKdf.extract(AUTH_INFO, 32)
- .then(ikm2 => new hkdf(salt, ikm2));
-
- // aesgcm requires extra context for the info parameter.
- context = concatArray([
- new Uint8Array([0]), P256DH_INFO,
- this._encodeLength(receiverKey), receiverKey,
- this._encodeLength(senderKey), senderKey
- ]);
- encryptInfo = AESGCM_ENCRYPT_INFO;
- } else {
- kdfPromise = Promise.resolve(new hkdf(salt, ikm));
- context = new Uint8Array(0);
- encryptInfo = AESGCM128_ENCRYPT_INFO;
- }
- return kdfPromise.then(kdf => Promise.all([
- kdf.extract(concatArray([encryptInfo, context]), 16)
- .then(gcmBits => crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
- ['decrypt'])),
- kdf.extract(concatArray([NONCE_INFO, context]), 12)
- ]));
- },
-
- _encodeLength(buffer) {
- return new Uint8Array([0, buffer.byteLength]);
- },
-
- _decodeChunk(aPadSize, aSlice, aIndex, aNonce, aKey) {
- let params = {
- name: 'AES-GCM',
- iv: generateNonce(aNonce, aIndex)
- };
- return crypto.subtle.decrypt(params, aKey, aSlice)
- .then(decoded => this._unpadChunk(aPadSize, new Uint8Array(decoded)));
- },
-
- /**
- * Removes padding from a decrypted chunk.
- *
- * @param {Number} padSize The size of the padding length prepended to each
- * chunk. For aesgcm, the padding length is expressed as a 16-bit unsigned
- * big endian integer. For aesgcm128, the padding is an 8-bit integer.
- * @param {Uint8Array} decoded The decrypted, padded chunk.
- * @returns {Uint8Array} The chunk with padding removed.
- */
- _unpadChunk(padSize, decoded) {
- if (padSize < 1 || padSize > 2) {
- throw new CryptoError('Unsupported pad size', BAD_CRYPTO);
- }
- if (decoded.length < padSize) {
- throw new CryptoError('Decoded array is too short!', BAD_PADDING);
- }
- var pad = decoded[0];
- if (padSize == 2) {
- pad = (pad << 8) | decoded[1];
- }
- if (pad > decoded.length) {
- throw new CryptoError('Padding is wrong!', BAD_PADDING);
- }
- // All padded bytes must be zero except the first one.
- for (var i = padSize; i <= pad; i++) {
- if (decoded[i] !== 0) {
- throw new CryptoError('Padding is wrong!', BAD_PADDING);
- }
- }
- return decoded.slice(pad + padSize);
+ return decoder.decode();
},
};
--- a/dom/push/test/xpcshell/test_crypto.js
+++ b/dom/push/test/xpcshell/test_crypto.js
@@ -1,90 +1,96 @@
'use strict';
const {
- getCryptoParams,
+ getCryptoParamsFromHeaders,
PushCrypto,
} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
function run_test() {
run_next_test();
}
-add_task(function* test_crypto_getCryptoParams() {
+add_task(async function test_crypto_getCryptoParamsFromHeaders() {
// These headers should parse correctly.
let shouldParse = [{
desc: 'aesgcm with multiple keys',
headers: {
encoding: 'aesgcm',
crypto_key: 'keyid=p256dh;dh=Iy1Je2Kv11A,p256ecdsa=o2M8QfiEKuI',
encryption: 'keyid=p256dh;salt=upk1yFkp1xI',
},
params: {
- dh: 'Iy1Je2Kv11A',
+ senderKey: 'Iy1Je2Kv11A',
salt: 'upk1yFkp1xI',
rs: 4096,
- padSize: 2,
},
}, {
desc: 'aesgcm with quoted key param',
headers: {
encoding: 'aesgcm',
crypto_key: 'dh="byfHbUffc-k"',
encryption: 'salt=C11AvAsp6Gc',
},
params: {
- dh: 'byfHbUffc-k',
+ senderKey: 'byfHbUffc-k',
salt: 'C11AvAsp6Gc',
rs: 4096,
- padSize: 2,
},
}, {
desc: 'aesgcm with Crypto-Key and rs = 24',
headers: {
encoding: 'aesgcm',
crypto_key: 'dh="ybuT4VDz-Bg"',
encryption: 'salt=H7U7wcIoIKs; rs=24',
},
params: {
- dh: 'ybuT4VDz-Bg',
+ senderKey: 'ybuT4VDz-Bg',
salt: 'H7U7wcIoIKs',
rs: 24,
- padSize: 2,
},
}, {
desc: 'aesgcm128 with Encryption-Key and rs = 2',
headers: {
encoding: 'aesgcm128',
encryption_key: 'keyid=legacy; dh=LqrDQuVl9lY',
encryption: 'keyid=legacy; salt=YngI8B7YapM; rs=2',
},
params: {
- dh: 'LqrDQuVl9lY',
+ senderKey: 'LqrDQuVl9lY',
salt: 'YngI8B7YapM',
rs: 2,
- padSize: 1,
},
}, {
desc: 'aesgcm128 with Encryption-Key',
headers: {
encoding: 'aesgcm128',
encryption_key: 'keyid=v2; dh=VA6wmY1IpiE',
encryption: 'keyid=v2; salt=khtpyXhpDKM',
},
params: {
- dh: 'VA6wmY1IpiE',
+ senderKey: 'VA6wmY1IpiE',
salt: 'khtpyXhpDKM',
rs: 4096,
- padSize: 1,
}
}];
for (let test of shouldParse) {
- let params = getCryptoParams(test.headers);
- deepEqual(params, test.params, test.desc);
+ let params = getCryptoParamsFromHeaders(test.headers);
+ let senderKey = ChromeUtils.base64URLDecode(test.params.senderKey, {
+ padding: 'reject',
+ });
+ let salt = ChromeUtils.base64URLDecode(test.params.salt, {
+ padding: 'reject',
+ });
+ deepEqual(new Uint8Array(params.senderKey), new Uint8Array(senderKey),
+ "Sender key should match for " + test.desc);
+ deepEqual(new Uint8Array(params.salt), new Uint8Array(salt),
+ "Salt should match for " + test.desc);
+ equal(params.rs, test.params.rs,
+ "Record size should match for " + test.desc);
}
// These headers should be rejected.
let shouldThrow = [{
desc: 'aesgcm128 with Crypto-Key',
headers: {
encoding: 'aesgcm128',
crypto_key: 'keyid=v2; dh=VA6wmY1IpiE',
@@ -98,32 +104,25 @@ add_task(function* test_crypto_getCrypto
}, {
desc: 'Invalid record size',
headers: {
encoding: 'aesgcm',
crypto_key: 'dh=pbmv1QkcEDY',
encryption: 'dh=Esao8aTBfIk;rs=bad',
},
}, {
- desc: 'Insufficiently large record size',
- headers: {
- encoding: 'aesgcm',
- crypto_key: 'dh=fK0EXaw5IU8',
- encryption: 'salt=orbLLmlbJfM;rs=1',
- },
- }, {
desc: 'aesgcm with Encryption-Key',
headers: {
encoding: 'aesgcm',
encryption_key: 'dh=FplK5KkvUF0',
encryption: 'salt=p6YHhFF3BQY',
},
}];
for (let test of shouldThrow) {
- throws(() => getCryptoParams(test.headers), test.desc);
+ throws(() => getCryptoParamsFromHeaders(test.headers), test.desc);
}
});
add_task(function* test_crypto_decodeMsg() {
let privateKey = {
crv: 'P-256',
d: '4h23G_KkXC9TvBSK2v0Q7ImpS2YAuRd8hQyN0rFAwBg',
ext: true,