Effective signatures additions restriction.
Link in email not implemented.esisar-restrictions
parent
cc6025baa1
commit
00e756795d
|
@ -173,12 +173,59 @@ class PGP {
|
||||||
key.users = key.users.filter(({userId}) => !userId || emails.includes(util.normalizeEmail(userId.email)));
|
key.users = key.users.filter(({userId}) => !userId || emails.includes(util.normalizeEmail(userId.email)));
|
||||||
return key.armor();
|
return key.armor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove signatures from source armored key which are not in compared armored key
|
||||||
|
* @param {String} srcArmored armored key block to be filtered
|
||||||
|
* @param {String} cmpArmored armored key block to be compare with
|
||||||
|
* @return {String, newSigs} filterd armored key block, list of new signatures
|
||||||
|
*/
|
||||||
|
async filterKeyBySignatures(srcArmored, cmpArmored) {
|
||||||
|
const {keys: [srcKey], err: srcErr} = await openpgp.key.readArmored(srcArmored);
|
||||||
|
if (srcErr) {
|
||||||
|
log.error('pgp', 'Failed to parse source PGP key:\n%s', srcArmored, srcErr);
|
||||||
|
util.throw(500, 'Failed to parse PGP key');
|
||||||
|
}
|
||||||
|
const {keys: [cmpKey], err: cmpErr} = await openpgp.key.readArmored(cmpArmored);
|
||||||
|
if (cmpErr) {
|
||||||
|
log.error('pgp', 'Failed to parse destination PGP key:\n%s', cmpArmored, cmpErr);
|
||||||
|
util.throw(500, 'Failed to parse PGP key');
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSigs=[];
|
||||||
|
if(cmpKey.hasSameFingerprintAs(srcKey)) {
|
||||||
|
await Promise.all(srcKey.users.map(async srcUser => {
|
||||||
|
await Promise.all(cmpKey.users.map(async dstUser => {
|
||||||
|
if ((srcUser.userId && dstUser.userId &&
|
||||||
|
(srcUser.userId.userid === dstUser.userId.userid)) ||
|
||||||
|
(srcUser.userAttribute && (srcUser.userAttribute.equals(dstUser.userAttribute)))) {
|
||||||
|
const source = srcUser.otherCertifications;
|
||||||
|
const dest = dstUser.otherCertifications;
|
||||||
|
for(let i = source.length-1; i >= 0; i--) {
|
||||||
|
const sourceSig = source[i];
|
||||||
|
if (!sourceSig.isExpired() && !dest.some(function(destSig) {
|
||||||
|
return util.equalsUint8Array(destSig.signature, sourceSig.signature);
|
||||||
|
})) {
|
||||||
|
// list new signatures
|
||||||
|
let userId = (srcUser.userId) ? srcUser.userId.userid : null;
|
||||||
|
let userAttribute = (srcUser.userAttribute) ? srcUser.userAttribute : null;
|
||||||
|
newSigs.push({user: {userId: userId, userAttribute: userAttribute}, signature: sourceSig});
|
||||||
|
// do not add new signatures
|
||||||
|
source.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return {armored: srcKey.armor(), newSigs: newSigs};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge (update) armored key blocks without adding new signatures
|
* Merge (update) armored key blocks without adding new signatures
|
||||||
* @param {String} srcArmored source amored key block
|
* @param {String} srcArmored source amored key block
|
||||||
* @param {String} dstArmored destination armored key block
|
* @param {String} dstArmored destination armored key block
|
||||||
* @return {String, newSigs} merged armored key block, list of new signatures
|
* @return {String} merged amored key block
|
||||||
*/
|
*/
|
||||||
async updateKey(srcArmored, dstArmored) {
|
async updateKey(srcArmored, dstArmored) {
|
||||||
const {keys: [srcKey], err: srcErr} = await openpgp.key.readArmored(srcArmored);
|
const {keys: [srcKey], err: srcErr} = await openpgp.key.readArmored(srcArmored);
|
||||||
|
@ -190,37 +237,16 @@ class PGP {
|
||||||
if (dstErr) {
|
if (dstErr) {
|
||||||
log.error('pgp', 'Failed to parse destination PGP key for update:\n%s', dstArmored, dstErr);
|
log.error('pgp', 'Failed to parse destination PGP key for update:\n%s', dstArmored, dstErr);
|
||||||
util.throw(500, 'Failed to parse PGP key');
|
util.throw(500, 'Failed to parse PGP key');
|
||||||
}
|
}
|
||||||
|
|
||||||
// list new signatures
|
|
||||||
const newSigs=[];
|
|
||||||
if(dstKey.hasSameFingerprintAs(srcKey)) {
|
|
||||||
const source = srcKey.directSignatures;
|
|
||||||
const dest = dstKey.directSignatures;
|
|
||||||
if(source) {
|
|
||||||
for(const sourceSig of source) {
|
|
||||||
if (!sourceSig.isExpired() && !dest.some(function(destSig) {
|
|
||||||
return util.equalsUint8Array(destSig.signature, sourceSig.signature);
|
|
||||||
})) {
|
|
||||||
newSigs.push(sourceSig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// do not add new signatures
|
|
||||||
source = source.filter(sourceSig => !newSigs.some(function(sig) {
|
|
||||||
return util.equalsUint8Array(sig.signature, sourceSig.signature);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
await dstKey.update(srcKey);
|
await dstKey.update(srcKey);
|
||||||
return {armored: dstKey.armor(), newSigs: newSigs};
|
return dstKey.armor();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns primary user and most significant (latest valid) self signature
|
* Returns primary user and most significant (latest valid) self signature
|
||||||
* - if multiple primary users exist, returns the one with the latest self signature
|
* - if multiple primary users exist, returns the one with the latest self signature
|
||||||
* - otherwise, returns the user with the latest self signature
|
* - otherwise, returns the user with the latest self signature
|
||||||
* @return {Object} The primary userId object: { name, email}
|
* @return {Object} The primary userId
|
||||||
*/
|
*/
|
||||||
async getPrimaryUser(publicKeyArmored) {
|
async getPrimaryUser(publicKeyArmored) {
|
||||||
const {keys: [key], err: srcErr} = await openpgp.key.readArmored(publicKeyArmored);
|
const {keys: [key], err: srcErr} = await openpgp.key.readArmored(publicKeyArmored);
|
||||||
|
@ -228,8 +254,8 @@ class PGP {
|
||||||
log.error('pgp', 'Failed to parse PGP key for getPrimaryUser:\n%s', publicKeyArmored, srcErr);
|
log.error('pgp', 'Failed to parse PGP key for getPrimaryUser:\n%s', publicKeyArmored, srcErr);
|
||||||
util.throw(500, 'Failed to parse PGP key');
|
util.throw(500, 'Failed to parse PGP key');
|
||||||
}
|
}
|
||||||
const primaryUser = key.getPrimaryUser();
|
const primaryUser = await key.getPrimaryUser();
|
||||||
return {name: primaryUser.name, email: primaryUser.email};
|
return primaryUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -88,18 +88,25 @@ class PublicKey {
|
||||||
if (verified) {
|
if (verified) {
|
||||||
key.userIds = await this._mergeUsers(verified.userIds, key.userIds, key.publicKeyArmored);
|
key.userIds = await this._mergeUsers(verified.userIds, key.userIds, key.publicKeyArmored);
|
||||||
// reduce new key to verified user IDs
|
// reduce new key to verified user IDs
|
||||||
const filteredPublicKeyArmored = await this._pgp.filterKeyByUserIds(key.userIds.filter(({verified}) => verified), key.publicKeyArmored);
|
let filteredPublicKeyArmored = await this._pgp.filterKeyByUserIds(key.userIds.filter(({verified}) => verified), key.publicKeyArmored);
|
||||||
// update verified key with new key and get new signatures
|
// reduce new key to verified signatures and get new signatures
|
||||||
const {armored, newSigs} = await this._pgp.updateKey(verified.publicKeyArmored, filteredPublicKeyArmored);
|
const {armored, newSigs} = await this._pgp.filterKeyBySignatures(filteredPublicKeyArmored, verified.publicKeyArmored);
|
||||||
key.publicKeyArmored = armored;
|
filteredPublicKeyArmored = armored;
|
||||||
|
// update verified key with new key
|
||||||
|
key.publicKeyArmored = await this._pgp.updateKey(verified.publicKeyArmored, filteredPublicKeyArmored);
|
||||||
// store pending signatures in key and generate nounce for confirmation
|
// store pending signatures in key and generate nounce for confirmation
|
||||||
if(!key.pendingSignatures)
|
if(newSigs.length) {
|
||||||
key.pendingSignatures = {sigs: newSigs, nonce: util.random()};
|
await this._formatArrays(newSigs);
|
||||||
else {
|
if(!verified.pendingSignatures)
|
||||||
key.pendingSignatures = key.pendingSignatures.concat(newSigs.filter(sourceSig => !key.pendingSignatures.some(function(sig) {
|
key.pendingSignatures = {sigs: newSigs, nonce: util.random()};
|
||||||
return util.equalsUint8Array(sig.signature, sourceSig.signature);
|
else {
|
||||||
})));
|
key.pendingSignatures = verified.pendingSignatures;
|
||||||
|
key.pendingSignatures.sigs = verified.pendingSignatures.sigs.concat(newSigs.filter(sourceSig => !verified.pendingSignatures.sigs.some(function(pendingSig) {
|
||||||
|
return pendingSig.signature.signature === sourceSig.signature.signature;
|
||||||
|
})));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send mails to verify all user ids
|
// send mails to verify all user ids
|
||||||
await this._sendVerifyEmail(key, origin, ctx);
|
await this._sendVerifyEmail(key, origin, ctx);
|
||||||
// send mail to confirm all new signatures
|
// send mail to confirm all new signatures
|
||||||
|
@ -172,6 +179,21 @@ class PublicKey {
|
||||||
_includeEmail(users, user) {
|
_includeEmail(users, user) {
|
||||||
return users.find(({email}) => email === user.email);
|
return users.find(({email}) => email === user.email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert all Uint8Array in every signatures to base64.
|
||||||
|
* @param {Array} signatures list of signatures to convert
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
async _formatArrays(signatures) {
|
||||||
|
signatures.map(function(sig) {
|
||||||
|
const signature = sig.signature;
|
||||||
|
signature.signatureData = util.base64EncArr(signature.signatureData);
|
||||||
|
signature.signedHashValue = util.base64EncArr(signature.signedHashValue);
|
||||||
|
signature.issuerFingerprint = util.base64EncArr(signature.issuerFingerprint);
|
||||||
|
signature.signature = util.base64EncArr(signature.signature);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send verification emails to the public keys user ids for verification.
|
* Send verification emails to the public keys user ids for verification.
|
||||||
|
@ -218,9 +240,10 @@ class PublicKey {
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
async _sendNewSigsEmail(key, origin, ctx) {
|
async _sendNewSigsEmail(key, origin, ctx) {
|
||||||
if(key.pendingSignatures.sigs.length){
|
if(key.pendingSignatures && key.pendingSignatures.sigs.length){
|
||||||
const primaryUser = this._pgp.getPrimaryUser(key.publicKeyArmored);
|
let primaryUser = await this._pgp.getPrimaryUser(key.publicKeyArmored);
|
||||||
await this._email.send({template: tpl.confirmNewSigs.bind(null, ctx), primaryUser, keyId: key.keyId, data: {name: primaryUser.name, sigsNb: key.pendingSignatures.sigs.length, nonce: key.pendingSignatures.nonce}, origin, publicKeyArmored: key.publicKeyArmored});
|
const userId = primaryUser.user.userId;
|
||||||
|
await this._email.send({template: tpl.confirmNewSigs.bind(null, ctx), userId, keyId: key.keyId, data: {name: userId.name, sigsNb: key.pendingSignatures.sigs.length, nonce: key.pendingSignatures.nonce}, origin, publicKeyArmored: key.publicKeyArmored});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +320,7 @@ class PublicKey {
|
||||||
let {publicKeyArmored, email} = key.userIds.find(userId => userId.nonce === nonce);
|
let {publicKeyArmored, email} = key.userIds.find(userId => userId.nonce === nonce);
|
||||||
// update armored key
|
// update armored key
|
||||||
if (key.publicKeyArmored) {
|
if (key.publicKeyArmored) {
|
||||||
publicKeyArmored = await this._pgp.updateKey(key.publicKeyArmored, publicKeyArmored).armored;
|
publicKeyArmored = await this._pgp.updateKey(key.publicKeyArmored, publicKeyArmored);
|
||||||
}
|
}
|
||||||
|
|
||||||
// flag the user id as verified
|
// flag the user id as verified
|
||||||
|
@ -386,6 +409,8 @@ class PublicKey {
|
||||||
email: uid.email,
|
email: uid.email,
|
||||||
verified: uid.verified
|
verified: uid.verified
|
||||||
}));
|
}));
|
||||||
|
if(key.pendingSignatures)
|
||||||
|
delete key.pendingSignatures.nonce
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,99 @@ exports.equalsUint8Array = function (array1, array2) {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a base64 byte to uint6
|
||||||
|
* @param {Integer} nChr base64 byte to decode
|
||||||
|
* @returns {Integer} the uint6 integer
|
||||||
|
* Comes from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||||
|
* All thanks to madmurphy
|
||||||
|
*/
|
||||||
|
function b64ToUint6 (nChr) {
|
||||||
|
return nChr > 64 && nChr < 91 ?
|
||||||
|
nChr - 65
|
||||||
|
: nChr > 96 && nChr < 123 ?
|
||||||
|
nChr - 71
|
||||||
|
: nChr > 47 && nChr < 58 ?
|
||||||
|
nChr + 4
|
||||||
|
: nChr === 43 ?
|
||||||
|
62
|
||||||
|
: nChr === 47 ?
|
||||||
|
63
|
||||||
|
:
|
||||||
|
0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a base64 String to Uint8Array
|
||||||
|
* @param {String} sBase64 base64 string to decode
|
||||||
|
* @returns {Uint8Array} decoded data
|
||||||
|
* Comes from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||||
|
* All thanks to madmurphy
|
||||||
|
*/
|
||||||
|
exports.base64DecToArr = function (sBase64) {
|
||||||
|
var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
|
||||||
|
nOutLen = nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen);
|
||||||
|
|
||||||
|
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
||||||
|
nMod4 = nInIdx & 3;
|
||||||
|
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
|
||||||
|
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||||
|
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
|
||||||
|
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
|
||||||
|
}
|
||||||
|
nUint24 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return taBytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a uint6 to base64 byte
|
||||||
|
* @param {Integer} nUint6 integer to encode
|
||||||
|
* @returns {Integer} the base64 byte
|
||||||
|
* Comes from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||||
|
* All thanks to madmurphy
|
||||||
|
*/
|
||||||
|
function uint6ToB64 (nUint6) {
|
||||||
|
return nUint6 < 26 ?
|
||||||
|
nUint6 + 65
|
||||||
|
: nUint6 < 52 ?
|
||||||
|
nUint6 + 71
|
||||||
|
: nUint6 < 62 ?
|
||||||
|
nUint6 - 4
|
||||||
|
: nUint6 === 62 ?
|
||||||
|
43
|
||||||
|
: nUint6 === 63 ?
|
||||||
|
47
|
||||||
|
:
|
||||||
|
65;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a Uint8Array to base64
|
||||||
|
* @param {Uint8Array} aBytes array to encode
|
||||||
|
* @returns {String} base64 String
|
||||||
|
* Comes from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||||
|
* All thanks to madmurphy
|
||||||
|
*/
|
||||||
|
exports.base64EncArr = function (aBytes) {
|
||||||
|
var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = "";
|
||||||
|
for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
|
||||||
|
nMod3 = nIdx % 3;
|
||||||
|
/* Uncomment the following line in order to split the output in lines 76-character long: */
|
||||||
|
/*
|
||||||
|
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; }
|
||||||
|
*/
|
||||||
|
nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
|
||||||
|
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
|
||||||
|
sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63));
|
||||||
|
nUint24 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eqLen === 0 ? sB64Enc : sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "==");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an error with a custom status attribute e.g. for http codes.
|
* Create an error with a custom status attribute e.g. for http codes.
|
||||||
|
|
Loading…
Reference in New Issue