Merge branch 'esisar-restrictions'

esisar-restrictions
Simon Vareille 2020-02-10 17:27:15 +01:00
commit f535a197d3
No known key found for this signature in database
GPG Key ID: 008AE8E706CC19F9
4 changed files with 95 additions and 11 deletions

View File

@ -74,7 +74,7 @@ class REST {
if (!util.isKeyId(q.keyId) || !util.isString(q.nonce)) {
ctx.throw(400, 'Invalid request!');
}
const {email} = await this._publicKey.verify(q);
const {email} = await this._publicKey.verify(q, util.origin(ctx), ctx);
// create link for sharing
const link = util.url(util.origin(ctx), `/pks/lookup?op=get&search=${email}`);
await ctx.render('verify-success', {email, link});

View File

@ -69,7 +69,12 @@ class PGP {
// check for at least one valid user id
const userIds = await this.parseUserIds(key.users, primaryKey, verifyDate);
if (!userIds.length) {
util.throw(400, 'Invalid PGP key: invalid user IDs');
if (status == 1) {
util.throw(400, 'Invalid PGP key: no user ID comes from a valid organisation');
}
else {
util.throw(400, 'Invalid PGP key: invalid user IDs');
}
}
// get algorithm details from primary key
@ -118,8 +123,9 @@ class PGP {
* Parse an array of user ids and verify signatures
* @param {Array} users A list of openpgp.js user objects
* @param {Object} primaryKey The primary key packet of the key
* @param {Date} verifyDate Verify user IDs at this point in time
* @return {Array} An array of user id objects
* @param {Date} verifyDate Verify user IDs at this point in time
* @return {Array, integer} An array of user id objects and a satus indicator.
* Values of status : 0 if no error, 1 if no address comes from a specific organisation.
*/
async parseUserIds(users, primaryKey, verifyDate = new Date()) {
if (!users || !users.length) {
@ -127,6 +133,7 @@ class PGP {
}
// at least one user id must be valid, revoked or expired
const result = [];
var isFromOrganisation = false;
for (const user of users) {
const userStatus = await user.verify(primaryKey, verifyDate);
if (userStatus !== openpgp.enums.keyStatus.invalid && user.userId && user.userId.userid) {
@ -140,11 +147,18 @@ class PGP {
email: util.normalizeEmail(uid.email),
verified: false
});
if(util.isFromOrganisation(util.normalizeEmail(uid.email)))
isFromOrganisation = true;
}
} catch (e) {}
}
}
return result;
var status = 0;
if(!isFromOrganisation){
result.length = 0;
status = 1;
}
return {userIds: result, status: status};
}
/**

View File

@ -91,6 +91,10 @@ class PublicKey {
const filteredPublicKeyArmored = await this._pgp.filterKeyByUserIds(key.userIds.filter(({verified}) => verified), key.publicKeyArmored);
// update verified key with new key
key.publicKeyArmored = await this._pgp.updateKey(verified.publicKeyArmored, filteredPublicKeyArmored);
// send mails to verify all user ids
await this._sendVerifyEmail(key, origin, ctx);
// store key in database
await this._persistKey(key);
} else {
key.userIds = key.userIds.filter(userId => userId.status === KEY_STATUS_VALID);
if (!key.userIds.length) {
@ -99,11 +103,11 @@ class PublicKey {
await this._addKeyArmored(key.userIds, key.publicKeyArmored);
// new key, set armored to null
key.publicKeyArmored = null;
// send mails to verify organisation's user ids
await this._sendVerifyOrganisationEmail(key, origin, ctx);
// store key in database
await this._persistKeyOrganisation(key);
}
// send mails to verify user ids
await this._sendVerifyEmail(key, origin, ctx);
// store key in database
await this._persistKey(key);
}
/**
@ -175,6 +179,24 @@ class PublicKey {
}
}
}
/**
* Send verification emails to the organisation's public keys user ids for verification.
* If a primary email address is provided only one email will be sent.
* @param {Array} userIds user id documents containg the verification nonces
* @param {Object} origin the server's origin (required for email links)
* @param {Object} ctx Context
* @return {Promise}
*/
async _sendVerifyOrganisationEmail({userIds, keyId}, origin, ctx) {
for (const userId of userIds) {
if (userId.notify && userId.notify === true && util.isFromOrganisation(userId.email)) {
// generate nonce for verification
userId.nonce = util.random();
await this._email.send({template: tpl.verifyKey.bind(null, ctx), userId, keyId, origin, publicKeyArmored: userId.publicKeyArmored});
}
}
}
/**
* Persist the public key and its user ids in the database.
@ -184,7 +206,7 @@ class PublicKey {
async _persistKey(key) {
// delete old/unverified key
await this._mongo.remove({keyId: key.keyId}, DB_TYPE);
// generate nonces for verification
for (const userId of key.userIds) {
// remove status from user
delete userId.status;
@ -197,26 +219,61 @@ class PublicKey {
util.throw(500, 'Failed to persist key');
}
}
/**
* Persist the public key and its user ids in the database.
* Mark all uids as unprocessed, except the ones with the organisation email.
* @param {Object} key public key parameters
* @return {Promise}
*/
async _persistKeyOrganisation(key) {
// delete old/unverified key
await this._mongo.remove({keyId: key.keyId}, DB_TYPE);
for (const userId of key.userIds) {
if(util.isFromOrganisation(userId.email))
{
// remove status from user
delete userId.status;
// remove notify flag from user
delete userId.notify;
}
}
// persist new key
const r = await this._mongo.create(key, DB_TYPE);
if (r.insertedCount !== 1) {
util.throw(500, 'Failed to persist key');
}
}
/**
* Verify a user id by proving knowledge of the nonce.
* @param {string} keyId Correspronding public key id
* @param {string} nonce The verification nonce proving email address ownership
* @param {Object} origin the server's origin (required for email links)
* @param {Object} ctx Context
* @return {Promise} The email that has been verified
*/
async verify({keyId, nonce}) {
async verify({keyId, nonce}, origin, ctx) {
// look for verification nonce in database
const query = {keyId, 'userIds.nonce': nonce};
const key = await this._mongo.get(query, DB_TYPE);
if (!key) {
util.throw(404, 'User ID not found');
}
// send mails to verify all unnotified user ids
await this._sendVerifyEmail(key, origin, ctx);
// store key in database
await this._persistKey(key);
await this._removeKeysWithSameEmail(key, nonce);
let {publicKeyArmored, email} = key.userIds.find(userId => userId.nonce === nonce);
// update armored key
if (key.publicKeyArmored) {
publicKeyArmored = await this._pgp.updateKey(key.publicKeyArmored, publicKeyArmored);
}
// flag the user id as verified
await this._mongo.update(query, {
publicKeyArmored,

View File

@ -78,6 +78,19 @@ exports.isEmail = function(data) {
return re.test(data);
};
/**
* Checks for a valid specific organisation email address.
* @param {string} data The email address
* @return {boolean} Wether the email address comes from organisation
*/
exports.isFromOrganisation = function(data) {
if (!this.isString(data)) {
return false;
}
const re = /^([a-z0-9\-.]+)@([a-z0-9.\-]*)esisar\.grenoble-inp\.fr$/;
return re.test(data);
};
/**
* Normalize email address to lowercase.
* @param {string} email The email address