mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-05 23:47:03 +03:00
Added signing using S/MIME #259
This commit is contained in:
parent
f83b60782a
commit
128e2f6254
10 changed files with 143 additions and 101 deletions
|
|
@ -250,12 +250,16 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
showBcc: false,
|
showBcc: false,
|
||||||
showReplyTo: false,
|
showReplyTo: false,
|
||||||
|
|
||||||
pgpSign: false,
|
doSign: false,
|
||||||
|
doEncrypt: false,
|
||||||
|
|
||||||
canPgpSign: false,
|
canPgpSign: false,
|
||||||
pgpEncrypt: false,
|
|
||||||
canPgpEncrypt: false,
|
canPgpEncrypt: false,
|
||||||
canMailvelope: false,
|
canMailvelope: false,
|
||||||
|
|
||||||
|
canSMimeSign: false,
|
||||||
|
canSMimeEncrypt: false,
|
||||||
|
|
||||||
draftsFolder: '',
|
draftsFolder: '',
|
||||||
draftUid: 0,
|
draftUid: 0,
|
||||||
sending: false,
|
sending: false,
|
||||||
|
|
@ -330,6 +334,9 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
attachmentsInProcessCount: () => this.attachmentsInProcess.length,
|
attachmentsInProcessCount: () => this.attachmentsInProcess.length,
|
||||||
isDraft: () => this.draftsFolder() && this.draftUid(),
|
isDraft: () => this.draftsFolder() && this.draftUid(),
|
||||||
|
|
||||||
|
canSign: () => this.canPgpSign() | this.canSMimeSign(),
|
||||||
|
canEncrypt: () => this.canPgpEncrypt() | this.canSMimeEncrypt(),
|
||||||
|
|
||||||
identitiesOptions: () =>
|
identitiesOptions: () =>
|
||||||
IdentityUserStore.map(item => ({
|
IdentityUserStore.map(item => ({
|
||||||
item: item,
|
item: item,
|
||||||
|
|
@ -349,9 +356,11 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
|
|
||||||
currentIdentity: value => {
|
currentIdentity: value => {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
const smime = !!(value.smimeKey() && value.smimeCertificate());
|
||||||
this.from(value.formattedName());
|
this.from(value.formattedName());
|
||||||
this.pgpEncrypt(value.pgpEncrypt()/* || SettingsUserStore.pgpEncrypt()*/);
|
this.doEncrypt(value.pgpEncrypt()/* || SettingsUserStore.pgpEncrypt()*/);
|
||||||
this.pgpSign(value.pgpSign()/* || SettingsUserStore.pgpSign()*/);
|
this.doSign(smime || value.pgpSign()/* || SettingsUserStore.pgpSign()*/);
|
||||||
|
this.canSMimeSign(smime);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -361,9 +370,9 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
value && PgpUserStore.getKeyForSigning(value).then(result => {
|
value && PgpUserStore.getKeyForSigning(value).then(result => {
|
||||||
console.log({
|
console.log({
|
||||||
email: value,
|
email: value,
|
||||||
canPgpSign:!!result
|
canPgpSign:result
|
||||||
});
|
});
|
||||||
this.canPgpSign(!!result)
|
this.canPgpSign(result)
|
||||||
});
|
});
|
||||||
this.initPgpEncrypt();
|
this.initPgpEncrypt();
|
||||||
},
|
},
|
||||||
|
|
@ -426,7 +435,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
case 'K': quota *= 1024;
|
case 'K': quota *= 1024;
|
||||||
}
|
}
|
||||||
// Issue: can't select signing key
|
// Issue: can't select signing key
|
||||||
// this.pgpSign(this.pgpSign() || confirm('Sign this message?'));
|
// this.doSign(this.doSign() || confirm('Sign this message?'));
|
||||||
mailvelope.createEditorContainer('#mailvelope-editor', PgpUserStore.mailvelopeKeyring, {
|
mailvelope.createEditorContainer('#mailvelope-editor', PgpUserStore.mailvelopeKeyring, {
|
||||||
// https://mailvelope.github.io/mailvelope/global.html#EditorContainerOptions
|
// https://mailvelope.github.io/mailvelope/global.html#EditorContainerOptions
|
||||||
quota: Math.max(2048, (quota / 1024)) - 48, // (text + attachments) limit in kilobytes
|
quota: Math.max(2048, (quota / 1024)) - 48, // (text + attachments) limit in kilobytes
|
||||||
|
|
@ -438,7 +447,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
quotedMailHeader: '', // header to be added before the quoted mail
|
quotedMailHeader: '', // header to be added before the quoted mail
|
||||||
keepAttachments: false, // add attachments of quotedMail to editor (default: false)
|
keepAttachments: false, // add attachments of quotedMail to editor (default: false)
|
||||||
// Issue: can't select signing key
|
// Issue: can't select signing key
|
||||||
signMsg: this.pgpSign()
|
signMsg: this.doSign()
|
||||||
*/
|
*/
|
||||||
}).then(editor => this.mailvelope = editor);
|
}).then(editor => this.mailvelope = editor);
|
||||||
}
|
}
|
||||||
|
|
@ -1338,8 +1347,8 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
this.showBcc(false);
|
this.showBcc(false);
|
||||||
this.showReplyTo(false);
|
this.showReplyTo(false);
|
||||||
|
|
||||||
this.pgpSign(SettingsUserStore.pgpSign());
|
this.doSign(SettingsUserStore.pgpSign());
|
||||||
this.pgpEncrypt(SettingsUserStore.pgpEncrypt());
|
this.doEncrypt(SettingsUserStore.pgpEncrypt());
|
||||||
|
|
||||||
this.attachments([]);
|
this.attachments([]);
|
||||||
|
|
||||||
|
|
@ -1449,8 +1458,8 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
linkedData: []
|
linkedData: []
|
||||||
},
|
},
|
||||||
recipients = draft ? [identity.email()] : this.allRecipients(),
|
recipients = draft ? [identity.email()] : this.allRecipients(),
|
||||||
sign = !draft && this.pgpSign() && this.canPgpSign(),
|
sign = !draft && this.doSign() && (this.canPgpSign() || this.canSMimeSign()),
|
||||||
encrypt = this.pgpEncrypt() && this.canPgpEncrypt(),
|
encrypt = this.doEncrypt() && (this.canPgpEncrypt() || this.canSMimeEncrypt()),
|
||||||
isHtml = this.oEditor.isHtml();
|
isHtml = this.oEditor.isHtml();
|
||||||
|
|
||||||
if (isHtml) {
|
if (isHtml) {
|
||||||
|
|
@ -1497,30 +1506,37 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
alternative.children.push(data);
|
alternative.children.push(data);
|
||||||
data = alternative;
|
data = alternative;
|
||||||
}
|
}
|
||||||
if (!draft && sign?.[1]) {
|
if (sign) {
|
||||||
if ('openpgp' == sign[0]) {
|
if (sign?.[1]) {
|
||||||
// Doesn't sign attachments
|
if ('openpgp' == sign[0]) {
|
||||||
params.html = params.plain = '';
|
// Doesn't sign attachments
|
||||||
let signed = new MimePart;
|
params.html = params.plain = '';
|
||||||
signed.headers['Content-Type'] =
|
let signed = new MimePart;
|
||||||
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"';
|
signed.headers['Content-Type'] =
|
||||||
signed.headers['Content-Transfer-Encoding'] = '7Bit';
|
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"';
|
||||||
signed.children.push(data);
|
signed.headers['Content-Transfer-Encoding'] = '7Bit';
|
||||||
let signature = new MimePart;
|
signed.children.push(data);
|
||||||
signature.headers['Content-Type'] = 'application/pgp-signature; name="signature.asc"';
|
let signature = new MimePart;
|
||||||
signature.headers['Content-Transfer-Encoding'] = '7Bit';
|
signature.headers['Content-Type'] = 'application/pgp-signature; name="signature.asc"';
|
||||||
signature.body = await OpenPGPUserStore.sign(data.toString(), sign[1], 1);
|
signature.headers['Content-Transfer-Encoding'] = '7Bit';
|
||||||
signed.children.push(signature);
|
signature.body = await OpenPGPUserStore.sign(data.toString(), sign[1], 1);
|
||||||
params.signed = signed.toString();
|
signed.children.push(signature);
|
||||||
params.boundary = signed.boundary;
|
params.signed = signed.toString();
|
||||||
data = signed;
|
params.boundary = signed.boundary;
|
||||||
} else if ('gnupg' == sign[0]) {
|
data = signed;
|
||||||
// TODO: sign in PHP fails
|
} else if ('gnupg' == sign[0]) {
|
||||||
// params.signData = data.toString();
|
// TODO: sign in PHP fails
|
||||||
params.signFingerprint = sign[1].fingerprint;
|
// params.signData = data.toString();
|
||||||
params.signPassphrase = await GnuPGUserStore.sign(sign[1]);
|
params.signFingerprint = sign[1].fingerprint;
|
||||||
} else {
|
params.signPassphrase = await GnuPGUserStore.sign(sign[1]);
|
||||||
throw 'Signing with ' + sign[0] + ' not yet implemented';
|
} else {
|
||||||
|
throw 'Signing with ' + sign[0] + ' not yet implemented';
|
||||||
|
}
|
||||||
|
} else if (this.canSMimeSign()) {
|
||||||
|
params.signCertificate = identity.smimeCertificate();
|
||||||
|
params.signPrivateKey = identity.smimeKey();
|
||||||
|
const pass = await AskPopupView.password('S/MIME key', 'OPENPGP/LABEL_SIGN');
|
||||||
|
params.signPassphrase = pass?.password;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
|
|
@ -1534,6 +1550,9 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
} else if ('gnupg' == encrypt) {
|
} else if ('gnupg' == encrypt) {
|
||||||
// Does encrypt attachments
|
// Does encrypt attachments
|
||||||
params.encryptFingerprints = JSON.stringify(GnuPGUserStore.getPublicKeyFingerprints(recipients));
|
params.encryptFingerprints = JSON.stringify(GnuPGUserStore.getPublicKeyFingerprints(recipients));
|
||||||
|
// } else {
|
||||||
|
// // S/MIME
|
||||||
|
// params.encryptCertificates = '';
|
||||||
} else {
|
} else {
|
||||||
throw 'Encryption with ' + encrypt + ' not yet implemented';
|
throw 'Encryption with ' + encrypt + ' not yet implemented';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
namespace MailSo\Imap;
|
namespace MailSo\Imap;
|
||||||
|
|
||||||
|
use MailSo\Mime\Enumerations\ContentType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @category MailSo
|
* @category MailSo
|
||||||
* @package Imap
|
* @package Imap
|
||||||
|
|
@ -165,10 +167,10 @@ class BodyStructure implements \JsonSerializable
|
||||||
{
|
{
|
||||||
return ('multipart/signed' === $this->sContentType
|
return ('multipart/signed' === $this->sContentType
|
||||||
&& !empty($this->aContentTypeParams['protocol'])
|
&& !empty($this->aContentTypeParams['protocol'])
|
||||||
&& 'application/pkcs7-signature' === \strtolower(\trim($this->aContentTypeParams['protocol']))
|
&& ContentType::isPkcs7Signature(\strtolower(\trim($this->aContentTypeParams['protocol'])))
|
||||||
// The multipart/signed body MUST consist of exactly two parts.
|
// The multipart/signed body MUST consist of exactly two parts.
|
||||||
&& 2 === \count($this->aSubParts)
|
&& 2 === \count($this->aSubParts)
|
||||||
&& 'application/pkcs7-signature' === $this->aSubParts[1]->ContentType()
|
&& ContentType::isPkcs7Signature($this->aSubParts[1]->ContentType())
|
||||||
) || ('application/pkcs7-mime' === $this->sContentType
|
) || ('application/pkcs7-mime' === $this->sContentType
|
||||||
&& !empty($this->aContentTypeParams['smime-type'])
|
&& !empty($this->aContentTypeParams['smime-type'])
|
||||||
&& 'signed-data' === \strtolower(\trim($this->aContentTypeParams['smime-type']))
|
&& 'signed-data' === \strtolower(\trim($this->aContentTypeParams['smime-type']))
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,10 @@ abstract class ContentType
|
||||||
const PGP_SIGNATURE = 'application/pgp-signature';
|
const PGP_SIGNATURE = 'application/pgp-signature';
|
||||||
const PKCS7_SIGNATURE = 'application/pkcs7-signature';
|
const PKCS7_SIGNATURE = 'application/pkcs7-signature';
|
||||||
const PKCS7_MIME = 'application/pkcs7-mime';
|
const PKCS7_MIME = 'application/pkcs7-mime';
|
||||||
|
|
||||||
|
public static function isPkcs7Signature(string $data) : bool
|
||||||
|
{
|
||||||
|
return 'application/pkcs7-signature' === $data
|
||||||
|
|| 'application/x-pkcs7-signature' === $data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
namespace MailSo\Mime;
|
namespace MailSo\Mime;
|
||||||
|
|
||||||
|
use MailSo\Mime\Enumerations\ContentType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @category MailSo
|
* @category MailSo
|
||||||
* @package Mime
|
* @package Mime
|
||||||
|
|
@ -96,10 +98,10 @@ class Part
|
||||||
{
|
{
|
||||||
$header = $this->Headers->GetByName(Enumerations\Header::CONTENT_TYPE);
|
$header = $this->Headers->GetByName(Enumerations\Header::CONTENT_TYPE);
|
||||||
return ($header
|
return ($header
|
||||||
&& \preg_match('#multipart/signed.+protocol=["\']?application/pkcs7-signature#si', $header->FullValue())
|
&& \preg_match('#multipart/signed.+protocol=["\']?application/(x-)?pkcs7-signature#si', $header->FullValue())
|
||||||
// The multipart/signed body MUST consist of exactly two parts.
|
// The multipart/signed body MUST consist of exactly two parts.
|
||||||
&& 2 === \count($this->SubParts)
|
&& 2 === \count($this->SubParts)
|
||||||
&& 'application/pkcs7-signature' === $this->SubParts[1]->ContentType()
|
&& ContentType::isPkcs7Signature($this->SubParts[1]->ContentType())
|
||||||
) || ($header
|
) || ($header
|
||||||
&& \preg_match('#application/pkcs7-mime.+smime-type=["\']?signed-data#si', $header->FullValue())
|
&& \preg_match('#application/pkcs7-mime.+smime-type=["\']?signed-data#si', $header->FullValue())
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1231,7 +1231,7 @@ trait Messages
|
||||||
|
|
||||||
$resource = $oBody->ToStream();
|
$resource = $oBody->ToStream();
|
||||||
\MailSo\Base\StreamFilters\LineEndings::appendTo($resource);
|
\MailSo\Base\StreamFilters\LineEndings::appendTo($resource);
|
||||||
$tmp = new \SnappyMail\File\Temporary;
|
$tmp = new \SnappyMail\File\Temporary('mimepart');
|
||||||
$tmp->writeFromStream($resource);
|
$tmp->writeFromStream($resource);
|
||||||
|
|
||||||
$oBody->Body = null;
|
$oBody->Body = null;
|
||||||
|
|
@ -1244,7 +1244,7 @@ trait Messages
|
||||||
$sSignature = $SMIME->sign($tmp, $sCertificate);
|
$sSignature = $SMIME->sign($tmp, $sCertificate);
|
||||||
|
|
||||||
if (!$sSignature) {
|
if (!$sSignature) {
|
||||||
throw new \Exception('GnuPG sign() failed');
|
throw new \Exception('S/MIME sign() failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
$oPart = new MimePart;
|
$oPart = new MimePart;
|
||||||
|
|
@ -1261,7 +1261,7 @@ trait Messages
|
||||||
|
|
||||||
$oSignaturePart = new MimePart;
|
$oSignaturePart = new MimePart;
|
||||||
$oSignaturePart->Headers->AddByName(MimeEnumHeader::CONTENT_TYPE, 'application/pkcs7-signature; name="signature.p7s"');
|
$oSignaturePart->Headers->AddByName(MimeEnumHeader::CONTENT_TYPE, 'application/pkcs7-signature; name="signature.p7s"');
|
||||||
$oSignaturePart->Headers->AddByName(MimeEnumHeader::CONTENT_TRANSFER_ENCODING, '7Bit');
|
$oSignaturePart->Headers->AddByName(MimeEnumHeader::CONTENT_TRANSFER_ENCODING, 'base64');
|
||||||
$oSignaturePart->Body = $sSignature;
|
$oSignaturePart->Body = $sSignature;
|
||||||
$oPart->SubParts->append($oSignaturePart);
|
$oPart->SubParts->append($oSignaturePart);
|
||||||
}
|
}
|
||||||
|
|
@ -1285,7 +1285,7 @@ trait Messages
|
||||||
} else {
|
} else {
|
||||||
$aCertificates = \json_decode($this->GetActionParam('encryptCertificates', ''), true);
|
$aCertificates = \json_decode($this->GetActionParam('encryptCertificates', ''), true);
|
||||||
if ($aCertificates) {
|
if ($aCertificates) {
|
||||||
$tmp = new \SnappyMail\File\Temporary;
|
$tmp = new \SnappyMail\File\Temporary('mimepart');
|
||||||
$tmp->writeFromStream($oMessage->GetRootPart()->ToStream());
|
$tmp->writeFromStream($oMessage->GetRootPart()->ToStream());
|
||||||
|
|
||||||
$oMessage->SubParts->Clear();
|
$oMessage->SubParts->Clear();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ namespace SnappyMail\File;
|
||||||
|
|
||||||
class Temporary
|
class Temporary
|
||||||
{
|
{
|
||||||
protected static string $filename = '';
|
protected string $filename = '';
|
||||||
|
|
||||||
function __construct(string $name, bool $prefix = true)
|
function __construct(string $name, bool $prefix = true)
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +15,7 @@ class Temporary
|
||||||
throw new \Exception("Failed to create directory {$tmpdir}");
|
throw new \Exception("Failed to create directory {$tmpdir}");
|
||||||
}
|
}
|
||||||
if (!\is_writable($tmpdir)) {
|
if (!\is_writable($tmpdir)) {
|
||||||
throw new \Exception("Failed to access directory {$tmpdir}");
|
// throw new \Exception("Failed to access directory {$tmpdir}");
|
||||||
}
|
}
|
||||||
if ($prefix) {
|
if ($prefix) {
|
||||||
$this->filename = \tempnam($tmpdir, $name);
|
$this->filename = \tempnam($tmpdir, $name);
|
||||||
|
|
@ -42,10 +42,10 @@ class Temporary
|
||||||
private $fp = null;
|
private $fp = null;
|
||||||
public function fopen()/* : resource|false*/
|
public function fopen()/* : resource|false*/
|
||||||
{
|
{
|
||||||
if (!$fp) {
|
if (!$this->fp) {
|
||||||
$fp = \fopen($this->filename, 'r+b');
|
$this->fp = \fopen($this->filename, 'r+b');
|
||||||
}
|
}
|
||||||
return $fp;
|
return $this->fp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function writeFromStream(/*resource*/ $from)/* : int|false*/
|
public function writeFromStream(/*resource*/ $from)/* : int|false*/
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ class Certificate
|
||||||
{
|
{
|
||||||
public
|
public
|
||||||
$x509 = null,
|
$x509 = null,
|
||||||
|
$pkey = null,
|
||||||
|
|
||||||
$digest = 'sha256',
|
$digest = 'sha256',
|
||||||
$cipher = \OPENSSL_CIPHER_AES_256_CBC,
|
$cipher = \OPENSSL_CIPHER_AES_256_CBC,
|
||||||
$keyBits = 4096,
|
$keyBits = 4096,
|
||||||
|
|
@ -32,10 +34,17 @@ class Certificate
|
||||||
* A string having the format file://path/to/cert.pem; the named file must contain a PEM encoded certificate
|
* A string having the format file://path/to/cert.pem; the named file must contain a PEM encoded certificate
|
||||||
* A string containing the content of a certificate, PEM encoded, may start with -----BEGIN CERTIFICATE-----
|
* A string containing the content of a certificate, PEM encoded, may start with -----BEGIN CERTIFICATE-----
|
||||||
*/
|
*/
|
||||||
function __construct($x509cert = null)
|
function __construct($x509cert = null, $privateKey = null)
|
||||||
{
|
{
|
||||||
if ($x509cert) {
|
if ($x509cert) {
|
||||||
$this->x509 = \openssl_x509_read($x509cert);
|
$x509cert = \openssl_x509_read($x509cert);
|
||||||
|
if (!$x509cert) {
|
||||||
|
throw new \RuntimeException('OpenSSL x509: ' . \openssl_error_string());
|
||||||
|
}
|
||||||
|
$this->x509 = $x509cert;
|
||||||
|
if ($privateKey && \openssl_x509_check_private_key($this->x509, $privateKey)) {
|
||||||
|
$this->pkey = $privateKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ class OpenSSL
|
||||||
) : void
|
) : void
|
||||||
{
|
{
|
||||||
$this->private_key = \openssl_pkey_get_private($private_key, $passphrase);
|
$this->private_key = \openssl_pkey_get_private($private_key, $passphrase);
|
||||||
|
if (!$this->private_key) {
|
||||||
|
throw new \RuntimeException('OpenSSL setPrivateKey: ' . \openssl_error_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function decrypt(string $data, $certificate = null, $private_key = null) : ?string
|
public function decrypt(string $data, $certificate = null, $private_key = null) : ?string
|
||||||
|
|
@ -67,31 +70,35 @@ class OpenSSL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$output = new Temporary('smimeout-');
|
$output = new Temporary('smimeout-');
|
||||||
if (!$input->putContents($data) || !\openssl_pkcs7_sign(
|
if (!\openssl_pkcs7_sign(
|
||||||
$input->filename(),
|
$input->filename(),
|
||||||
$output->filename(),
|
$output->filename(),
|
||||||
$certificate ?: $this->certificate, // \openssl_pkey_get_public();
|
$certificate ?: $this->certificate, // \openssl_pkey_get_public();
|
||||||
$private_key ?: $this->private_key, // \openssl_pkey_get_private($private_key, ?string $passphrase = null);
|
$private_key ?: $this->private_key, // \openssl_pkey_get_private($private_key, ?string $passphrase = null);
|
||||||
$this->headers,
|
$this->headers,
|
||||||
\PKCS7_DETACHED, // | PKCS7_NOCERTS | PKCS7_NOATTR
|
\PKCS7_DETACHED | \PKCS7_BINARY, // | PKCS7_NOCERTS | PKCS7_NOATTR
|
||||||
$this->untrusted_certificates_filename
|
$this->untrusted_certificates_filename
|
||||||
)) {
|
)) {
|
||||||
return null;
|
throw new \RuntimeException('OpenSSL sign: ' . \openssl_error_string());
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
$body = $output->getContents();
|
$body = $output->getContents();
|
||||||
// The message returned by openssl contains both headers and body, so need to split them up
|
if (\preg_match('/\\.p7s"\R\R(.+?)------/s', $body, $match)) {
|
||||||
$parts = explode("\n\n", $body, 2);
|
return \trim($match[1]);
|
||||||
$this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;
|
}
|
||||||
$body = $parts[1];
|
|
||||||
*/
|
throw new \RuntimeException('OpenSSL sign: failed to find p7s');
|
||||||
return $output->getContents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verify(string $data, $signers_certificates_filename = null)
|
public function verify(string $data, $signers_certificates_filename = null)
|
||||||
{
|
{
|
||||||
$input = new Temporary('smimein-');
|
if (\is_string($input)) {
|
||||||
return $input->putContents($data) && true === \openssl_pkcs7_verify(
|
$input = new Temporary('smimein-');
|
||||||
|
if (!$input->putContents($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true === \openssl_pkcs7_verify(
|
||||||
$input->filename(),
|
$input->filename(),
|
||||||
$flags = 0,
|
$flags = 0,
|
||||||
$signers_certificates_filename ?: null,
|
$signers_certificates_filename ?: null,
|
||||||
|
|
|
||||||
|
|
@ -60,15 +60,15 @@
|
||||||
<span data-i18n="COMPOSE/BUTTON_MARK_AS_IMPORTANT"></span>
|
<span data-i18n="COMPOSE/BUTTON_MARK_AS_IMPORTANT"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="toggle: pgpSign, visible: canPgpSign">
|
<li data-bind="toggle: doSign, visible: canSign">
|
||||||
<a>
|
<a>
|
||||||
<i class="fontastic" data-bind="text: pgpSign() ? '☑' : '☐'"></i>
|
<i class="fontastic" data-bind="text: doSign() ? '☑' : '☐'"></i>
|
||||||
<span data-icon="✍" data-i18n="OPENPGP/LABEL_SIGN"></span>
|
<span data-icon="✍" data-i18n="OPENPGP/LABEL_SIGN"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="toggle: pgpEncrypt, visible: canPgpEncrypt">
|
<li data-bind="toggle: doEncrypt, visible: canEncrypt">
|
||||||
<a>
|
<a>
|
||||||
<i class="fontastic" data-bind="text: pgpEncrypt() || 'mailvelope' == viewArea() ? '☑' : '☐'"></i>
|
<i class="fontastic" data-bind="text: doEncrypt() || 'mailvelope' == viewArea() ? '☑' : '☐'"></i>
|
||||||
<span data-icon="🔒" data-i18n="OPENPGP/LABEL_ENCRYPT"></span>
|
<span data-icon="🔒" data-i18n="OPENPGP/LABEL_ENCRYPT"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -137,10 +137,10 @@
|
||||||
<div style="display:flex">
|
<div style="display:flex">
|
||||||
<div class="btn-group" style="flex-grow:1"></div>
|
<div class="btn-group" style="flex-grow:1"></div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn fontastic" data-bind="toggle: pgpSign, visible: canPgpSign, css: {'btn-success': pgpSign()}" data-i18n="[title]OPENPGP/LABEL_SIGN">
|
<a class="btn fontastic" data-bind="toggle: doSign, visible: canSign, css: {'btn-success': doSign()}" data-i18n="[title]OPENPGP/LABEL_SIGN">
|
||||||
✍
|
✍
|
||||||
</a>
|
</a>
|
||||||
<a class="btn fontastic" data-bind="toggle: pgpEncrypt, visible: canPgpEncrypt, css: {'btn-success': pgpEncrypt() || 'mailvelope' == viewArea()}" data-i18n="[title]OPENPGP/LABEL_ENCRYPT">
|
<a class="btn fontastic" data-bind="toggle: doEncrypt, visible: canEncrypt, css: {'btn-success': doEncrypt() || 'mailvelope' == viewArea()}" data-i18n="[title]OPENPGP/LABEL_ENCRYPT">
|
||||||
🔒
|
🔒
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -55,40 +55,37 @@
|
||||||
<input type="radio" name="identitytabs" id="tab-identity-crypto">
|
<input type="radio" name="identitytabs" id="tab-identity-crypto">
|
||||||
<label data-i18n="CONTACTS/TAB_CRYPTO" for="tab-identity-crypto" role="tab" aria-selected="false" aria-controls="panel3" tabindex="0"></label>
|
<label data-i18n="CONTACTS/TAB_CRYPTO" for="tab-identity-crypto" role="tab" aria-selected="false" aria-controls="panel3" tabindex="0"></label>
|
||||||
<div class="form-horizontal tab-content" role="tabpanel" aria-hidden="false">
|
<div class="form-horizontal tab-content" role="tabpanel" aria-hidden="false">
|
||||||
<details>
|
<div class="control-group">
|
||||||
<summary><strong>PGP</strong></summary>
|
<div data-bind="component: {
|
||||||
<div class="control-group">
|
name: 'Checkbox',
|
||||||
<div data-bind="component: {
|
params: {
|
||||||
name: 'Checkbox',
|
label: 'OPENPGP/LABEL_SIGN',
|
||||||
params: {
|
value: pgpSign,
|
||||||
label: 'OPENPGP/LABEL_SIGN',
|
name: 'pgpSign'
|
||||||
value: pgpSign,
|
}
|
||||||
name: 'pgpSign'
|
}"></div>
|
||||||
}
|
</div>
|
||||||
}"></div>
|
<div class="control-group">
|
||||||
</div>
|
<div data-bind="component: {
|
||||||
<div class="control-group">
|
name: 'Checkbox',
|
||||||
<div data-bind="component: {
|
params: {
|
||||||
name: 'Checkbox',
|
label: 'OPENPGP/LABEL_ENCRYPT',
|
||||||
params: {
|
value: pgpEncrypt,
|
||||||
label: 'OPENPGP/LABEL_ENCRYPT',
|
name: 'pgpEncrypt'
|
||||||
value: pgpEncrypt,
|
}
|
||||||
name: 'pgpEncrypt'
|
}"></div>
|
||||||
}
|
</div>
|
||||||
}"></div>
|
|
||||||
</div>
|
|
||||||
<!--
|
<!--
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label data-i18n="OPENPGP/LABEL_ENCRYPT"></label>
|
<label data-i18n="OPENPGP/LABEL_ENCRYPT"></label>
|
||||||
<select name="PgpEncrypt" data-bind="value: pgpEncrypt">
|
<select name="PgpEncrypt" data-bind="value: pgpEncrypt">
|
||||||
<option value="Ask" data-i18n="CONTACTS/ASK"></option>
|
<option value="Ask" data-i18n="CONTACTS/ASK"></option>
|
||||||
<option value="Never" data-i18n="CONTACTS/NEVER"></option>
|
<option value="Never" data-i18n="CONTACTS/NEVER"></option>
|
||||||
<option value="Always" data-i18n="CONTACTS/ALWAYS"></option>
|
<option value="Always" data-i18n="CONTACTS/ALWAYS"></option>
|
||||||
<option value="IfPossible" data-i18n="CONTACTS/ALWAYS_IF_POSSIBLE"></option>
|
<option value="IfPossible" data-i18n="CONTACTS/ALWAYS_IF_POSSIBLE"></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
</details>
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>S/MIME</strong></summary>
|
<summary><strong>S/MIME</strong></summary>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue