mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-27 19:19:21 +03:00
Changes for #89
Now it does not fetch the PGP signature, because validation was broken anyway. Instead it validates multipart/signed according to RFC 3156 section 5 and returns details for the signed part: * BodyPartId * SigPartId * MicAlg So in the future several implementations (GnuPG, OpenPGP.js, etc.) can use the correct data for verification.
This commit is contained in:
parent
ba49d06d1a
commit
8dcd0cf833
14 changed files with 514 additions and 356 deletions
|
|
@ -347,7 +347,7 @@ class AppUser extends AbstractApp {
|
|||
}
|
||||
|
||||
reloadOpenPgpKeys() {
|
||||
if (PgpUserStore.capaOpenPGP()) {
|
||||
if (PgpUserStore.openpgp) {
|
||||
const keys = [],
|
||||
email = new EmailModel(),
|
||||
openpgpKeyring = PgpUserStore.openpgpKeyring,
|
||||
|
|
@ -784,7 +784,6 @@ class AppUser extends AbstractApp {
|
|||
}
|
||||
}
|
||||
PgpUserStore.openpgpKeyring = new openpgp.Keyring();
|
||||
PgpUserStore.capaOpenPGP(true);
|
||||
this.reloadOpenPgpKeys();
|
||||
};
|
||||
script.onerror = () => console.error(script.src);
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ import { AbstractModel } from 'Knoin/AbstractModel';
|
|||
|
||||
import PreviewHTML from 'Html/PreviewMessage.html';
|
||||
|
||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||
|
||||
const
|
||||
/*eslint-disable max-len*/
|
||||
url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi,
|
||||
|
|
@ -102,7 +100,7 @@ export class MessageModel extends AbstractModel {
|
|||
hasImages: false,
|
||||
hasExternals: false,
|
||||
|
||||
isPgpSigned: false,
|
||||
pgpSigned: null, // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
|
||||
isPgpEncrypted: false,
|
||||
pgpSignedVerifyStatus: SignedVerifyStatus.None,
|
||||
pgpSignedVerifyUser: '',
|
||||
|
|
@ -181,7 +179,7 @@ export class MessageModel extends AbstractModel {
|
|||
this.hasExternals(false);
|
||||
this.attachments(new AttachmentCollectionModel);
|
||||
|
||||
this.isPgpSigned(false);
|
||||
this.pgpSigned(null);
|
||||
this.isPgpEncrypted(false);
|
||||
this.pgpSignedVerifyStatus(SignedVerifyStatus.None);
|
||||
this.pgpSignedVerifyUser('');
|
||||
|
|
@ -404,7 +402,7 @@ export class MessageModel extends AbstractModel {
|
|||
viewHtml() {
|
||||
const body = this.body;
|
||||
if (body && this.html()) {
|
||||
let html = this.html().toString()
|
||||
let html = this.html()
|
||||
.replace(/font-size:\s*[0-9]px/g, 'font-size:11px')
|
||||
// Strip utm_* tracking
|
||||
.replace(/(\\?|&|&)utm_[a-z]+=[a-z0-9_-]*/si, '$1');
|
||||
|
|
@ -465,7 +463,7 @@ export class MessageModel extends AbstractModel {
|
|||
if (body && this.plain()) {
|
||||
body.classList.toggle('html', 0);
|
||||
body.classList.toggle('plain', 1);
|
||||
body.innerHTML = plainToHtml(this.plain().toString())
|
||||
body.innerHTML = plainToHtml(this.plain())
|
||||
// Strip utm_* tracking
|
||||
.replace(/(\\?|&|&)utm_[a-z]+=[a-z0-9_-]*/si, '$1')
|
||||
.replace(url, '$1<a href="$2" target="_blank">$2</a>')
|
||||
|
|
@ -479,8 +477,6 @@ export class MessageModel extends AbstractModel {
|
|||
}
|
||||
|
||||
initView() {
|
||||
PgpUserStore.initMessageBodyControls(this.body, this);
|
||||
|
||||
// init BlockquoteSwitcher
|
||||
this.body.querySelectorAll('blockquote:not(.rl-bq-switcher)').forEach(node => {
|
||||
if (node.textContent.trim() && !node.parentNode.closest('blockquote')) {
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ export const MessageUserStore = new class {
|
|||
message.hasImages(body.rlHasImages);
|
||||
} else {
|
||||
body = Element.fromHTML('<div id="' + id + '" hidden="" class="b-text-part '
|
||||
+ (message.isPgpSigned() ? ' openpgp-signed' : '')
|
||||
+ (message.pgpSigned() ? ' openpgp-signed' : '')
|
||||
+ (message.isPgpEncrypted() ? ' openpgp-encrypted' : '')
|
||||
+ '">'
|
||||
+ '</div>');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import ko from 'ko';
|
||||
|
||||
import { i18n } from 'Common/Translator';
|
||||
import { isArray, arrayLength, pString, addComputablesTo } from 'Common/Utils';
|
||||
|
||||
import { AccountUserStore } from 'Stores/User/Account';
|
||||
|
|
@ -9,132 +8,8 @@ import { showScreenPopup } from 'Knoin/Knoin';
|
|||
|
||||
import { MessageOpenPgpPopupView } from 'View/Popup/MessageOpenPgp';
|
||||
|
||||
function controlsHelper(dom, verControl, success, title, text)
|
||||
{
|
||||
dom.classList.toggle('error', !success);
|
||||
dom.classList.toggle('success', success);
|
||||
verControl.classList.toggle('error', !success);
|
||||
verControl.classList.toggle('success', success);
|
||||
dom.title = verControl.title = title;
|
||||
|
||||
if (undefined !== text) {
|
||||
dom.textContent = text.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function domControlEncryptedClickHelper(store, dom, armoredMessage, recipients) {
|
||||
return function() {
|
||||
let message = null;
|
||||
|
||||
if (this.classList.contains('success')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
message = store.openpgp.message.readArmored(armoredMessage);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (message && message.getText && message.verify && message.decrypt) {
|
||||
store.decryptMessage(
|
||||
message,
|
||||
recipients,
|
||||
(validPrivateKey, decryptedMessage, validPublicKey, signingKeyIds) => {
|
||||
if (decryptedMessage) {
|
||||
if (validPublicKey) {
|
||||
controlsHelper(
|
||||
dom,
|
||||
this,
|
||||
true,
|
||||
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
|
||||
USER: validPublicKey.user + ' (' + validPublicKey.id + ')'
|
||||
}),
|
||||
decryptedMessage.getText()
|
||||
);
|
||||
} else if (validPrivateKey) {
|
||||
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
|
||||
additional = keyIds
|
||||
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
|
||||
: '';
|
||||
|
||||
controlsHelper(
|
||||
dom,
|
||||
this,
|
||||
false,
|
||||
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : ''),
|
||||
decryptedMessage.getText()
|
||||
);
|
||||
} else {
|
||||
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
|
||||
}
|
||||
} else {
|
||||
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
function domControlSignedClickHelper(store, dom, armoredMessage) {
|
||||
return function() {
|
||||
let message = null;
|
||||
|
||||
if (this.classList.contains('success') || this.classList.contains('error')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
message = store.openpgp.cleartext.readArmored(armoredMessage);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (message && message.getText && message.verify) {
|
||||
store.verifyMessage(message, (validKey, signingKeyIds) => {
|
||||
if (validKey) {
|
||||
controlsHelper(
|
||||
dom,
|
||||
this,
|
||||
true,
|
||||
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
|
||||
USER: validKey.user + ' (' + validKey.id + ')'
|
||||
}),
|
||||
message.getText()
|
||||
);
|
||||
} else {
|
||||
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
|
||||
additional = keyIds
|
||||
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
|
||||
: '';
|
||||
|
||||
controlsHelper(
|
||||
dom,
|
||||
this,
|
||||
false,
|
||||
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
export const PgpUserStore = new class {
|
||||
constructor() {
|
||||
this.capaOpenPGP = ko.observable(false);
|
||||
|
||||
this.openpgp = null;
|
||||
|
||||
this.openpgpkeys = ko.observableArray();
|
||||
|
|
@ -335,28 +210,4 @@ export const PgpUserStore = new class {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} dom
|
||||
* @param {MessageModel} rainLoopMessage
|
||||
*/
|
||||
initMessageBodyControls(dom, rainLoopMessage) {
|
||||
const cl = dom.classList,
|
||||
signed = cl.contains('openpgp-signed'),
|
||||
encrypted = cl.contains('openpgp-encrypted');
|
||||
if (encrypted || signed) {
|
||||
const
|
||||
domText = dom.textContent,
|
||||
recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : [],
|
||||
verControl = Element.fromHTML('<div class="b-openpgp-control"><i class="fontastic">🔒</i></div>');
|
||||
if (encrypted) {
|
||||
verControl.title = i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC');
|
||||
verControl.addEventListener('click', domControlEncryptedClickHelper(this, dom, domText, recipients));
|
||||
} else {
|
||||
verControl.title = i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC');
|
||||
verControl.addEventListener('click', domControlSignedClickHelper(this, dom, domText));
|
||||
}
|
||||
|
||||
dom.prepend(verControl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -429,27 +429,28 @@ html.rl-no-preview-pane {
|
|||
}
|
||||
}
|
||||
*/
|
||||
.b-openpgp-control {
|
||||
}
|
||||
|
||||
color: #FA0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
margin: 15px;
|
||||
.b-openpgp-control {
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
color: #FA0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
margin: 15px;
|
||||
|
||||
&.success {
|
||||
color: green;
|
||||
opacity: 1;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: red;
|
||||
opacity: 1;
|
||||
}
|
||||
&.success {
|
||||
color: green;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: red;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
|
||||
this.bSkipNextHide = false;
|
||||
|
||||
this.capaOpenPGP = PgpUserStore.capaOpenPGP;
|
||||
this.capaOpenPGP = !!PgpUserStore.openpgp;
|
||||
|
||||
this.identities = IdentityUserStore;
|
||||
|
||||
|
|
@ -552,7 +552,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
}
|
||||
|
||||
openOpenPgpPopup() {
|
||||
if (PgpUserStore.capaOpenPGP() && !this.oEditor.isHtml()) {
|
||||
if (PgpUserStore.openpgp && !this.oEditor.isHtml()) {
|
||||
showScreenPopup(ComposeOpenPgpPopupView, [
|
||||
result => this.editor(editor => editor.setPlain(result)),
|
||||
this.oEditor.getData(false),
|
||||
|
|
|
|||
|
|
@ -40,6 +40,67 @@ import Remote from 'Remote/User/Fetch';
|
|||
import { decorateKoCommands, createCommand } from 'Knoin/Knoin';
|
||||
import { AbstractViewRight } from 'Knoin/AbstractViews';
|
||||
|
||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||
|
||||
function controlsHelper(dom, verControl, success, title, text)
|
||||
{
|
||||
dom.classList.toggle('error', !success);
|
||||
dom.classList.toggle('success', success);
|
||||
// verControl.classList.toggle('error', !success);
|
||||
// verControl.classList.toggle('success', success);
|
||||
dom.title = verControl.title = title;
|
||||
|
||||
if (undefined !== text) {
|
||||
dom.textContent = text.trim();
|
||||
}
|
||||
}
|
||||
|
||||
function pgpClickHelper(dom, armoredMessage) {
|
||||
if (dom.classList.contains('success') || dom.classList.contains('error')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = null;
|
||||
try {
|
||||
message = PgpUserStore.openpgp.cleartext.readArmored(armoredMessage);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (message && message.getText && message.verify) {
|
||||
PgpUserStore.verifyMessage(message, (validKey, signingKeyIds) => {
|
||||
console.dir([validKey, signingKeyIds]);
|
||||
/*
|
||||
if (validKey) {
|
||||
controlsHelper(
|
||||
dom,
|
||||
this,
|
||||
true,
|
||||
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
|
||||
USER: validKey.user + ' (' + validKey.id + ')'
|
||||
}),
|
||||
message.getText()
|
||||
);
|
||||
} else {
|
||||
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
|
||||
additional = keyIds
|
||||
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
|
||||
: '';
|
||||
|
||||
controlsHelper(
|
||||
dom,
|
||||
this,
|
||||
false,
|
||||
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '')
|
||||
);
|
||||
}
|
||||
*/
|
||||
});
|
||||
} else {
|
||||
controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
|
||||
}
|
||||
}
|
||||
|
||||
export class MailMessageView extends AbstractViewRight {
|
||||
constructor() {
|
||||
super('MailMessageView');
|
||||
|
|
@ -176,6 +237,12 @@ export class MailMessageView extends AbstractViewRight {
|
|||
return '';
|
||||
},
|
||||
|
||||
pgpSigned: () => PgpUserStore.openpgp
|
||||
&& MessageUserStore.message() && !!MessageUserStore.message().pgpSigned(),
|
||||
|
||||
pgpEncrypted: () => PgpUserStore.openpgp
|
||||
&& MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
|
||||
|
||||
messageListOrViewLoading:
|
||||
() => MessageUserStore.listIsLoading() | MessageUserStore.messageLoading()
|
||||
});
|
||||
|
|
@ -614,4 +681,14 @@ export class MailMessageView extends AbstractViewRight {
|
|||
rl.app.reloadFlagsCurrentMessageListAndMessageFromCache();
|
||||
}
|
||||
}
|
||||
|
||||
pgpDecrypt(self/*, event*/) {
|
||||
const message = self.message();
|
||||
message && pgpClickHelper(message.body, message.plain(), message.getEmails(['from', 'to', 'cc']));
|
||||
}
|
||||
|
||||
pgpVerify(self/*, event*/) {
|
||||
const message = self.message();
|
||||
message && pgpClickHelper(message.body, message.plain());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue