diff --git a/Gruntfile.js b/Gruntfile.js index b3254c2c..d52d2c60 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -87,6 +87,31 @@ module.exports = function(grunt) { plugin: [ 'browserify-derequire' ] } }, + openpgp_browser: { + files: { + 'dist/openpgp_browser.js': [ './src/index.js' ] + }, + options: { + browserifyOptions: { + debug: true, + standalone: 'openpgp' + }, + external: [ 'crypto', 'node-localstorage', 'node-fetch' ], + transform: [ + ["babelify", { + plugins: ["transform-async-to-generator", + "syntax-async-functions", + "transform-regenerator", + "transform-runtime"], + ignore: ['*.min.js'], + presets: [ + "es2015" + ] + }] + ], + plugin: [ 'browserify-derequire' ] + } + }, worker: { files: { 'dist/openpgp.worker.js': [ './src/worker/worker.js' ] @@ -97,7 +122,7 @@ module.exports = function(grunt) { 'test/lib/unittests-bundle.js': [ './test/unittests.js' ] }, options: { - external: [ 'crypto', 'buffer' , 'node-localstorage', 'node-fetch', 'openpgp', '../../dist/openpgp', '../../../dist/openpgp' ] + external: [ 'crypto', 'node-localstorage', 'node-fetch', 'openpgp', '../../dist/openpgp' ] } } }, @@ -190,7 +215,7 @@ module.exports = function(grunt) { } }, copy: { - browsertest: { + browser: { expand: true, flatten: true, cwd: 'node_modules/', @@ -262,6 +287,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks('grunt-saucelabs'); + grunt.loadNpmTasks('grunt-keepalive'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.registerTask('set_version', function() { @@ -304,6 +330,7 @@ module.exports = function(grunt) { // Test/Dev tasks grunt.registerTask('test', [ 'eslint', 'mochaTest']); grunt.registerTask('coverage', ['mocha_istanbul:coverage']); - grunt.registerTask('saucelabs', ['default', 'copy:browsertest', 'connect:test', 'saucelabs-mocha']); + grunt.registerTask('saucelabs', ['default', 'copy:browser', 'connect:test', 'saucelabs-mocha']); + grunt.registerTask('browsertest', ['browserify:openpgp_browser', 'copy:browser', 'connect:test', 'keepalive']); }; diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js index 839f731e..ca874b40 100644 --- a/src/crypto/public_key/elliptic/curves.js +++ b/src/crypto/public_key/elliptic/curves.js @@ -27,16 +27,27 @@ 'use strict'; +import ASN1 from 'asn1.js'; + import {ec as EC} from 'elliptic'; import {KeyPair} from './key.js'; import BigInteger from '../jsbn.js'; import config from '../../../config'; import enums from '../../../enums.js'; import util from '../../../util.js'; +import base64 from '../../../encoding/base64.js'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); + +var ECPrivateKey = ASN1.define('ECPrivateKey', function() { + this.seq().obj( + this.key('r').int(), // FIXME int or BN? + this.key('s').int() // FIXME int or BN? + ); +}); + var webCurves = [], nodeCurves = []; if (webCrypto && config.use_native) { // see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Supported_algorithms @@ -87,7 +98,9 @@ const curves = { node: false, // FIXME nodeCurves.includes('secp256k1'), // this is because jwk-to-pem does not support this curve. web: false - } + }, + curve25519 : {}, + ed25519 : {} }; function Curve(name, {oid, hash, cipher, namedCurve, opensslCurve, hashName, node, web}) { @@ -114,7 +127,7 @@ Curve.prototype.keyFromPublic = function (pub) { Curve.prototype.genKeyPair = async function () { var keyPair; if (webCrypto && config.use_native && this.web) { - keyPair = await webGenKeyPair(this.namedCurve); + keyPair = await webGenKeyPair(this.namedCurve, "ECDSA"); // FIXME } else if (nodeCrypto && config.use_native && this.node) { keyPair = await nodeGenKeyPair(this.opensslCurve); } else { @@ -163,24 +176,26 @@ module.exports = { ////////////////////////// -async function webGenKeyPair(namedCurve) { +async function webGenKeyPair(namedCurve, algorithm) { try { - var keyPair = await webCrypto.generateKey( + var webCryptoKey = await webCrypto.generateKey( { - name: "ECDSA", // FIXME or "ECDH" - // "P-256", "P-384", or "P-521" + name: algorithm === "ECDH" ? "ECDH" : "ECDSA", namedCurve: namedCurve }, - // TODO whether the key is extractable (i.e. can be used in exportKey) - false, - // FIXME this can be any combination of "sign" and "verify" - // or "deriveKey" and "deriveBits" for ECDH - ["sign", "verify"] + true, + algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"] ); + var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey); + var publicKey = await webCrypto.exportKey("jwk", webCryptoKey.publicKey); + return { - pub: keyPair.publicKey.encode(), // FIXME encoding - priv: keyPair.privateKey.toArray() // FIXME encoding + pub: { + x: base64.decode(publicKey.x, 'base64url'), + y: base64.decode(publicKey.y, 'base64url') + }, + priv: base64.decode(privateKey.d, 'base64url') }; } catch(err) { throw new Error(err); diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js index 957b18d1..e14163a2 100644 --- a/src/crypto/public_key/elliptic/ecdsa.js +++ b/src/crypto/public_key/elliptic/ecdsa.js @@ -33,12 +33,13 @@ import curves from './curves.js'; import BigInteger from '../jsbn.js'; import config from '../../../config'; import enums from '../../../enums.js'; -import util from '../../../util.js' +import util from '../../../util.js'; +import base64 from '../../../encoding/base64.js'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); -var ECDSASignature = ASN1.define('ecdsa-sig', function() { +var ECDSASignature = ASN1.define('ECDSASignature', function() { this.seq().obj( this.key('r').int(), // FIXME int or BN? this.key('s').int() // FIXME int or BN? @@ -115,9 +116,9 @@ async function webSign(curve, hash_algo, m, keyPair) { { "kty": "EC", "crv": curve.namedCurve, - "x": keyPair.getPublic().getX().toBuffer().base64Slice(), - "y": keyPair.getPublic().getY().toBuffer().base64Slice(), - "d": keyPair.getPrivate().toBuffer().base64Slice(), + "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray()), null, 'base64url'), + "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray()), null, 'base64url'), + "d": base64.encode(new Uint8Array(keyPair.getPrivate()), null, 'base64url'), "use": "sig", "kid": "ECDSA Private Key" }, @@ -151,8 +152,8 @@ async function webVerify(curve, hash_algo, signature, m, publicKey) { { "kty": "EC", "crv": curve.namedCurve, - "x": publicKey.getX().toBuffer().base64Slice(), - "y": publicKey.getY().toBuffer().base64Slice(), + "x": base64.encode(new Uint8Array(publicKey.getX().toArray()), null, 'base64url'), + "y": base64.encode(new Uint8Array(publicKey.getY().toArray()), null, 'base64url'), "use": "sig", "kid": "ECDSA Public Key" }, @@ -189,9 +190,9 @@ async function nodeSign(curve, hash_algo, message, keyPair) { { "kty": "EC", "crv": curve.namedCurve, - "x": keyPair.getPublic().getX().toBuffer().base64Slice(), - "y": keyPair.getPublic().getY().toBuffer().base64Slice(), - "d": keyPair.getPrivate().toBuffer().base64Slice(), + "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray())), + "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray())), + "d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray())), "use": "sig", "kid": "ECDSA Private Key" }, @@ -223,8 +224,8 @@ async function nodeVerify(curve, hash_algo, signature, message, publicKey) { { "kty": "EC", "crv": curve.namedCurve, - "x": publicKey.getX().toBuffer().base64Slice(), - "y": publicKey.getY().toBuffer().base64Slice(), + "x": base64.encode(new Uint8Array(publicKey.getX().toArray())), + "y": base64.encode(new Uint8Array(publicKey.getY().toArray())), "use": "sig", "kid": "ECDSA Public Key" }, diff --git a/src/encoding/base64.js b/src/encoding/base64.js index bb670945..403a73d5 100644 --- a/src/encoding/base64.js +++ b/src/encoding/base64.js @@ -18,6 +18,7 @@ 'use strict'; var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +var b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; /** * Convert binary array to radix-64 @@ -25,8 +26,9 @@ var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; * @returns {string} radix-64 version of input string * @static */ -function s2r(t, o) { +function s2r(t, o, u) { // TODO check btoa alternative + var b64 = (u === "base64url") ? b64u : b64s; var a, c, n; var r = o ? o : [], l = 0, @@ -36,21 +38,21 @@ function s2r(t, o) { for (n = 0; n < tl; n++) { c = t[n]; if (s === 0) { - r.push(b64s.charAt((c >> 2) & 63)); + r.push(b64.charAt((c >> 2) & 63)); a = (c & 3) << 4; } else if (s === 1) { - r.push(b64s.charAt(a | ((c >> 4) & 15))); + r.push(b64.charAt(a | ((c >> 4) & 15))); a = (c & 15) << 2; } else if (s === 2) { - r.push(b64s.charAt(a | ((c >> 6) & 3))); + r.push(b64.charAt(a | ((c >> 6) & 3))); l += 1; - if ((l % 60) === 0) { + if ((l % 60) === 0 && !u) { r.push("\n"); } - r.push(b64s.charAt(c & 63)); + r.push(b64.charAt(c & 63)); } l += 1; - if ((l % 60) === 0) { + if ((l % 60) === 0 && !u) { r.push("\n"); } @@ -60,16 +62,18 @@ function s2r(t, o) { } } if (s > 0) { - r.push(b64s.charAt(a)); + r.push(b64.charAt(a)); l += 1; - if ((l % 60) === 0) { + if ((l % 60) === 0 && !u) { r.push("\n"); } - r.push('='); - l += 1; + if (u !== 'base64url') { + r.push('='); + l += 1; + } } - if (s === 1) { - if ((l % 60) === 0) { + if (s === 1 && u !== 'base64url') { + if ((l % 60) === 0 && !u) { r.push("\n"); } r.push('='); @@ -87,8 +91,9 @@ function s2r(t, o) { * @returns {Uint8Array} binary array version of input string * @static */ -function r2s(t) { +function r2s(t, u) { // TODO check atob alternative + var b64 = (u === "base64url") ? b64u : b64s; var c, n; var r = [], s = 0, @@ -96,7 +101,7 @@ function r2s(t) { var tl = t.length; for (n = 0; n < tl; n++) { - c = b64s.indexOf(t.charAt(n)); + c = b64.indexOf(t.charAt(n)); if (c >= 0) { if (s) { r.push(a | ((c >> (6 - s)) & 255)); diff --git a/test/unittests.html b/test/unittests.html index 47f16d5d..8f860f19 100644 --- a/test/unittests.html +++ b/test/unittests.html @@ -10,11 +10,11 @@
- + -