Parse user IDs
Also, support comments when creating user IDs
This commit is contained in:
parent
cf3c2790f2
commit
6c2fec3450
|
@ -29,8 +29,8 @@ module.exports = function(grunt) {
|
||||||
transform: [
|
transform: [
|
||||||
["babelify", {
|
["babelify", {
|
||||||
global: true,
|
global: true,
|
||||||
// Only babelify asmcrypto in node_modules
|
// Only babelify asmcrypto and address-rfc2822 in node_modules
|
||||||
only: /^(?:.*\/node_modules\/asmcrypto\.js\/|(?!.*\/node_modules\/)).*$/,
|
only: /^(?:.*\/node_modules\/asmcrypto\.js\/|.*\/node_modules\/address-rfc2822\/|(?!.*\/node_modules\/)).*$/,
|
||||||
plugins: ["transform-async-to-generator",
|
plugins: ["transform-async-to-generator",
|
||||||
"syntax-async-functions",
|
"syntax-async-functions",
|
||||||
"transform-regenerator",
|
"transform-regenerator",
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
"whatwg-fetch": "^2.0.3"
|
"whatwg-fetch": "^2.0.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"address-rfc2822": "^2.0.3",
|
||||||
"asmcrypto.js": "^0.22.0",
|
"asmcrypto.js": "^0.22.0",
|
||||||
"asn1.js": "^5.0.0",
|
"asn1.js": "^5.0.0",
|
||||||
"bn.js": "^4.11.8",
|
"bn.js": "^4.11.8",
|
||||||
|
|
|
@ -1244,7 +1244,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) {
|
||||||
|
|
||||||
await Promise.all(options.userIds.map(async function(userId, index) {
|
await Promise.all(options.userIds.map(async function(userId, index) {
|
||||||
const userIdPacket = new packet.Userid();
|
const userIdPacket = new packet.Userid();
|
||||||
userIdPacket.read(util.str_to_Uint8Array(userId));
|
userIdPacket.format(userId);
|
||||||
|
|
||||||
const dataToSign = {};
|
const dataToSign = {};
|
||||||
dataToSign.userid = userIdPacket;
|
dataToSign.userid = userIdPacket;
|
||||||
|
|
|
@ -115,7 +115,7 @@ export function destroyWorker() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}] }) {
|
export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}] }) {
|
||||||
userIds = formatUserIds(userIds);
|
userIds = toArray(userIds);
|
||||||
const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys };
|
const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys };
|
||||||
if (util.getWebCryptoAll() && numBits < 2048) {
|
if (util.getWebCryptoAll() && numBits < 2048) {
|
||||||
throw new Error('numBits should be 2048 or 4096, found: ' + numBits);
|
throw new Error('numBits should be 2048 or 4096, found: ' + numBits);
|
||||||
|
@ -146,7 +146,7 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date}) {
|
export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date}) {
|
||||||
userIds = formatUserIds(userIds);
|
userIds = toArray(userIds);
|
||||||
const options = { privateKey, userIds, passphrase, keyExpirationTime, date};
|
const options = { privateKey, userIds, passphrase, keyExpirationTime, date};
|
||||||
if (asyncProxy) {
|
if (asyncProxy) {
|
||||||
return asyncProxy.delegate('reformatKey', options);
|
return asyncProxy.delegate('reformatKey', options);
|
||||||
|
@ -484,36 +484,6 @@ function checkCleartextOrMessage(message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format user ids for internal use.
|
|
||||||
*/
|
|
||||||
function formatUserIds(userIds) {
|
|
||||||
if (!userIds) {
|
|
||||||
return userIds;
|
|
||||||
}
|
|
||||||
userIds = toArray(userIds); // normalize to array
|
|
||||||
userIds = userIds.map(id => {
|
|
||||||
if (util.isString(id) && !util.isUserId(id)) {
|
|
||||||
throw new Error('Invalid user id format');
|
|
||||||
}
|
|
||||||
if (util.isUserId(id)) {
|
|
||||||
return id; // user id is already in correct format... no conversion necessary
|
|
||||||
}
|
|
||||||
// name and email address can be empty but must be of the correct type
|
|
||||||
id.name = id.name || '';
|
|
||||||
id.email = id.email || '';
|
|
||||||
if (!util.isString(id.name) || (id.email && !util.isEmailAddress(id.email))) {
|
|
||||||
throw new Error('Invalid user id format');
|
|
||||||
}
|
|
||||||
id.name = id.name.trim();
|
|
||||||
if (id.name.length > 0) {
|
|
||||||
id.name += ' ';
|
|
||||||
}
|
|
||||||
return id.name + '<' + id.email + '>';
|
|
||||||
});
|
|
||||||
return userIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize parameter to an array if it is not undefined.
|
* Normalize parameter to an array if it is not undefined.
|
||||||
* @param {Object} param the parameter to be normalized
|
* @param {Object} param the parameter to be normalized
|
||||||
|
|
|
@ -41,6 +41,10 @@ function Userid() {
|
||||||
* @type {String}
|
* @type {String}
|
||||||
*/
|
*/
|
||||||
this.userid = '';
|
this.userid = '';
|
||||||
|
|
||||||
|
this.name = '';
|
||||||
|
this.email = '';
|
||||||
|
this.comment = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +52,17 @@ function Userid() {
|
||||||
* @param {Uint8Array} input payload of a tag 13 packet
|
* @param {Uint8Array} input payload of a tag 13 packet
|
||||||
*/
|
*/
|
||||||
Userid.prototype.read = function (bytes) {
|
Userid.prototype.read = function (bytes) {
|
||||||
this.userid = util.decode_utf8(util.Uint8Array_to_str(bytes));
|
this.parse(util.decode_utf8(util.Uint8Array_to_str(bytes)));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse userid string, e.g. 'John Doe <john@example.com>'
|
||||||
|
*/
|
||||||
|
Userid.prototype.parse = function (userid) {
|
||||||
|
try {
|
||||||
|
Object.assign(this, util.parseUserId(userid));
|
||||||
|
} catch(e) {}
|
||||||
|
this.userid = userid;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,4 +73,15 @@ Userid.prototype.write = function () {
|
||||||
return util.str_to_Uint8Array(util.encode_utf8(this.userid));
|
return util.str_to_Uint8Array(util.encode_utf8(this.userid));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set userid string from object, e.g. { name:'Phil Zimmermann', email:'phil@openpgp.org' }
|
||||||
|
*/
|
||||||
|
Userid.prototype.format = function (userid) {
|
||||||
|
if (util.isString(userid)) {
|
||||||
|
userid = util.parseUserId(userid);
|
||||||
|
}
|
||||||
|
Object.assign(this, userid);
|
||||||
|
this.userid = util.formatUserId(userid);
|
||||||
|
};
|
||||||
|
|
||||||
export default Userid;
|
export default Userid;
|
||||||
|
|
25
src/util.js
25
src/util.js
|
@ -17,11 +17,13 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This object contains utility functions
|
* This object contains utility functions
|
||||||
|
* @requires address-rfc2822
|
||||||
* @requires config
|
* @requires config
|
||||||
* @requires encoding/base64
|
* @requires encoding/base64
|
||||||
* @module util
|
* @module util
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import rfc2822 from 'address-rfc2822';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
import util from './util'; // re-import module to access util functions
|
import util from './util'; // re-import module to access util functions
|
||||||
import b64 from './encoding/base64';
|
import b64 from './encoding/base64';
|
||||||
|
@ -569,6 +571,29 @@ export default {
|
||||||
return re.test(data);
|
return re.test(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format user id for internal use.
|
||||||
|
*/
|
||||||
|
formatUserId: function(id) {
|
||||||
|
// name and email address can be empty but must be of the correct type
|
||||||
|
if ((id.name && !util.isString(id.name)) || (id.email && !util.isEmailAddress(id.email))) {
|
||||||
|
throw new Error('Invalid user id format');
|
||||||
|
}
|
||||||
|
return new rfc2822.Address(id.name, id.email, id.comment).format();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse user id.
|
||||||
|
*/
|
||||||
|
parseUserId: function(userid) {
|
||||||
|
try {
|
||||||
|
const [{ phrase: name, address: email, comment }] = rfc2822.parse(userid);
|
||||||
|
return { name, email, comment: comment.replace(/^\(|\)$/g, '') };
|
||||||
|
} catch(e) {
|
||||||
|
throw new Error('Invalid user id format');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
isUserId: function(data) {
|
isUserId: function(data) {
|
||||||
if (!util.isString(data)) {
|
if (!util.isString(data)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -156,8 +156,14 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
return data[name].priv_key;
|
return data[name].priv_key;
|
||||||
}
|
}
|
||||||
it('Load public key', function (done) {
|
it('Load public key', function (done) {
|
||||||
load_pub_key('romeo');
|
const romeoPublic = load_pub_key('romeo');
|
||||||
load_pub_key('juliet');
|
expect(romeoPublic.users[0].userId.name).to.equal('Romeo Montague');
|
||||||
|
expect(romeoPublic.users[0].userId.email).to.equal('romeo@example.net');
|
||||||
|
expect(romeoPublic.users[0].userId.comment).to.equal('secp256k1');
|
||||||
|
const julietPublic = load_pub_key('juliet');
|
||||||
|
expect(julietPublic.users[0].userId.name).to.equal('Juliet Capulet');
|
||||||
|
expect(julietPublic.users[0].userId.email).to.equal('juliet@example.net');
|
||||||
|
expect(julietPublic.users[0].userId.comment).to.equal('secp256k1');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
it('Load private key', async function () {
|
it('Load private key', async function () {
|
||||||
|
|
|
@ -1297,6 +1297,9 @@ p92yZgB3r2+f6/GIe2+7
|
||||||
const primUser = await key.getPrimaryUser();
|
const primUser = await key.getPrimaryUser();
|
||||||
expect(primUser).to.exist;
|
expect(primUser).to.exist;
|
||||||
expect(primUser.user.userId.userid).to.equal('Signature Test <signature@test.com>');
|
expect(primUser.user.userId.userid).to.equal('Signature Test <signature@test.com>');
|
||||||
|
expect(primUser.user.userId.name).to.equal('Signature Test');
|
||||||
|
expect(primUser.user.userId.email).to.equal('signature@test.com');
|
||||||
|
expect(primUser.user.userId.comment).to.equal('');
|
||||||
expect(primUser.selfCertification).to.be.an.instanceof(openpgp.packet.Signature);
|
expect(primUser.selfCertification).to.be.an.instanceof(openpgp.packet.Signature);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1315,13 +1318,16 @@ p92yZgB3r2+f6/GIe2+7
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Generate key - single userid', function() {
|
it('Generate key - single userid', function() {
|
||||||
const userId = 'test <a@b.com>';
|
const userId = { name: 'test', email: 'a@b.com', comment: 'test comment' };
|
||||||
const opt = {numBits: 512, userIds: userId, passphrase: '123'};
|
const opt = {numBits: 512, userIds: userId, passphrase: '123'};
|
||||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||||
return openpgp.generateKey(opt).then(function(key) {
|
return openpgp.generateKey(opt).then(function(key) {
|
||||||
key = key.key;
|
key = key.key;
|
||||||
expect(key.users.length).to.equal(1);
|
expect(key.users.length).to.equal(1);
|
||||||
expect(key.users[0].userId.userid).to.equal(userId);
|
expect(key.users[0].userId.userid).to.equal('test <a@b.com> (test comment)');
|
||||||
|
expect(key.users[0].userId.name).to.equal(userId.name);
|
||||||
|
expect(key.users[0].userId.email).to.equal(userId.email);
|
||||||
|
expect(key.users[0].userId.comment).to.equal(userId.comment);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -371,73 +371,57 @@ describe('OpenPGP.js public api tests', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generateKey - unit tests', function() {
|
describe('generateKey - validate user ids', function() {
|
||||||
let keyGenStub;
|
let rsaGenStub;
|
||||||
let keyObjStub;
|
let rsaGenValue = openpgp.crypto.publicKey.rsa.generate(2048, "10001");
|
||||||
let getWebCryptoAllStub;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
keyObjStub = {
|
rsaGenStub = stub(openpgp.crypto.publicKey.rsa, 'generate');
|
||||||
armor: function() {
|
rsaGenStub.returns(rsaGenValue);
|
||||||
return 'priv_key';
|
|
||||||
},
|
|
||||||
toPublic: function() {
|
|
||||||
return {
|
|
||||||
armor: function() {
|
|
||||||
return 'pub_key';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
keyGenStub = stub(openpgp.key, 'generate');
|
|
||||||
keyGenStub.returns(resolves(keyObjStub));
|
|
||||||
getWebCryptoAllStub = stub(openpgp.util, 'getWebCryptoAll');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
keyGenStub.restore();
|
rsaGenStub.restore();
|
||||||
openpgp.destroyWorker();
|
|
||||||
getWebCryptoAllStub.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail for invalid user name', function() {
|
it('should fail for invalid user name', async function() {
|
||||||
const opt = {
|
const opt = {
|
||||||
userIds: [{ name: {}, email: 'text@example.com' }]
|
userIds: [{ name: {}, email: 'text@example.com' }]
|
||||||
};
|
};
|
||||||
const test = openpgp.generateKey.bind(null, opt);
|
const test = openpgp.generateKey(opt);
|
||||||
expect(test).to.throw(/Invalid user id format/);
|
await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail for invalid user email address', function() {
|
it('should fail for invalid user email address', async function() {
|
||||||
const opt = {
|
const opt = {
|
||||||
userIds: [{ name: 'Test User', email: 'textexample.com' }]
|
userIds: [{ name: 'Test User', email: 'textexample.com' }]
|
||||||
};
|
};
|
||||||
const test = openpgp.generateKey.bind(null, opt);
|
const test = openpgp.generateKey(opt);
|
||||||
expect(test).to.throw(/Invalid user id format/);
|
await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail for invalid user email address', function() {
|
it('should fail for invalid user email address', async function() {
|
||||||
const opt = {
|
const opt = {
|
||||||
userIds: [{ name: 'Test User', email: 'text@examplecom' }]
|
userIds: [{ name: 'Test User', email: 'text@examplecom' }]
|
||||||
};
|
};
|
||||||
const test = openpgp.generateKey.bind(null, opt);
|
const test = openpgp.generateKey(opt);
|
||||||
expect(test).to.throw(/Invalid user id format/);
|
await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail for invalid string user id', function() {
|
it('should fail for invalid string user id', async function() {
|
||||||
const opt = {
|
const opt = {
|
||||||
userIds: ['Test User text@example.com>']
|
userIds: ['Test User text@example.com>']
|
||||||
};
|
};
|
||||||
const test = openpgp.generateKey.bind(null, opt);
|
const test = openpgp.generateKey(opt);
|
||||||
expect(test).to.throw(/Invalid user id format/);
|
await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail for invalid single string user id', function() {
|
it('should fail for invalid single string user id', async function() {
|
||||||
const opt = {
|
const opt = {
|
||||||
userIds: 'Test User text@example.com>'
|
userIds: 'Test User text@example.com>'
|
||||||
};
|
};
|
||||||
const test = openpgp.generateKey.bind(null, opt);
|
const test = openpgp.generateKey(opt);
|
||||||
expect(test).to.throw(/Invalid user id format/);
|
await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work for valid single string user id', function() {
|
it('should work for valid single string user id', function() {
|
||||||
|
@ -481,6 +465,36 @@ describe('OpenPGP.js public api tests', function() {
|
||||||
};
|
};
|
||||||
return openpgp.generateKey(opt);
|
return openpgp.generateKey(opt);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateKey - unit tests', function() {
|
||||||
|
let keyGenStub;
|
||||||
|
let keyObjStub;
|
||||||
|
let getWebCryptoAllStub;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
keyObjStub = {
|
||||||
|
armor: function() {
|
||||||
|
return 'priv_key';
|
||||||
|
},
|
||||||
|
toPublic: function() {
|
||||||
|
return {
|
||||||
|
armor: function() {
|
||||||
|
return 'pub_key';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
keyGenStub = stub(openpgp.key, 'generate');
|
||||||
|
keyGenStub.returns(resolves(keyObjStub));
|
||||||
|
getWebCryptoAllStub = stub(openpgp.util, 'getWebCryptoAll');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
keyGenStub.restore();
|
||||||
|
openpgp.destroyWorker();
|
||||||
|
getWebCryptoAllStub.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('should have default params set', function() {
|
it('should have default params set', function() {
|
||||||
const now = openpgp.util.normalizeDate(new Date());
|
const now = openpgp.util.normalizeDate(new Date());
|
||||||
|
@ -492,7 +506,7 @@ describe('OpenPGP.js public api tests', function() {
|
||||||
};
|
};
|
||||||
return openpgp.generateKey(opt).then(function(newKey) {
|
return openpgp.generateKey(opt).then(function(newKey) {
|
||||||
expect(keyGenStub.withArgs({
|
expect(keyGenStub.withArgs({
|
||||||
userIds: ['Test User <text@example.com>'],
|
userIds: [{ name: 'Test User', email: 'text@example.com' }],
|
||||||
passphrase: 'secret',
|
passphrase: 'secret',
|
||||||
numBits: 2048,
|
numBits: 2048,
|
||||||
keyExpirationTime: 0,
|
keyExpirationTime: 0,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user