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

Jesse Bickel - NOAA Affiliate jesse.bickel@REDACTED
Wed Sep 22 00:21:41 CEST 2021


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).

> 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.

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.verburg@REDACTED> 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.andin@REDACTED> 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

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


More information about the erlang-questions mailing list