[stunnel-users] updated patch for Start TLS for LDAP clients (RFC 2830)

Seth Grover Seth.D.Grover at gmail.com
Tue Dec 3 22:29:51 CET 2019


Back in 2013 Bart Dopheide submitted a patch to the mailing list to add
LDAP StartTLS (elevate connection to TLS after initial connection is
initiated) support to the list of supported protocols in protocol.c (
https://www.stunnel.org/pipermail/stunnel-users/2013-November/004437.html).
It doesn't look like this patch was ever accepted into stunnel.

I've run into a similar requirement and have updated the patch to work
against stunnel 5.56. In addition, there are a few other minor changes, the
most significant being as follows. It would appear that Windows Active
Directory servers do not implement the ldap extended response PDU in the
same way as OpenLDAP (see this thread:
https://www.openldap.org/lists/openldap-software/200401/msg00800.html).
With this patch you can specify either "protocol = winldap" or "protocol =
openldap" and have it work either way. I haven't modified the logic of
Bart's original patch as far as OpenLDAP goes, but I have split the code
path where applicable to handle the Windows case.

I am testing this against a virtual Windows Server 2016 Active Directory
instance with self-signed certificates with the following config:

[stunnel.winldap]
client = yes
accept = localhost:3268
connect =mymachine.example.com:3268
protocol = winldap

And the following ldapsearch command:

LDAPTLS_REQCERT=never ldapsearch -Z -h localhost -p 3268 -D
"nginx_bind_dn at localdomain.lan" -W -b "dc=localdomain,dc=lan" -s sub
"objectClass=person"

The ldapsearch output returns as expected and I can see in wireshark that
the LDAP_START_TLS_OID request/response happens as it should.

The patch is at the end of this message.

-SG

diff -Nurp a/src/protocol.c b/src/protocol.c
--- a/src/protocol.c 2019-05-15 13:35:16.000000000 -0600
+++ b/src/protocol.c 2019-12-03 13:54:47.536940900 -0700
@@ -64,6 +64,8 @@ NOEXPORT char *pop3_server(CLI *, SERVIC
 NOEXPORT char *imap_client(CLI *, SERVICE_OPTIONS *, const PHASE);
 NOEXPORT char *imap_server(CLI *, SERVICE_OPTIONS *, const PHASE);
 NOEXPORT char *nntp_client(CLI *, SERVICE_OPTIONS *, const PHASE);
+NOEXPORT char *openldap_client(CLI *, SERVICE_OPTIONS *, const PHASE);
+NOEXPORT char *winldap_client(CLI *, SERVICE_OPTIONS *, const PHASE);
 NOEXPORT char *connect_server(CLI *, SERVICE_OPTIONS *, const PHASE);
 NOEXPORT char *connect_client(CLI *, SERVICE_OPTIONS *, const PHASE);
 #ifndef OPENSSL_NO_MD4
@@ -113,6 +115,14 @@ char *protocol(CLI *c, SERVICE_OPTIONS *
         return opt->option.client ?
             nntp_client(c, opt, phase) :
             "The 'nntp' protocol is not supported in the server mode";
+    if(!strcasecmp(opt->protocol, "openldap"))
+        return opt->option.client ?
+            openldap_client(c, opt, phase) :
+            "The 'openldap' protocol is not supported in the server mode";
+    if(!strcasecmp(opt->protocol, "winldap"))
+        return opt->option.client ?
+            winldap_client(c, opt, phase) :
+            "The 'winldap' protocol is not supported in the server mode";
     if(!strcasecmp(opt->protocol, "connect"))
         return opt->option.client ?
             connect_client(c, opt, phase) :
@@ -1119,6 +1129,182 @@ NOEXPORT char *nntp_client(CLI *c, SERVI
     return NULL;
 }

+/**************************************** LDAP, RFC 2830 */
+uint8_t ldap_startssl_message[0x1d + 2] =
+{
+  0x30,        /* tag = UNIVERSAL SEQUENCE */
+  0x1d,        /* len = 29 (the remaining number of bytes in this message)
*/
+  0x02,        /*   messageID */
+  0x01,        /*   len = 1 */
+  0x01,        /*   value = 1 (this is messageID 1) */
+               /*   --- */
+  0x77,        /*   protocolOp = APPLICATION (23) (=ExtendedRequest)
+                 *     0b01xxxxxx => APPLICATION
+                 *     0bxx1xxxxx => ?
+                 *     0xxxx10111 => 23
+               */
+  0x18,        /*   len = 24 */
+  0x80,        /*   type = requstName? */
+  0x16,        /*   len = 22 */
+  /* OID: 1.3.6.1.4.1.1466.20037 (=LDAP_START_TLS_OID)*/
+  '1', '.',
+  '3', '.',
+  '6', '.',
+  '1', '.',
+  '4', '.',
+  '1', '.',
+  '1', '4', '6', '6', '.',
+  '2', '0', '0', '3', '7'
+  /* No requestValue, as per RFC2830 (in 2.1: "The requestValue field is
absent") */
+};
+
+typedef enum {
+    LDAP_OPENLDAP,
+    LDAP_WINLDAP
+} LDAP_MODE;
+
+#define LDAP_UNIVERSAL_SEQUENCE                0x30
+#define LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG        0x84
+#define LDAP_RESPONSE_MSG_ID_TYPE_INT          0x02
+#define LDAP_RESPONSE_EXPECTED_MSG_ID_LEN      0x01
+#define LDAP_RESPONSE_EXPECTED_MSG_ID          0x01
+#define LDAP_RESPONSE_EXT_RESP                 0x0a
+#define LDAP_RESPONSE_EXT_RESP_APPLICATION     0x78
+#define LDAP_RESPONSE_EXPECTED_ERR_LEN         0x01
+#define LDAP_RESPONSE_SUCCESS                  0x00
+
+NOEXPORT char *ldap_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE
phase, const LDAP_MODE ldap_mode) {
+
+    /* thanks to these threads for help with these PDUs
+
https://www.stunnel.org/pipermail/stunnel-users/2013-November/004437.html
+
https://www.openldap.org/lists/openldap-software/200401/msg00800.html */
+
+    uint8_t buffer_8[1];
+    uint32_t buffer_32[1];
+    uint32_t resp_len;
+    uint8_t ldap_response[256];
+    uint8_t *resp_ptr;
+
+    (void)opt; /* squash the unused parameter warning */
+
+    if(phase!=PROTOCOL_MIDDLE)
+        return NULL;
+
+    /* send "Start TLS" request to AD server */
+    s_log(LOG_DEBUG, "Requesting LDAP Start TLS");
+    s_write(c, c->remote_fd.fd, ldap_startssl_message,
(size_t)ldap_startssl_message[1] + 2);
+
+    /* LDAP_UNIVERSAL_SEQUENCE (1 byte) */
+    s_read(c, c->remote_fd.fd, buffer_8, 1);
+    if(buffer_8[0] != LDAP_UNIVERSAL_SEQUENCE) {
+        s_log(LOG_ERR, "start tag is not UNIVERSAL SEQUENCE");
+        throw_exception(c, 1);
+    }
+
+    if(ldap_mode == LDAP_OPENLDAP) {
+      /* OpenLDAP - response length (1 byte) */
+      s_log(LOG_DEBUG, "Reading OpenLDAP message size (1 byte)");
+      s_read(c, c->remote_fd.fd, buffer_8, 1);
+      resp_len = buffer_8;
+
+    } else if(ldap_mode == LDAP_WINLDAP) {
+
+      /* WinLDAP - "response length is 4 bytes" flag -
LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG (1-byte) */
+      s_read(c, c->remote_fd.fd, buffer_8, 1);
+      if(buffer_8[0] != LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG) {
+          s_log(LOG_ERR, "LDAP message length flag is an unexpected
value");
+          throw_exception(c, 1);
+      }
+
+      /* WinLDAP - response length (4 bytes, network byte order) */
+      s_log(LOG_DEBUG, "Reading WinLDAP message size (4 bytes)");
+      s_read(c, c->remote_fd.fd, buffer_32, 4);
+      resp_len = ntohl(buffer_32[0]);
+
+    } else {
+      s_log(LOG_ERR, "Unsupported LDAP mode");
+      throw_exception(c, 1);
+    }
+
+    /* LDAP response message */
+    s_log(LOG_DEBUG, "Reading LDAP message (%u byte(s))", resp_len);
+    s_read(c, c->remote_fd.fd, ldap_response, resp_len);
+
+    resp_ptr = &ldap_response[0];
+
+    /* LDAP_RESPONSE_MSG_ID_TYPE_INT - 1 byte */
+    if(*resp_ptr != LDAP_RESPONSE_MSG_ID_TYPE_INT) {
+        s_log(LOG_ERR, "LDAP response has an incorrect message ID type");
+        throw_exception(c, 1);
+    }
+    resp_ptr++;
+
+    /* LDAP_RESPONSE_EXPECTED_MSG_ID_LEN - 1 byte */
+    if(*resp_ptr != LDAP_RESPONSE_EXPECTED_MSG_ID_LEN) {
+        s_log(LOG_ERR, "LDAP response has an unexpected message ID
length");
+        throw_exception(c, 1);
+    }
+    resp_ptr++;
+
+    /* LDAP_RESPONSE_EXPECTED_MSG_ID - 1 byte */
+    if(*resp_ptr != LDAP_RESPONSE_EXPECTED_MSG_ID) {
+        s_log(LOG_ERR, "LDAP response has an unexpected message ID");
+        throw_exception(c, 1);
+    }
+    resp_ptr++;
+
+    /* LDAP_RESPONSE_EXT_RESP_APPLICATION - 1 byte */
+    if(*resp_ptr != LDAP_RESPONSE_EXT_RESP_APPLICATION) {
+        s_log(LOG_ERR, "LDAP response protocolOp is not APPLICATION");
+        throw_exception(c, 1);
+    }
+    resp_ptr++;
+
+    if(ldap_mode == LDAP_WINLDAP) {
+      /* WinLDAP - "response length is 4 bytes" flag -
LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG (1-byte) */
+      if(*resp_ptr != LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG) {
+          s_log(LOG_ERR, "LDAP extendedResp length flag is an unexpected
value");
+          throw_exception(c, 1);
+      }
+      /* WinLDAP - extended response message length (4-bytes) */
+      resp_ptr += 5;
+
+    } else {
+      /* OpenLDAP - extended response message length (1-byte) */
+      resp_ptr++;
+    }
+
+    /* LDAP_RESPONSE_EXT_RESP - 1 byte */
+    if(*resp_ptr != LDAP_RESPONSE_EXT_RESP) {
+        s_log(LOG_ERR, "LDAP response type is not EXT_RESP");
+        throw_exception(c, 1);
+    }
+    resp_ptr++;
+
+    /* LDAP_RESPONSE_EXT_RESP - 1 byte */
+    if(*resp_ptr != LDAP_RESPONSE_EXPECTED_ERR_LEN) {
+        s_log(LOG_ERR, "LDAP response has an unexpected error code
length");
+        throw_exception(c, 1);
+    }
+    resp_ptr++;
+
+    if(*resp_ptr != LDAP_RESPONSE_SUCCESS) {
+        s_log(LOG_ERR, "LDAP response has indicated an error (%u)",
*resp_ptr);
+        throw_exception(c, 1);
+    }
+
+    return NULL;
+}
+
+
+NOEXPORT char *openldap_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE
phase) {
+  return ldap_client(c, opt, phase, LDAP_OPENLDAP);
+}
+
+NOEXPORT char *winldap_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE
phase) {
+  return ldap_client(c, opt, phase, LDAP_WINLDAP);
+}
+
 /**************************************** connect */

 NOEXPORT char *connect_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE
phase) {
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.stunnel.org/pipermail/stunnel-users/attachments/20191203/c2d5795e/attachment-0001.htm>


More information about the stunnel-users mailing list