How To fetch ssl subjectAltName (san) extension data in HAProxy?

Hello,

I am looking to fetch openssl subjectAltName (san) extension data from client side cert in HAProxy.
But I am not able to find any API in HAProxy to do so.

Could someone help how-to fetch ssl subjectAltName (san) extension data from cert in HAProxy using HAProxy API in LUA script?

The HAProxy APIs are present for cert’s DN and SNI fields only but not for cert’s subjectAltName (san) extension data.
Is there any workaround, please suggest.

Thanks,
Deepak

@thierry @lukastribus Please help with this problem statement.

@bwmetcalf @adkhare @ericm @enigmament @ia8Wet5U @mohamed.naseer @old_bear @covidium @sirhopcount @yanggis

Please help with this problem statement.

I looked into below haproxy code, but could not make out how to extract san extension through HAProxy API.
HAProxy san code

Apologies for tagging multiple contributors!

Gentle Reminder!

@thierry @lukastribus Please help!

I have seen this thread and I am not responding to it because I don’t know the answer to your question.

Now please stop pushing this thread.

Hi,

HAProxy doesn’t have any method to extract and return the subjectAltName.

However, you can try to retrieve the certificate with “ssl_f_der” (TXN.f.ssl_f_der()), and decode it with openssl.

I never tried this.

Note that the subjectAltName is an extension of the certificate, and maybe OpenSSL can’t provide method for decoding it. I wrote a python code for extracting it, maybe you can port this code to Lua.

There is the code:

   # Look for subjectAltName extension, and decode it.
   for i in range(0, cert.get_extension_count() - 1): 
      ext = cert.get_extension(i)
      if (ext.get_short_name() == 'subjectAltName'):
         b = ext.get_data() # 'b' like Buffer

         # decode the ASN.1 subjectAltName. I use the ASN.1 DER encoding specs:
         #
         #    http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
         #
         # This is the description in the RFC 5280:
         #
         #    id-ce OBJECT IDENTIFIER  ::=  {joint-iso-ccitt(2) ds(5) 29}
         #
         #    id-ce-subjectAltName OBJECT IDENTIFIER ::=  { id-ce 17 }
         #
         #    SubjectAltName ::= GeneralNames
         #
         #    GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
         #
         #    GeneralName ::= CHOICE {
         #         otherName                       [0]     OtherName,
         #         rfc822Name                      [1]     IA5String,
         #         dNSName                         [2]     IA5String,
         #         x400Address                     [3]     ORAddress,
         #         directoryName                   [4]     Name,
         #         ediPartyName                    [5]     EDIPartyName,
         #         uniformResourceIdentifier       [6]     IA5String,
         #         iPAddress                       [7]     OCTET STRING,
         #         registeredID                    [8]     OBJECT IDENTIFIER }
         #
         #    OtherName ::= SEQUENCE {
         #         type-id    OBJECT IDENTIFIER,
         #         value      [0] EXPLICIT ANY DEFINED BY type-id }
         #
         #    EDIPartyName ::= SEQUENCE {
         #         nameAssigner            [0]     DirectoryString OPTIONAL,
         #         partyName               [1]     DirectoryString }

         # first we have an sequence, so the 0x30 byte is expected.
         # 0x30 = 00 (universal) 1 (composed) 10000 (sequence)
         if (ord(b[0]) != 0x30):
            raise NameError("ASN.1 subjectAltName error: expect 0x30 sequence marker")

         # Now we read the length. <128 is the payload length, >128, is the number
         # of bytes composing the length (-128). Once the length computed, we
         # extract the sequence. The sequence is stored in the buffer 'b' (its overwrited).
         l = ord(b[1])
         if (l < 128):
            b = b[2:2 + l]
         else:
            nbytes =  l - 128 
            if (nbytes > 2): 
               raise NameError("ASN.1 subjectAltName error: unsupported sequence length over 2 bytes")
            l = 0
            for i in range(2, 2 + nbytes):
               l = l * 256
               l += ord(b[i])
            b = b[2 + nbytes:2 + nbytes + l]

         # Now we decode the sequence.
         i = 0
         while True:

            # We expect a choice "GeneralName".
            # This is a contextual value, so the code starts by 0x8 for the MSB.
            # We only support the 'dNSName' type. Other types are ignored.
            # So, dNSName = 0x80 + 0x02 => 0x82, but the values from 080 to 0x88
            # are correct. We support only dns names with length < 127, otherwise
            # an error is raised.
            if (ord(b[i]) < 0x80 or ord(b[i]) > 0x88):
               raise NameError("ASN.1 subjectAltName error: unexpected type")
            if (ord(b[i + 1]) > 127):
               raise NameError("ASN.1 subjectAltName error: unsupported GeneralName length")
            if (ord(b[i]) == 0x82):
               names.append(b[i + 2:i + 2 + ord(b[i + 1])])

            # Jump bytes to the next entry. Check the end of the string.
            i = i + 2 + ord(b[i + 1])
            if (i >= len(b)):
               break

         break

If you can do this, don’t hesitate to share your result.

Thierry