cryptography icon indicating copy to clipboard operation
cryptography copied to clipboard

Signed S/MIME message has `CRLF` termination, whereas OpenSSL uses `LF`

Open facutuesca opened this issue 1 year ago • 0 comments

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 LF or CRLF terminations. On Windows, OpenSSL outputs CRLF terminations, meaning the output certificate has different terminations depending on the OS.

facutuesca avatar Feb 28 '24 16:02 facutuesca