mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-09-16 10:07:22 +08:00
Upgraded OpenPGP to v5
This commit is contained in:
parent
9422d02373
commit
eb3e26e153
6 changed files with 138 additions and 134 deletions
6
api.js
6
api.js
|
@ -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));
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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)]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
61
lib/tools.js
61
lib/tools.js
|
@ -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') {
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue