This commit is contained in:
the-djmaze 2024-02-12 01:01:19 +01:00
parent a45cd5904f
commit 7da5692865
11 changed files with 115 additions and 56 deletions

View file

@ -15,6 +15,7 @@ import { FileInfo, RFC822 } from 'Common/File';
import { AttachmentCollectionModel } from 'Model/AttachmentCollection'; import { AttachmentCollectionModel } from 'Model/AttachmentCollection';
import { EmailCollectionModel } from 'Model/EmailCollection'; import { EmailCollectionModel } from 'Model/EmailCollection';
import { MimeHeaderCollectionModel } from 'Model/MimeHeaderCollection'; import { MimeHeaderCollectionModel } from 'Model/MimeHeaderCollection';
//import { MimeHeaderAutocryptModel } from 'Model/MimeHeaderAutocrypt';
import { AbstractModel } from 'Knoin/AbstractModel'; import { AbstractModel } from 'Knoin/AbstractModel';
import PreviewHTML from 'Html/PreviewMessage.html'; import PreviewHTML from 'Html/PreviewMessage.html';
@ -75,7 +76,7 @@ export class MessageModel extends AbstractModel {
messageId: '', messageId: '',
inReplyTo: '', inReplyTo: '',
references: '', references: '',
autocrypt: {/*addr:'', 'prefer-encrypt':'nopreference', keydata:'BASE64'*/}, // autocrypt: ko.observableArray(),
hasVirus: null, // or boolean when scanned hasVirus: null, // or boolean when scanned
priority: 3, // Normal priority: 3, // Normal
internalTimestamp: 0, internalTimestamp: 0,
@ -287,12 +288,9 @@ export class MessageModel extends AbstractModel {
} }
// https://autocrypt.org/level1.html#the-autocrypt-header // https://autocrypt.org/level1.html#the-autocrypt-header
if (value = headers.valueByName('Autocrypt')) { headers.valuesByName('Autocrypt').forEach(value => {
value.split(';').forEach(entry => { this.autocrypt.push(new MimeHeaderAutocryptModel(value));
entry = entry.trim().split('=', 2); });
json.autocrypt[entry[0]] = entry[1];
});
}
*/ */
return true; return true;
} }

View file

@ -0,0 +1,32 @@
//import { AbstractModel } from 'Knoin/AbstractModel';
export class MimeHeaderAutocryptModel/* extends AbstractModel*/
{
constructor(value) {
// super();
this.addr = '';
this.prefer_encrypt = 'nopreference', // nopreference or mutual
this.keydata = '';
if (value) {
value.split(';').forEach(entry => {
entry = entry.split('=');
const trim = str => (str || '').trim().replace(/^["']|["']+$/g, '');
this[trim(entry[0]).replace('-', '_')] = trim(entry[1]);
});
}
}
toString() {
if ('mutual' === this.prefer_encrypt) {
return `addr=${this.addr}; prefer-encrypt=mutual; keydata=${this.keydata}`;
}
return `addr=${this.addr}; keydata=${this.keydata}`;
}
key() {
return '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n'
+ this.keydata
+ '\n-----END PGP PUBLIC KEY BLOCK-----';
}
}

View file

@ -29,4 +29,10 @@ export class MimeHeaderCollectionModel extends AbstractCollectionModel
return header ? header.value : ''; return header ? header.value : '';
} }
valuesByName(name)
{
name = name.toLowerCase();
return this.filter(header => header.name.toLowerCase() === name).map(header => header.value);
}
} }

View file

@ -170,13 +170,6 @@ export class AbstractFetchRemote
}); });
} }
/**
* @param {?Function} fCallback
*/
getPublicKey(fCallback) {
this.request('GetPublicKey', fCallback);
}
setTrigger(trigger, value) { setTrigger(trigger, value) {
if (trigger) { if (trigger) {
value = !!value; value = !!value;

View file

@ -20,7 +20,7 @@ const
? {password:Passphrases.get(key), remember:false} ? {password:Passphrases.get(key), remember:false}
: await AskPopupView.password('GnuPG key<br>' + key + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt); : await AskPopupView.password('GnuPG key<br>' + key + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt);
pass && pass.remember && Passphrases.set(key, pass.password); pass && pass.remember && Passphrases.set(key, pass.password);
return pass.password; return pass?.password;
}, },
findGnuPGKey = (keys, query/*, sign*/) => findGnuPGKey = (keys, query/*, sign*/) =>
@ -77,29 +77,31 @@ export const GnuPGUserStore = new class {
); );
} }
}; };
key.view = () => { key.fetch = async callback => {
const fetch = pass => Remote.request('GnupgExportKey', if (key.armor) {
(iError, oData) => { callback && callback();
if (oData?.Result) {
key.armor = oData.Result;
showScreenPopup(OpenPgpKeyPopupView, [key]);
} else {
Passphrases.delete(key.id);
}
}, {
keyId: key.id,
isPrivate: isPrivate,
passphrase: pass
}
);
if (isPrivate) {
askPassphrase(key, 'POPUP_VIEW_TITLE').then(passphrase => {
(null !== passphrase) && fetch(passphrase);
});
} else { } else {
fetch(''); let pass = '';
if (isPrivate) {
pass = await askPassphrase(key, 'POPUP_VIEW_TITLE');
}
if (null != pass) {
const result = await Remote.post('GnupgExportKey', null, {
keyId: key.id,
isPrivate: isPrivate,
passphrase: pass
});
if (result?.Result) {
key.armor = result.Result;
callback && callback();
} else {
Passphrases.delete(key.id);
}
}
} }
return key.armor;
}; };
key.view = () => key.fetch(() => showScreenPopup(OpenPgpKeyPopupView, [key]));
return key; return key;
}; };
this.publicKeys(oData.Result.public.map(key => initKey(key, 0))); this.publicKeys(oData.Result.public.map(key => initKey(key, 0)));

View file

@ -106,8 +106,7 @@ export const
* Returns the first library that can. * Returns the first library that can.
*/ */
async hasPublicKeyForEmails(recipients) { async hasPublicKeyForEmails(recipients) {
const count = recipients.length; if (recipients.length) {
if (count) {
if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) { if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) {
return 'gnupg'; return 'gnupg';
} }
@ -228,12 +227,37 @@ export const
} }
} }
getPublicKeyOfEmails(recipients) {
if (recipients.length) {
let result = {};
recipients.forEach(email => {
OpenPGPUserStore.publicKeys().forEach(key => {
if (key.emails.includes(email)) {
result[email] = key.armor;
}
});
GnuPGUserStore.publicKeys.map(async key => {
if (!result[email] && key.emails.includes(email)) {
result[email] = await key.fetch();
}
});
});
return result;
}
return false;
}
/** /**
* Returns headers that should be added to an outgoing email. * Returns headers that should be added to an outgoing email.
* So far this is only the autocrypt header. * So far this is only the autocrypt header.
*/ */
/* /*
mailvelopeKeyring.additionalHeadersForOutgoingEmail(headers) mailvelopeKeyring.additionalHeadersForOutgoingEmail(from: 'abc@web.de')
.then(function(additional) {
console.log('additionalHeadersForOutgoingEmail', additional);
// logs: {autocrypt: "addr=abc@web.de; prefer-encrypt=mutual; keydata=..."}
});
mailvelopeKeyring.addSyncHandler(syncHandlerObj) mailvelopeKeyring.addSyncHandler(syncHandlerObj)
mailvelopeKeyring.createKeyBackupContainer(selector, options) mailvelopeKeyring.createKeyBackupContainer(selector, options)
mailvelopeKeyring.createKeyGenContainer(selector, { mailvelopeKeyring.createKeyGenContainer(selector, {

View file

@ -41,6 +41,7 @@ import Remote from 'Remote/User/Fetch';
import { ComposeAttachmentModel } from 'Model/ComposeAttachment'; import { ComposeAttachmentModel } from 'Model/ComposeAttachment';
import { EmailModel } from 'Model/Email'; import { EmailModel } from 'Model/Email';
import { MimeHeaderAutocryptModel } from 'Model/MimeHeaderAutocrypt';
import { addressparser } from 'Mime/Address'; import { addressparser } from 'Mime/Address';
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin'; import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
@ -885,13 +886,8 @@ export class ComposePopupView extends AbstractViewPopup {
this.aDraftInfo = ['reply', oLastMessage.uid, oLastMessage.folder]; this.aDraftInfo = ['reply', oLastMessage.uid, oLastMessage.folder];
this.sInReplyTo = oLastMessage.messageId; this.sInReplyTo = oLastMessage.messageId;
this.sReferences = (oLastMessage.references + ' ' + oLastMessage.messageId).trim(); this.sReferences = (oLastMessage.references + ' ' + oLastMessage.messageId).trim();
// OpenPGP “Transferable Public Key” oLastMessage.headers().valuesByName('autocrypt').forEach(value => {
let autocrypt = {}, value = oLastMessage.headers().valueByName('autocrypt'); let autocrypt = new MimeHeaderAutocryptModel(value);
if (value) {
value.split(';').forEach(entry => {
entry = entry.split('=', 2);
autocrypt[entry[0].trim()] = entry[1].trim();
});
if (autocrypt.addr && autocrypt.keydata) { if (autocrypt.addr && autocrypt.keydata) {
PgpUserStore.hasPublicKeyForEmails([autocrypt.addr]).then(result => PgpUserStore.hasPublicKeyForEmails([autocrypt.addr]).then(result =>
result || PgpUserStore.importKey( result || PgpUserStore.importKey(
@ -908,7 +904,7 @@ export class ComposePopupView extends AbstractViewPopup {
*/ */
); );
} }
} });
} break; } break;
case ComposeType.Forward: case ComposeType.Forward:
@ -1444,6 +1440,7 @@ export class ComposePopupView extends AbstractViewPopup {
dsn: this.requestDsn() ? 1 : 0, dsn: this.requestDsn() ? 1 : 0,
requireTLS: this.requireTLS() ? 1 : 0, requireTLS: this.requireTLS() ? 1 : 0,
readReceiptRequest: this.requestReadReceipt() ? 1 : 0, readReceiptRequest: this.requestReadReceipt() ? 1 : 0,
autocrypt: [],
/** /**
* Basic support for Linked Data (Structured Email) * Basic support for Linked Data (Structured Email)
* https://json-ld.org/ * https://json-ld.org/
@ -1475,6 +1472,11 @@ export class ComposePopupView extends AbstractViewPopup {
params.encrypted = draft params.encrypted = draft
? await this.mailvelope.createDraft() ? await this.mailvelope.createDraft()
: await this.mailvelope.encrypt(recipients); : await this.mailvelope.encrypt(recipients);
/*
Object.entries(PgpUserStore.getPublicKeyOfEmails(recipients) || {}).forEach(([k,v]) =>
params.autocrypt.push({addr:k, keydata:v.replace(/-----(BEGIN|END) PGP PUBLIC KEY BLOCK-----/).trim()})
);
*/
} else if (sign || encrypt) { } else if (sign || encrypt) {
if (!draft && !hasAttachments && !Text.length) { if (!draft && !hasAttachments && !Text.length) {
throw i18n('COMPOSE/ERROR_EMPTY_BODY'); throw i18n('COMPOSE/ERROR_EMPTY_BODY');
@ -1522,6 +1524,9 @@ export class ComposePopupView extends AbstractViewPopup {
} }
} }
if (encrypt) { if (encrypt) {
Object.entries(PgpUserStore.getPublicKeyOfEmails(recipients) || {}).forEach(([k,v]) =>
params.autocrypt.push({addr:k, keydata:v.replace(/-----(BEGIN|END) PGP PUBLIC KEY BLOCK-----/).trim()})
);
if ('openpgp' == encrypt) { if ('openpgp' == encrypt) {
// Doesn't encrypt attachments // Doesn't encrypt attachments
params.encrypted = await OpenPGPUserStore.encrypt(data.toString(), recipients); params.encrypted = await OpenPGPUserStore.encrypt(data.toString(), recipients);

View file

@ -11,8 +11,6 @@
namespace MailSo\Imap; namespace MailSo\Imap;
use MailSo\Mime\ParameterCollection;
/** /**
* @category MailSo * @category MailSo
* @package Imap * @package Imap

View file

@ -169,6 +169,7 @@ class Header implements \JsonSerializable
return \in_array(\strtolower($this->sName), array( return \in_array(\strtolower($this->sName), array(
\strtolower(Enumerations\Header::CONTENT_TYPE), \strtolower(Enumerations\Header::CONTENT_TYPE),
\strtolower(Enumerations\Header::CONTENT_DISPOSITION) \strtolower(Enumerations\Header::CONTENT_DISPOSITION)
// ,\strtolower(Enumerations\Header::AUTOCRYPT)
)); ));
} }

View file

@ -27,7 +27,7 @@ class ParameterCollection extends \MailSo\Base\Collection
public function append($oParameter, bool $bToTop = false) : void public function append($oParameter, bool $bToTop = false) : void
{ {
assert($oParameter instanceof Parameter); assert($oParameter instanceof Parameter);
parent::append($oParameter, $bToTop); parent::offsetSet(\strtolower($oParameter->Name()), $oParameter);
} }
public function ParameterValueByName(string $sName) : string public function ParameterValueByName(string $sName) : string
@ -39,12 +39,7 @@ class ParameterCollection extends \MailSo\Base\Collection
public function getParameter(string $sName) : ?Parameter public function getParameter(string $sName) : ?Parameter
{ {
$sName = \strtolower(\trim($sName)); $sName = \strtolower(\trim($sName));
foreach ($this as $oParam) { return parent::offsetExists($sName) ? parent::offsetGet($sName) : null;
if ($sName === \strtolower($oParam->Name())) {
return $oParam;
}
}
return null;
} }
public function setParameter(string $sName, string $sValue) : void public function setParameter(string $sName, string $sValue) : void
@ -53,7 +48,7 @@ class ParameterCollection extends \MailSo\Base\Collection
if ($oParam) { if ($oParam) {
$oParam->setValue($sValue); $oParam->setValue($sValue);
} else { } else {
parent::append(new Parameter(\trim($sName), $sValue)); $this->append(new Parameter(\trim($sName), $sValue));
} }
} }

View file

@ -1034,6 +1034,11 @@ trait Messages
$oMessage->SetInReplyTo($this->GetActionParam('inReplyTo', '')); $oMessage->SetInReplyTo($this->GetActionParam('inReplyTo', ''));
$oMessage->SetReferences($this->GetActionParam('references', '')); $oMessage->SetReferences($this->GetActionParam('references', ''));
$aAutocrypt = $this->GetActionParam('autocrypt', []);
foreach ($aAutocrypt as $header) {
$oMessage->SetCustomHeader('Autocrypt', "addr={$header['addr']}; keydata={$header['keydata']}");
}
$aFoundCids = array(); $aFoundCids = array();
$aFoundDataURL = array(); $aFoundDataURL = array();
$aFoundContentLocationUrls = array(); $aFoundContentLocationUrls = array();