diff --git a/package.json b/package.json index bc7f0f1a..d9e43876 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "test": "grunt test" }, "devDependencies": { + "aes": "^0.1.0", "asmcrypto-lite": "^1.0.0", "babel-core": "^6.26.0", "babel-preset-es2015": "^6.3.13", diff --git a/src/crypto/index.js b/src/crypto/index.js index e337b4cb..d87a18ee 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -14,6 +14,7 @@ import signature from './signature'; import random from './random'; import pkcs1 from './pkcs1'; import crypto from './crypto.js'; +import rfc3394 from './rfc3394.js'; const mod = { /** @see module:crypto/cipher */ @@ -32,6 +33,8 @@ const mod = { random: random, /** @see module:crypto/pkcs1 */ pkcs1: pkcs1, + /** @see module:crypto/rfc3394 */ + rfc3394: rfc3394, }; for (var i in crypto) { diff --git a/src/crypto/rfc3394.js b/src/crypto/rfc3394.js new file mode 100644 index 00000000..93f3143f --- /dev/null +++ b/src/crypto/rfc3394.js @@ -0,0 +1,142 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +// Implementation of RFC 3394 Key Wrap & Key Unwrap funcions + +import AES from 'aes'; + +function createArrayBuffer(data) { + if (typeof data === "string") { + var length = data.length; + var buffer = new ArrayBuffer(length); + var view = new Uint8Array(buffer); + for (var j = 0; j < length; ++j) { + view[j] = data.charCodeAt(j); + } + return buffer; + } + return new Uint8Array(data).buffer; +} + +function unpack(data) { + var length = data.length; + var buffer = createArrayBuffer(data); + var view = new DataView(buffer); + var arr = new Uint32Array(length/4); + for (var i=0; i= 0; --j) { + for (var i = n - 1; i >= 0; --i) { + t[1] = n * j + (i + 1); + // B = A ^ t + B[0] = A[0] ^ t[0]; + B[1] = A[1] ^ t[1]; + // B = (A ^ t) || R[i] + B[2] = R[2*i]; + B[3] = R[2*i+1]; + // B = AES-1(B) + B = aes.decrypt(B); + // A = MSB(64, B) + A = B.subarray(0, 2); + // R[i] = LSB(64, B) + R[2*i] = B[2]; + R[2*i+1] = B[3]; + } + } + if (A[0] === IV[0] && A[1] === IV[1]) { + return pack(R); + } + throw new Error("Key Data Integrity failed"); +} + +module.exports = { + wrap: wrap, + unwrap: unwrap +}; diff --git a/test/crypto/index.js b/test/crypto/index.js index f04a73d0..56685ed9 100644 --- a/test/crypto/index.js +++ b/test/crypto/index.js @@ -3,4 +3,5 @@ describe('Crypto', function () { require('./hash'); require('./random.js'); require('./crypto.js'); + require('./rfc3394.js'); }); diff --git a/test/crypto/rfc3394.js b/test/crypto/rfc3394.js new file mode 100644 index 00000000..6887db79 --- /dev/null +++ b/test/crypto/rfc3394.js @@ -0,0 +1,59 @@ +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +var expect = require('chai').expect; + +describe('AES Key Wrap and Unwrap', function () { + var test_vectors = [ + [ + "128 bits of Key Data with a 128-bit KEK", + "000102030405060708090A0B0C0D0E0F", + "00112233445566778899AABBCCDDEEFF", + "1FA68B0A8112B447 AEF34BD8FB5A7B82 9D3E862371D2CFE5" + ], + [ + "128 bits of Key Data with a 192-bit KEK", + "000102030405060708090A0B0C0D0E0F1011121314151617", + "00112233445566778899AABBCCDDEEFF", + "96778B25AE6CA435 F92B5B97C050AED2 468AB8A17AD84E5D" + ], + [ + "128 bits of Key Data with a 256-bit KEK", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "00112233445566778899AABBCCDDEEFF", + "64E8C3F9CE0F5BA2 63E9777905818A2A 93C8191E7D6E8AE7" + ], + [ + "192 bits of Key Data with a 192-bit KEK", + "000102030405060708090A0B0C0D0E0F1011121314151617", + "00112233445566778899AABBCCDDEEFF0001020304050607", + "031D33264E15D332 68F24EC260743EDC E1C6C7DDEE725A93 6BA814915C6762D2" + ], + [ + "192 bits of Key Data with a 256-bit KEK", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "00112233445566778899AABBCCDDEEFF0001020304050607", + "A8F9BC1612C68B3F F6E6F4FBE30E71E4 769C8B80A32CB895 8CD5D17D6B254DA1" + ], + [ + "256 bits of Key Data with a 256-bit KEK", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F", + "28C9F404C4B810F4 CBCCB35CFB87F826 3F5786E2D80ED326 CBC7F0E71A99F43B FB988B9B7A02DD21" + ] + ]; + + test_vectors.forEach(function(test) { + it(test[0], function(done) { + var kek = openpgp.util.hex2bin(test[1]); + var input = test[2].replace(/\s/g, ""); + var input_bin = openpgp.util.hex2bin(input); + var output = test[3].replace(/\s/g, ""); + var output_bin = openpgp.util.hex2bin(output); + expect(openpgp.util.hexidump(openpgp.crypto.rfc3394.wrap(kek, input_bin)).toUpperCase()).to.equal(output); + expect(openpgp.util.hexidump(openpgp.crypto.rfc3394.unwrap(kek, output_bin)).toUpperCase()).to.equal(input); + done(); + }); + }); +});