snappymail/dev/Stores/User/OpenPGP.js

259 lines
7.1 KiB
JavaScript
Raw Normal View History

/**
* OpenPGP.js
*/
import ko from 'ko';
2022-01-31 18:51:15 +08:00
import { arrayLength } from 'Common/Utils';
import { delegateRunOnDestroy } from 'Common/UtilsUser';
import Remote from 'Remote/User/Fetch';
import { showScreenPopup } from 'Knoin/Knoin';
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
const
findOpenPGPKey = (keys, query/*, sign*/) =>
keys.find(key =>
key.emails.includes(query) || query == key.id || query == key.fingerprint
),
/**
* OpenPGP.js v5 removed the localStorage (keyring)
* This should be compatible with the old OpenPGP.js v2
*/
publicKeysItem = 'openpgp-public-keys',
privateKeysItem = 'openpgp-private-keys',
storage = window.localStorage,
loadOpenPgpKeys = async itemname => {
let keys = [], key,
armoredKeys = JSON.parse(storage.getItem(itemname)),
i = arrayLength(armoredKeys);
while (i--) {
key = await openpgp.readKey({armoredKey:armoredKeys[i]});
if (!key.err) {
keys.push(new OpenPgpKeyModel(armoredKeys[i], key));
}
}
return keys;
},
storeOpenPgpKeys = (keys, section) => {
let armoredKeys = keys.map(item => item.armor);
if (armoredKeys.length) {
storage.setItem(section, JSON.stringify(armoredKeys));
} else {
storage.removeItem(section);
}
};
class OpenPgpKeyModel {
constructor(armor, key) {
this.key = key;
const aEmails = [];
if (key.users) {
key.users.forEach(user => user.userID.email && aEmails.push(user.userID.email));
}
2022-01-31 19:34:41 +08:00
this.id = key.getKeyID().toHex().toUpperCase();
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.getUserIDs()
// key.getPrimaryUser()
}
view() {
showScreenPopup(OpenPgpKeyPopupView, [this]);
}
remove() {
if (this.askDelete()) {
if (this.key.isPrivate()) {
OpenPGPUserStore.privateKeys.remove(this);
storeOpenPgpKeys(OpenPGPUserStore.privateKeys, privateKeysItem);
} else {
OpenPGPUserStore.publicKeys.remove(this);
storeOpenPgpKeys(OpenPGPUserStore.publicKeys, publicKeysItem);
}
delegateRunOnDestroy(this);
}
}
/*
toJSON() {
return this.armor;
}
*/
}
export const OpenPGPUserStore = new class {
constructor() {
this.publicKeys = ko.observableArray();
this.privateKeys = ko.observableArray();
}
loadKeyrings() {
loadOpenPgpKeys(publicKeysItem).then(keys => {
this.publicKeys(keys || []);
console.log('openpgp.js public keys loaded');
});
loadOpenPgpKeys(privateKeysItem).then(keys => {
this.privateKeys(keys || [])
console.log('openpgp.js private keys loaded');
});
}
/**
* @returns {boolean}
*/
isSupported() {
return !!window.openpgp;
}
importKey(armoredKey) {
openpgp.readKey({armoredKey:armoredKey}).then(key => {
if (!key.err) {
if (key.isPrivate()) {
this.privateKeys.push(new OpenPgpKeyModel(armoredKey, key));
storeOpenPgpKeys(this.privateKeys, privateKeysItem);
} else {
this.publicKeys.push(new OpenPgpKeyModel(armoredKey, key));
storeOpenPgpKeys(this.publicKeys, publicKeysItem);
}
}
});
}
/**
keyPair.privateKey
keyPair.publicKey
keyPair.revocationCertificate
*/
storeKeyPair(keyPair) {
openpgp.readKey({armoredKey:keyPair.publicKey}).then(key => {
this.publicKeys.push(new OpenPgpKeyModel(keyPair.publicKey, key));
storeOpenPgpKeys(this.publicKeys, publicKeysItem);
});
openpgp.readKey({armoredKey:keyPair.privateKey}).then(key => {
this.privateKeys.push(new OpenPgpKeyModel(keyPair.privateKey, key));
storeOpenPgpKeys(this.privateKeys, privateKeysItem);
});
}
/**
* Checks if verifying/encrypting a message is possible with given email addresses.
*/
hasPublicKeyForEmails(recipients, all) {
const count = recipients.length,
length = count ? recipients.filter(email =>
this.publicKeys().find(key => key.emails.includes(email))
).length : 0;
return length && (!all || length === count);
}
getPrivateKeyFor(query/*, sign*/) {
return findOpenPGPKey(this.privateKeys, query/*, sign*/);
}
getPublicKeyFor(query/*, sign*/) {
return findOpenPGPKey(this.publicKeys, query/*, sign*/);
}
2022-01-31 18:51:15 +08:00
/**
* https://docs.openpgpjs.org/#encrypt-and-decrypt-string-data-with-pgp-keys
*/
async decrypt(armoredText, sender)
{
const message = await openpgp.readMessage({ armoredMessage: armoredText }),
privateKeys = this.privateKeys(),
msgEncryptionKeyIDs = message.getEncryptionKeyIDs().map(key => key.bytes);
// Find private key that can decrypt message
let i = privateKeys.length, privateKey;
while (i--) {
if ((await privateKeys[i].key.getDecryptionKeys()).find(
key => msgEncryptionKeyIDs.includes(key.getKeyID().bytes)
)) {
privateKey = privateKeys[i];
break;
}
}
if (privateKey) try {
// Ask passphrase of private key
const passphrase = prompt('OpenPGP.js Passphrase for ' + privateKey.id + ' ' + privateKey.emails[0]);
if (null !== passphrase) {
const
decryptedKey = await openpgp.decryptKey({
privateKey: privateKey.key,
passphrase
});
return await openpgp.decrypt({
message,
verificationKeys: this.getPublicKeyFor(sender),
// expectSigned: true,
// signature: '', // Detached signature
decryptionKeys: decryptedKey
});
}
} catch (err) {
alert(err);
console.error(err);
}
}
/**
* https://docs.openpgpjs.org/#sign-and-verify-cleartext-messages
*/
async verify(message) {
const data = message.pgpSigned(), // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
publicKey = this.publicKeys().find(key => key.emails.includes(message.from[0].email));
if (data && publicKey) {
data.Folder = message.folder;
data.Uid = message.uid;
data.GnuPG = 0;
2022-02-03 00:00:27 +08:00
let response = data.SigPartId
? await Remote.post('MessagePgpVerify', null, data)
: { Result: { text: message.plain(), signature: null } };
if (response) {
const signature = response.Result.signature
? await openpgp.readSignature({ armoredSignature: response.Result.signature })
: null;
const signedMessage = signature
? await openpgp.createMessage({ text: response.Result.text })
: await openpgp.readCleartextMessage({ cleartextMessage: response.Result.text });
// (signature||signedMessage).getSigningKeyIDs();
let result = await openpgp.verify({
message: signedMessage,
verificationKeys: publicKey.key,
// expectSigned: true, // !!detachedSignature
signature: signature
});
return {
fingerprint: publicKey.fingerprint,
success: result && !!result.signatures.length
};
}
}
}
2022-02-02 23:50:27 +08:00
async signCleartext(text, privateKey) {
const passphrase = prompt('OpenPGP.js Passphrase for ' + privateKey.id + ' ' + privateKey.emails[0]);
if (null !== passphrase) {
privateKey = await openpgp.decryptKey({
privateKey: privateKey.key,
passphrase
});
const unsignedMessage = await openpgp.createCleartextMessage({ text: text });
return await openpgp.sign({
message: unsignedMessage, // CleartextMessage or Message object
signingKeys: privateKey
// detached: false
});
}
return false;
}
};