mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-07 08:27:03 +03:00
#89 verify detached signature using GnuPG
This commit is contained in:
parent
08ea226dbe
commit
93db6e6e0e
6 changed files with 82 additions and 71 deletions
|
|
@ -32,15 +32,6 @@ const
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
SignedVerifyStatus = {
|
|
||||||
UnknownPublicKeys: -4,
|
|
||||||
UnknownPrivateKey: -3,
|
|
||||||
Unverified: -2,
|
|
||||||
Error: -1,
|
|
||||||
None: 0,
|
|
||||||
Success: 1
|
|
||||||
},
|
|
||||||
|
|
||||||
replyHelper = (emails, unic, localEmails) => {
|
replyHelper = (emails, unic, localEmails) => {
|
||||||
emails.forEach(email => {
|
emails.forEach(email => {
|
||||||
if (undefined === unic[email.email]) {
|
if (undefined === unic[email.email]) {
|
||||||
|
|
@ -87,8 +78,7 @@ export class MessageModel extends AbstractModel {
|
||||||
pgpSigned: null,
|
pgpSigned: null,
|
||||||
pgpEncrypted: null,
|
pgpEncrypted: null,
|
||||||
isPgpEncrypted: false,
|
isPgpEncrypted: false,
|
||||||
pgpSignedVerifyStatus: SignedVerifyStatus.None,
|
pgpVerified: null,
|
||||||
pgpSignedVerifyUser: '',
|
|
||||||
|
|
||||||
readReceipt: '',
|
readReceipt: '',
|
||||||
|
|
||||||
|
|
@ -166,8 +156,7 @@ export class MessageModel extends AbstractModel {
|
||||||
this.pgpSigned(null);
|
this.pgpSigned(null);
|
||||||
this.pgpEncrypted(null);
|
this.pgpEncrypted(null);
|
||||||
this.isPgpEncrypted(false);
|
this.isPgpEncrypted(false);
|
||||||
this.pgpSignedVerifyStatus(SignedVerifyStatus.None);
|
this.pgpVerified(null);
|
||||||
this.pgpSignedVerifyUser('');
|
|
||||||
|
|
||||||
this.priority(MessagePriority.Normal);
|
this.priority(MessagePriority.Normal);
|
||||||
this.readReceipt('');
|
this.readReceipt('');
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,21 @@ export const GnuPGUserStore = new class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(/*message, fCallback*/) {
|
async verify(message) {
|
||||||
|
let data = message.pgpSigned(); // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
|
||||||
|
if (data) {
|
||||||
|
// const sender = message.from[0].email;
|
||||||
|
// let mode = await this.hasPublicKeyForEmails([sender]);
|
||||||
|
data.Folder = message.folder;
|
||||||
|
data.Uid = message.uid;
|
||||||
|
let response = await Remote.post('MessagePgpVerify', null, data);
|
||||||
|
if (response && response.Result) {
|
||||||
|
return {
|
||||||
|
fingerprint: response.Result.fingerprint,
|
||||||
|
success: 0 == response.Result.status // GOODSIG
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,47 @@ export const PgpUserStore = new class {
|
||||||
return GnuPGUserStore.decrypt(message);
|
return GnuPGUserStore.decrypt(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async verify(message) {
|
||||||
|
const plain = message.plain();
|
||||||
|
if (/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain)) {
|
||||||
|
let result = await OpenPGPUserStore.verify(plain);
|
||||||
|
console.dir(result);
|
||||||
|
}
|
||||||
|
if (message.pgpSigned()) {
|
||||||
|
const sender = message.from[0].email;
|
||||||
|
let mode = await this.hasPublicKeyForEmails([sender]);
|
||||||
|
if ('gnupg' === mode) {
|
||||||
|
return GnuPGUserStore.verify(message);
|
||||||
|
}
|
||||||
|
if ('openpgp' === mode) {
|
||||||
|
const publicKey = OpenPGPUserStore.getPublicKeyFor(sender);
|
||||||
|
OpenPGPUserStore.verify(plain, null/*detachedSignature*/, publicKey).then(result => {
|
||||||
|
if (result) {
|
||||||
|
message.plain(result.data);
|
||||||
|
message.viewPlain();
|
||||||
|
console.dir({signatures:result.signatures});
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if (validKey) {
|
||||||
|
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(', ')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '');
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an iframe with an editor for a new encrypted mail.
|
* Creates an iframe with an editor for a new encrypted mail.
|
||||||
* The iframe will be injected into the container identified by selector.
|
* The iframe will be injected into the container identified by selector.
|
||||||
|
|
|
||||||
|
|
@ -436,14 +436,6 @@ html.rl-no-preview-pane {
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
&.success {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
span {
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
@ -460,6 +452,19 @@ html.rl-no-preview-pane {
|
||||||
border: 1px dashed #FA0;
|
border: 1px dashed #FA0;
|
||||||
color: #FA0;
|
color: #FA0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
border-color: #090;
|
||||||
|
color: #090;
|
||||||
|
}
|
||||||
|
&.error {
|
||||||
|
border-color: #F00;
|
||||||
|
color: #F00;
|
||||||
|
}
|
||||||
|
&.error button,
|
||||||
|
&.success button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.b-text-part > iframe {
|
.b-text-part > iframe {
|
||||||
min-height: 50vh;
|
min-height: 50vh;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ import { decorateKoCommands, createCommand } from 'Knoin/Knoin';
|
||||||
import { AbstractViewRight } from 'Knoin/AbstractViews';
|
import { AbstractViewRight } from 'Knoin/AbstractViews';
|
||||||
|
|
||||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||||
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
|
||||||
|
|
||||||
import PostalMime from '../../../../vendors/postal-mime/src/postal-mime.js';
|
import PostalMime from '../../../../vendors/postal-mime/src/postal-mime.js';
|
||||||
import { AttachmentModel } from 'Model/Attachment';
|
import { AttachmentModel } from 'Model/Attachment';
|
||||||
|
|
@ -61,11 +60,12 @@ const
|
||||||
currentMessage = () => MessageUserStore.message(),
|
currentMessage = () => MessageUserStore.message(),
|
||||||
|
|
||||||
mimeToMessage = (data, message) => {
|
mimeToMessage = (data, message) => {
|
||||||
// TODO: Check multipart/signed
|
// TODO: Check multipart/signed application/pgp-signature application/pgp-keys
|
||||||
const headers = data.split(/\r?\n\r?\n/)[0];
|
const headers = data.split(/\r?\n\r?\n/)[0];
|
||||||
if (/Content-Type:.+; boundary=/.test(headers)) {
|
if (/Content-Type:.+; boundary=/.test(headers)) {
|
||||||
// https://github.com/postalsys/postal-mime
|
// https://github.com/postalsys/postal-mime
|
||||||
(new PostalMime).parse(data).then(result => {
|
(new PostalMime).parse(data).then(result => {
|
||||||
|
// TODO: multipart/signed
|
||||||
let html = result.html,
|
let html = result.html,
|
||||||
regex = /^<+|>+$/g;
|
regex = /^<+|>+$/g;
|
||||||
result.attachments.forEach(data => {
|
result.attachments.forEach(data => {
|
||||||
|
|
@ -90,7 +90,6 @@ const
|
||||||
});
|
});
|
||||||
message.hasAttachments(message.attachments().hasVisible());
|
message.hasAttachments(message.attachments().hasVisible());
|
||||||
// result.headers;
|
// result.headers;
|
||||||
// TODO: strip script tags and all other security that PHP also does
|
|
||||||
message.plain(result.text || '');
|
message.plain(result.text || '');
|
||||||
if (html) {
|
if (html) {
|
||||||
message.html(html.replace(/<\/?script[\s\S]*?>/gi, '') || '');
|
message.html(html.replace(/<\/?script[\s\S]*?>/gi, '') || '');
|
||||||
|
|
@ -639,51 +638,14 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pgpVerify() {
|
pgpVerify(/*self, event*/) {
|
||||||
const oMessage = currentMessage();
|
const oMessage = currentMessage()/*, ctrl = event.target.closest('.openpgp-control')*/;
|
||||||
if (oMessage.pgpSigned()) {
|
PgpUserStore.verify(oMessage).then(result => {
|
||||||
const sender = oMessage.from[0].email;
|
console.dir({result:result});
|
||||||
PgpUserStore.hasPublicKeyForEmails([oMessage.from[0].email]).then(mode => {
|
if (result) {
|
||||||
if ('gnupg' === mode) {
|
oMessage.pgpVerified(result);
|
||||||
let params = oMessage.pgpSigned(); // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
|
}
|
||||||
params.Folder = oMessage.folder;
|
});
|
||||||
params.Uid = oMessage.uid;
|
|
||||||
rl.app.Remote.post('MessagePgpVerify', null, params)
|
|
||||||
.then(data => {
|
|
||||||
// TODO
|
|
||||||
console.dir(data);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// TODO
|
|
||||||
console.dir(error);
|
|
||||||
});
|
|
||||||
} else if ('openpgp' === mode) {
|
|
||||||
const publicKey = OpenPGPUserStore.getPublicKeyFor(sender);
|
|
||||||
OpenPGPUserStore.verify(oMessage.plain(), null/*detachedSignature*/, publicKey).then(result => {
|
|
||||||
if (result) {
|
|
||||||
oMessage.plain(result.data);
|
|
||||||
oMessage.viewPlain();
|
|
||||||
console.dir({signatures:result.signatures});
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if (validKey) {
|
|
||||||
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
|
|
||||||
USER: validKey.user + ' (' + validKey.id + ')'
|
|
||||||
});
|
|
||||||
oMessage.getText()
|
|
||||||
} else {
|
|
||||||
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
|
|
||||||
additional = keyIds
|
|
||||||
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
|
|
||||||
: '';
|
|
||||||
|
|
||||||
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '');
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@
|
||||||
<span data-icon="🔒" data-i18n="MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC"></span>
|
<span data-icon="🔒" data-i18n="MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC"></span>
|
||||||
<button class="btn" data-bind="visible: pgpSupported, click: pgpDecrypt" data-i18n="OPENPGP/BUTTON_DECRYPT"></button>
|
<button class="btn" data-bind="visible: pgpSupported, click: pgpDecrypt" data-i18n="OPENPGP/BUTTON_DECRYPT"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="openpgp-control signed" data-bind="visible: message().pgpSigned()">
|
<div class="openpgp-control signed" data-bind="visible: message().pgpSigned(), css: {'success': message().pgpVerified() && message().pgpVerified().success, 'error': message().pgpVerified() && !message().pgpVerified().success}">
|
||||||
<span data-icon="✍" data-i18n="MESSAGE/PGP_SIGNED_MESSAGE_DESC"></span>
|
<span data-icon="✍" data-i18n="MESSAGE/PGP_SIGNED_MESSAGE_DESC"></span>
|
||||||
<button class="btn" data-bind="visible: pgpSupported, click: pgpVerify" data-i18n="MESSAGE/BUTTON_PGP_VERIFY"></button>
|
<button class="btn" data-bind="visible: pgpSupported, click: pgpVerify" data-i18n="MESSAGE/BUTTON_PGP_VERIFY"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue