From 8aa15b66a9b4cac2d81189fff47d116d27fc4f43 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 22 Mar 2016 16:25:24 +0800 Subject: [PATCH] Cleanup and unit test gcm.js --- src/crypto/gcm.js | 54 +++++++++++++---------------------- test/crypto/crypto.js | 65 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 44 deletions(-) diff --git a/src/crypto/gcm.js b/src/crypto/gcm.js index 29a927ea..feccb104 100644 --- a/src/crypto/gcm.js +++ b/src/crypto/gcm.js @@ -23,12 +23,14 @@ 'use strict'; import util from '../util.js'; +import config from '../config'; import asmCrypto from 'asmcrypto-lite'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); const Buffer = util.getNodeBuffer(); export const ivLength = 12; +const ALGO = 'AES-GCM'; /** * Encrypt plaintext input. @@ -40,26 +42,18 @@ export const ivLength = 12; */ export function encrypt(cipher, plaintext, key, iv) { if (cipher.substr(0,3) !== 'aes') { - return Promise.reject(new Error('Invalid cipher for GCM mode')); + return Promise.reject(new Error('GCM mode supports only AES cipher')); } - if (webCrypto) { // native WebCrypto api - const keyOptions = { - name: 'AES-GCM' - }, - encryptOptions = { - name: 'AES-GCM', - iv: iv - }; - return webCrypto.importKey('raw', key, keyOptions, false, ['encrypt']).then(keyObj => { - return webCrypto.encrypt(encryptOptions, keyObj, plaintext); - }).then(ciphertext => { - return new Uint8Array(ciphertext); - }); + const keySize = cipher.substr(3,3); + if (webCrypto && config.useNative && keySize !== '192') { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support + return webCrypto.importKey('raw', key, { name: ALGO }, false, ['encrypt']) + .then(keyObj => webCrypto.encrypt({ name: ALGO, iv }, keyObj, plaintext)) + .then(ciphertext => new Uint8Array(ciphertext)); - } else if(nodeCrypto) { // native node crypto library - let cipherObj = new nodeCrypto.createCipheriv('aes-' + cipher.substr(3,3) + '-gcm', new Buffer(key), new Buffer(iv)); - let encrypted = Buffer.concat([cipherObj.update(new Buffer(plaintext)), cipherObj.final()]); + } else if (nodeCrypto && config.useNative) { // Node crypto library + const en = new nodeCrypto.createCipheriv('aes-' + keySize + '-gcm', new Buffer(key), new Buffer(iv)); + const encrypted = Buffer.concat([en.update(new Buffer(plaintext)), en.final()]); return Promise.resolve(new Uint8Array(encrypted)); } else { // asm.js fallback @@ -77,26 +71,18 @@ export function encrypt(cipher, plaintext, key, iv) { */ export function decrypt(cipher, ciphertext, key, iv) { if (cipher.substr(0,3) !== 'aes') { - return Promise.reject(new Error('Invalid cipher for GCM mode')); + return Promise.reject(new Error('GCM mode supports only AES cipher')); } - if (webCrypto) { // native WebCrypto api - const keyOptions = { - name: 'AES-GCM' - }, - decryptOptions = { - name: 'AES-GCM', - iv: iv - }; - return webCrypto.importKey('raw', key, keyOptions, false, ['decrypt']).then(keyObj => { - return webCrypto.decrypt(decryptOptions, keyObj, ciphertext); - }).then(plaintext => { - return new Uint8Array(plaintext); - }); + const keySize = cipher.substr(3,3); + if (webCrypto && config.useNative && keySize !== '192') { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support + return webCrypto.importKey('raw', key, { name: ALGO }, false, ['decrypt']) + .then(keyObj => webCrypto.decrypt({ name: ALGO, iv }, keyObj, ciphertext)) + .then(plaintext => new Uint8Array(plaintext)); - } else if(nodeCrypto) { // native node crypto library - let decipherObj = new nodeCrypto.createDecipheriv('aes-' + cipher.substr(3,3) + '-gcm', new Buffer(key), new Buffer(iv)); - let decrypted = Buffer.concat([decipherObj.update(new Buffer(ciphertext)), decipherObj.final()]); + } else if (nodeCrypto && config.useNative) { // Node crypto library + let de = new nodeCrypto.createDecipheriv('aes-' + keySize + '-gcm', new Buffer(key), new Buffer(iv)); + let decrypted = Buffer.concat([de.update(new Buffer(ciphertext)), de.final()]); return Promise.resolve(new Uint8Array(decrypted)); } else { // asm.js fallback diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 08afcfeb..1cfd6642 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -80,7 +80,7 @@ describe('API functional testing', function() { 0x51,0xe0,0x22,0xf0,0xff,0xa7,0x42,0xd4,0xde,0x0b,0x47,0x8f,0x2b, 0xf5,0x4d,0x04,0x32,0x91,0x89,0x4b,0x0e,0x05,0x8d,0x70,0xf9,0xbb, 0xe7,0xd6,0x76,0xea,0x0e,0x1a,0x90,0x30,0xf5,0x98,0x01,0xc5,0x73])]; - + var DSApubMPIstrs = [ new Uint8Array([0x08,0x00,0xa8,0x85,0x5c,0x28,0x05,0x94,0x03,0xbe,0x07,0x6c,0x13,0x3e,0x65, 0xfb,0xb5,0xe1,0x99,0x7c,0xfa,0x84,0xe3,0xac,0x47,0xa5,0xc4,0x46,0xd8,0x5f, @@ -143,7 +143,7 @@ describe('API functional testing', function() { new Uint8Array([0x01,0x00,0x9b,0x58,0xa8,0xf4,0x04,0xb1,0xd5,0x14,0x09,0xe1,0xe1,0xa1,0x8a, 0x0b,0xa3,0xc3,0xa3,0x66,0xaa,0x27,0x99,0x50,0x1c,0x4d,0xba,0x24,0xee,0xdf, 0xdf,0xb8,0x8e,0x8e])]; - + var ElgamalpubMPIstrs = [ new Uint8Array([0x08,0x00,0xea,0xcc,0xbe,0xe2,0xe4,0x5a,0x51,0x18,0x93,0xa1,0x12,0x2f,0x00, 0x99,0x42,0xd8,0x5c,0x1c,0x2f,0xb6,0x3c,0xd9,0x94,0x61,0xb4,0x55,0x8d,0x4e, @@ -200,13 +200,13 @@ describe('API functional testing', function() { RSAsecMPIs[i] = new openpgp.MPI(); RSAsecMPIs[i].read(RSAsecMPIstrs[i]); } - + var DSAsecMPIs = []; for (i = 0; i < 1; i++) { DSAsecMPIs[i] = new openpgp.MPI(); DSAsecMPIs[i].read(DSAsecMPIstrs[i]); } - + var DSApubMPIs = []; for (i = 0; i < 4; i++) { DSApubMPIs[i] = new openpgp.MPI(); @@ -217,7 +217,7 @@ describe('API functional testing', function() { ElgamalsecMPIs[i] = new openpgp.MPI(); ElgamalsecMPIs[i].read(ElgamalsecMPIstrs[i]); } - + var ElgamalpubMPIs = []; for (i = 0; i < 3; i++) { ElgamalpubMPIs[i] = new openpgp.MPI(); @@ -287,6 +287,25 @@ describe('API functional testing', function() { }); } + function testAESGCM(plaintext) { + symmAlgos.forEach(function(algo) { + if(algo.substr(0,3) === 'aes') { + it(algo, function(done) { + var key = openpgp.crypto.generateSessionKey(algo); + var iv = openpgp.crypto.random.getRandomValues(new Uint8Array(openpgp.crypto.gcm.ivLength)); + + openpgp.crypto.gcm.encrypt(algo, util.str2Uint8Array(plaintext), key, iv).then(function(ciphertext) { + return openpgp.crypto.gcm.decrypt(algo, ciphertext, key, iv); + }).then(function(decrypted) { + var decryptedStr = util.Uint8Array2str(decrypted); + expect(decryptedStr).to.equal(plaintext); + done(); + }); + }); + } + }); + } + it("Symmetric with OpenPGP CFB resync", function () { testCFB("hello", true); testCFB("1234567", true); @@ -301,11 +320,37 @@ describe('API functional testing', function() { testCFB("12345678901234567890123456789012345678901234567890", false); }); - it("asmCrypto AES without OpenPGP CFB resync", function () { - testCFB("hello"); - testCFB("1234567"); - testCFB("foobarfoobar1234567890"); - testCFB("12345678901234567890123456789012345678901234567890"); + it.skip("asmCrypto AES without OpenPGP CFB resync", function () { + testAESCFB("hello"); + testAESCFB("1234567"); + testAESCFB("foobarfoobar1234567890"); + testAESCFB("12345678901234567890123456789012345678901234567890"); + }); + + describe('Symmetric AES-GCM (native)', function() { + var useNativeVal; + beforeEach(function() { + useNativeVal = openpgp.config.useNative; + openpgp.config.useNative = true; + }); + afterEach(function() { + openpgp.config.useNative = useNativeVal; + }); + + testAESGCM("12345678901234567890123456789012345678901234567890"); + }); + + describe('Symmetric AES-GCM (asm.js fallback)', function() { + var useNativeVal; + beforeEach(function() { + useNativeVal = openpgp.config.useNative; + openpgp.config.useNative = false; + }); + afterEach(function() { + openpgp.config.useNative = useNativeVal; + }); + + testAESGCM("12345678901234567890123456789012345678901234567890"); }); it('Asymmetric using RSA with eme_pkcs1 padding', function (done) {