added ldap3 lib and ldap3 backend

This commit is contained in:
Esther Kleinhenz
2018-06-28 17:24:46 +02:00
parent 00c10dbaa8
commit 95b1878141
281 changed files with 67200 additions and 6 deletions
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
"""
slapdtest - module for spawning test instances of OpenLDAP's slapd server
See https://www.python-ldap.org/ for details.
"""
__version__ = '3.1.0'
from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler
from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls
from slapdtest._slapdtest import skip_unless_ci
@@ -0,0 +1,598 @@
# -*- coding: utf-8 -*-
"""
slapdtest - module for spawning test instances of OpenLDAP's slapd server
See https://www.python-ldap.org/ for details.
"""
from __future__ import unicode_literals
import os
import socket
import sys
import time
import subprocess
import logging
import atexit
from logging.handlers import SysLogHandler
import unittest
# Switch off processing .ldaprc or ldap.conf before importing _ldap
os.environ['LDAPNOINIT'] = '1'
import ldap
from ldap.compat import quote_plus, which
HERE = os.path.abspath(os.path.dirname(__file__))
# a template string for generating simple slapd.conf file
SLAPD_CONF_TEMPLATE = r"""
serverID %(serverid)s
moduleload back_%(database)s
%(include_directives)s
loglevel %(loglevel)s
allow bind_v2
authz-regexp
"gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth"
"%(rootdn)s"
database %(database)s
directory "%(directory)s"
suffix "%(suffix)s"
rootdn "%(rootdn)s"
rootpw "%(rootpw)s"
TLSCACertificateFile "%(cafile)s"
TLSCertificateFile "%(servercert)s"
TLSCertificateKeyFile "%(serverkey)s"
# ignore missing client cert but fail with invalid client cert
TLSVerifyClient try
authz-regexp
"C=DE, O=python-ldap, OU=slapd-test, CN=([A-Za-z]+)"
"ldap://ou=people,dc=local???($1)"
"""
LOCALHOST = '127.0.0.1'
CI_DISABLED = set(os.environ.get('CI_DISABLED', '').split(':'))
if 'LDAPI' in CI_DISABLED:
HAVE_LDAPI = False
else:
HAVE_LDAPI = hasattr(socket, 'AF_UNIX')
def identity(test_item):
"""Identity decorator
"""
return test_item
def skip_unless_ci(reason, feature=None):
"""Skip test unless test case is executed on CI like Travis CI
"""
if not os.environ.get('CI', False):
return unittest.skip(reason)
elif feature in CI_DISABLED:
return unittest.skip(reason)
else:
# Don't skip on Travis
return identity
def requires_tls():
"""Decorator for TLS tests
Tests are not skipped on CI (e.g. Travis CI)
"""
if not ldap.TLS_AVAIL:
return skip_unless_ci("test needs ldap.TLS_AVAIL", feature='TLS')
else:
return identity
def requires_sasl():
if not ldap.SASL_AVAIL:
return skip_unless_ci(
"test needs ldap.SASL_AVAIL", feature='SASL')
else:
return identity
def requires_ldapi():
if not HAVE_LDAPI:
return skip_unless_ci(
"test needs ldapi support (AF_UNIX)", feature='LDAPI')
else:
return identity
def _add_sbin(path):
"""Add /sbin and related directories to a command search path"""
directories = path.split(os.pathsep)
if sys.platform != 'win32':
for sbin in '/usr/local/sbin', '/sbin', '/usr/sbin':
if sbin not in directories:
directories.append(sbin)
return os.pathsep.join(directories)
def combined_logger(
log_name,
log_level=logging.WARN,
sys_log_format='%(levelname)s %(message)s',
console_log_format='%(asctime)s %(levelname)s %(message)s',
):
"""
Returns a combined SysLogHandler/StreamHandler logging instance
with formatters
"""
if 'LOGLEVEL' in os.environ:
log_level = os.environ['LOGLEVEL']
try:
log_level = int(log_level)
except ValueError:
pass
# for writing to syslog
new_logger = logging.getLogger(log_name)
if sys_log_format and os.path.exists('/dev/log'):
my_syslog_formatter = logging.Formatter(
fmt=' '.join((log_name, sys_log_format)))
my_syslog_handler = logging.handlers.SysLogHandler(
address='/dev/log',
facility=SysLogHandler.LOG_DAEMON,
)
my_syslog_handler.setFormatter(my_syslog_formatter)
new_logger.addHandler(my_syslog_handler)
if console_log_format:
my_stream_formatter = logging.Formatter(fmt=console_log_format)
my_stream_handler = logging.StreamHandler()
my_stream_handler.setFormatter(my_stream_formatter)
new_logger.addHandler(my_stream_handler)
new_logger.setLevel(log_level)
return new_logger # end of combined_logger()
class SlapdObject(object):
"""
Controller class for a slapd instance, OpenLDAP's server.
This class creates a temporary data store for slapd, runs it
listening on a private Unix domain socket and TCP port,
and initializes it with a top-level entry and the root user.
When a reference to an instance of this class is lost, the slapd
server is shut down.
An instance can be used as a context manager. When exiting the context
manager, the slapd server is shut down and the temporary data store is
removed.
.. versionchanged:: 3.1
Added context manager functionality
"""
slapd_conf_template = SLAPD_CONF_TEMPLATE
database = 'mdb'
suffix = 'dc=slapd-test,dc=python-ldap,dc=org'
root_cn = 'Manager'
root_pw = 'password'
slapd_loglevel = 'stats stats2'
local_host = '127.0.0.1'
testrunsubdirs = (
'schema',
)
openldap_schema_files = (
'core.schema',
)
TMPDIR = os.environ.get('TMP', os.getcwd())
if 'SCHEMA' in os.environ:
SCHEMADIR = os.environ['SCHEMA']
elif os.path.isdir("/etc/openldap/schema"):
SCHEMADIR = "/etc/openldap/schema"
elif os.path.isdir("/etc/ldap/schema"):
SCHEMADIR = "/etc/ldap/schema"
else:
SCHEMADIR = None
BIN_PATH = os.environ.get('BIN', os.environ.get('PATH', os.defpath))
SBIN_PATH = os.environ.get('SBIN', _add_sbin(BIN_PATH))
# time in secs to wait before trying to access slapd via LDAP (again)
_start_sleep = 1.5
# create loggers once, multiple calls mess up refleak tests
_log = combined_logger('python-ldap-test')
def __init__(self):
self._proc = None
self._port = self._avail_tcp_port()
self.server_id = self._port % 4096
self.testrundir = os.path.join(self.TMPDIR, 'python-ldap-test-%d' % self._port)
self._schema_prefix = os.path.join(self.testrundir, 'schema')
self._slapd_conf = os.path.join(self.testrundir, 'slapd.conf')
self._db_directory = os.path.join(self.testrundir, "openldap-data")
self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port)
if HAVE_LDAPI:
ldapi_path = os.path.join(self.testrundir, 'ldapi')
self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path)
self.default_ldap_uri = self.ldapi_uri
# use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools
self.cli_sasl_external = ldap.SASL_AVAIL
else:
self.ldapi_uri = None
self.default_ldap_uri = self.ldap_uri
# Use simple bind via LDAP uri
self.cli_sasl_external = False
self._find_commands()
if self.SCHEMADIR is None:
raise ValueError('SCHEMADIR is None, ldap schemas are missing.')
# TLS certs
self.cafile = os.path.join(HERE, 'certs/ca.pem')
self.servercert = os.path.join(HERE, 'certs/server.pem')
self.serverkey = os.path.join(HERE, 'certs/server.key')
self.clientcert = os.path.join(HERE, 'certs/client.pem')
self.clientkey = os.path.join(HERE, 'certs/client.key')
@property
def root_dn(self):
return 'cn={self.root_cn},{self.suffix}'.format(self=self)
def _find_commands(self):
self.PATH_LDAPADD = self._find_command('ldapadd')
self.PATH_LDAPDELETE = self._find_command('ldapdelete')
self.PATH_LDAPMODIFY = self._find_command('ldapmodify')
self.PATH_LDAPWHOAMI = self._find_command('ldapwhoami')
self.PATH_SLAPD = os.environ.get('SLAPD', None)
if not self.PATH_SLAPD:
self.PATH_SLAPD = self._find_command('slapd', in_sbin=True)
self.PATH_SLAPTEST = self._find_command('slaptest', in_sbin=True)
def _find_command(self, cmd, in_sbin=False):
if in_sbin:
path = self.SBIN_PATH
var_name = 'SBIN'
else:
path = self.BIN_PATH
var_name = 'BIN'
command = which(cmd, path=path)
if command is None:
raise ValueError(
"Command '{}' not found. Set the {} environment variable to "
"override slapdtest's search path.".format(cmd, var_name)
)
return command
def setup_rundir(self):
"""
creates rundir structure
for setting up a custom directory structure you have to override
this method
"""
os.mkdir(self.testrundir)
os.mkdir(self._db_directory)
self._create_sub_dirs(self.testrunsubdirs)
self._ln_schema_files(self.openldap_schema_files, self.SCHEMADIR)
def _cleanup_rundir(self):
"""
Recursively delete whole directory specified by `path'
"""
# cleanup_rundir() is called in atexit handler. Until Python 3.4,
# the rest of the world is already destroyed.
import os, os.path
if not os.path.exists(self.testrundir):
return
self._log.debug('clean-up %s', self.testrundir)
for dirpath, dirnames, filenames in os.walk(
self.testrundir,
topdown=False
):
for filename in filenames:
self._log.debug('remove %s', os.path.join(dirpath, filename))
os.remove(os.path.join(dirpath, filename))
for dirname in dirnames:
self._log.debug('rmdir %s', os.path.join(dirpath, dirname))
os.rmdir(os.path.join(dirpath, dirname))
os.rmdir(self.testrundir)
self._log.info('cleaned-up %s', self.testrundir)
def _avail_tcp_port(self):
"""
find an available port for TCP connection
"""
sock = socket.socket()
try:
sock.bind((self.local_host, 0))
port = sock.getsockname()[1]
finally:
sock.close()
self._log.info('Found available port %d', port)
return port
def gen_config(self):
"""
generates a slapd.conf and returns it as one string
for generating specific static configuration files you have to
override this method
"""
include_directives = '\n'.join(
'include "{schema_prefix}/{schema_file}"'.format(
schema_prefix=self._schema_prefix,
schema_file=schema_file,
)
for schema_file in self.openldap_schema_files
)
config_dict = {
'serverid': hex(self.server_id),
'schema_prefix':self._schema_prefix,
'include_directives': include_directives,
'loglevel': self.slapd_loglevel,
'database': self.database,
'directory': self._db_directory,
'suffix': self.suffix,
'rootdn': self.root_dn,
'rootpw': self.root_pw,
'root_uid': os.getuid(),
'root_gid': os.getgid(),
'cafile': self.cafile,
'servercert': self.servercert,
'serverkey': self.serverkey,
}
return self.slapd_conf_template % config_dict
def _create_sub_dirs(self, dir_names):
"""
create sub-directories beneath self.testrundir
"""
for dname in dir_names:
dir_name = os.path.join(self.testrundir, dname)
self._log.debug('Create directory %s', dir_name)
os.mkdir(dir_name)
def _ln_schema_files(self, file_names, source_dir):
"""
write symbolic links to original schema files
"""
for fname in file_names:
ln_source = os.path.join(source_dir, fname)
ln_target = os.path.join(self._schema_prefix, fname)
self._log.debug('Create symlink %s -> %s', ln_source, ln_target)
os.symlink(ln_source, ln_target)
def _write_config(self):
"""Writes the slapd.conf file out, and returns the path to it."""
self._log.debug('Writing config to %s', self._slapd_conf)
with open(self._slapd_conf, 'w') as config_file:
config_file.write(self.gen_config())
self._log.info('Wrote config to %s', self._slapd_conf)
def _test_config(self):
self._log.debug('testing config %s', self._slapd_conf)
popen_list = [
self.PATH_SLAPTEST,
"-f", self._slapd_conf,
'-u',
]
if self._log.isEnabledFor(logging.DEBUG):
popen_list.append('-v')
popen_list.extend(['-d', 'config'])
else:
popen_list.append('-Q')
proc = subprocess.Popen(popen_list)
if proc.wait() != 0:
raise RuntimeError("configuration test failed")
self._log.info("config ok: %s", self._slapd_conf)
def _start_slapd(self):
"""
Spawns/forks the slapd process
"""
urls = [self.ldap_uri]
if self.ldapi_uri:
urls.append(self.ldapi_uri)
slapd_args = [
self.PATH_SLAPD,
'-f', self._slapd_conf,
'-F', self.testrundir,
'-h', ' '.join(urls),
]
if self._log.isEnabledFor(logging.DEBUG):
slapd_args.extend(['-d', '-1'])
else:
slapd_args.extend(['-d', '0'])
self._log.info('starting slapd: %r', ' '.join(slapd_args))
self._proc = subprocess.Popen(slapd_args)
# Waits until the LDAP server socket is open, or slapd crashed
# no cover to avoid spurious coverage changes, see
# https://github.com/python-ldap/python-ldap/issues/127
for _ in range(10): # pragma: no cover
if self._proc.poll() is not None:
self._stopped()
raise RuntimeError("slapd exited before opening port")
time.sleep(self._start_sleep)
try:
self._log.debug(
"slapd connection check to %s", self.default_ldap_uri
)
self.ldapwhoami()
except RuntimeError:
pass
else:
return
raise RuntimeError("slapd did not start properly")
def start(self):
"""
Starts the slapd server process running, and waits for it to come up.
"""
if self._proc is None:
# prepare directory structure
atexit.register(self.stop)
self._cleanup_rundir()
self.setup_rundir()
self._write_config()
self._test_config()
self._start_slapd()
self._log.debug(
'slapd with pid=%d listening on %s and %s',
self._proc.pid, self.ldap_uri, self.ldapi_uri
)
def stop(self):
"""
Stops the slapd server, and waits for it to terminate and cleans up
"""
if self._proc is not None:
self._log.debug('stopping slapd with pid %d', self._proc.pid)
self._proc.terminate()
self.wait()
self._cleanup_rundir()
if hasattr(atexit, 'unregister'):
# Python 3
atexit.unregister(self.stop)
elif hasattr(atexit, '_exithandlers'):
# Python 2, can be None during process shutdown
try:
atexit._exithandlers.remove(self.stop)
except ValueError:
pass
def restart(self):
"""
Restarts the slapd server with same data
"""
self._proc.terminate()
self.wait()
self._start_slapd()
def wait(self):
"""Waits for the slapd process to terminate by itself."""
if self._proc:
self._proc.wait()
self._stopped()
def _stopped(self):
"""Called when the slapd server is known to have terminated"""
if self._proc is not None:
self._log.info('slapd[%d] terminated', self._proc.pid)
self._proc = None
def _cli_auth_args(self):
if self.cli_sasl_external:
authc_args = [
'-Y', 'EXTERNAL',
]
if not self._log.isEnabledFor(logging.DEBUG):
authc_args.append('-Q')
else:
authc_args = [
'-x',
'-D', self.root_dn,
'-w', self.root_pw,
]
return authc_args
# no cover to avoid spurious coverage changes
def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None,
stdin_data=None): # pragma: no cover
if ldap_uri is None:
ldap_uri = self.default_ldap_uri
args = [
ldapcommand,
'-H', ldap_uri,
] + self._cli_auth_args() + (extra_args or [])
self._log.debug('Run command: %r', ' '.join(args))
proc = subprocess.Popen(
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
self._log.debug('stdin_data=%r', stdin_data)
stdout_data, stderr_data = proc.communicate(stdin_data)
if stdout_data is not None:
self._log.debug('stdout_data=%r', stdout_data)
if stderr_data is not None:
self._log.debug('stderr_data=%r', stderr_data)
if proc.wait() != 0:
raise RuntimeError(
'{!r} process failed:\n{!r}\n{!r}'.format(
args, stdout_data, stderr_data
)
)
return stdout_data, stderr_data
def ldapwhoami(self, extra_args=None):
"""
Runs ldapwhoami on this slapd instance
"""
self._cli_popen(self.PATH_LDAPWHOAMI, extra_args=extra_args)
def ldapadd(self, ldif, extra_args=None):
"""
Runs ldapadd on this slapd instance, passing it the ldif content
"""
self._cli_popen(self.PATH_LDAPADD, extra_args=extra_args,
stdin_data=ldif.encode('utf-8'))
def ldapmodify(self, ldif, extra_args=None):
"""
Runs ldapadd on this slapd instance, passing it the ldif content
"""
self._cli_popen(self.PATH_LDAPMODIFY, extra_args=extra_args,
stdin_data=ldif.encode('utf-8'))
def ldapdelete(self, dn, recursive=False, extra_args=None):
"""
Runs ldapdelete on this slapd instance, deleting 'dn'
"""
if extra_args is None:
extra_args = []
if recursive:
extra_args.append('-r')
extra_args.append(dn)
self._cli_popen(self.PATH_LDAPDELETE, extra_args=extra_args)
def __enter__(self):
self.start()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.stop()
class SlapdTestCase(unittest.TestCase):
"""
test class which also clones or initializes a running slapd
"""
server_class = SlapdObject
server = None
ldap_object_class = None
def _open_ldap_conn(self, who=None, cred=None, **kwargs):
"""
return a LDAPObject instance after simple bind
"""
ldap_conn = self.ldap_object_class(self.server.ldap_uri, **kwargs)
ldap_conn.protocol_version = 3
#ldap_conn.set_option(ldap.OPT_REFERRALS, 0)
ldap_conn.simple_bind_s(who or self.server.root_dn, cred or self.server.root_pw)
return ldap_conn
@classmethod
def setUpClass(cls):
cls.server = cls.server_class()
cls.server.start()
@classmethod
def tearDownClass(cls):
cls.server.stop()
@@ -0,0 +1,24 @@
python-ldap test certificates
=============================
Certificates and keys
---------------------
* ``ca.pem``: internal root CA certificate
* ``server.pem``: TLS server certificate for slapd, signed by root CA. The
server cert is valid for DNS Name ``localhost`` and IPs ``127.0.0.1`` and
``:1``.
* ``server.key``: private key for ``server.pem``, no password protection
* ``client.pem``: certificate for TLS client cert authentication, signed by
root CA.
* ``client.key``: private key for ``client.pem``, no password protection
Configuration and scripts
-------------------------
* ``ca.conf`` contains the CA definition as well as extensions for the
client and server certificates.
* ``client.conf`` and ``server.conf`` hold the subject and base configuration
for server and client certs.
* ``gencerts.sh`` creates new CA, client and server certificates.
* ``gennssdb.sh`` can be used to create a NSSDB for all certs and keys.
@@ -0,0 +1,77 @@
# Written by Christian Heimes
[default]
ca = "ca"
tmpdir = $ENV::CATMPDIR
outdir = $ENV::CAOUTDIR
name_opt = multiline,-esc_msb,utf8
[req]
default_bits = 2048
encrypt_key = no
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = ca_dn
[ca_dn]
countryName = "DE"
organizationName = "python-ldap"
organizationalUnitName = "slapd-test"
commonName = "Python LDAP Test CA"
[ca]
default_ca = python_ldap_ca
[python_ldap_ca]
certificate = $outdir/$ca.pem
private_key = $outdir/$ca.key
new_certs_dir = $tmpdir
serial = $tmpdir/$ca.crt.srl
crlnumber = $tmpdir/$ca.crl.srl
database = $tmpdir/$ca.db
unique_subject = no
default_days = 3652
default_md = sha256
policy = match_pol
email_in_dn = no
preserve = no
name_opt = $name_opt
cert_opt = ca_default
copy_extensions = none
default_crl_days = 3651
[match_pol]
countryName = match
stateOrProvinceName = optional
localityName = optional
organizationName = match
organizationalUnitName = match
commonName = supplied
[ca_ext]
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
[server_san]
DNS.1 = localhost
IP.1 = 127.0.0.1
IP.2 = ::1
[server_ext]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = critical,serverAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
subjectAltName = @server_san
[client_ext]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature
extendedKeyUsage = critical,clientAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
@@ -0,0 +1,80 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA
Validity
Not Before: Dec 2 11:57:47 2017 GMT
Not After : Sep 4 11:57:47 2027 GMT
Subject: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:af:1f:cf:0f:c5:95:66:2d:eb:85:cc:21:fc:0d:
0f:44:d8:2f:a8:85:08:ef:60:67:57:fa:0b:c5:e4:
b3:fb:f1:6f:cb:30:7a:47:0d:a7:f1:b5:37:81:5f:
f6:39:28:e2:f9:4d:6c:2e:a6:5c:0e:3c:db:4d:c9:
2a:64:ce:0d:15:30:c7:75:52:b8:74:c5:0b:00:4c:
2f:94:1b:dd:fb:83:2c:58:02:73:b0:86:3a:6a:aa:
55:f2:d5:49:99:17:a5:e2:44:ec:dd:62:5f:8d:ce:
77:29:0b:8d:87:23:e2:4b:d6:1c:25:f3:06:a9:ee:
33:6f:ac:ed:22:9e:35:ec:55:e7:1b:38:68:7e:46:
e3:c3:42:ac:06:0b:0a:7a:84:c9:3d:ef:3d:a5:6e:
e9:10:24:c3:28:fe:1f:4a:9a:23:8a:3c:db:0a:66:
5d:07:f8:c5:17:68:53:e4:0e:37:33:c4:d2:ad:58:
62:6b:8a:87:ab:73:eb:bc:2b:ac:07:69:84:8d:e3:
c4:a9:78:9b:6c:1e:03:63:df:b4:96:18:bd:3c:2e:
be:7f:2c:d5:a8:f8:12:b9:ab:27:52:b0:de:38:62:
3c:54:a7:f3:aa:37:a3:11:12:b2:a7:6f:8d:96:10:
ce:01:cb:25:24:a6:51:18:93:69:9b:9e:5c:8a:ff:
fe:89
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Key Usage: critical
Certificate Sign, CRL Sign
X509v3 Subject Key Identifier:
3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4
X509v3 Authority Key Identifier:
keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4
Signature Algorithm: sha256WithRSAEncryption
0a:e7:dc:38:ce:03:dd:a8:99:11:d0:24:be:ef:1a:18:9d:7c:
95:75:4a:4a:29:44:23:28:fc:66:d5:81:ce:05:c2:c0:6b:71:
d6:8d:33:a9:53:a6:1c:f1:4e:50:ae:a3:b1:72:d6:69:53:ad:
a9:62:a9:45:27:68:17:35:41:97:ec:e9:65:91:62:12:ed:eb:
45:3a:9b:cc:09:bc:e3:ad:22:6b:13:6b:b0:67:ef:ce:01:83:
5e:6c:95:e2:b3:73:b9:69:9a:33:49:f9:5f:52:4e:39:94:c9:
db:93:6f:d8:ba:10:92:ce:fa:12:6b:bc:31:ff:c1:67:70:63:
07:dc:53:7a:3a:a3:51:20:15:44:cf:1c:a9:cd:b7:30:1d:8e:
55:93:8a:56:8c:3d:e9:8b:ae:0c:77:8d:5c:8b:fd:22:d8:4c:
3e:e4:76:e8:d9:e8:c3:98:f4:98:ff:02:60:95:8e:3e:26:7a:
e2:fe:2c:0a:a4:52:8d:4c:3d:dd:4c:fd:2f:2c:db:83:4c:2b:
25:24:37:78:9a:07:27:52:f9:1c:c0:65:65:cb:50:77:b4:2d:
fa:f4:af:bb:42:1c:43:65:c6:01:6e:f1:4b:fe:b8:4a:3c:29:
8b:b6:84:1e:17:99:61:98:65:fe:f2:e9:ce:bb:ac:87:69:cb:
e6:13:42:bf
-----BEGIN CERTIFICATE-----
MIIDijCCAnKgAwIBAgIBATANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU
MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV
BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ3WhcNMjcwOTA0
MTE1NzQ3WjBWMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR
BgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNVBAMME1B5dGhvbiBMREFQIFRlc3QgQ0Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvH88PxZVmLeuFzCH8DQ9E
2C+ohQjvYGdX+gvF5LP78W/LMHpHDafxtTeBX/Y5KOL5TWwuplwOPNtNySpkzg0V
MMd1Urh0xQsATC+UG937gyxYAnOwhjpqqlXy1UmZF6XiROzdYl+NzncpC42HI+JL
1hwl8wap7jNvrO0injXsVecbOGh+RuPDQqwGCwp6hMk97z2lbukQJMMo/h9KmiOK
PNsKZl0H+MUXaFPkDjczxNKtWGJrioerc+u8K6wHaYSN48SpeJtsHgNj37SWGL08
Lr5/LNWo+BK5qydSsN44YjxUp/OqN6MRErKnb42WEM4ByyUkplEYk2mbnlyK//6J
AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud
DgQWBBQ7HzL0/lfRb0mRVfIk8QpmO6Xu1DAfBgNVHSMEGDAWgBQ7HzL0/lfRb0mR
VfIk8QpmO6Xu1DANBgkqhkiG9w0BAQsFAAOCAQEACufcOM4D3aiZEdAkvu8aGJ18
lXVKSilEIyj8ZtWBzgXCwGtx1o0zqVOmHPFOUK6jsXLWaVOtqWKpRSdoFzVBl+zp
ZZFiEu3rRTqbzAm8460iaxNrsGfvzgGDXmyV4rNzuWmaM0n5X1JOOZTJ25Nv2LoQ
ks76Emu8Mf/BZ3BjB9xTejqjUSAVRM8cqc23MB2OVZOKVow96YuuDHeNXIv9IthM
PuR26Nnow5j0mP8CYJWOPiZ64v4sCqRSjUw93Uz9Lyzbg0wrJSQ3eJoHJ1L5HMBl
ZctQd7Qt+vSvu0IcQ2XGAW7xS/64Sjwpi7aEHheZYZhl/vLpzrush2nL5hNCvw==
-----END CERTIFICATE-----
@@ -0,0 +1,16 @@
# Written by Christian Heimes
[req]
default_bits = 2048
encrypt_key = no
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = client_dn
[client_dn]
countryName = "DE"
organizationName = "python-ldap"
organizationalUnitName = "slapd-test"
commonName = "client"
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDGxvbMEbahViK4
P6aoWUkciIf1dEYTBuU8M1eShREUZ3Ytq/ee425pXRyxxDrAa8ygRjqs7tauwhgA
KNuPRGyw7hyZ1Ku4vQObwX9rzyHQ6Fj606U4HHbYfAVb0AF7OzLbhNH3isCRtNcm
EUSYG1Nkfn9zQkV4pz2KJM4ePt4GyGJV0NhRUdHdwgsWiuRt2EIRPwLXdkf/4svm
2EahAJax+SMaZYe/lg9w9SBxl6DTaF9lFpoPqzeW+KnXCmE8e+qSWea8EjkzIRXa
4KJ/EnUMP2fUL+Je2agGF2YfCId9YvLTQV8YDv7Im1Sp0sbIUDoWX14GDUbZ8cBr
wfOyI0TfAgMBAAECggEATWv1eGp1zcU05Lq1+OA938U1316YZJTM+HOu6jy1+FKL
7yIJ4nMG8Db6FCswDv5txwdTl0O3jn2+x2Eik1y9UPSNY0U4VU4Zd7MYJC+bJjk5
XwjMU1yS1aMIm0gbK5pVJrdG6Lm8Y4QiQIt9Qhlyk7PJhGUNlf7ds06+kX0/ETiO
vx5SatExeKu5F+JRnGFdAN0106SF5vBum+UbrgOSnJmfwX5VoOXARD21ppxgMzAr
JyGBpgBgy++GpV15gXGuA7DVMIADdHw8hV4OuBLjpkUL+ntArjhpUi7TP7VU3WKR
uUmvLm9CX1l8O/xZMpt9N1+o71a//7asnz8AMtT6cQKBgQD4FgefUkVnXDA1xKDW
1JbArVQeHiLGlRdLakRUY/HdGj72YgAOLt3UsrON4VQXl0C6rks/8HKCFaMexBlF
OecJNWsEVgBEAfsQ+NvrApOQsTszc8Zqna0Kqe2vA0VNa+SAzdHzhBbFcaVkzXJb
JB7M0/OIt5IaqXg6Y5eX2eZF1QKBgQDNHkIoJ/2hYtlSgXpGaniM+0XemQJgJXig
edAQdGKKfqwmjSFjByDM01ZaidMu5fEkeGhMRE73IbwNw0pWsMXylD6bI6+sk7yQ
biM+fslFEEDbgSJe41Jy2eerh5am+dnrMWNhd7QZV1K6tmaqrIzkmIV21/EPXIPp
BNHO8GV14wKBgGOybrO/GzcTXChvcXeEDWU3AqPr1mvZhHgBJ56GX69MGdtnvL/2
Y51Th0bQM7wbQ58B5im21j2itl/pzIH+Z/NSbURbz1WFOkEy0SYbbfPq1XCy6Rz1
apHrgiIf/VzErBp7HBFxlrkYF7Bvw7IOzPXhg3AA3Y0rZ66HUWdr4NdVAoGBAJfC
E2Bydgy5feC1OypuC9MC9abDviY0kxLoDTCfa2jcX7IGKPWDiJkCo5lI7557Mfax
vzjuMR5XLzNfkdih4VKgq9FMjeU5SQHy+tB6LZ+Tbuj4md1qgs3GuskGAEh6Auko
GUc7sVwuZ18NJNiR4Ywf7F8JVajv4gi9MB3Tbr3RAoGARSnVu+6rYSQTyEqvbsaB
gIW7Ezea5q06GcQF072nk3tNSXuU/52YMlodAJ1UfFPbBAtaa7wEFN8oRG1IyKON
MGyf6RD8GoInJjaDihkdCsR28RkchwymG1UMPnPzqRxSAb7da5YuMR8PEioVbL68
dxhsgNi1Wtc2nGqN96qufG0=
-----END PRIVATE KEY-----
@@ -0,0 +1,83 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA
Validity
Not Before: Dec 2 11:57:48 2017 GMT
Not After : Dec 2 11:57:48 2027 GMT
Subject: C=DE, O=python-ldap, OU=slapd-test, CN=client
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:c6:c6:f6:cc:11:b6:a1:56:22:b8:3f:a6:a8:59:
49:1c:88:87:f5:74:46:13:06:e5:3c:33:57:92:85:
11:14:67:76:2d:ab:f7:9e:e3:6e:69:5d:1c:b1:c4:
3a:c0:6b:cc:a0:46:3a:ac:ee:d6:ae:c2:18:00:28:
db:8f:44:6c:b0:ee:1c:99:d4:ab:b8:bd:03:9b:c1:
7f:6b:cf:21:d0:e8:58:fa:d3:a5:38:1c:76:d8:7c:
05:5b:d0:01:7b:3b:32:db:84:d1:f7:8a:c0:91:b4:
d7:26:11:44:98:1b:53:64:7e:7f:73:42:45:78:a7:
3d:8a:24:ce:1e:3e:de:06:c8:62:55:d0:d8:51:51:
d1:dd:c2:0b:16:8a:e4:6d:d8:42:11:3f:02:d7:76:
47:ff:e2:cb:e6:d8:46:a1:00:96:b1:f9:23:1a:65:
87:bf:96:0f:70:f5:20:71:97:a0:d3:68:5f:65:16:
9a:0f:ab:37:96:f8:a9:d7:0a:61:3c:7b:ea:92:59:
e6:bc:12:39:33:21:15:da:e0:a2:7f:12:75:0c:3f:
67:d4:2f:e2:5e:d9:a8:06:17:66:1f:08:87:7d:62:
f2:d3:41:5f:18:0e:fe:c8:9b:54:a9:d2:c6:c8:50:
3a:16:5f:5e:06:0d:46:d9:f1:c0:6b:c1:f3:b2:23:
44:df
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage: critical
TLS Web Client Authentication
X509v3 Subject Key Identifier:
67:63:38:F4:B4:BC:F3:6B:BC:74:0E:7C:27:C9:BB:C2:CC:58:AC:16
X509v3 Authority Key Identifier:
keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4
Signature Algorithm: sha256WithRSAEncryption
76:24:42:6b:33:4f:d6:59:07:48:5b:04:9c:3c:d3:3f:63:80:
75:4d:78:d7:d5:85:b1:77:81:31:a3:91:cb:c9:a3:8c:0e:00:
28:08:74:71:6c:fc:83:8c:80:ec:1c:e8:ee:83:e0:7f:49:3b:
f3:42:33:5a:1f:68:0c:a5:41:42:ce:bf:77:29:07:f2:18:a7:
81:17:d7:76:47:04:d9:8a:dd:e8:5a:26:26:ea:a4:76:70:e1:
f1:fa:e1:db:bc:f2:24:b2:37:a8:58:2f:e3:66:89:77:02:55:
87:ef:3c:1f:66:ce:4e:86:b3:4c:57:43:86:7f:4c:ab:5a:33:
dd:ca:e3:2f:3b:af:b4:43:5a:53:8b:e0:12:da:e7:c0:13:76:
b2:68:d5:14:f8:1a:07:ce:8a:87:5c:91:bd:35:d7:83:c6:2a:
a4:e0:92:50:01:b9:c2:fa:69:06:5c:8a:80:ee:9c:24:f9:49:
64:e3:59:c1:a6:69:29:ce:b7:89:20:a9:7c:d6:9f:df:2a:d1:
a4:98:2a:6d:7b:93:6a:52:e3:ae:de:1a:d8:f3:2e:cf:02:7e:
ba:9a:fa:f4:b3:b5:6e:9a:23:10:70:53:53:30:d5:8a:32:35:
01:52:58:6d:9d:f5:8e:bb:b9:76:bd:41:16:88:26:f8:d3:ce:
70:03:c8:59
-----BEGIN CERTIFICATE-----
MIIDkjCCAnqgAwIBAgIBAzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU
MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV
BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ4WhcNMjcxMjAy
MTE1NzQ4WjBJMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR
BgNVBAsMCnNsYXBkLXRlc3QxDzANBgNVBAMMBmNsaWVudDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMbG9swRtqFWIrg/pqhZSRyIh/V0RhMG5TwzV5KF
ERRndi2r957jbmldHLHEOsBrzKBGOqzu1q7CGAAo249EbLDuHJnUq7i9A5vBf2vP
IdDoWPrTpTgcdth8BVvQAXs7MtuE0feKwJG01yYRRJgbU2R+f3NCRXinPYokzh4+
3gbIYlXQ2FFR0d3CCxaK5G3YQhE/Atd2R//iy+bYRqEAlrH5Ixplh7+WD3D1IHGX
oNNoX2UWmg+rN5b4qdcKYTx76pJZ5rwSOTMhFdrgon8SdQw/Z9Qv4l7ZqAYXZh8I
h31i8tNBXxgO/sibVKnSxshQOhZfXgYNRtnxwGvB87IjRN8CAwEAAaN4MHYwDAYD
VR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUH
AwIwHQYDVR0OBBYEFGdjOPS0vPNrvHQOfCfJu8LMWKwWMB8GA1UdIwQYMBaAFDsf
MvT+V9FvSZFV8iTxCmY7pe7UMA0GCSqGSIb3DQEBCwUAA4IBAQB2JEJrM0/WWQdI
WwScPNM/Y4B1TXjX1YWxd4Exo5HLyaOMDgAoCHRxbPyDjIDsHOjug+B/STvzQjNa
H2gMpUFCzr93KQfyGKeBF9d2RwTZit3oWiYm6qR2cOHx+uHbvPIksjeoWC/jZol3
AlWH7zwfZs5OhrNMV0OGf0yrWjPdyuMvO6+0Q1pTi+AS2ufAE3ayaNUU+BoHzoqH
XJG9NdeDxiqk4JJQAbnC+mkGXIqA7pwk+Ulk41nBpmkpzreJIKl81p/fKtGkmCpt
e5NqUuOu3hrY8y7PAn66mvr0s7VumiMQcFNTMNWKMjUBUlhtnfWOu7l2vUEWiCb4
085wA8hZ
-----END CERTIFICATE-----
@@ -0,0 +1,68 @@
#!/bin/sh
# Written by Christian Heimes
set -e
export CAOUTDIR=.
export CATMPDIR=tmp
rm -rf $CATMPDIR
rm -rf ca.pem ca.key server.pem server.key client.pem client.key
rm -rf cert9.db key4.db pkcs11.tx
mkdir -p $CAOUTDIR
mkdir -p $CATMPDIR
touch $CATMPDIR/ca.db
touch $CATMPDIR/ca.db.attr
echo '01' > $CATMPDIR/ca.crt.srl
echo '01' > $CATMPDIR/ca.crl.srl
# root CA
openssl req -new \
-config ca.conf \
-out $CATMPDIR/ca.csr \
-keyout $CAOUTDIR/ca.key \
-batch
openssl ca -selfsign \
-config ca.conf \
-in $CATMPDIR/ca.csr \
-out $CAOUTDIR/ca.pem \
-extensions ca_ext \
-days 3563 \
-batch
# server cert
openssl req -new \
-config server.conf \
-out $CATMPDIR/server.csr \
-keyout $CAOUTDIR/server.key \
-batch
openssl ca \
-config ca.conf \
-in $CATMPDIR/server.csr \
-out $CAOUTDIR/server.pem \
-policy match_pol \
-extensions server_ext \
-batch
# client cert
openssl req -new \
-config client.conf \
-out $CATMPDIR/client.csr \
-keyout $CAOUTDIR/client.key \
-batch
openssl ca \
-config ca.conf \
-in $CATMPDIR/client.csr \
-out $CAOUTDIR/client.pem \
-policy match_pol \
-extensions client_ext \
-batch
# cleanup
rm -rf $CATMPDIR ca.key
echo DONE
@@ -0,0 +1,28 @@
#!/bin/sh
# Written by Christian Heimes
set -e
CATMPDIR=tmp
PASSFILE=${CATMPDIR}/passwd.txt
NSSDB=sql:${CAOUTDIR}
mkdir -p $CATMPDIR
# Create PKCS#12 files for NSSDB import
echo "dummy" > $PASSFILE
openssl pkcs12 -name "servercert" -in server.pem -inkey server.key \
-caname "testca" -CAfile ca.pem \
-password "file:${PASSFILE}" -export -out server.p12
openssl pkcs12 -name "clientcert" -in client.pem -inkey client.key \
-caname "testca" -CAfile ca.pem \
-password "file:${PASSFILE}" -export -out client.p12
# Create NSS DB
certutil -d $NSSDB -N --empty-password
certutil -d $NSSDB -A -n "testca" -t CT,, -a -i ca.pem
pk12util -d $NSSDB -i server.p12 -w ${PASSFILE}
pk12util -d $NSSDB -i client.p12 -w ${PASSFILE}
certutil -d $NSSDB -L
# cleanup
rm -rf $CATMPDIR server.p12 client.p12
@@ -0,0 +1,16 @@
# Written by Christian Heimes
[req]
default_bits = 2048
encrypt_key = no
default_md = sha256
utf8 = yes
string_mask = utf8only
prompt = no
distinguished_name = server_dn
[server_dn]
countryName = "DE"
organizationName = "python-ldap"
organizationalUnitName = "slapd-test"
commonName = "server cert for localhost"
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAgcI7Pj89Aw4r
rb+N8j3t1ynJgXRhQNxbxQcQmUCi8AtpGKNXu+aM9u2HxZ677ALfhsEivtQA5QKz
Ll5G2G2IQa7uzgIco73OL/kMZIJt7sKfnvfACtSoOlD0IyOzVEEu0AVA7hMHb6Ul
I0mCNfdk2FWFbc1nUmIAEBhIODbFoNW+Rc6lz94NsUqArDayvWpAsXkUbubQikpi
KNX1OpOOC9GUbEhwI/G90ZnUg9STk/2oxsvwLlYdOhGhpyfl53tTu7eLMBriMxFl
UTvkY1GUgPj0fA/giWtCerGOyeKu1hFlbS4LjborsfrlyYPwfwTg3YL4hVnt9fF0
rpjzY1mlAgMBAAECggEAJY6rSEeiqtKXxynEv3rNXkOmIWwiOn8e/sB32mMr2x4d
+8kUxR8hocrjGKQTjfJDtTxjHdZBIlOLrU2UkxnSdMzrxidm/hNsCngNjL9nOu9k
BSRMjakPSCrodFkOtAPyG6H2BG7uQ3siqxYxVzgUJhaWyMtdUZUfDYgWVLCy7udU
5ML/OTOi7virueMmshjXoyrDug9OpiEMKiLu3ndAaDk/26m05ePAXB6TjW8SFw1B
qn7cITSG0G5MZ9pOw0KwT9irY1SdppBHVWIg7dkYWRCni0BPCFewastU+GVKH5PJ
+dYSvafhkEGD1bBu484KN9yX1BcHV41ZKR8pGgMM2QKBgQD3/0R2vZsTxoO1CHNI
IT7nBnuPIOP45iTFm/SNRY7e4dhQBy6HM6JD3Sr6Iksm8jRoboz+tnAso6l6QHRS
842uqBiOHdnka2RslDmrEun1lJv1MWuPM8JN0o8pYjVG/IRtaAFnYSEk72UoNy2h
bHC4OGFNwMbAadVm7DK5OiMfXwKBgQDGuBRxz7jkVZoMbbaeIqmGZAIejWkJweDZ
AK+txM+6Sg+Li14t190N3Xf6tyyidKhUAEWaINzLjZB+luxNaDXtxqWzLYHCwQKA
qfrjWVeZOS1clLya7jwl1jJqBtBiGKHv9eRL21hgX/9gX3odxqFMvX3vm6L7F1q1
5CNApW0ZewKBgGO8qNcsWBLy8oM7G8n1fOvCwqyEaMrwG/fRSeALCnN+1tUQnljH
nkm2yBMC+cB3Bja9xzylOKXrSDyfcWjvBJsqhX2aacggnKnCTxMLL0aR9sr8jipw
gYN03Bijo5Oh+MxbWL0v5fmJweATmOljyE1+dzui/QvjRGz5L0kpJXj3AoGBAIa4
3+t1B4WN312TuB4no8Tf4mvyNQcPcS/Nfk0RxD8o3Lcfal8sHMq8ng3Ux6bv7frd
IFLo+qfpts+L5HJqNz2X0ljSfkmZ7udp1hTySigwEmfU0rU61H5WZGFrczU+O/Ni
Qj+HWrgj/Q/KSxEKy+oqAcpDOtB+Odpc6+V1Aa0nAoGBAItWHP9UjTNFqOfyjZhG
qaUiZd1S2KyRR0l/lVcn+rJ46Yg5i+lMGwHMF1xPyWH4ELz+QCUX3doOI4yB2ikg
XXFcc8/bqgaR4AfOvP98T86s7+f33kaAKZsgyAFB2cjo+fz8ArTz+GjPeHbiOPaR
Ra7+BVwl9GE0+bCdirq+99GO
-----END PRIVATE KEY-----
@@ -0,0 +1,86 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 2 (0x2)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=DE, O=python-ldap, OU=slapd-test, CN=Python LDAP Test CA
Validity
Not Before: Dec 2 11:57:48 2017 GMT
Not After : Dec 2 11:57:48 2027 GMT
Subject: C=DE, O=python-ldap, OU=slapd-test, CN=server cert for localhost
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:c0:81:c2:3b:3e:3f:3d:03:0e:2b:ad:bf:8d:f2:
3d:ed:d7:29:c9:81:74:61:40:dc:5b:c5:07:10:99:
40:a2:f0:0b:69:18:a3:57:bb:e6:8c:f6:ed:87:c5:
9e:bb:ec:02:df:86:c1:22:be:d4:00:e5:02:b3:2e:
5e:46:d8:6d:88:41:ae:ee:ce:02:1c:a3:bd:ce:2f:
f9:0c:64:82:6d:ee:c2:9f:9e:f7:c0:0a:d4:a8:3a:
50:f4:23:23:b3:54:41:2e:d0:05:40:ee:13:07:6f:
a5:25:23:49:82:35:f7:64:d8:55:85:6d:cd:67:52:
62:00:10:18:48:38:36:c5:a0:d5:be:45:ce:a5:cf:
de:0d:b1:4a:80:ac:36:b2:bd:6a:40:b1:79:14:6e:
e6:d0:8a:4a:62:28:d5:f5:3a:93:8e:0b:d1:94:6c:
48:70:23:f1:bd:d1:99:d4:83:d4:93:93:fd:a8:c6:
cb:f0:2e:56:1d:3a:11:a1:a7:27:e5:e7:7b:53:bb:
b7:8b:30:1a:e2:33:11:65:51:3b:e4:63:51:94:80:
f8:f4:7c:0f:e0:89:6b:42:7a:b1:8e:c9:e2:ae:d6:
11:65:6d:2e:0b:8d:ba:2b:b1:fa:e5:c9:83:f0:7f:
04:e0:dd:82:f8:85:59:ed:f5:f1:74:ae:98:f3:63:
59:a5
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage: critical
TLS Web Server Authentication
X509v3 Subject Key Identifier:
1B:78:45:40:0D:50:8A:8B:3B:C1:0A:F8:3F:7A:48:7B:A6:3C:28:09
X509v3 Authority Key Identifier:
keyid:3B:1F:32:F4:FE:57:D1:6F:49:91:55:F2:24:F1:0A:66:3B:A5:EE:D4
X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
Signature Algorithm: sha256WithRSAEncryption
ad:08:3f:7d:b1:09:a1:a5:6c:c3:58:80:1d:e5:33:a5:bb:c0:
33:39:95:aa:88:ee:c4:8e:38:3b:59:a7:0e:39:74:6c:fe:11:
33:5e:fa:50:cb:20:4b:67:b7:c9:5e:96:a7:9e:d8:47:46:e1:
ab:fe:5d:8b:9a:2d:1a:1b:43:08:f9:93:0f:2a:e3:ce:83:4a:
94:cd:02:f0:8e:25:f2:41:0d:55:10:f5:4c:5b:39:8b:77:5e:
ab:78:16:64:a1:48:d5:e1:f6:69:9a:0f:d8:30:a6:cc:92:4d:
81:df:46:74:ab:cf:1d:b7:d4:01:b9:6d:d5:f4:14:b8:d5:54:
84:79:11:42:69:55:7f:74:ce:01:96:2f:3f:51:23:b3:11:fb:
72:dc:4c:b9:a3:89:ef:31:e4:c0:49:06:fa:8d:09:71:e1:c1:
74:a9:ed:f8:96:87:67:16:b5:5d:16:5d:59:70:ff:1c:b5:a1:
6c:d2:22:11:3a:0e:6f:76:9b:69:cb:f3:85:a7:79:ad:53:f5:
34:e8:87:cc:dd:09:51:25:e0:28:ee:79:a0:a3:dc:0a:dd:f0:
1b:e3:c9:5f:14:d3:95:f5:12:4d:23:95:45:2c:3c:32:94:ad:
ce:1e:a0:5f:e6:e8:28:c6:f9:c7:fb:57:06:ad:0b:eb:86:ca:
0e:d2:a8:67
-----BEGIN CERTIFICATE-----
MIID1TCCAr2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJERTEU
MBIGA1UECgwLcHl0aG9uLWxkYXAxEzARBgNVBAsMCnNsYXBkLXRlc3QxHDAaBgNV
BAMME1B5dGhvbiBMREFQIFRlc3QgQ0EwHhcNMTcxMjAyMTE1NzQ4WhcNMjcxMjAy
MTE1NzQ4WjBcMQswCQYDVQQGEwJERTEUMBIGA1UECgwLcHl0aG9uLWxkYXAxEzAR
BgNVBAsMCnNsYXBkLXRlc3QxIjAgBgNVBAMMGXNlcnZlciBjZXJ0IGZvciBsb2Nh
bGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAgcI7Pj89Aw4r
rb+N8j3t1ynJgXRhQNxbxQcQmUCi8AtpGKNXu+aM9u2HxZ677ALfhsEivtQA5QKz
Ll5G2G2IQa7uzgIco73OL/kMZIJt7sKfnvfACtSoOlD0IyOzVEEu0AVA7hMHb6Ul
I0mCNfdk2FWFbc1nUmIAEBhIODbFoNW+Rc6lz94NsUqArDayvWpAsXkUbubQikpi
KNX1OpOOC9GUbEhwI/G90ZnUg9STk/2oxsvwLlYdOhGhpyfl53tTu7eLMBriMxFl
UTvkY1GUgPj0fA/giWtCerGOyeKu1hFlbS4LjborsfrlyYPwfwTg3YL4hVnt9fF0
rpjzY1mlAgMBAAGjgacwgaQwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAw
FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFBt4RUANUIqLO8EK+D96
SHumPCgJMB8GA1UdIwQYMBaAFDsfMvT+V9FvSZFV8iTxCmY7pe7UMCwGA1UdEQQl
MCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0B
AQsFAAOCAQEArQg/fbEJoaVsw1iAHeUzpbvAMzmVqojuxI44O1mnDjl0bP4RM176
UMsgS2e3yV6Wp57YR0bhq/5di5otGhtDCPmTDyrjzoNKlM0C8I4l8kENVRD1TFs5
i3deq3gWZKFI1eH2aZoP2DCmzJJNgd9GdKvPHbfUAblt1fQUuNVUhHkRQmlVf3TO
AZYvP1EjsxH7ctxMuaOJ7zHkwEkG+o0JceHBdKnt+JaHZxa1XRZdWXD/HLWhbNIi
EToOb3abacvzhad5rVP1NOiHzN0JUSXgKO55oKPcCt3wG+PJXxTTlfUSTSOVRSw8
MpStzh6gX+boKMb5x/tXBq0L64bKDtKoZw==
-----END CERTIFICATE-----