From 043e77a6eaa7b3048c3cd2bd1465947f764cf2f1 Mon Sep 17 00:00:00 2001 From: Wiktor Kwapisiewicz Date: Fri, 25 May 2018 21:57:12 +0200 Subject: [PATCH] Add Web Key Directory lookup This change implements Web Key Directory lookup using user's e-mail address. The target host is the same as the e-mail's domain and the local-part is hashed with SHA-1 and encoded using Z-Base32 encoding. Implemented is basic flow of version 06 of OpenPGP Web Key Directory draft [0]. It was necessary to update node-fetch package to allow returning array buffers from HTTP responses. If openpgpjs is used in the browser all keys retrieved from Web Key Directory should have `Access-Control-Allow-Origin` header set to `*` (including 404 Not found responses). [0]: https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/ --- npm-shrinkwrap.json | 23 ++----------- package.json | 2 +- src/index.js | 6 ++++ src/wkd.js | 77 +++++++++++++++++++++++++++++++++++++++++++ test/general/index.js | 1 + test/general/wkd.js | 48 +++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 src/wkd.js create mode 100644 test/general/wkd.js diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b191293f..0fac657c 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2325,14 +2325,6 @@ "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", "dev": true }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "0.4.19" - } - }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -5221,11 +5213,6 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -6006,13 +5993,9 @@ } }, "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" - } + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" }, "node-localstorage": { "version": "1.3.0", diff --git a/package.json b/package.json index 31b42e92..1e719052 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "compressjs": "github:openpgpjs/compressjs", "elliptic": "github:openpgpjs/elliptic", "hash.js": "^1.1.3", - "node-fetch": "^1.7.3", + "node-fetch": "^2.1.2", "node-localstorage": "~1.3.0", "pako": "^1.0.6", "rusha": "^0.8.12" diff --git a/src/index.js b/src/index.js index 0b771afe..7b2b7f14 100644 --- a/src/index.js +++ b/src/index.js @@ -141,3 +141,9 @@ export { default as AsyncProxy } from './worker/async_proxy'; * @name module:openpgp.HKP */ export { default as HKP } from './hkp'; + +/** + * @see module:wkd + * @name module:openpgp.WKD + */ +export { default as WKD } from './wkd'; diff --git a/src/wkd.js b/src/wkd.js new file mode 100644 index 00000000..4c3dfa69 --- /dev/null +++ b/src/wkd.js @@ -0,0 +1,77 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2018 Wiktor Kwapisiewicz +// +// 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 + +/** + * @fileoverview This class implements a client for the Web Key Directory (wkd) protocol + * in order to lookup keys on designated servers. + * See: https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/ + * @module wkd + */ + +import util from './util'; +import crypto from './crypto'; +import * as keyMod from './key'; + +/** + * Initialize the WKD client + * @constructor + */ +function WKD() { + this._fetch = typeof window !== 'undefined' ? window.fetch : require('node-fetch'); +} + +/** + * Search for a public key using Web Key Directory protocol. + * @param {String} options.email User's email. + * @param {Boolean} options.rawBytes Returns Uint8Array instead of parsed key. + * @returns {Promise, + * err: (Array|null)}>} The public key. + * @async + */ +WKD.prototype.lookup = function(options) { + const fetch = this._fetch; + + if (!options.email) { + throw new Error('You must provide an email parameter!'); + } + + if (!util.isEmailAddress(options.email)) { + throw new Error('Invalid e-mail address.'); + } + + const [, localPart, domain] = /(.*)@(.*)/.exec(options.email); + const localEncoded = util.encodeZBase32(crypto.hash.sha1(util.str_to_Uint8Array(localPart.toLowerCase()))); + + const url = `https://${domain}/.well-known/openpgpkey/hu/${localEncoded}`; + + return fetch(url).then(function(response) { + if (response.status === 200) { + return response.arrayBuffer(); + } + }).then(function(publicKey) { + if (publicKey) { + const rawBytes = new Uint8Array(publicKey); + if (options.rawBytes) { + return rawBytes; + } + return keyMod.read(rawBytes); + } + }); +}; + +export default WKD; diff --git a/test/general/index.js b/test/general/index.js index ebd7cb32..1a64ce9a 100644 --- a/test/general/index.js +++ b/test/general/index.js @@ -7,6 +7,7 @@ describe('General', function () { require('./key.js'); require('./openpgp.js'); require('./hkp.js'); + require('./wkd.js'); require('./oid.js'); require('./ecc_nist.js'); require('./x25519.js'); diff --git a/test/general/wkd.js b/test/general/wkd.js new file mode 100644 index 00000000..23368388 --- /dev/null +++ b/test/general/wkd.js @@ -0,0 +1,48 @@ +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +const chai = require('chai'); + +const { expect } = chai; + +describe('WKD unit tests', function() { + this.timeout(60000); + + let wkd; + + beforeEach(function() { + wkd = new openpgp.WKD(); + }); + + afterEach(function() {}); + + describe('lookup', function() { + it('by email address should work', function() { + return wkd.lookup({ + email: 'test-wkd@metacode.biz', + rawBytes: true + }).then(function(key) { + expect(key).to.exist; + expect(key).to.be.an.instanceof(Uint8Array); + }); + }); + + it('by email address should work', function() { + return wkd.lookup({ + email: 'test-wkd@metacode.biz' + }).then(function(key) { + expect(key).to.exist; + expect(key).to.have.property('keys'); + expect(key.keys).to.have.lengthOf(1); + }); + }); + + it('by email address should not find a key', function() { + return wkd.lookup({ + email: 'test-wkd-does-not-exist@metacode.biz' + }).then(function(key) { + expect(key).to.be.undefined; + }); + }); + }); + +});