Signed S/MIME message has `CRLF` termination, whereas OpenSSL uses `LF`
Description
Signed S/MIME message using cryptography:
b'MIME-Version: 1.0\r\nContent-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha-512"; boundary="===============3031221955182774630=="\r\n\r\nThis is an S/MIME signed message\r\n\r\n....'
Signed S/MIME message using OpenSSL 3.2.0:
b'MIME-Version: 1.0\nContent-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha-256"; boundary="----E9313AF891A4913F5C5CCBBD0E0E0B80"\n\nThis is an S/MIME signed message\n\n...'
The cryptography output has CRLF line endings, while the OpenSSL output has LF line endings.
How to reproduce
Here is code to generate the outputs:
For OpenSSL:
$ echo "message" > message.txt
$ openssl req -x509 -newkey rsa:4096 -keyout signer_key.pem -out signer.pem -sha256 -days 365 -nodes -subj "/C=US/ST=AAA/L=BBB/O=CCC/CN=www.example.com"
$ openssl smime -sign -in message.txt -text -signer signer.pem -inkey signer_key.pem -out out.txt
For cryptography:
from cryptography.hazmat.primitives.serialization import load_pem_private_key, pkcs7, Encoding
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.x509 import load_pem_x509_certificate
with open('signer_key.pem', 'rb') as key_data:
key = load_pem_private_key(key_data.read(), password=None)
with open('signer.pem', 'rb') as cert_data:
cert = load_pem_x509_certificate(cert_data.read())
with open('message.txt', 'rb') as data:
output = pkcs7.PKCS7SignatureBuilder().set_data(
b'message'
).add_signer(
cert, key, hashes.SHA512(), rsa_padding=padding.PKCS1v15()
).sign(
Encoding.SMIME, [pkcs7.PKCS7Options.DetachedSignature, pkcs7.PKCS7Options.Text]
)
print(output)
Extra context
When encoding a signed PKCS7/SMIME message, OpenSSL usually “canonicalizes” the data to be signed by using CRLF as EOL. The hash is then computed over the canonicalized data. Having done that, it emits the signed data with all the corresponding headers:
$ openssl smime -sign -in message.txt -text -signer signer.pem -inkey signer_key.pem
MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha-256"; boundary="----7908462305F5532112445928685D0F77"
This is an S/MIME signed message
.....
(signed data)
.....
Note, however, that the output itself has normal LF terminations. The LF->CRLF transformation was only applied to the input of the hash function, not to the rest of the message.
The inconsistency I noticed is that cryptography not only (correctly) canonicalizes the input data to the hash function, but it also emits the entire message using CRLF.
The end result is that OpenSSL outputs messages with LF termination, whereas cryptography outputs them with CRLF instead.
TODO
- [x] See if running the same OpenSSL command on Windows outputs
LForCRLFterminations. On Windows, OpenSSL outputs CRLF terminations, meaning the output certificate has different terminations depending on the OS.