Skip to content

Commit 27e1ae5

Browse files
authored
Merge pull request #11118 from nextcloud/fix/smime/encoding
fix(smime): use proper binary encoding when signing messages
2 parents 556ab9f + 646bdae commit 27e1ae5

File tree

4 files changed

+61
-5
lines changed

4 files changed

+61
-5
lines changed

REUSE.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ SPDX-FileCopyrightText = "2024 Nextcloud GmbH and Nextcloud contributors"
108108
SPDX-License-Identifier = "AGPL-3.0-or-later"
109109

110110
[[annotations]]
111-
path = ["tests/data/imip/*"]
111+
path = ["tests/data/imip/*", "tests/data/html-with-signature.txt"]
112112
precedence = "aggregate"
113113
SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"
114114
SPDX-License-Identifier = "AGPL-3.0-or-later"

lib/Service/SmimeService.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -371,15 +371,14 @@ public function signMimePart(Horde_Mime_Part $part,
371371
file_put_contents($inPath, $part->toString([
372372
'canonical' => true,
373373
'headers' => true,
374+
'encode' => Horde_Mime_Part::ENCODE_8BIT,
374375
]));
375-
if (!openssl_pkcs7_sign($inPath, $outPath, $decryptedCertificate, $decryptedKey, null, PKCS7_DETACHED, $decryptedCertificateFile)) {
376+
if (!openssl_pkcs7_sign($inPath, $outPath, $decryptedCertificate, $decryptedKey, null, PKCS7_DETACHED | PKCS7_BINARY, $decryptedCertificateFile)) {
376377
throw new SmimeSignException('Failed to sign MIME part');
377378
}
378379

379380
try {
380-
$parsedPart = Horde_Mime_Part::parseMessage(file_get_contents($outPath), [
381-
'forcemime' => true,
382-
]);
381+
$parsedPart = Horde_Mime_Part::parseMessage(file_get_contents($outPath));
383382
} catch (Horde_Mime_Exception $e) {
384383
throw new SmimeSignException(
385384
'Failed to parse signed MIME part: ' . $e->getMessage(),

tests/Unit/Service/SmimeServiceTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,4 +457,34 @@ public function testParseCertificate(SmimeCertificate $certificate,
457457
$this->smimeService->parseCertificate($certificate->getCertificate()),
458458
);
459459
}
460+
461+
public function testSignMimePart(): void {
462+
$plainPart = Horde_Mime_Part::parseMessage(file_get_contents(__DIR__ . '/../../data/html-with-signature.txt'));
463+
$certificate = $this->getTestCertificate('[email protected]');
464+
465+
$this->crypto
466+
->method('decrypt')
467+
->will($this->returnArgument(0));
468+
$this->tempManager
469+
->method('getTemporaryFile')
470+
->willReturnCallback(function () {
471+
return $this->createTempFile();
472+
});
473+
474+
// Can't compare to serialized part as the boundaries inside the signed MIME message are
475+
// generated randomly each time. => Verify the signed part instead.
476+
$actualPart = $this->smimeService->signMimePart($plainPart, $certificate);
477+
$messageTemp = $this->createTempFile();
478+
file_put_contents($messageTemp, $actualPart->toString([
479+
'canonical' => true,
480+
'headers' => true,
481+
'encode' => Horde_Mime_Part::ENCODE_8BIT,
482+
]));
483+
$this->assertTrue(openssl_pkcs7_verify(
484+
$messageTemp,
485+
0,
486+
null,
487+
[__DIR__ . '/../../data/smime-certs/imap.localhost.ca.crt'],
488+
));
489+
}
460490
}

tests/data/html-with-signature.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Content-Type: multipart/alternative; boundary="=_OCjivB0eKOELIVVHw6KUaJG"
2+
MIME-Version: 1.0
3+
Content-Transfer-Encoding: 8bit
4+
5+
This message is in MIME format.
6+
7+
--=_OCjivB0eKOELIVVHw6KUaJG
8+
Content-Type: text/plain; charset=utf-8
9+
Content-Transfer-Encoding: 8bit
10+
11+
This is a test message ...
12+
 
13+
... with empty lines.
14+
--  
15+
This is a signature.
16+
 
17+
With rich text.
18+
--=_OCjivB0eKOELIVVHw6KUaJG
19+
Content-Type: text/html; charset=utf-8
20+
Content-Transfer-Encoding: 8bit
21+
22+
<!DOCTYPE html>
23+
<html><meta http-equiv="content-type" content="text/html; charset=UTF-8"><body>
24+
<p style="margin:0;">This is a test message ...</p><p style="margin:0;"> </p><p style="margin:0;">... with empty lines.</p><div class="signature">-- <p style="margin:0;"> </p><p style="margin:0;">This is a signature.</p><p style="margin:0;"> </p><p style="margin:0;"><strong>With</strong> <i>rich</i> <span style="color:hsl(0,75%,60%);">text</span>.</p></div>
25+
</body></html>
26+
27+
--=_OCjivB0eKOELIVVHw6KUaJG--

0 commit comments

Comments
 (0)