Skip to content

Commit 610b2ee

Browse files
tirangraingert
authored andcommitted
Implement support for OPT_X_TLS_PEERCERT
Co-authored-by: Thomas Grainger <tagrain@gmail.com> Signed-off-by: Christian Heimes <cheimes@redhat.com>
1 parent e712033 commit 610b2ee

File tree

6 files changed

+68
-2
lines changed

6 files changed

+68
-2
lines changed

Doc/reference/ldap.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,12 @@ TLS options
406406

407407
.. py:data:: OPT_X_TLS_PEERCERT
408408
409-
Get peer's certificate as binary ASN.1 data structure (not supported)
409+
Get peer's certificate as binary ASN.1 data structure (DER)
410+
411+
.. versionadded:: 3.4.1
412+
413+
.. note::
414+
The option leaks memory with OpenLDAP < 2.5.8.
410415

411416
.. py:data:: OPT_X_TLS_PROTOCOL_MIN
412417

Lib/ldap/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,9 @@ class Str(Constant):
301301
# Added in OpenLDAP 2.4.52
302302
TLSInt('OPT_X_TLS_REQUIRE_SAN', optional=True),
303303

304+
# Added in OpenLDAP 2.5
305+
TLSInt('OPT_X_TLS_PEERCERT', optional=True),
306+
304307
Int('OPT_X_SASL_MECH'),
305308
Int('OPT_X_SASL_REALM'),
306309
Int('OPT_X_SASL_AUTHCID'),

Modules/berval.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ LDAPberval_to_object(const struct berval *bv)
1717
{
1818
PyObject *ret = NULL;
1919

20-
if (!bv) {
20+
if (!bv || !bv->bv_val) {
2121
ret = Py_None;
2222
Py_INCREF(ret);
2323
}

Modules/constants_generated.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ add_int(OPT_X_TLS_PACKAGE);
263263
add_int(OPT_X_TLS_REQUIRE_SAN);
264264
#endif
265265

266+
267+
#if defined(LDAP_OPT_X_TLS_PEERCERT)
268+
add_int(OPT_X_TLS_PEERCERT);
269+
#endif
270+
266271
#endif
267272

268273
add_int(OPT_X_SASL_MECH);

Modules/options.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "LDAPObject.h"
66
#include "ldapcontrol.h"
77
#include "options.h"
8+
#include "berval.h"
89

910
void
1011
set_timeval_from_double(struct timeval *tv, double d)
@@ -58,6 +59,9 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value)
5859
case LDAP_OPT_API_FEATURE_INFO:
5960
#ifdef HAVE_SASL
6061
case LDAP_OPT_X_SASL_SSF:
62+
#endif
63+
#ifdef LDAP_OPT_X_TLS_PEERCERT
64+
case LDAP_OPT_X_TLS_PEERCERT:
6165
#endif
6266
/* Read-only options */
6367
PyErr_SetString(PyExc_ValueError, "read-only option");
@@ -254,6 +258,7 @@ LDAP_get_option(LDAPObject *self, int option)
254258
LDAPAPIInfo apiinfo;
255259
LDAPControl **lcs;
256260
char *strval;
261+
struct berval berbytes;
257262
#if HAVE_SASL
258263
/* unsigned long */
259264
ber_len_t blen;
@@ -406,6 +411,19 @@ LDAP_get_option(LDAPObject *self, int option)
406411
ldap_memfree(strval);
407412
return v;
408413

414+
#ifdef HAVE_TLS
415+
#ifdef LDAP_OPT_X_TLS_PEERCERT
416+
case LDAP_OPT_X_TLS_PEERCERT:
417+
#endif
418+
#endif
419+
/* Options dealing with raw data */
420+
res = LDAP_int_get_option(self, option, &berbytes);
421+
if (res != LDAP_OPT_SUCCESS)
422+
return option_error(res, "ldap_get_option");
423+
v = LDAPberval_to_object(&berbytes);
424+
ldap_memfree(berbytes.bv_val);
425+
return v;
426+
409427
case LDAP_OPT_TIMEOUT:
410428
case LDAP_OPT_NETWORK_TIMEOUT:
411429
/* Double-valued timeval options */

Tests/t_ldapobject.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
from slapdtest import requires_ldapi, requires_sasl, requires_tls
2121
from slapdtest import requires_init_fd
2222

23+
try:
24+
from ssl import PEM_cert_to_DER_cert
25+
except ImportError:
26+
PEM_cert_to_DER_cert = None
27+
2328

2429
LDIF_TEMPLATE = """dn: %(suffix)s
2530
objectClass: dcObject
@@ -421,6 +426,36 @@ def test_multiple_starttls(self):
421426
l.simple_bind_s(self.server.root_dn, self.server.root_pw)
422427
self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn)
423428

429+
@requires_tls()
430+
@unittest.skipUnless(
431+
hasattr(ldap, "OPT_X_TLS_PEERCERT"),
432+
reason="Requires OPT_X_TLS_PEERCERT"
433+
)
434+
def test_get_tls_peercert(self):
435+
l = self.ldap_object_class(self.server.ldap_uri)
436+
peercert = l.get_option(ldap.OPT_X_TLS_PEERCERT)
437+
self.assertEqual(peercert, None)
438+
with self.assertRaises(ValueError):
439+
l.set_option(ldap.OPT_X_TLS_PEERCERT, b"")
440+
441+
l.set_option(ldap.OPT_X_TLS_CACERTFILE, self.server.cafile)
442+
l.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
443+
l.start_tls_s()
444+
445+
peercert = l.get_option(ldap.OPT_X_TLS_PEERCERT)
446+
self.assertTrue(peercert)
447+
self.assertIsInstance(peercert, bytes)
448+
449+
if PEM_cert_to_DER_cert is not None:
450+
with open(self.server.servercert) as f:
451+
server_pem = f.read()
452+
# remove text
453+
begin = server_pem.find("-----BEGIN CERTIFICATE-----")
454+
server_pem = server_pem[begin:-1]
455+
456+
server_der = PEM_cert_to_DER_cert(server_pem)
457+
self.assertEqual(server_der, peercert)
458+
424459
def test_dse(self):
425460
dse = self._ldap_conn.read_rootdse_s()
426461
self.assertIsInstance(dse, dict)

0 commit comments

Comments
 (0)