Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add intermediate certificates for S/MIME signing #1879

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion dev/Model/Identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class IdentityModel extends EmailModel /*AbstractModel*/ {

smimeKey: '',
smimeCertificate: '',
smimeCertificateChain: '',

askDelete: false,

Expand All @@ -33,7 +34,9 @@ export class IdentityModel extends EmailModel /*AbstractModel*/ {
addComputablesTo(this, {
smimeKeyEncrypted: () => this.smimeKey().includes('-----BEGIN ENCRYPTED PRIVATE KEY-----'),
smimeKeyValid: () => /^-----BEGIN (ENCRYPTED |RSA )?PRIVATE KEY-----/.test(this.smimeKey()),
smimeCertificateValid: () => /^-----BEGIN CERTIFICATE-----/.test(this.smimeCertificate())
smimeCertificateValid: () => /^-----BEGIN CERTIFICATE-----/.test(this.smimeCertificate()),
smimeCertificateChainValid: () => !this.smimeCertificateChain()
|| /^-----BEGIN CERTIFICATE-----/.test(this.smimeCertificateChain())
});
}

Expand Down
4 changes: 3 additions & 1 deletion dev/View/Popup/Compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,8 @@ export class ComposePopupView extends AbstractViewPopup {
key && options.push(['OpenPGP', key]);
key = GnuPGUserStore.getPrivateKeyFor(email, 1);
key && options.push(['GnuPG', key]);
identity.smimeKeyValid() && identity.smimeCertificateValid() && identity.email === email
identity.smimeKeyValid() && identity.smimeCertificateValid()
&& identity.smimeCertificateChainValid() && identity.email === email
&& options.push(['S/MIME']);
console.dir({signOptions: options});
this.signOptions(options);
Expand Down Expand Up @@ -1610,6 +1611,7 @@ export class ComposePopupView extends AbstractViewPopup {
// TODO: sign in PHP fails
params.sign = 'S/MIME';
// params.signCertificate = identity.smimeCertificate();
// params.signCertificateChain = identity.smimeCertificateChain();
// params.signPrivateKey = identity.smimeKey();
// params.attachCertificate = false;
if (identity.smimeKeyEncrypted()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,7 @@ private function buildMessage(Account $oAccount, bool $bWithDraftInfo = true) :
$oPart->SubParts->append($oSignaturePart);
} else {
$sCertificate = $this->GetActionParam('signCertificate', '');
$sCertificateChain = $this->GetActionParam('signCertificateChain', '');
$sPrivateKey = $this->GetActionParam('signPrivateKey', '');
if ('S/MIME' === $this->GetActionParam('sign', '')) {
$sID = $this->GetActionParam('identityID', '');
Expand All @@ -1263,6 +1264,7 @@ private function buildMessage(Account $oAccount, bool $bWithDraftInfo = true) :
&& ($oIdentity->Id() === $sID || $oIdentity->Email() === $oFrom->GetEmail())
) {
$sCertificate = $oIdentity->smimeCertificate;
$sCertificateChain = $oIdentity->smimeCertificateChain;
$sPrivateKey = $oIdentity->smimeKey;
break;
}
Expand Down Expand Up @@ -1303,6 +1305,7 @@ private function buildMessage(Account $oAccount, bool $bWithDraftInfo = true) :

$SMIME = $this->SMIME();
$SMIME->setCertificate($sCertificate);
$SMIME->setCertificateChain($sCertificateChain);
$SMIME->setPrivateKey($sPrivateKey, $oPassphrase);
$sSignature = $SMIME->sign($tmp, $detached);

Expand Down
6 changes: 5 additions & 1 deletion snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Identity implements \JsonSerializable

private ?SensitiveString $smimeKey = null;
private string $smimeCertificate = '';
private string $smimeCertificateChain = '';

function __construct(string $sId = '', string $sEmail = '')
{
Expand Down Expand Up @@ -114,6 +115,7 @@ public function FromJSON(array $aData, bool $bJson = false): bool
$this->pgpSign = !empty($aData['pgpSign']);
$this->smimeKey = new SensitiveString(isset($aData['smimeKey']) ? $aData['smimeKey'] : '');
$this->smimeCertificate = isset($aData['smimeCertificate']) ? $aData['smimeCertificate'] : '';
$this->smimeCertificateChain = isset($aData['smimeCertificateChain']) ? $aData['smimeCertificateChain'] : '';
return true;
}

Expand All @@ -136,7 +138,8 @@ public function ToSimpleJSON(): array
'pgpEncrypt' => $this->pgpEncrypt,
'pgpSign' => $this->pgpSign,
'smimeKey' => (string) $this->smimeKey,
'smimeCertificate' => $this->smimeCertificate
'smimeCertificate' => $this->smimeCertificate,
'smimeCertificateChain' => $this->smimeCertificateChain
);
}

Expand All @@ -158,6 +161,7 @@ public function jsonSerialize()
'pgpSign' => $this->pgpSign,
'smimeKey' => (string) $this->smimeKey,
'smimeCertificate' => $this->smimeCertificate,
'smimeCertificateChain' => $this->smimeCertificateChain,
'exists' => $this->exists
);
}
Expand Down
19 changes: 18 additions & 1 deletion snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class OpenSSL
// Used for sign and decrypt
private $certificate; // OpenSSLCertificate|array|string
private $privateKey; // OpenSSLAsymmetricKey|OpenSSLCertificate|array|string
private ?string $certificateChain = null;

function __construct(string $homedir)
{
Expand Down Expand Up @@ -131,6 +132,15 @@ public function setCertificate(/*OpenSSLCertificate|string*/$certificate)
}
}

public function setCertificateChain(/*string*/$certificateChain)
{
if ($certificateChain === "") {
$this->certificateChain = null;
} else {
$this->certificateChain = $certificateChain;
}
}

public function setPrivateKey(/*OpenSSLAsymmetricKey|string*/$privateKey,
?\SnappyMail\SensitiveString $passphrase = null
) : void
Expand Down Expand Up @@ -244,6 +254,13 @@ public function sign(/*string|Temporary*/$input, bool $detached = true)
}
$input = $tmp;
}
if (\is_string($this->certificateChain)) {
$tmp = new Temporary('smimechain-');
if (!$tmp->putContents($this->certificateChain)) {
return null;
}
$certificateChain = $tmp;
}
$output = new Temporary('smimeout-');
if (!\openssl_pkcs7_sign(
$input->filename(),
Expand All @@ -252,7 +269,7 @@ public function sign(/*string|Temporary*/$input, bool $detached = true)
$this->privateKey,
$this->headers,
$detached ? \PKCS7_DETACHED | \PKCS7_BINARY : 0, // | PKCS7_NOCERTS | PKCS7_NOATTR
$this->untrusted_certificates_filename
$certificateChain ?? null
)) {
throw new \RuntimeException('OpenSSL sign: ' . \openssl_error_string());
}
Expand Down
1 change: 1 addition & 0 deletions snappymail/v/0.0.0/app/localization/de/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@
"SMIME": {
"POPUP_IMPORT_TITLE": "S\/MIME-Zertifikat importieren",
"CERTIFICATE": "Zertifikat",
"CERTIFICATECHAIN": "Zwischenzertifikat(e)",
"CERTIFICATES": "S\/MIME-Zertifikate",
"SIGNED_MESSAGE": "S\/MIME-signierte Nachricht",
"ENCRYPTED_MESSAGE": "S\/MIME-verschlüsselte Nachricht",
Expand Down
1 change: 1 addition & 0 deletions snappymail/v/0.0.0/app/localization/en/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@
"SMIME": {
"POPUP_IMPORT_TITLE": "Import S\/MIME certificate",
"CERTIFICATE": "Certificate",
"CERTIFICATECHAIN": "Intermediate Certificate(s)",
"CERTIFICATES": "S\/MIME Certificates",
"SIGNED_MESSAGE": "S\/MIME signed message",
"ENCRYPTED_MESSAGE": "S\/MIME encrypted message",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ <h3 data-bind="visible: edit" data-i18n="POPUPS_IDENTITY/TITLE_UPDATE_IDENTITY">
<label data-i18n="SMIME/CERTIFICATE"></label>
<textarea name="smimeCertificate" class="input-xxlarge" rows="14" autofocus="" autocomplete="off" data-bind="value: smimeCertificate"></textarea>
</div>
<div class="control-group" data-bind="css: {'error': smimeCertificateChain() && !smimeCertificateChainValid()}">
<label data-i18n="SMIME/CERTIFICATECHAIN"></label>
<textarea name="smimeCertificateChain" class="input-xxlarge" rows="14" autofocus="" autocomplete="off" data-bind="value: smimeCertificateChain"></textarea>
</div>
<div class="control-group" data-bind="hidden:smimeCertificate">
<label></label>
<button type="button" data-bind="click: $root.createSelfSigned" data-i18n="CRYPTO/CREATE_SELF_SIGNED"></button>
Expand Down