mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-09-20 07:35:55 +08:00
Added: GnuPG encrypt (sign still fails)
This commit is contained in:
parent
f3717815e1
commit
8fff6fa759
|
@ -150,12 +150,16 @@ export const GnuPGUserStore = new class {
|
|||
return length && length === count;
|
||||
}
|
||||
|
||||
getPrivateKeyFor(query, sign) {
|
||||
return findGnuPGKey(this.privateKeys, query, sign);
|
||||
getPublicKeyFingerprints(recipients) {
|
||||
const fingerprints = [];
|
||||
recipients.forEach(email => {
|
||||
fingerprints.push(this.publicKeys.find(key => key.emails.includes(email)).fingerprint);
|
||||
});
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
getPublicKeyFor(query, sign) {
|
||||
return findGnuPGKey(this.publicKeys, query, sign);
|
||||
getPrivateKeyFor(query, sign) {
|
||||
return findGnuPGKey(this.privateKeys, query, sign);
|
||||
}
|
||||
|
||||
async decrypt(message) {
|
||||
|
@ -207,12 +211,8 @@ export const GnuPGUserStore = new class {
|
|||
}
|
||||
}
|
||||
|
||||
async sign(/*text, privateKey, detached*/) {
|
||||
throw 'Sign failed';
|
||||
}
|
||||
|
||||
async encrypt(/*text, recipients, signPrivateKey*/) {
|
||||
throw 'Encrypt failed';
|
||||
async sign(privateKey) {
|
||||
return await askPassphrase(privateKey);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -161,10 +161,6 @@ export const OpenPGPUserStore = new class {
|
|||
return findOpenPGPKey(this.privateKeys, query/*, sign*/);
|
||||
}
|
||||
|
||||
getPublicKeyFor(query/*, sign*/) {
|
||||
return findOpenPGPKey(this.publicKeys, query/*, sign*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* https://docs.openpgpjs.org/#encrypt-and-decrypt-string-data-with-pgp-keys
|
||||
*/
|
||||
|
|
|
@ -91,12 +91,12 @@ export const PgpUserStore = new class {
|
|||
async hasPublicKeyForEmails(recipients) {
|
||||
const count = recipients.length;
|
||||
if (count) {
|
||||
if (OpenPGPUserStore.hasPublicKeyForEmails(recipients)) {
|
||||
return 'openpgp';
|
||||
}
|
||||
if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) {
|
||||
return 'gnupg';
|
||||
}
|
||||
if (OpenPGPUserStore.hasPublicKeyForEmails(recipients)) {
|
||||
return 'openpgp';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ export const PgpUserStore = new class {
|
|||
* Returns the first library that can.
|
||||
*/
|
||||
async getKeyForSigning(email) {
|
||||
/*
|
||||
/* // TODO: sign in PHP fails
|
||||
let key = GnuPGUserStore.getPrivateKeyFor(email, 1);
|
||||
if (key) {
|
||||
return ['gnupg', key];
|
||||
|
|
|
@ -30,6 +30,7 @@ import { AccountUserStore } from 'Stores/User/Account';
|
|||
import { FolderUserStore } from 'Stores/User/Folder';
|
||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
||||
import { GnuPGUserStore } from 'Stores/User/GnuPG';
|
||||
import { MessageUserStore } from 'Stores/User/Message';
|
||||
|
||||
import Remote from 'Remote/User/Fetch';
|
||||
|
@ -407,15 +408,12 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
params.Encrypted = draft
|
||||
? await this.mailvelope.createDraft()
|
||||
: await this.mailvelope.encrypt(recipients);
|
||||
} else if (encrypt || sign) {
|
||||
} else if ('openpgp' == encrypt || (sign && 'openpgp' == sign[0])) {
|
||||
let data = new MimePart;
|
||||
data.headers['Content-Type'] = 'text/'+(TextIsHtml?'html':'plain')+'; charset="utf-8"';
|
||||
data.headers['Content-Transfer-Encoding'] = 'base64';
|
||||
data.body = base64_encode(Text);
|
||||
if (sign && sign[1]) {
|
||||
if ('openpgp' != sign[0]) {
|
||||
throw 'Signing with ' + sign[0] + ' not yet implemented';
|
||||
}
|
||||
let signed = new MimePart;
|
||||
signed.headers['Content-Type'] =
|
||||
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"';
|
||||
|
@ -429,13 +427,26 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
data = signed;
|
||||
}
|
||||
if (encrypt) {
|
||||
if ('openpgp' != encrypt) {
|
||||
throw 'Encryption with ' + encrypt + ' not yet implemented';
|
||||
}
|
||||
params.Encrypted = await OpenPGPUserStore.encrypt(data.toString(), recipients);
|
||||
} else {
|
||||
params.Signed = data.toString();
|
||||
}
|
||||
} else if ('gnupg' == encrypt || (sign && 'gnupg' == sign[0])) {
|
||||
params.Html = TextIsHtml ? Text : '';
|
||||
params.Text = TextIsHtml ? '' : Text;
|
||||
/* // TODO: sign in PHP fails
|
||||
if (sign) {
|
||||
params.SignFingerprint = sign[1].fingerprint;
|
||||
params.SignPassphrase = await GnuPGUserStore.sign(sign[1]);
|
||||
}
|
||||
*/
|
||||
if (encrypt) {
|
||||
params.EncryptFingerprints = GnuPGUserStore.getPublicKeyFingerprints(recipients).join(',');
|
||||
}
|
||||
} else if (encrypt) {
|
||||
throw 'Encryption with ' + encrypt + ' not yet implemented';
|
||||
} else if (sign) {
|
||||
throw 'Signing with ' + sign[0] + ' not yet implemented';
|
||||
} else {
|
||||
params.Html = TextIsHtml ? Text : '';
|
||||
params.Text = TextIsHtml ? '' : Text;
|
||||
|
|
|
@ -45,7 +45,7 @@ class TempFile
|
|||
public function stream_open(string $sPath) : bool
|
||||
{
|
||||
$bResult = false;
|
||||
$aPath = parse_url($sPath);
|
||||
$aPath = \parse_url($sPath);
|
||||
|
||||
if (isset($aPath['host']) && isset($aPath['scheme']) &&
|
||||
\strlen($aPath['host']) && \strlen($aPath['scheme']) &&
|
||||
|
@ -53,7 +53,7 @@ class TempFile
|
|||
{
|
||||
$sHashName = $aPath['host'];
|
||||
if (isset(self::$aStreams[$sHashName]) &&
|
||||
is_resource(self::$aStreams[$sHashName]))
|
||||
\is_resource(self::$aStreams[$sHashName]))
|
||||
{
|
||||
$this->rSream = self::$aStreams[$sHashName];
|
||||
\fseek($this->rSream, 0);
|
||||
|
@ -61,7 +61,7 @@ class TempFile
|
|||
}
|
||||
else
|
||||
{
|
||||
$this->rSream = fopen('php://memory', 'r+b');
|
||||
$this->rSream = \fopen('php://memory', 'r+b');
|
||||
self::$aStreams[$sHashName] = $this->rSream;
|
||||
|
||||
$bResult = true;
|
||||
|
@ -78,37 +78,37 @@ class TempFile
|
|||
|
||||
public function stream_flush() : bool
|
||||
{
|
||||
return fflush($this->rSream);
|
||||
return \fflush($this->rSream);
|
||||
}
|
||||
|
||||
public function stream_read(int $iLen) : string
|
||||
{
|
||||
return fread($this->rSream, $iLen);
|
||||
return \fread($this->rSream, $iLen);
|
||||
}
|
||||
|
||||
public function stream_write(string $sInputString) : int
|
||||
{
|
||||
return fwrite($this->rSream, $sInputString);
|
||||
return \fwrite($this->rSream, $sInputString);
|
||||
}
|
||||
|
||||
public function stream_tell() : int
|
||||
{
|
||||
return ftell($this->rSream);
|
||||
return \ftell($this->rSream);
|
||||
}
|
||||
|
||||
public function stream_eof() : bool
|
||||
{
|
||||
return feof($this->rSream);
|
||||
return \feof($this->rSream);
|
||||
}
|
||||
|
||||
public function stream_stat() : array
|
||||
{
|
||||
return fstat($this->rSream);
|
||||
return \fstat($this->rSream);
|
||||
}
|
||||
|
||||
public function stream_seek(int $iOffset, int $iWhence = SEEK_SET) : int
|
||||
{
|
||||
return fseek($this->rSream, $iOffset, $iWhence);
|
||||
return \fseek($this->rSream, $iOffset, $iWhence);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ class Header
|
|||
|
||||
private function wordWrapHelper(string $sValue, string $sGlue = "\r\n ") : string
|
||||
{
|
||||
return \trim(substr(wordwrap($this->NameWithDelimitrom().$sValue,
|
||||
return \trim(\substr(\wordwrap($this->NameWithDelimitrom().$sValue,
|
||||
74, $sGlue
|
||||
), \strlen($this->NameWithDelimitrom())));
|
||||
}
|
||||
|
|
|
@ -331,16 +331,13 @@ class Message extends Part
|
|||
(\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : '')).'@'.$sHostName.'>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource|bool
|
||||
*/
|
||||
public function ToStream(bool $bWithoutBcc = false)
|
||||
public function GetRootPart() : Part
|
||||
{
|
||||
if (!\count($this->SubParts)) {
|
||||
if ($this->bAddEmptyTextPart) {
|
||||
$oPart = new Part;
|
||||
$oPart->Headers->AddByName(Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"');
|
||||
$oPart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString('');
|
||||
$oPart->Body = '';
|
||||
$this->SubParts->append($oPart);
|
||||
} else {
|
||||
$aAttachments = $this->oAttachmentCollection->getArrayCopy();
|
||||
|
@ -373,7 +370,7 @@ class Message extends Part
|
|||
}
|
||||
}
|
||||
if (!\is_resource($oPart->Body)) {
|
||||
$oPart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString('');
|
||||
$oPart->Body = '';
|
||||
}
|
||||
|
||||
$this->SubParts->append($oPart);
|
||||
|
@ -425,6 +422,16 @@ class Message extends Part
|
|||
}
|
||||
}
|
||||
|
||||
return $oRootPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource|bool
|
||||
*/
|
||||
public function ToStream(bool $bWithoutBcc = false)
|
||||
{
|
||||
$oRootPart = $this->GetRootPart();
|
||||
|
||||
/**
|
||||
* setDefaultHeaders
|
||||
*/
|
||||
|
@ -455,7 +462,9 @@ class Message extends Part
|
|||
}
|
||||
}
|
||||
|
||||
return $oRootPart->ToStream();
|
||||
$resource = $oRootPart->ToStream();
|
||||
\MailSo\Base\StreamFilters\LineEndings::appendTo($resource);
|
||||
return $resource;
|
||||
}
|
||||
/*
|
||||
public function ToString(bool $bWithoutBcc = false) : string
|
||||
|
|
|
@ -32,6 +32,11 @@ class Part
|
|||
*/
|
||||
public $Body = null;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
public $Raw = null;
|
||||
|
||||
/**
|
||||
* @var PartCollection
|
||||
*/
|
||||
|
@ -166,36 +171,43 @@ class Part
|
|||
*/
|
||||
public function ToStream()
|
||||
{
|
||||
if ($this->SubParts->count()) {
|
||||
$sBoundary = $this->HeaderBoundary();
|
||||
if (!\strlen($sBoundary)) {
|
||||
$this->Headers->GetByName(Enumerations\Header::CONTENT_TYPE)->setParameter(
|
||||
Enumerations\Parameter::BOUNDARY,
|
||||
$this->SubParts->Boundary()
|
||||
);
|
||||
} else {
|
||||
$this->SubParts->SetBoundary($sBoundary);
|
||||
}
|
||||
}
|
||||
|
||||
$aSubStreams = array(
|
||||
$this->Headers . "\r\n\r\n"
|
||||
);
|
||||
|
||||
if ($this->Body) {
|
||||
if (\is_resource($this->Body)) {
|
||||
$aMeta = \stream_get_meta_data($this->Body);
|
||||
if (!empty($aMeta['seekable'])) {
|
||||
\rewind($this->Body);
|
||||
if ($this->Raw) {
|
||||
$aSubStreams = array(
|
||||
$this->Raw
|
||||
);
|
||||
} else {
|
||||
if ($this->SubParts->count()) {
|
||||
$sBoundary = $this->HeaderBoundary();
|
||||
if (!\strlen($sBoundary)) {
|
||||
$this->Headers->GetByName(Enumerations\Header::CONTENT_TYPE)->setParameter(
|
||||
Enumerations\Parameter::BOUNDARY,
|
||||
$this->SubParts->Boundary()
|
||||
);
|
||||
} else {
|
||||
$this->SubParts->SetBoundary($sBoundary);
|
||||
}
|
||||
}
|
||||
$aSubStreams[] = $this->Body;
|
||||
}
|
||||
|
||||
if ($this->SubParts->count()) {
|
||||
$rSubPartsStream = $this->SubParts->ToStream();
|
||||
if (\is_resource($rSubPartsStream)) {
|
||||
$aSubStreams[] = $rSubPartsStream;
|
||||
$aSubStreams = array(
|
||||
$this->Headers . "\r\n"
|
||||
);
|
||||
|
||||
if ($this->Body) {
|
||||
$aSubStreams[0] .= "\r\n";
|
||||
if (\is_resource($this->Body)) {
|
||||
$aMeta = \stream_get_meta_data($this->Body);
|
||||
if (!empty($aMeta['seekable'])) {
|
||||
\rewind($this->Body);
|
||||
}
|
||||
}
|
||||
$aSubStreams[] = $this->Body;
|
||||
}
|
||||
|
||||
if ($this->SubParts->count()) {
|
||||
$rSubPartsStream = $this->SubParts->ToStream();
|
||||
if (\is_resource($rSubPartsStream)) {
|
||||
$aSubStreams[] = $rSubPartsStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class PartCollection extends \MailSo\Base\Collection
|
|||
$aResult[] = "\r\n--{$this->sBoundary}\r\n";
|
||||
$aResult[] = $oPart->ToStream();
|
||||
}
|
||||
$aResult[] = "\r\n--{$this->sBoundary}--\r\n";
|
||||
$aResult[] = "\r\n--{$this->sBoundary}--";
|
||||
return \MailSo\Base\StreamWrappers\SubStreams::CreateStream($aResult);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -161,10 +161,6 @@ trait Messages
|
|||
|
||||
$this->Plugins()->RunHook('filter.send-message', array($oMessage));
|
||||
|
||||
/*
|
||||
TODO: PGP encrypt/sign
|
||||
*/
|
||||
|
||||
$mResult = false;
|
||||
try
|
||||
{
|
||||
|
@ -1269,6 +1265,84 @@ trait Messages
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: sign, but verify is still invalid
|
||||
$sFingerprint = $this->GetActionParam('SignFingerprint', '');
|
||||
$sPassphrase = $this->GetActionParam('SignPassphrase', '');
|
||||
if ($sFingerprint) {
|
||||
$GPG = $this->GnuPG();
|
||||
$oBody = $oMessage->GetRootPart();
|
||||
$fp = \fopen('php://memory', 'r+b');
|
||||
$resource = $oBody->ToStream();
|
||||
// \MailSo\Base\StreamFilters\LineEndings::appendTo($resource);
|
||||
\stream_copy_to_stream($resource, $fp);
|
||||
$GPG->addSignKey($sFingerprint, $sPassphrase);
|
||||
$GPG->setsignmode(GNUPG_SIG_MODE_DETACH);
|
||||
$sSignature = $GPG->signStream($fp);
|
||||
|
||||
$oMessage->SubParts->Clear();
|
||||
$oMessage->Attachments()->Clear();
|
||||
|
||||
$oPart = new MimePart;
|
||||
$oPart->Headers->AddByName(
|
||||
\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
|
||||
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"'
|
||||
);
|
||||
$oMessage->SubParts->append($oPart);
|
||||
|
||||
\rewind($fp);
|
||||
$oBody->Raw = $fp;
|
||||
$oBody->Body = null;
|
||||
$oBody->SubParts->Clear();
|
||||
$oPart->SubParts->append($oBody);
|
||||
|
||||
$oAlternativePart = new MimePart;
|
||||
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/pgp-signature; name="signature.asc"');
|
||||
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit');
|
||||
$oAlternativePart->Body = $sSignature;
|
||||
$oPart->SubParts->append($oAlternativePart);
|
||||
}
|
||||
*/
|
||||
|
||||
// TODO: encrypt
|
||||
$sFingerprints = $this->GetActionParam('EncryptFingerprints', '');
|
||||
if ($sFingerprints) {
|
||||
$GPG = $this->GnuPG();
|
||||
$oBody = $oMessage->GetRootPart();
|
||||
$fp = \fopen('php://memory', 'r+b');
|
||||
$resource = $oBody->ToStream();
|
||||
// \MailSo\Base\StreamFilters\LineEndings::appendTo($resource);
|
||||
\stream_copy_to_stream($resource, $fp);
|
||||
foreach (\explode(',', $sFingerprints) as $sFingerprint) {
|
||||
$GPG->addEncryptKey($sFingerprint);
|
||||
}
|
||||
$sEncrypted = $GPG->encryptStream($fp);
|
||||
|
||||
$oMessage->SubParts->Clear();
|
||||
$oMessage->Attachments()->Clear();
|
||||
|
||||
$oPart = new MimePart;
|
||||
$oPart->Headers->AddByName(
|
||||
\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
|
||||
'multipart/encrypted; protocol="application/pgp-encrypted"'
|
||||
);
|
||||
$oMessage->SubParts->append($oPart);
|
||||
|
||||
$oAlternativePart = new MimePart;
|
||||
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/pgp-encrypted');
|
||||
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION, 'attachment');
|
||||
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit');
|
||||
$oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString('Version: 1');
|
||||
$oPart->SubParts->append($oAlternativePart);
|
||||
|
||||
$oAlternativePart = new MimePart;
|
||||
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/octet-stream');
|
||||
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_DISPOSITION, 'inline; filename="msg.asc"');
|
||||
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit');
|
||||
$oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sEncrypted)));
|
||||
$oPart->SubParts->append($oAlternativePart);
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('filter.build-message', array($oMessage));
|
||||
|
||||
return $oMessage;
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
namespace SnappyMail\PGP;
|
||||
|
||||
defined('GNUPG_SIG_MODE_NORMAL') || define('GNUPG_SIG_MODE_NORMAL', 0);
|
||||
defined('GNUPG_SIG_MODE_DETACH') || define('GNUPG_SIG_MODE_DETACH', 1);
|
||||
defined('GNUPG_SIG_MODE_CLEAR') || define('GNUPG_SIG_MODE_CLEAR', 2);
|
||||
|
||||
class GnuPG
|
||||
{
|
||||
private
|
||||
|
@ -193,24 +197,12 @@ class GnuPG
|
|||
: $this->GPG->encryptFile($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts and signs a given text
|
||||
*/
|
||||
public function encryptSign(string $plaintext) /*: string|false*/
|
||||
public function encryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false*/
|
||||
{
|
||||
\rewind($fp);
|
||||
return $this->GnuPG
|
||||
? $this->GnuPG->encryptsign($plaintext)
|
||||
: $this->GPG->encryptsign($plaintext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts and signs a given text
|
||||
*/
|
||||
public function encryptSignFile(string $filename) /*: string|false*/
|
||||
{
|
||||
return $this->GnuPG
|
||||
? $this->GnuPG->encryptsign(\file_get_contents($filename))
|
||||
: $this->GPG->encryptsignFile($filename);
|
||||
? $this->GnuPG->encrypt(\stream_get_contents($fp))
|
||||
: $this->GPG->encryptStream($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -360,6 +352,17 @@ class GnuPG
|
|||
: $this->GPG->signFile($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a given file
|
||||
*/
|
||||
public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/
|
||||
{
|
||||
\rewind($fp);
|
||||
return $this->GnuPG
|
||||
? $this->GnuPG->sign(\stream_get_contents($fp))
|
||||
: $this->GPG->signStream($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a signed text
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue