Is this a bad certificate? (Having nine-bit long key usage extension)

269 views
Skip to first unread message

Jesse Bickel - NOAA Affiliate

unread,
Sep 17, 2021, 12:24:07 PM9/17/21
to erlang-q...@erlang.org
Good morning,

This is part question and part potential bug report. I don't have a github
account associated with my workplace at the moment which is why I ask here.

When we tested an upgrade to rabbitmq, the tls v1.2 server rejected client
certificates with the following message: "In state certify at
ssl_handshake.erl:1975 generated SERVER ALERT: Fatal - Bad Certificate." The
issue happens with otp versions 24.0.4 and later. The earlier version 24.0.3
interoperates with the certificates successfully. After we took a closer look,
the issue has something to do with the key usage extension. If we generate a
similar certificate in all other respects but remove the key usage extension
otp does not reject the certificate. The issue also occurs without mutual
authentication as a CLIENT ALERT when an erlang/otp client validates the same
kind of server certificate.

The certificates that otp 24.0.4+ rejects were generated by gnutls certtool,
but more specifically the difference in the certificate itself is the length of
the bit string in the key usage extension. Both openssl and gnutls certtool in
their high-level text forms of the working and not working certificates show
the key usage as having the same two values of digital signature and key
encipherment. The difference underneath is that the one generated by gnutls
certtool version 3.6.9 (rejected by otp) is nine bits, value 101000000, whereas
the one generated by openssl 1.1.1f (accepted by otp) is three bits, value 101.
Both versions appear to be valid asn.1, several tools appear to see both
versions as valid certificates, and I cannot easily tell from the relevant ietf
and itu documents that either is invalid.

Below are sample certificate authority and leaf certificates that can reproduce
the issue in two local erlang/otp sessions in a typical mode of server-only
authentication with tls v1.2 during the handshake.

Is this a bug introduced in otp 24.0.4 or is this an invalid certificate?

Jesse Bickel

--
Contractor, ERT, Inc.
Federal Affiliation: NWC/OWP/NOAA/DOC

Sample certificate authority x.509 certificate:

-----BEGIN CERTIFICATE-----
MIIEMzCCApugAwIBAgIUcgjKevzc6MaGOyaSC0VdidlvgO4wDQYJKoZIhvcNAQEL
BQAwMTEXMBUGA1UEAxMORmFrZWRvbWFpbjIgQ0ExFjAUBgNVBAoTDUZha2UgRG9t
YWluIDIwHhcNMjEwOTE0MTgzNDI5WhcNMjIwOTE0MTgzNDMxWjAxMRcwFQYDVQQD
Ew5GYWtlZG9tYWluMiBDQTEWMBQGA1UEChMNRmFrZSBEb21haW4gMjCCAaIwDQYJ
KoZIhvcNAQEBBQADggGPADCCAYoCggGBALLOuMsXBwU57q2cauVHr1zUneJ3KKt7
6H8RzZJB/yoltld/9RjGeQGZ3EfeLb1dm9Gag2AwTBRzrnYIcQz215ABLfcdMYyo
B3UE4rU+E3N9XMmGAEWbQh3lAprYeUnKbt7yjKOuUrQvfNtHhvNOpDQjg2VRbYnx
TQS6Go/ycRSywoBKSlr3G/sZ0pKt+8BumfMfIZZtPQw9j46+VibBW9UyQZJ7iBUH
q1AXT8XiFtAzVq33dwb+B5gd1QVrIu1vIzyGQt5dliEHHfJcdksmZBLru1GR6uzF
bndg3Y9MyYj5YQ+6M9wtcLKgJwHHfLgb30FPmY5a945tSHL9cv38sk11o7zgI6Vn
d+KhHxFRs8g0dBq8/ZKXF4UA7RxihXAB/SwVW4XVgpMGCMh/hrDzqKS/oexuOt8n
NDumTd2NYsUx06+uV/lc405DGmnF0cvCsV+3wulBs09lUsQlwFvFhU3z2pDSWWPs
8qCwcPn1WKBx1bZvBMZzys8qPc6NP4sjIQIDAQABo0MwQTAPBgNVHRMBAf8EBTAD
AQH/MA8GA1UdDwEB/wQFAwMHhAAwHQYDVR0OBBYEFFJsVCuIulfHcznrk/ALXKSn
Q+9IMA0GCSqGSIb3DQEBCwUAA4IBgQA6/WBJ5IYfcvxQnMeCQI2ewRiMxEQQxqFX
E9aXJDq5ohhY+lquWcMD8+yNAQbsE2Sw3KQppelEvN1aLgDwW8e7LR5Dw7SLtwZB
tFtWx2eJoqjuTLoxhjnWaR3A50YUgjNOiV5UIV5E8A1f1wsJcdM2VobQgdNyfvnm
vDdHKaGP9+TfmrdGimVwVLmQCtXzp62pyIkJbc8lOOGifCNzDmRgl1ehsEYn4T/M
NWxan3mRGLBjGn90ojxAMBXASB6BrQNa4Kvrs0bzyUToBPHMeBWsYA6SlyfLMN9L
svajh99xEYN63+AVNpZMDIBe4Dhmw8dTU8z1beDJqdODG8+Krmp58KwTAZQYGp6o
iyA5W1PmEms3Our9pBoJZ6guE8qMMxo6th+CwXsOQQLcmUIienLfFv6x1hZidN6G
bTmD2GOK25J/2zXlEXQuT4HNZfDuIh2IBxlo607ct72Dq9OGENLL6ujGhzU2XL6J
/TM/05Gfa+buDfysohjCSbtAIqvolr8=
-----END CERTIFICATE-----

Sample leaf certificate signed by above certificate authority:

-----BEGIN CERTIFICATE-----
MIIEfDCCAuSgAwIBAgIUdcWg+bxobl7OWSOjbNxyaXTnqx8wDQYJKoZIhvcNAQEL
BQAwMTEXMBUGA1UEAxMORmFrZWRvbWFpbjIgQ0ExFjAUBgNVBAoTDUZha2UgRG9t
YWluIDIwHhcNMjEwOTE0MTgzOTA0WhcNMjIwOTEzMTgzOTA2WjAsMRIwEAYDVQQD
Ewlsb2NhbGhvc3QxFjAUBgNVBAoTDUZha2UgRG9tYWluIDIwggGiMA0GCSqGSIb3
DQEBAQUAA4IBjwAwggGKAoIBgQDcNlMZrrMMWS5mKBCZKegx78hQutjQN4BEWsnK
WNPEpgtBV6H7Ihk43Jn19EArCujglZfI5sdV9il/8uJFB9DplICnPvet6HvspxLa
4hLFm+dI1Av6MglB6vaJInEXK6zVM39NMJVAy7LcryJYAhVMIuC4wTUOHRqAgx8o
w6dtb7fKSV3OTN3UmGJRPnzGULR3wc8yjGCXnPgu0D497w2FoZ4IWU2H/T+djQDx
i8giZ7KV+11XHfm3ZEsukutJIwsehP3XmCrvttZz8oQuNzJvA05BYRmgOkc7rJeW
ioXL8SC3rMeN4tZHPOd1O6QMVA3IqRGqoobwvt6HKKgE5LWw8GMG5HBDUFuYFDWY
cNkNObTM+x06WVoOKqjDUMb0x+clZwHSclTbEACzxT+iv3hh7+zQ9/0Z7BgYKxfN
QQgtDxIvguFI5O/BKzWAeBY24InHyBphprXmAL+q79hqjpt6Diaz8GEY7qHJcvss
npx7Q9HWqPd+GsPMBjGHRmy6Wk0CAwEAAaOBkDCBjTAUBgNVHREEDTALgglsb2Nh
bGhvc3QwDAYDVR0TAQH/BAIwADAPBgNVHQ8BAf8EBQMDB6AAMBYGA1UdJQEB/wQM
MAoGCCsGAQUFBwMBMB0GA1UdDgQWBBSblx0hTlJdxGO9+sAQnevrQUtX+jAfBgNV
HSMEGDAWgBRSbFQriLpXx3M565PwC1ykp0PvSDANBgkqhkiG9w0BAQsFAAOCAYEA
fHfAPrLNL2oChRjhS5h7twylgoxlAL06CITEwQtRSbdoP/g+XUdv/CFp7fqE9rKz
XNrtgM6KSgK7N7riYojCheEo/76Jk+PHPljzCrv9BoX6K44liU1Nf2IXIAG2+llY
aDmMeZPFI3pZ92oFCmKmzMvCaf/idOqNFihS/hBNXfbczuROnAuw/zip1QItnCJt
YTk1Og7BMjF3LNQYZRwz3KuG/fyDJx7DMrKL+EOsOmebFnIqiM0Em0TGIzdY2aTG
3hDbMUQz82R/8kO5d9keIGiL4pXp1gGDf8TdK7cEDblcwt1cyNX6jm6F1+qGWgCw
EbnNn2MjpdBD0BDNluQaysQb6KDomv2HHhwDtB6RDM26GN3uUyZfPDzvzH8TmNbH
UIymPlYs9XHlvEYJeeu9shd9hLegAidWHBs31eUKZaURJrsHd110iGhVpGEjvdvS
+YxkNsVwgmiPjJpjxsHOABdwpFBO54F8ayEiYDiLhboXEHJj0ju57TMstzcOmQBL
-----END CERTIFICATE-----

Ingela Andin

unread,
Sep 21, 2021, 1:28:27 AM9/21/21
to Erlang-Questions Questions


Hi!

Are you sure you got your configuration correct?   I could decode both these certificates using  OTP-24.0.4.
Looking at your error row 1975 in ssl_handshake is this clause:

path_validation_alert({bad_cert, invalid_signature}) ->
    ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE);

This indicates that we could not find a validate the certificate chain and that a signature check failed.
We will enhance the alert creation with extra info for the log message (as done in many other places).

Could you create dummy certs with a dummy key that fails so that I can recreate your scenario?
You can send them to ing...@erlang.org

Regards Ingela - Erlang/OTP team - Ericsson AB

Ingela Andin

unread,
Sep 21, 2021, 3:48:42 AM9/21/21
to Jesse Bickel - NOAA Affiliate, Erlang-Questions Questions
Hello again! 

I thought of a possible cause  of your strange problem. If the ROOT-cert is sent as part of the chain (optional in the protocol).  We must check that it is a certificate that we trust, part of our "trust store". This was done by  a match of the DER-version of the cert and we should match the decode  structure instead. 

In such cases the following patch ought to solve your problem.  Can you please test it? It is based on the latest maint!


diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index cbeb4e4521..1a277964b6 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -596,13 +596,13 @@ path_candidate(Cert, ChainCandidateCAs, CertDbHandle) ->
             [Root | lists:reverse(Chain)]
     end.
 
-handle_partial_chain([#cert{der=DERIssuerCert, otp=OtpIssuerCert}=Cert| Rest] = Path, PartialChainHandler,
+handle_partial_chain([#cert{otp=OtpIssuerCert}=Cert| Rest] = Path, PartialChainHandler,
                      CertDbHandle, CertDbRef) ->
     case public_key:pkix_is_self_signed(OtpIssuerCert) of
         true -> %% IssuerCert = ROOT (That is ROOT was included in chain)
             {ok, {SerialNr, IssuerId}} = public_key:pkix_issuer_id(OtpIssuerCert, self),
             case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, IssuerId) of
-                {ok, #cert{der=DERIssuerCert}} -> %% Match sent ROOT to trusted ROOT
+                {ok, #cert{otp = OtpIssuerCert}} -> %% Match sent ROOT to trusted ROOT
                     maybe_shorten_path(Path, PartialChainHandler, {Cert, Rest});
                 {ok, _} -> %% Did not match trusted ROOT
                     maybe_shorten_path(Path, PartialChainHandler, {invalid_issuer, Path});


Regards Ingela Erlang/OTP team- Ericsson AB


Den fre 17 sep. 2021 kl 18:24 skrev Jesse Bickel - NOAA Affiliate <jesse....@noaa.gov>:

Bram Verburg

unread,
Sep 21, 2021, 4:26:28 AM9/21/21
to Ingela Andin, Jesse Bickel - NOAA Affiliate, Erlang-Questions Questions
Hi Ingela,

I was looking into this too, and I noticed that this particular leaf certificate, once decoded as an OTPCertificate record, does not encode back to the same DER value (which is likely due to the unusual representation of the KeyUsage extension in the original file):

1> {ok, PEM} = file:read_file("cert.pem").                                                     
{ok,<<"-----BEGIN CERTIFICATE-----\nMIIEfDCCAuSgAwIBAgIUdcWg+bxobl7OWSOjbNxyaXTnqx8wDQYJKoZIhvcNAQEL\nBQAwMTEXMBUGA1U"...>>}
2> DER = element(2, hd(public_key:pem_decode(PEM))).
<<48,130,4,124,48,130,2,228,160,3,2,1,2,2,20,117,197,160,
  249,188,104,110,94,206,89,35,163,108,220,...>>
3> DER = public_key:pkix_encode('OTPCertificate', public_key:pkix_decode_cert(DER, otp), otp).
** exception error: no match of right hand side value <<48,130,4,123,48,130,2,227,160,3,2,1,2,2,20,117,197,160,
                                                        249,188,104,110,94,206,89,35,163,108,220,...>>

(That would presumably mean the original DER representation is not really proper DER, as I believe ASN.1's DER encoding is supposed to always produce the same (canonical) byte sequence, in order to allow for reliable signature verification...)

I haven't quite pinned it down, but this commit https://github.com/erlang/otp/pull/4941/commits/80f9b71323c9c5a3bc111fcbc5c6c5ee497e27a6 that was included in 24.0.4 seems to sometimes reconstruct a DER representation from an OTPCertificate record, and if that DER value is passed to public_key:pkix_verify/2 instead of the original DER representation, the signature won't match and ssl will throw the reported error.

Hope that might help,

Bram

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐

Kenneth Lundin

unread,
Sep 21, 2021, 5:15:10 AM9/21/21
to Bram Verburg, Jesse Bickel - NOAA Affiliate, Erlang-Questions Questions
In this case it is the encoding of KeyUsage which is the problem, it is questionable if it is correctly encoded by GnuTLS.

It is declared like this:
KeyUsage ::= BIT STRING {
     digitalSignature        (0),
     nonRepudiation          (1),
     keyEncipherment         (2),
     dataEncipherment        (3),
     keyAgreement            (4),
     keyCertSign             (5),
     cRLSign                 (6),
     encipherOnly            (7),
     decipherOnly            (8) }

And encoded like this by GnuTLS (as I understand it from Jesses initial description)
<<3,3,7,16#A0,0>> with the interpretation of bytes and bits like this:
Byte 0: Value 3:  The UNIVERSAL tag for BIT STRING
Byte 1: Value 3: Length = 3
Byte 2: Value 7: Number of unused bits in last octet = 7
Byte 3: Value 16#A0: (2#10100000) The first 8 bits of the bitstring with bit (0) digitalSignature and bit (2) keyEncipherment set
Byte 4: Value 0: The first bit is zero and the 7 remaining bits are unused

If we read the standard docs below I interpret this as 1) we have a BIT STRING with a NamedBitList (22.7 in X.680 applies) and then we apply 11.2.1 and especially 11.2.2 from X.690 which says that trailing zero bits should be removed before encoding which I don't think has been done in the above case.

So I think GnuTLS might be wrong here in that it does not encode according to DER.

The encoding that Erlang/OTP is using which I claim is according to DER is like this:
<<3,2,5,16#A0>> with the interpretation of bytes and bits like this:
Byte 0: Value 3:  The UNIVERSAL tag for BIT STRING
Byte 1: Value 2: Length = 2
Byte 2: Value 5: Number of unused bits in last octet = 5
Byte 3: Value 16#A0: (2#10100000) The first 3 bits of the bitstring with bit (0) digitalSignature and bit (2) keyEncipherment set
                        , the remaining 5 bits are unused.
This encoding is removing all trailing zero bits which make the encoding use as few bytes as possible

From the standard in X.690 and X.680 we have :

X.690
11.2 Unused bits
11.2.1 Each unused bit in the final octet of the encoding of a bit string value shall be set to zero.

11.2.2 Where Rec. ITU-T X.680 | ISO/IEC 8824-1, 22.7, applies, the bitstring shall have all trailing 0 bits removed before
it is encoded.

NOTE 1 In the case where a size constraint has been applied, the abstract value delivered by a decoder to the application will be on e
of those satisfying the size constraint and differing from the transmitted value only in the number of trailing 0 bits.

NOTE 2 If a bitstring value has no 1 bits, then an encoder shall encode the value with a length of 1 and an initial octet set to 0


X.680
22.7 When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
ISO/IEC 8824-1:2021 (E)
Rec. ITU-T X.680 (02/2021) 43

therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
0 bits.

This is just one case where the intention with DER does not hold since many certificates created also with OpenSSL does not follow the standard (The ASN.1 specs) for example when it comes to the encoding of CountryName (there are a few other data fields more which are problematic). The CountryName is sometimes encoded as Printable STRING and sometimes as UTF8 STRING and the length constraint of 2
is also violated in some occasions. As the UNIVERSAL Tag for these types differ the encoded bytes will be different.
The Erlang/OTP SSL implementation have been forced to accept those "non confirming to standard" encoding because otherwise the interoperability with other servers and clients would have been to limited.

The solution is to never trust that a certificate which is decoded up to its internal Erlang representation and then encoded back again will result in exactly the same sequence of bytes as in the original.

/Kenneth

Ingela Andin

unread,
Sep 21, 2021, 11:37:39 AM9/21/21
to Ingela Andin, Jesse Bickel - NOAA Affiliate, Erlang-Questions Questions
Hi Jesse!

I created  https://github.com/IngelaAndin/otp/pull/new/ingela/ssl/cert-encode-decode-issue that hopefully will solve the problem that you are having by avoiding to re-encode a receive cert. 
Please try that instead of my original patch, that case of comparison I think should actually work.  

Regards Ingela Erlang/OTP team - Ericsson AB

Den tis 21 sep. 2021 kl 12:59 skrev Ingela Andin <ing...@andin.se>:

Hi Bram!

Interesting observation!  I will keep digging. I actually believe my first patch solution will not fix the problem as although this code was changed in the version at hand, I checked further and the change should be backwards compatible.

Regards Ingela

On Tue, Sep 21, 2021 at 10:26 AM Bram Verburg <bram.v...@voltone.net> wrote:

Ingela Andin

unread,
Sep 22, 2021, 6:28:53 AM9/22/21
to Jesse Bickel - NOAA Affiliate, Kenneth Lundin, Erlang-Questions Questions

Hi  Jesse!

Thank you very much for sending me the test data. I now know why it used to work and is no longer working.  Indeed it has to do with re-encoding a decode certificate however the place that is failing is not in the ssl application code but in the public_key applications code, although it is a change in the ssl application that causes the situation. Even if the root cause is that the certificate is wrongly encoded in the first place.

So the first patch I made should not be needed as in this case we are verifying that a possible sent ROOT cert matches the ROOT cert that we trust. And if it is the same they will have the same possible incorrect DER encoding protected by the signature and they will match.  That is we have not performed an decode and then encoded it back again.

My PR, we will want to have to be able to tolerate such certificates (in a different scenario than yours)  but we will need to add some additional commit to handle your case.  So the change that made it fail is optimization effort to avoid decoding certs multiple times.  Although, I realized when looking into this issue, actually  instead creates a need for encoding the certs again.  So before the public_key application got the certs in DER and now they get them decoded. I think the "sane" thing will be to allow public_key to also accept a list with both encoded (as received from peer) and decoded.  This will avoid all extra encode and decode needs, and allow for tolerance of some decoding errors in certificates that can be "worked around". 

Always these interop tradeoffs, well this will be best for performance also. I will add it to my PR. 

Regards Ingela Erlang/OTP team - Ericsson AB



Den ons 22 sep. 2021 kl 00:21 skrev Jesse Bickel - NOAA Affiliate <jesse....@noaa.gov>:
I apologize for the delayed response. Thank you all for working on this issue even though it may be more of a gnutls issue.


Kenneth Lundin wrote:
> In this case it is the encoding of KeyUsage which is the problem, it is questionable if it is correctly encoded by GnuTLS.
>
> It is declared like this:
> KeyUsage ::= BIT STRING {
>      digitalSignature        (0),
>      nonRepudiation          (1),
>      keyEncipherment         (2),
>      dataEncipherment        (3),
>      keyAgreement            (4),
>      keyCertSign             (5),
>      cRLSign                 (6),
>      encipherOnly            (7),
>      decipherOnly            (8) }
>
> And encoded like this by GnuTLS (as I understand it from Jesses initial description)
> <<3,3,7,16#A0,0>> with the interpretation of bytes and bits like this:
> Byte 0: Value 3:  The UNIVERSAL tag for BIT STRING
> Byte 1: Value 3: Length = 3
> Byte 2: Value 7: Number of unused bits in last octet = 7
> Byte 3: Value 16#A0: (2#10100000) The first 8 bits of the bitstring with bit (0) digitalSignature and bit (2) keyEncipherment set
> Byte 4: Value 0: The first bit is zero and the 7 remaining bits are unused
>
> If we read the standard docs below I interpret this as 1) we have a BIT STRING with a NamedBitList (22.7 in X.680 applies) and then we apply 11.2.1 and
> especially 11.2.2 from X.690 which says that trailing zero bits should be removed before encoding which I don't think has been done in the above case.
>
> So I think GnuTLS might be wrong here in that it does not encode according to DER.

Yes, this is consistent with what I see. Gnutls keeps the trailing zero bits while openssl strips the trailing zero bits. But both tolerate each others' differing representations (for better or worse).
I am not as familiar with interpreting the x.680/x.690 documents but I think you make a strong case. It would make sense to have exactly one correct encoding of a certificate such that one always gets exactly the same signature, for example.


> The solution is to never trust that a certificate which is decoded up to its internal Erlang representation and then encoded back again will result in
> exactly the same sequence of bytes as in the original.
>
> /Kenneth
>
>  Bram Verburg <bram.v...@voltone.net> wrote:
>
>  Hi Ingela,
>
>  I was looking into this too, and I noticed that this particular leaf certificate, once decoded as an OTPCertificate record, does not encode back to
>  the same DER value (which is likely due to the unusual representation of the KeyUsage extension in the original file):
>
>  1> {ok, PEM} = file:read_file("cert.pem").                                                     
>  {ok,<<"-----BEGIN CERTIFICATE-----\nMIIEfDCCAuSgAwIBAgIUdcWg+bxobl7OWSOjbNxyaXTnqx8wDQYJKoZIhvcNAQEL\nBQAwMTEXMBUGA1U"...>>}
>  2> DER = element(2, hd(public_key:pem_decode(PEM))).
>  <<48,130,4,124,48,130,2,228,160,3,2,1,2,2,20,117,197,160,
>    249,188,104,110,94,206,89,35,163,108,220,...>>
>  3> DER = public_key:pkix_encode('OTPCertificate', public_key:pkix_decode_cert(DER, otp), otp).
>  ** exception error: no match of right hand side value <<48,130,4,123,48,130,2,227,160,3,2,1,2,2,20,117,197,160,
>                                                          249,188,104,110,94,206,89,35,163,108,220,...>>
>
>  (That would presumably mean the original DER representation is not really proper DER, as I believe ASN.1's DER encoding is supposed to always produce
>  the same (canonical) byte sequence, in order to allow for reliable signature verification...)
>
>  I haven't quite pinned it down, but this commit https://github.com/erlang/otp/pull/4941/commits/80f9b71323c9c5a3bc111fcbc5c6c5ee497e27a6 that was
>  included in 24.0.4 seems to sometimes reconstruct a DER representation from an OTPCertificate record, and if that DER value is passed to
>  public_key:pkix_verify/2 instead of the original DER representation, the signature won't match and ssl will throw the reported error.
>
>  Hope that might help,
>
>  Bram
>
>
>  Ingela Andin <ingela...@gmail.com> wrote:
>
>  Hello again!
>
>  I thought of a possible cause  of your strange problem. If the ROOT-cert is sent as part of the chain (optional in the protocol).  We must check
>  that it is a certificate that we trust, part of our "trust store". This was done by  a match of the DER-version of the cert and we should match
>  the decode  structure instead.
>
>  In such cases the following patch ought to solve your problem.  Can you please test it? It is based on the latest maint!

I tried this to no avail but we see there is a later patch at https://github.com/erlang/otp/pull/5222 which I assume is related. When I tried this patch a few minutes ago, I still get this:
** exception error: no match of right hand side value {error,{tls_alert,{bad_certificate,"TLS client: In state certify at ssl_handshake.erl:1989 generated CLIENT ALERT: Fatal - Bad Certificate\n"}}}

Ingela, I directly emailed you the dummy/throwaway/test key corresponding to the leaf certificate that I posted earlier and also the commands my teammate found to help reproduce the issue. I hope this helps, thank you for your help too.

Best,

Jesse Bickel - NOAA Affiliate

unread,
Sep 22, 2021, 7:22:13 AM9/22/21
to Kenneth Lundin, Jesse Bickel - NOAA Affiliate, Erlang-Questions Questions
I apologize for the delayed response. Thank you all for working on this issue even though it may be more of a gnutls issue.

Kenneth Lundin wrote:
> In this case it is the encoding of KeyUsage which is the problem, it is questionable if it is correctly encoded by GnuTLS.
>
> It is declared like this:
> KeyUsage ::= BIT STRING {
> digitalSignature (0),
> nonRepudiation (1),
> keyEncipherment (2),
> dataEncipherment (3),
> keyAgreement (4),
> keyCertSign (5),
> cRLSign (6),
> encipherOnly (7),
> decipherOnly (8) }
>
> And encoded like this by GnuTLS (as I understand it from Jesses initial description)
> <<3,3,7,16#A0,0>> with the interpretation of bytes and bits like this:
> Byte 0: Value 3: The UNIVERSAL tag for BIT STRING
> Byte 1: Value 3: Length = 3
> Byte 2: Value 7: Number of unused bits in last octet = 7
> Byte 3: Value 16#A0: (2#10100000) The first 8 bits of the bitstring with bit (0) digitalSignature and bit (2) keyEncipherment set
> Byte 4: Value 0: The first bit is zero and the 7 remaining bits are unused
>
> If we read the standard docs below I interpret this as 1) we have a BIT STRING with a NamedBitList (22.7 in X.680 applies) and then we apply 11.2.1 and
> especially 11.2.2 from X.690 which says that trailing zero bits should be removed before encoding which I don't think has been done in the above case.
>
> So I think GnuTLS might be wrong here in that it does not encode according to DER.

Yes, this is consistent with what I see. Gnutls keeps the trailing zero bits while openssl strips the trailing zero bits. But both tolerate each others' differing representations (for better or worse).

I am not as familiar with interpreting the x.680/x.690 documents but I think you make a strong case. It would make sense to have exactly one correct encoding of a certificate such that one always gets exactly the same signature, for example.

> The solution is to never trust that a certificate which is decoded up to its internal Erlang representation and then encoded back again will result in
> exactly the same sequence of bytes as in the original.
>
> /Kenneth
>
> Bram Verburg <bram.v...@voltone.net> wrote:
>
> Hi Ingela,
>
> I was looking into this too, and I noticed that this particular leaf certificate, once decoded as an OTPCertificate record, does not encode back to
> the same DER value (which is likely due to the unusual representation of the KeyUsage extension in the original file):
>
> 1> {ok, PEM} = file:read_file("cert.pem").
> {ok,<<"-----BEGIN CERTIFICATE-----\nMIIEfDCCAuSgAwIBAgIUdcWg+bxobl7OWSOjbNxyaXTnqx8wDQYJKoZIhvcNAQEL\nBQAwMTEXMBUGA1U"...>>}
> 2> DER = element(2, hd(public_key:pem_decode(PEM))).
> <<48,130,4,124,48,130,2,228,160,3,2,1,2,2,20,117,197,160,
> 249,188,104,110,94,206,89,35,163,108,220,...>>
> 3> DER = public_key:pkix_encode('OTPCertificate', public_key:pkix_decode_cert(DER, otp), otp).
> ** exception error: no match of right hand side value <<48,130,4,123,48,130,2,227,160,3,2,1,2,2,20,117,197,160,
> 249,188,104,110,94,206,89,35,163,108,220,...>>
>
> (That would presumably mean the original DER representation is not really proper DER, as I believe ASN.1's DER encoding is supposed to always produce
> the same (canonical) byte sequence, in order to allow for reliable signature verification...)
>
> I haven't quite pinned it down, but this commit https://github.com/erlang/otp/pull/4941/commits/80f9b71323c9c5a3bc111fcbc5c6c5ee497e27a6 that was
> included in 24.0.4 seems to sometimes reconstruct a DER representation from an OTPCertificate record, and if that DER value is passed to
> public_key:pkix_verify/2 instead of the original DER representation, the signature won't match and ssl will throw the reported error.
>
> Hope that might help,
>
> Bram
>
>
> Ingela Andin <ingela...@gmail.com> wrote:
>
> Hello again!
>
> I thought of a possible cause of your strange problem. If the ROOT-cert is sent as part of the chain (optional in the protocol). We must check
> that it is a certificate that we trust, part of our "trust store". This was done by a match of the DER-version of the cert and we should match
> the decode structure instead.
>
> In such cases the following patch ought to solve your problem. Can you please test it? It is based on the latest maint!

I tried this to no avail but we see there is a later patch at https://github.com/erlang/otp/pull/5222 which I assume is related. When I tried this patch a few minutes ago, I still get this:
** exception error: no match of right hand side value {error,{tls_alert,{bad_certificate,"TLS client: In state certify at ssl_handshake.erl:1989 generated CLIENT ALERT: Fatal - Bad Certificate\n"}}}

Ingela, I directly emailed you the dummy/throwaway/test key corresponding to the leaf certificate that I posted earlier and also the commands my teammate found to help reproduce the issue. I hope this helps, thank you for your help too.

Best,

Reply all
Reply to author
Forward
0 new messages