diff --git a/sunbeam-python/sunbeam/features/interface/utils.py b/sunbeam-python/sunbeam/features/interface/utils.py index 2463fb77b..cc8e34506 100644 --- a/sunbeam-python/sunbeam/features/interface/utils.py +++ b/sunbeam-python/sunbeam/features/interface/utils.py @@ -199,6 +199,13 @@ def generate_ca_chain(certificate: str, ca_certificate: str, ca_chain: str) -> s if not certificate_decoded or not ca_certificate_decoded or not ca_chain_decoded: raise binascii.Error("Unable to decode one of the certificates") + # Normalize line endings to LF to ensure consistent comparison and output. + # Certificates with CRLF line endings may otherwise be treated as different + # from equivalent certificates with LF line endings. + certificate_decoded = certificate_decoded.replace("\r\n", "\n") + ca_certificate_decoded = ca_certificate_decoded.replace("\r\n", "\n") + ca_chain_decoded = ca_chain_decoded.replace("\r\n", "\n") + # If ca_certificate is already part of ca_chain, do not add it to the final ca chain # manual-tls-certificates checks if the final ca_chain is in proper order and each # certificate is signed by the successor one. diff --git a/sunbeam-python/tests/unit/sunbeam/features/test_utils.py b/sunbeam-python/tests/unit/sunbeam/features/test_utils.py index 992ddb6ea..c24071cac 100644 --- a/sunbeam-python/tests/unit/sunbeam/features/test_utils.py +++ b/sunbeam-python/tests/unit/sunbeam/features/test_utils.py @@ -21,3 +21,29 @@ def test_generate_ca_chain(): ca_chain_decoded = decode_base64_as_string(ca_chain) expected_chain = cert1 + "\n" + cert2 + "\n" + cert3 assert ca_chain_decoded == expected_chain + + +def test_generate_ca_chain_deduplicates_ca_cert_with_crlf(): + """Test that ca_certificate with CRLF endings is not duplicated when + + ca_chain already contains an equivalent certificate with LF endings. + """ + cert1 = "CERT1" + # ca_certificate uses CRLF line endings + cert2_crlf = ( + "-----BEGIN CERTIFICATE-----\r\nISSUINGCA\r\n-----END CERTIFICATE-----\r\n" + ) + # ca_chain already contains the same cert with LF line endings + cert2_lf = "-----BEGIN CERTIFICATE-----\nISSUINGCA\n-----END CERTIFICATE-----\n" + cert3 = "-----BEGIN CERTIFICATE-----\nROOTCA\n-----END CERTIFICATE-----\n" + ca_chain_input = cert2_lf + "\n" + cert3 + + ca_chain = generate_ca_chain( + encode_base64_as_string(cert1), + encode_base64_as_string(cert2_crlf), + encode_base64_as_string(ca_chain_input), + ) + ca_chain_decoded = decode_base64_as_string(ca_chain) + # cert2 must appear only once in the output + assert ca_chain_decoded.count("ISSUINGCA") == 1 + assert ca_chain_decoded.count("ROOTCA") == 1