mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-08 22:18:28 +03:00
#89 decrypt using OpenPGP.js
This commit is contained in:
parent
e28c5bdbb6
commit
c012fe09f0
4 changed files with 150 additions and 131 deletions
|
|
@ -4,16 +4,13 @@
|
|||
|
||||
import ko from 'ko';
|
||||
|
||||
import { isArray, arrayLength } from 'Common/Utils';
|
||||
import { arrayLength } from 'Common/Utils';
|
||||
import { delegateRunOnDestroy } from 'Common/UtilsUser';
|
||||
|
||||
import { showScreenPopup } from 'Knoin/Knoin';
|
||||
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
|
||||
|
||||
const
|
||||
findKeyByHex = (keys, hash) =>
|
||||
keys.find(item => item && (hash === item.id || item.ids.includes(hash))),
|
||||
|
||||
findOpenPGPKey = (keys, query/*, sign*/) =>
|
||||
keys.find(key =>
|
||||
key.emails.includes(query) || query == key.id || query == key.fingerprint
|
||||
|
|
@ -162,114 +159,37 @@ export const OpenPGPUserStore = new class {
|
|||
return findOpenPGPKey(this.publicKeys, query/*, sign*/);
|
||||
}
|
||||
|
||||
decrypt(text, fCallback)
|
||||
/**
|
||||
* https://docs.openpgpjs.org/#encrypt-and-decrypt-string-data-with-pgp-keys
|
||||
*/
|
||||
async decrypt(armoredText, privateKey, publicKey)
|
||||
{
|
||||
/*
|
||||
decryptMessage(message, recipients, fCallback) {
|
||||
message = store.openpgp.message.readArmored(armoredMessage);
|
||||
try {
|
||||
message = store.openpgp.message.readArmored(armoredMessage);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
const passphrase = prompt('Passphrase');
|
||||
if (!passphrase) {
|
||||
return;
|
||||
}
|
||||
if (message && message.getText && message.verify && message.decrypt) {
|
||||
if (message && message.getEncryptionKeyIds) {
|
||||
// findPrivateKeysByEncryptionKeyIds
|
||||
const encryptionKeyIds = message.getEncryptionKeyIds();
|
||||
let privateKeys = isArray(encryptionKeyIds)
|
||||
? encryptionKeyIds.map(id => {
|
||||
// openpgpKeyring.publicKeys.getForId(id.toHex())
|
||||
// openpgpKeyring.privateKeys.getForId(id.toHex())
|
||||
const key = id && id.toHex ? findKeyByHex(this.privateKeys, id.toHex()) : null;
|
||||
return key ? [key] : [null];
|
||||
}).flat().filter(v => v)
|
||||
: [];
|
||||
if (!privateKeys.length && arrayLength(recipients)) {
|
||||
privateKeys = recipients.map(sEmail =>
|
||||
(sEmail
|
||||
? this.privateKeys.filter(item => item && item.emails.includes(sEmail)) : 0)
|
||||
|| [null]
|
||||
).flat().validUnique(key => key.id);
|
||||
}
|
||||
|
||||
if (privateKeys && privateKeys.length) {
|
||||
showScreenPopup(OpenPgpSelectorPopupView, [
|
||||
(decryptedKey) => {
|
||||
if (decryptedKey) {
|
||||
message.decrypt(decryptedKey).then(
|
||||
(decryptedMessage) => {
|
||||
let privateKey = null;
|
||||
if (decryptedMessage) {
|
||||
privateKey = findKeyByHex(this.privateKeys, decryptedKey.primaryKey.keyid.toHex());
|
||||
if (privateKey) {
|
||||
this.verifyMessage(decryptedMessage, (oValidKey, aSigningKeyIds) => {
|
||||
fCallback(privateKey, decryptedMessage, oValidKey || null, aSigningKeyIds || null);
|
||||
});
|
||||
} else {
|
||||
fCallback(privateKey, decryptedMessage);
|
||||
}
|
||||
} else {
|
||||
fCallback(privateKey, decryptedMessage);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
fCallback(null, null);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
fCallback(null, null);
|
||||
}
|
||||
},
|
||||
privateKeys
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fCallback(null, null);
|
||||
|
||||
*/
|
||||
const message = await openpgp.readMessage({ armoredMessage: armoredText });
|
||||
const decryptedKey = await openpgp.decryptKey({
|
||||
// privateKey: await openpgp.readPrivateKey({ armoredKey: armoredPrivateKey }),
|
||||
privateKey: privateKey,
|
||||
passphrase
|
||||
});
|
||||
return await openpgp.decrypt({
|
||||
message,
|
||||
verificationKeys: publicKey,
|
||||
// expectSigned: true,
|
||||
// signature: '', // Detached signature
|
||||
decryptionKeys: decryptedKey
|
||||
});
|
||||
}
|
||||
|
||||
verify(message, fCallback) {
|
||||
let text = null;
|
||||
try {
|
||||
// TODO: if message.pgpSigned().SigPartId then fetch raw from server
|
||||
text = openpgp.cleartext.readArmored(message.plain);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (text && text.getText && text.verify) {
|
||||
if (message && message.getSigningKeyIds) {
|
||||
const signingKeyIds = message.getSigningKeyIds();
|
||||
if (signingKeyIds && signingKeyIds.length) {
|
||||
// findPublicKeysBySigningKeyIds
|
||||
const publicKeys = signingKeyIds.map(id => {
|
||||
const key = id && id.toHex ? findKeyByHex(this.publicKeys, id.toHex()) : null;
|
||||
return key ? key.key : [null];
|
||||
}).flat().filter(v => v);
|
||||
if (publicKeys && publicKeys.length) {
|
||||
try {
|
||||
const result = message.verify(publicKeys),
|
||||
valid = (isArray(result) ? result : []).find(item => item && item.valid && item.keyid);
|
||||
|
||||
if (valid && valid.keyid && valid.keyid && valid.keyid.toHex) {
|
||||
fCallback(findKeyByHex(this.publicKeys, valid.keyid.toHex()));
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
fCallback(null, signingKeyIds);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fCallback(null);
|
||||
return false;
|
||||
async verify(message, detachedSignature, publicKey) {
|
||||
return await openpgp.verify({
|
||||
message,
|
||||
verificationKeys: publicKey,
|
||||
// expectSigned: true, // !!detachedSignature
|
||||
signature: detachedSignature
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -131,10 +131,15 @@ export const PgpUserStore = new class {
|
|||
*/
|
||||
async getKeyForDecryption(ids, email) {
|
||||
ids = [email].concat(ids);
|
||||
let i = ids.length,
|
||||
key = await this.getMailvelopePrivateKeyFor({email:email});
|
||||
if (key) {
|
||||
return key;
|
||||
let i = ids.length, key;
|
||||
|
||||
try {
|
||||
key = await this.getMailvelopePrivateKeyFor(email);
|
||||
if (key) {
|
||||
return key;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
/* Not working, needs full fingerprint
|
||||
while (i--) {
|
||||
|
|
@ -148,13 +153,14 @@ export const PgpUserStore = new class {
|
|||
}
|
||||
i = ids.length;
|
||||
*/
|
||||
/*
|
||||
while (i--) {
|
||||
key = this.getGnuPGPrivateKeyFor(ids[i]);
|
||||
if (key) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
i = ids.length;
|
||||
while (i--) {
|
||||
key = this.getOpenPGPPrivateKeyFor(ids[i]);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,90 @@ class OpenPgpKeyPopupView extends AbstractViewPopup {
|
|||
onShow(openPgpKey) {
|
||||
// TODO: show more info
|
||||
this.key(openPgpKey ? openPgpKey.armor : '');
|
||||
/*
|
||||
this.key = key;
|
||||
const aEmails = [];
|
||||
if (key.users) {
|
||||
key.users.forEach(user => user.userID.email && aEmails.push(user.userID.email));
|
||||
}
|
||||
this.id = key.getKeyID().toHex();
|
||||
this.fingerprint = key.getFingerprint();
|
||||
this.can_encrypt = !!key.getEncryptionKey();
|
||||
this.can_sign = !!key.getSigningKey();
|
||||
this.emails = aEmails;
|
||||
this.armor = armor;
|
||||
this.askDelete = ko.observable(false);
|
||||
this.openForDeletion = ko.observable(null).askDeleteHelper();
|
||||
|
||||
key.id = key.subkeys[0].keyid;
|
||||
key.fingerprint = key.subkeys[0].fingerprint;
|
||||
key.uids.forEach(uid => uid.email && aEmails.push(uid.email));
|
||||
key.emails = aEmails;
|
||||
"disabled": false,
|
||||
"expired": false,
|
||||
"revoked": false,
|
||||
"is_secret": true,
|
||||
"can_sign": true,
|
||||
"can_decrypt": true
|
||||
"can_verify": true
|
||||
"can_encrypt": true,
|
||||
"uids": [
|
||||
{
|
||||
"name": "demo",
|
||||
"comment": "",
|
||||
"email": "demo@snappymail.eu",
|
||||
"uid": "demo <demo@snappymail.eu>",
|
||||
"revoked": false,
|
||||
"invalid": false
|
||||
}
|
||||
],
|
||||
"subkeys": [
|
||||
{
|
||||
"fingerprint": "2C223F20EA2ADB4CB68F81D95F3A5CDC09AD8AE3",
|
||||
"keyid": "5F3A5CDC09AD8AE3",
|
||||
"timestamp": 1643381672,
|
||||
"expires": 0,
|
||||
"is_secret": false,
|
||||
"invalid": false,
|
||||
"can_encrypt": false,
|
||||
"can_sign": true,
|
||||
"disabled": false,
|
||||
"expired": false,
|
||||
"revoked": false,
|
||||
"can_certify": true,
|
||||
"can_authenticate": false,
|
||||
"is_qualified": false,
|
||||
"is_de_vs": false,
|
||||
"pubkey_algo": 303,
|
||||
"length": 256,
|
||||
"keygrip": "5A1A6C7310D0508C68E8E74F15068301E83FD1AE",
|
||||
"is_cardkey": false,
|
||||
"curve": "ed25519"
|
||||
},
|
||||
{
|
||||
"fingerprint": "3CD720549D8833872C267D08F1230DCE2A561ADE",
|
||||
"keyid": "F1230DCE2A561ADE",
|
||||
"timestamp": 1643381672,
|
||||
"expires": 0,
|
||||
"is_secret": false,
|
||||
"invalid": false,
|
||||
"can_encrypt": true,
|
||||
"can_sign": false,
|
||||
"disabled": false,
|
||||
"expired": false,
|
||||
"revoked": false,
|
||||
"can_certify": false,
|
||||
"can_authenticate": false,
|
||||
"is_qualified": false,
|
||||
"is_de_vs": false,
|
||||
"pubkey_algo": 302,
|
||||
"length": 256,
|
||||
"keygrip": "886921A7E06BE56F8E8C51797BB476BB26DF21BF",
|
||||
"is_cardkey": false,
|
||||
"curve": "cv25519"
|
||||
}
|
||||
]
|
||||
*/
|
||||
}
|
||||
|
||||
onBuild() {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import { decorateKoCommands, createCommand } from 'Knoin/Knoin';
|
|||
import { AbstractViewRight } from 'Knoin/AbstractViews';
|
||||
|
||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
||||
|
||||
const currentMessage = () => MessageUserStore.message();
|
||||
|
||||
|
|
@ -626,6 +627,7 @@ export class MailMessageView extends AbstractViewRight {
|
|||
|
||||
pgpDecrypt(self) {
|
||||
const message = self.message(),
|
||||
sender = message && message.from[0].email,
|
||||
pgpInfo = message && message.pgpEncrypted();
|
||||
if (pgpInfo) {
|
||||
// Also check message.from[0].email
|
||||
|
|
@ -652,7 +654,7 @@ export class MailMessageView extends AbstractViewRight {
|
|||
message.plain(),
|
||||
PgpUserStore.mailvelopeKeyring,
|
||||
{
|
||||
senderAddress: message.from[0].email
|
||||
senderAddress: sender
|
||||
}
|
||||
).then(status => {
|
||||
if (status.error && status.error.message) {
|
||||
|
|
@ -665,8 +667,18 @@ export class MailMessageView extends AbstractViewRight {
|
|||
});
|
||||
}
|
||||
else if ('openpgp' === result[0]) {
|
||||
// TODO
|
||||
// PgpUserStore.decryptMessage(message, recipients, fCallback)
|
||||
const publicKey = OpenPGPUserStore.getPublicKeyFor(sender);
|
||||
// OpenPGPUserStore.decrypt(message.plain(), result[1].armor
|
||||
OpenPGPUserStore.decrypt(message.plain(), result[1].key, publicKey ? publicKey.key : null).then(result => {
|
||||
if (result) {
|
||||
// TODO: if result.data is not cleartext then
|
||||
// parse mime to find and verify signature
|
||||
message.plain(result.data);
|
||||
message.viewPlain();
|
||||
} else {
|
||||
// controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
|
||||
}
|
||||
});
|
||||
}
|
||||
else if ('gnupg' === result[0]) {
|
||||
let params = {
|
||||
|
|
@ -695,7 +707,8 @@ export class MailMessageView extends AbstractViewRight {
|
|||
|
||||
pgpVerify(self) {
|
||||
if (self.pgpSigned()) {
|
||||
const message = self.message();
|
||||
const message = self.message(),
|
||||
sender = message && message.from[0].email;
|
||||
PgpUserStore.hasPublicKeyForEmails([message.from[0].email]).then(mode => {
|
||||
if ('gnupg' === mode) {
|
||||
let params = message.pgpSigned(); // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
|
||||
|
|
@ -713,16 +726,15 @@ export class MailMessageView extends AbstractViewRight {
|
|||
});
|
||||
}
|
||||
} else if ('openpgp' === mode) {
|
||||
let text = null;
|
||||
try {
|
||||
// TODO: if message.pgpSigned().SigPartId then fetch raw from server
|
||||
text = PgpUserStore.openpgp.cleartext.readArmored(message.plain);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (text && text.getText && text.verify) {
|
||||
PgpUserStore.verifyMessage(text, (validKey, signingKeyIds) => {
|
||||
console.dir([validKey, signingKeyIds]);
|
||||
const publicKey = OpenPGPUserStore.getPublicKeyFor(sender);
|
||||
OpenPGPUserStore.verify(message.plain(), null/*detachedSignature*/, publicKey).then(result => {
|
||||
if (result) {
|
||||
// TODO: if result.data is not cleartext then
|
||||
// parse mime to find and verify signature
|
||||
message.plain(result.data);
|
||||
message.viewPlain();
|
||||
console.dir({signatures:result.signatures});
|
||||
}
|
||||
/*
|
||||
if (validKey) {
|
||||
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
|
||||
|
|
@ -738,10 +750,7 @@ export class MailMessageView extends AbstractViewRight {
|
|||
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '');
|
||||
}
|
||||
*/
|
||||
});
|
||||
} else {
|
||||
// controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue