Skip to content

Commit 693ebc2

Browse files
authored
Merge pull request #4 from LasLabs/feature/master/certificate-request
[IMP] Add data models and make API more Pythonic
2 parents 5418e0c + 91033b1 commit 693ebc2

26 files changed

Lines changed: 638 additions & 53 deletions

README.rst

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@
44
Python CFSSL Library
55
====================
66

7-
This library allows you to interact with a remote CFSSL using Python.
7+
This library allows you to interact with a remote CFSSL server using Python.
8+
9+
CFSSL is CloudFlare's open source toolkit for everything TLS/SSL. CFSSL is used by
10+
CloudFlare for their internal Certificate Authority infrastructure and for all of
11+
their TLS certificates.
12+
13+
* `Read more on the CloudFlare blog
14+
<https://blog.cloudflare.com/introducing-cfssl/>`_.
15+
* `View the CFSSL source
16+
<https://github.com/cloudflare/cfssl>`_.
817

918
Installation
1019
============
@@ -19,13 +28,13 @@ A pre-existing CFSSL server is required to use this library.
1928
Usage
2029
=====
2130

22-
`API Documentation <https://laslabs.github.io/python-cfssl>`_
31+
`Read The API Documentation <https://laslabs.github.io/python-cfssl>`_
2332

2433
Known Issues / Road Map
2534
=======================
2635

2736
- Installation, setup, usage - in ReadMe
28-
- Add a Certificate Request data structure
37+
- Add type checking in datamodels
2938

3039
Credits
3140
=======

cfssl/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,20 @@
22
# Copyright 2016 LasLabs Inc.
33
# License MIT (https://opensource.org/licenses/MIT).
44

5+
# API
56
from .cfssl import CFSSL
7+
8+
# Models
9+
from .models.certificate_request import CertificateRequest
10+
11+
from .models.config_client import ConfigClient
12+
from .models.config_key import ConfigKey
13+
from .models.config_server import ConfigServer
14+
15+
from .models.host import Host
16+
17+
from .models.policy_auth import PolicyAuth
18+
from .models.policy_sign import PolicySign
19+
from .models.policy_use import PolicyUse
20+
21+
from .models.subject_info import SubjectInfo

cfssl/cfssl.py

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from .exceptions import CFSSLException, CFSSLRemoteException
88

9+
from .models.config_key import ConfigKey
10+
911

1012
class CFSSL(object):
1113
""" It provides Python bindings to a remote CFSSL server via HTTP(S).
@@ -23,8 +25,7 @@ def auth_sign(self, token, request, datetime=None, remote_address=None):
2325
2426
Args:
2527
token: (str) The authentication token.
26-
request: (mixed) Signing request document (e.g. as
27-
documented in endpoint_sign.txt, but not JSON encoded).
28+
request: (cfssl.CertificateRequest) Signing request document.
2829
datetime: (datetime.datetime) Authentication timestamp.
2930
remote_address: (str) An address used in making the request.
3031
Returns:
@@ -33,7 +34,7 @@ def auth_sign(self, token, request, datetime=None, remote_address=None):
3334
"""
3435
data = self._clean_mapping({
3536
'token': token,
36-
'request': request,
37+
'request': request.to_api(),
3738
'datetime': datetime,
3839
'remote_address': remote_address,
3940
})
@@ -65,11 +66,11 @@ def bundle(self, certificate, private_key=None,
6566
6667
If only the ``domain`` parameter is present, the following
6768
parameter is valid:
68-
69+
6970
ip: (str) The IP address of the remote host; this will fetch the
7071
certificate from the IP, and verify that it is valid for the
7172
domain name.
72-
73+
7374
Returns:
7475
(dict) Object repesenting the bundle, with the following keys:
7576
* bundle contains the concatenated list of PEM certificates
@@ -162,42 +163,45 @@ def init_ca(self, hosts, names, common_name=None, key=None, ca=None):
162163
""" It initializes a new certificate authority.
163164
164165
Args:
165-
hosts: (list) Of SANs (subject alternative names) for the
166-
requested CA certificate.
167-
names: (list) the certificate subject for the requested CA
168-
certificate.
166+
hosts: (iter of cfssl.Host) Subject Alternative Name(s) for the
167+
requested CA certificate.
168+
names: (iter of cfssl.SubjectInfo) The Subject Info(s) for the
169+
requested CA certificate.
169170
common_name: (str) the common name for the certificate subject in
170171
the requested CA certificate.
171-
key: the key algorithm and size for the newly generated private key,
172-
default to ECDSA-256.
173-
ca: the CA configuration of the requested CA, including CA pathlen
174-
and CA default expiry.
172+
key: (cfssl.ConfigKey) Cipher and strength to use for certificate.
173+
ca: (cfssl.ConfigServer) the CA configuration of the requested CA,
174+
including CA pathlen and CA default expiry.
175175
Returns:
176176
(dict) Mapping with two keys:
177177
* private key: (str) a PEM-encoded CA private key.
178178
* certificate: (str) a PEM-encoded self-signed CA certificate.
179179
"""
180+
key = key or ConfigKey()
180181
data = self._clean_mapping({
181-
'hosts': hosts,
182-
'names': names,
182+
'hosts': [
183+
host.to_api() for host in hosts
184+
],
185+
'names': [
186+
name.to_api() for name in names
187+
],
183188
'CN': common_name,
184-
'key': key,
185-
'ca': ca,
189+
'key': key.to_api(),
190+
'ca': ca and ca.to_api() or None,
186191
})
187192
return self.call('init_ca', 'POST', data=data)
188193

189194
def new_key(self, hosts, names, common_name=None, key=None, ca=None):
190195
""" It generates and returns a new private key + CSR.
191196
192197
Args:
193-
hosts: (list) Of SANs (subject alternative names) for the
194-
requested CA certificate.
195-
names: (list) the certificate subject for the requested CA
196-
certificate.
198+
hosts: (iter of cfssl.Host) Subject Alternative Name(s) for the
199+
requested certificate.
200+
names: (iter of cfssl.SubjectInfo) The Subject Info(s) for the
201+
requested certificate.
197202
CN: (str) the common name for the certificate subject in the
198203
requestedrequested CA certificate.
199-
key: the key algorithm and size for the newly generated private key,
200-
default to ECDSA-256.
204+
key: (cfssl.ConfigKey) Cipher and strength to use for certificate.
201205
ca: the CA configuration of the requested CA, including CA pathlen
202206
and CA default expiry.
203207
Returns:
@@ -208,8 +212,12 @@ def new_key(self, hosts, names, common_name=None, key=None, ca=None):
208212
certificate request
209213
"""
210214
data = self._clean_mapping({
211-
'hosts': hosts,
212-
'names': names,
215+
'hosts': [
216+
host.to_api() for host in hosts
217+
],
218+
'names': [
219+
name.to_api() for name in names
220+
],
213221
'CN': common_name,
214222
'key': key,
215223
'ca': ca,
@@ -220,7 +228,8 @@ def new_cert(self, request, label=None, profile=None, bundle=None):
220228
""" It generates and returns a new private key and certificate.
221229
222230
Args:
223-
request: (dict) Specifying the certificate request.
231+
request: (cfssl.CertificateRequest) CSR to be used for
232+
certificate creation.
224233
label: (str) Specifying which signer to be appointed to sign
225234
the CSR, useful when interacting with cfssl server that stands
226235
in front of a remote multi-root CA signer.
@@ -238,7 +247,7 @@ def new_cert(self, request, label=None, profile=None, bundle=None):
238247
if the bundle parameter was set).
239248
"""
240249
data = self._clean_mapping({
241-
'request': request,
250+
'request': request.to_api(),
242251
'label': label,
243252
'profile': profile,
244253
'bundle': bundle,
@@ -269,12 +278,12 @@ def scan(self, host, ip=None, timeout=None, family=None, scanner=None):
269278
""" It scans servers to determine the quality of their TLS setup.
270279
271280
Args:
272-
host: the hostname (optionally including port) to scan.
273-
ip: IP Address to override DNS lookup of host.
274-
timeout: The amount of time allotted for the scan to complete
281+
host: (cfssl.Host) The host to scan.
282+
ip: (str) IP Address to override DNS lookup of host.
283+
timeout: (str) The amount of time allotted for the scan to complete
275284
(default: 1 minute).
276-
family: regular expression specifying scan famil(ies) to run.
277-
scanner: regular expression specifying scanner(s) to run.
285+
family: (str) regular expression specifying scan famil(ies) to run.
286+
scanner: (str) regular expression specifying scanner(s) to run.
278287
Returns:
279288
(dict) Mapping with keys for each scan family. Each of these
280289
objects contains keys for each scanner run in that family
@@ -290,7 +299,7 @@ def scan(self, host, ip=None, timeout=None, family=None, scanner=None):
290299
* output: (dict) Arbitrary data retrieved during the scan.
291300
"""
292301
data = self._clean_mapping({
293-
'host': host,
302+
'host': host.to_api(),
294303
'ip': ip,
295304
'timeout': timeout,
296305
'family': family,
@@ -314,8 +323,8 @@ def sign(self, certificate_request, hosts=None, subject=None,
314323
""" It signs and returns a certificate.
315324
316325
Args:
317-
certificate_request: (str) the CSR bytes to be signed in PEM.
318-
hosts: (iter) of SAN (subject alternative .names)
326+
certificate_request: (str) the CSR bytes to be signed (in PEM).
327+
hosts: (iter of cfssl.Host) of SAN (subject alternative .names)
319328
which overrides the ones in the CSR
320329
subject: (str) The certificate subject which overrides
321330
the ones in the CSR.
@@ -324,19 +333,22 @@ def sign(self, certificate_request, hosts=None, subject=None,
324333
label: (str) Specifying which signer to be appointed to sign
325334
the CSR, useful when interacting with a remote multi-root CA
326335
signer.
327-
profile: (str) Specifying the signing profile for the signer,
328-
useful when interacting with a remote multi-root CA signer.
336+
profile: (cfssl.ConfigServer) Specifying the signing profile for
337+
the signer, useful when interacting with a remote multi-root
338+
CA signer.
329339
Returns:
330340
(str) A PEM-encoded certificate that has been signed by the
331341
server.
332342
"""
333343
data = self._clean_mapping({
334-
'certificate_request': certificate_request,
335-
'hosts': hosts,
344+
'certificate_request': certificate_request.to_api(),
345+
'hosts': [
346+
host.to_api() for host in hosts
347+
],
336348
'subject': subject,
337349
'serial_sequence': serial_sequence,
338350
'label': label,
339-
'profile': profile,
351+
'profile': profile.to_api(),
340352
})
341353
result = self.call('sign', 'POST', data=data)
342354
return result['certificate']

cfssl/defaults.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2016 LasLabs Inc.
3+
# License MIT (https://opensource.org/licenses/MIT).
4+
5+
DEFAULT_ALGORITHM = 'rsa'
6+
DEFAULT_STRENGTH = 4096
7+
DEFAULT_EXPIRE_MINUTES = 365 * 24 * 60

cfssl/models/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2016 LasLabs Inc.
3+
# License MIT (https://opensource.org/licenses/MIT).
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2016 LasLabs Inc.
3+
# License MIT (https://opensource.org/licenses/MIT).
4+
5+
from .host import Host
6+
from .config_key import ConfigKey
7+
from .subject_info import SubjectInfo
8+
9+
10+
class CertificateRequest(object):
11+
""" It provides a Certificate Request compatible with CFSSL. """
12+
13+
def __init__(self, common_name, names=None, hosts=None, key=None):
14+
self.common_name = common_name
15+
self.names = names or []
16+
self.hosts = hosts or []
17+
self.key = key or KeyConfig()
18+
19+
def to_api(self):
20+
""" It returns an object compatible with the API. """
21+
return {
22+
'CN': self.common_name,
23+
'names': [
24+
name.to_api() for name in self.names
25+
],
26+
'hosts': [
27+
host.to_api() for host in self.hosts
28+
],
29+
'key': self.key.to_api(),
30+
}

cfssl/models/config_client.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2016 LasLabs Inc.
3+
# License MIT (https://opensource.org/licenses/MIT).
4+
5+
from .config_mixer import ConfigMixer
6+
7+
8+
class ConfigClient(ConfigMixer):
9+
""" It provides a Client Config compatible with CFSSL. """
10+
11+
def __init__(self, sign_policy_default,
12+
sign_policies_add, auth_policies, remotes):
13+
super(ConfigClient, self).__init__(
14+
sign_policy_default, auth_policies, remotes,
15+
)
16+
self.remotes = remotes
17+
18+
def to_api(self):
19+
""" It returns an object compatible with the API. """
20+
res = super(ConfigClient, self).to_api()
21+
res['remotes'] = {
22+
r.name: r.to_api() for r in self.remotes
23+
}
24+
return res

cfssl/models/config_key.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2016 LasLabs Inc.
3+
# License MIT (https://opensource.org/licenses/MIT).
4+
5+
from ..defaults import DEFAULT_ALGORITHM, DEFAULT_STRENGTH
6+
7+
8+
class ConfigKey(object):
9+
""" It provides a Key Config compatible with CFSSL. """
10+
11+
def __init__(self, algorithm=DEFAULT_ALGORITHM,
12+
strength=DEFAULT_STRENGTH):
13+
self.algorithm = algorithm
14+
self.strength = strength
15+
16+
def to_api(self):
17+
""" It returns an object compatible with the API. """
18+
return {
19+
'algo': self.algorithm,
20+
'size': self.strength,
21+
}

cfssl/models/config_mixer.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2016 LasLabs Inc.
3+
# License MIT (https://opensource.org/licenses/MIT).
4+
5+
6+
class ConfigMixer(object):
7+
""" It provides a mixer for the Client and Server Configs """
8+
9+
def __init__(self, sign_policy_default, sign_policies_add, auth_policies):
10+
self.sign_policy = sign_policy_default
11+
self.sign_policies = sign_policies_add
12+
self.auth_policies = auth_policies
13+
14+
def to_api(self):
15+
""" It returns an object compatible with the API. """
16+
return {
17+
'signing': {
18+
'default': self.sign_policy.to_api(),
19+
'profiles': {
20+
p.name: p.to_api() for p in self.sign_policies
21+
},
22+
},
23+
'auth_keys': {
24+
k.name: k.to_api() for k in self.auth_policies
25+
},
26+
}

cfssl/models/config_server.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2016 LasLabs Inc.
3+
# License MIT (https://opensource.org/licenses/MIT).
4+
5+
from .config_mixer import ConfigMixer
6+
7+
8+
class ConfigServer(ConfigMixer):
9+
""" It provides a Server Config compatible with CFSSL. """

0 commit comments

Comments
 (0)