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

View file

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

View file

@ -106,8 +106,7 @@ export const
* Returns the first library that can.
*/
async hasPublicKeyForEmails(recipients) {
const count = recipients.length;
if (count) {
if (recipients.length) {
if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) {
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.
* 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.createKeyBackupContainer(selector, options)
mailvelopeKeyring.createKeyGenContainer(selector, {

View file

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

View file

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

View file

@ -169,6 +169,7 @@ class Header implements \JsonSerializable
return \in_array(\strtolower($this->sName), array(
\strtolower(Enumerations\Header::CONTENT_TYPE),
\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
{
assert($oParameter instanceof Parameter);
parent::append($oParameter, $bToTop);
parent::offsetSet(\strtolower($oParameter->Name()), $oParameter);
}
public function ParameterValueByName(string $sName) : string
@ -39,12 +39,7 @@ class ParameterCollection extends \MailSo\Base\Collection
public function getParameter(string $sName) : ?Parameter
{
$sName = \strtolower(\trim($sName));
foreach ($this as $oParam) {
if ($sName === \strtolower($oParam->Name())) {
return $oParam;
}
}
return null;
return parent::offsetExists($sName) ? parent::offsetGet($sName) : null;
}
public function setParameter(string $sName, string $sValue) : void
@ -53,7 +48,7 @@ class ParameterCollection extends \MailSo\Base\Collection
if ($oParam) {
$oParam->setValue($sValue);
} 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->SetReferences($this->GetActionParam('references', ''));
$aAutocrypt = $this->GetActionParam('autocrypt', []);
foreach ($aAutocrypt as $header) {
$oMessage->SetCustomHeader('Autocrypt', "addr={$header['addr']}; keydata={$header['keydata']}");
}
$aFoundCids = array();
$aFoundDataURL = array();
$aFoundContentLocationUrls = array();