Upgraded OpenPGP to v5

This commit is contained in:
Andris Reinman 2021-09-03 21:32:39 +03:00
parent 9422d02373
commit eb3e26e153
6 changed files with 138 additions and 134 deletions

6
api.js
View file

@ -141,7 +141,7 @@ if (config.api.secure && certOptions.key) {
let httpsServerOptions = {};
httpsServerOptions.key = certOptions.key;
if (httpsServerOptions.ca) {
if (certOptions.ca) {
httpsServerOptions.ca = certOptions.ca;
}
httpsServerOptions.cert = certOptions.cert;
@ -151,7 +151,9 @@ if (config.api.secure && certOptions.key) {
httpsServerOptions.SNICallback = (servername, cb) => {
certs
.getContextForServername(servername, httpsServerOptions)
.then(context => cb(null, context || defaultSecureContext))
.then(context => {
cb(null, context || defaultSecureContext);
})
.catch(err => cb(err));
};

View file

@ -305,7 +305,6 @@ const acquireCert = async (domain, acmeOptions, certificateData, certHandler) =>
try {
await releaseLock(lock);
} catch (err) {
console.error(lock);
log.error('Lock', 'Failed to release lock for %s: %s', domainOpLockKey, err);
}
}

View file

@ -8,8 +8,6 @@ const ObjectId = require('mongodb').ObjectId;
const tools = require('../tools');
const errors = require('../errors');
const openpgp = require('openpgp');
const addressparser = require('nodemailer/lib/addressparser');
const libmime = require('libmime');
const consts = require('../consts');
const roles = require('../roles');
const imapTools = require('../../imap-core/lib/imap-tools');
@ -484,7 +482,7 @@ module.exports = (db, server, userHandler) => {
}
try {
await checkPubKey(result.value.pubKey);
await getKeyInfo(result.value.pubKey);
} catch (err) {
res.status(400);
res.json({
@ -996,7 +994,7 @@ module.exports = (db, server, userHandler) => {
}
try {
await checkPubKey(result.value.pubKey);
await getKeyInfo(result.value.pubKey);
} catch (err) {
res.status(400);
res.json({
@ -1523,92 +1521,33 @@ module.exports = (db, server, userHandler) => {
);
};
async function getKeyInfo(pubKey) {
async function getKeyInfo(pubKeyArmored) {
if (!pubKeyArmored) {
return false;
}
let pubKey = await openpgp.readKey({ armoredKey: tools.prepareArmoredPubKey(pubKeyArmored), config: { tolerant: true } });
if (!pubKey) {
return false;
throw new Error('Failed to process public key');
}
// try to encrypt something with that key
let armored;
try {
armored = (await openpgp.key.readArmored(pubKey)).keys;
} catch (E) {
return false;
}
if (!armored || !armored[0]) {
return false;
}
let fingerprint = armored[0].primaryKey.fingerprint;
let name, address;
if (armored && armored[0] && armored[0].users && armored[0].users[0] && armored[0].users[0].userId) {
let user = addressparser(armored[0].users[0].userId.userid);
if (user && user[0] && user[0].address) {
address = tools.normalizeAddress(user[0].address);
try {
name = libmime.decodeWords(user[0].name || '').trim();
} catch (E) {
// failed to parse value
name = user[0].name || '';
}
}
}
if (fingerprint && typeof fingerprint === 'object') {
// convert to hex string
fingerprint = Array.from(fingerprint)
.map(byte => (byte < 0x10 ? '0' : '') + byte.toString(16))
.join('');
}
return {
name,
address,
fingerprint
};
}
async function checkPubKey(pubKey) {
if (!pubKey) {
return false;
}
// try to encrypt something with that key
let armored = (await openpgp.key.readArmored(pubKey)).keys;
if (!armored || !armored[0]) {
throw new Error('Did not find key information');
}
let fingerprint = armored[0].primaryKey.fingerprint;
let name, address;
if (armored && armored[0] && armored[0].users && armored[0].users[0] && armored[0].users[0].userId) {
let user = addressparser(armored[0].users[0].userId.userid);
if (user && user[0] && user[0].address) {
address = tools.normalizeAddress(user[0].address);
try {
name = libmime.decodeWords(user[0].name || '').trim();
} catch (E) {
// failed to parse value
name = user[0].name || '';
}
}
}
let fingerprint = pubKey.getFingerprint();
let { name, address } = tools.getPGPUserId(pubKey);
let ciphertext = await openpgp.encrypt({
message: openpgp.message.fromText('Hello, World!'),
publicKeys: armored
message: await openpgp.createMessage({ text: 'Hello, World!' }),
encryptionKeys: pubKey, // for encryption
format: 'armored'
});
if (/^-----BEGIN PGP MESSAGE/.test(ciphertext.data)) {
if (/^-----BEGIN PGP MESSAGE/.test(ciphertext)) {
// everything checks out
return {
address,
name,
address,
fingerprint
};
}
throw new Error('Unexpected message');
throw new Error('Failed to verify public key');
}

View file

@ -14,11 +14,13 @@ const tools = require('./tools');
const openpgp = require('openpgp');
const parseDate = require('../imap-core/lib/parse-date');
const log = require('npmlog');
const packageData = require('../package.json');
// index only the following headers for SEARCH
const INDEXED_HEADERS = ['to', 'cc', 'subject', 'from', 'sender', 'reply-to', 'message-id', 'thread-index', 'list-id', 'delivered-to'];
openpgp.config.commentstring = 'Plaintext message encrypted by WildDuck Mail Server';
openpgp.config.versionString = `WildDuck v${packageData.version}`;
class MessageHandler {
constructor(options) {
@ -1655,8 +1657,14 @@ class MessageHandler {
}
encryptMessage(pubKey, raw, callback) {
if (!pubKey) {
return callback(null, false);
this.encryptMessageAsync(pubKey, raw)
.then(res => callback(null, res))
.catch(err => callback(err));
}
async encryptMessageAsync(pubKeyArmored, raw) {
if (!pubKeyArmored) {
return false;
}
if (raw && Array.isArray(raw.chunks) && raw.chunklen) {
@ -1718,7 +1726,7 @@ class MessageHandler {
let value = parts.slice(1).join(':');
if (value.split(';').shift().trim().toLowerCase() === 'multipart/encrypted') {
// message is already encrypted, do nothing
return callback(null, false);
return false;
}
bodyHeaders.push(lastHeader);
} else if (/^content-transfer-encoding:/i.test(line)) {
@ -1739,55 +1747,50 @@ class MessageHandler {
headers = Buffer.from(headers.map(line => line.join('\r\n')).join('\r\n'), 'binary');
bodyHeaders = Buffer.from(bodyHeaders.map(line => line.join('\r\n')).join('\r\n'), 'binary');
openpgp.key
.readArmored(pubKey)
.then(armored => {
let publicKeys = armored.keys;
let pubKey;
try {
pubKey = await openpgp.readKey({ armoredKey: tools.prepareArmoredPubKey(pubKeyArmored), config: { tolerant: true } });
} catch (err) {
return false;
}
if (!pubKey) {
return false;
}
openpgp
.encrypt({
message: openpgp.message.fromBinary(Buffer.concat([Buffer.from(bodyHeaders + '\r\n\r\n'), body])),
publicKeys
})
.then(ciphertext => {
let text =
'This is an OpenPGP/MIME encrypted message\r\n\r\n' +
'--' +
boundary +
'\r\n' +
'Content-Type: application/pgp-encrypted\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'\r\n' +
'Version: 1\r\n' +
'\r\n' +
'--' +
boundary +
'\r\n' +
'Content-Type: application/octet-stream; name=encrypted.asc\r\n' +
'Content-Disposition: inline; filename=encrypted.asc\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'\r\n' +
ciphertext.data +
'\r\n--' +
boundary +
'--\r\n';
callback(null, Buffer.concat([headers, breaker, Buffer.from(text)]));
})
.catch(err => {
if (err) {
// ignore
}
// encryption failed, keep message as is
callback(null, false);
});
})
.catch(err => {
if (err) {
// ignore
}
callback(null, false);
let ciphertext;
try {
ciphertext = await openpgp.encrypt({
message: await openpgp.createMessage({ binary: Buffer.concat([Buffer.from(bodyHeaders + '\r\n\r\n'), body]) }),
encryptionKeys: pubKey,
format: 'armored'
});
} catch (err) {
return false;
}
let text =
'This is an OpenPGP/MIME encrypted message\r\n\r\n' +
'--' +
boundary +
'\r\n' +
'Content-Type: application/pgp-encrypted\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'\r\n' +
'Version: 1\r\n' +
'\r\n' +
'--' +
boundary +
'\r\n' +
'Content-Type: application/octet-stream; name=encrypted.asc\r\n' +
'Content-Disposition: inline; filename=encrypted.asc\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'\r\n' +
ciphertext +
'\r\n--' +
boundary +
'--\r\n';
return Buffer.concat([headers, breaker, Buffer.from(text)]);
}
}

View file

@ -14,6 +14,7 @@ const net = require('net');
const ipaddr = require('ipaddr.js');
const ObjectId = require('mongodb').ObjectId;
const log = require('npmlog');
const addressparser = require('nodemailer/lib/addressparser');
let templates = false;
@ -497,6 +498,63 @@ function normalizeIp(ip) {
return ip;
}
function prepareArmoredPubKey(pubKey) {
pubKey = (pubKey || '').toString().replace(/\r?\n/g, '\n').trim();
if (/^-----[^-]+-----\n/.test(pubKey) && !/\n\n/.test(pubKey)) {
// header is missing, add blank line after first newline
pubKey = pubKey.replace(/\n/, '\n\n');
}
return pubKey;
}
function getPGPUserId(pubKey) {
let name = '';
let address = '';
if (!pubKey || !pubKey.users || !pubKey.users.length) {
return { name, address };
}
let userData = pubKey.users.find(u => u && u.userID && (u.userID.userID || u.userID.name || u.userID.email));
if (!userData) {
return { name, address };
}
name = userData.userID.name || '';
address = userData.userID.address || '';
if (!name || !address) {
let user = addressparser(userData.userID.userID);
if (user && user.length) {
if (!address && user[0].address) {
address = normalizeAddress(user[0].address);
}
if (!name && user[0].name) {
try {
name = libmime.decodeWords(user[0].name || '').trim();
} catch (E) {
// failed to parse value
name = user[0].name || '';
}
}
}
}
return { name, address };
}
function formatFingerprint(fingerprint) {
if (typeof fingerprint === 'string') {
return fingerprint.match(/.{1,2}/g).join(':');
}
let out = [];
for (let nr of fingerprint) {
out.push((nr < 0x10 ? '0' : '') + nr.toString(16).toLowerCase());
}
return out.join(':');
}
module.exports = {
normalizeAddress,
normalizeDomain,
@ -515,6 +573,9 @@ module.exports = {
escapeRegexStr,
validationErrors,
checkSocket,
prepareArmoredPubKey,
getPGPUserId,
formatFingerprint,
formatMetaData: metaData => {
if (typeof metaData === 'string') {

View file

@ -55,7 +55,7 @@
"humanname": "0.2.2",
"iconv-lite": "0.6.3",
"ioredfour": "1.2.0-ioredis-06",
"ioredis": "4.27.8",
"ioredis": "4.27.9",
"ipaddr.js": "2.0.1",
"isemail": "3.2.0",
"joi": "17.4.2",
@ -71,8 +71,8 @@
"mongodb-extended-json": "1.11.1",
"node-forge": "0.10.0",
"nodemailer": "6.6.3",
"npmlog": "5.0.0",
"openpgp": "4.10.10",
"npmlog": "5.0.1",
"openpgp": "5.0.0",
"pem-jwk": "2.0.0",
"punycode": "2.1.1",
"pwnedpasswords": "1.0.5",