Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
Python CFSSL Library
====================

This library allows you to interact with a remote CFSSL using Python.
This library allows you to interact with a remote CFSSL server using Python.

CFSSL is CloudFlare's open source toolkit for everything TLS/SSL. CFSSL is used by
CloudFlare for their internal Certificate Authority infrastructure and for all of
their TLS certificates.

* `Read more on the CloudFlare blog
<https://blog.cloudflare.com/introducing-cfssl/>`_.
* `View the CFSSL source
<https://github.com/cloudflare/cfssl>`_.

Installation
------------
Expand All @@ -14,13 +23,13 @@ Setup
Usage
-----

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

Known Issues / Road Map
-----------------------

- Installation, setup, usage - in ReadMe
- Add a Certificate Request data structure
- Add type checking in datamodels

.. |Build Status| image:: https://api.travis-ci.org/laslabs/Python-CFSSL.svg?branch=master
:target: https://travis-ci.org/laslabs/Python-CFSSL
Expand Down
16 changes: 16 additions & 0 deletions cfssl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,20 @@
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).

# API
from .cfssl import CFSSL

# Models
from .models.certificate_request import CertificateRequest

from .models.config_client import ConfigClient
from .models.config_key import ConfigKey
from .models.config_server import ConfigServer

from .models.host import Host

from .models.policy_auth import PolicyAuth
from .models.policy_sign import PolicySign
from .models.policy_use import PolicyUse

from .models.subject_info import SubjectInfo
92 changes: 52 additions & 40 deletions cfssl/cfssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from .exceptions import CFSSLException, CFSSLRemoteException

from .models.config_key import ConfigKey


class CFSSL(object):
""" It provides Python bindings to a remote CFSSL server via HTTP(S).
Expand All @@ -23,8 +25,7 @@ def auth_sign(self, token, request, datetime=None, remote_address=None):

Args:
token: (str) The authentication token.
request: (mixed) Signing request document (e.g. as
documented in endpoint_sign.txt, but not JSON encoded).
request: (cfssl.CertificateRequest) Signing request document.
datetime: (datetime.datetime) Authentication timestamp.
remote_address: (str) An address used in making the request.
Returns:
Expand All @@ -33,7 +34,7 @@ def auth_sign(self, token, request, datetime=None, remote_address=None):
"""
data = self._clean_mapping({
'token': token,
'request': request,
'request': request.to_api(),
'datetime': datetime,
'remote_address': remote_address,
})
Expand Down Expand Up @@ -65,11 +66,11 @@ def bundle(self, certificate, private_key=None,

If only the ``domain`` parameter is present, the following
parameter is valid:

ip: (str) The IP address of the remote host; this will fetch the
certificate from the IP, and verify that it is valid for the
domain name.

Returns:
(dict) Object repesenting the bundle, with the following keys:
* bundle contains the concatenated list of PEM certificates
Expand Down Expand Up @@ -162,42 +163,45 @@ def init_ca(self, hosts, names, common_name=None, key=None, ca=None):
""" It initializes a new certificate authority.

Args:
hosts: (list) Of SANs (subject alternative names) for the
requested CA certificate.
names: (list) the certificate subject for the requested CA
certificate.
hosts: (iter of cfssl.Host) Subject Alternative Name(s) for the
requested CA certificate.
names: (iter of cfssl.SubjectInfo) The Subject Info(s) for the
requested CA certificate.
common_name: (str) the common name for the certificate subject in
the requested CA certificate.
key: the key algorithm and size for the newly generated private key,
default to ECDSA-256.
ca: the CA configuration of the requested CA, including CA pathlen
and CA default expiry.
key: (cfssl.ConfigKey) Cipher and strength to use for certificate.
ca: (cfssl.ConfigServer) the CA configuration of the requested CA,
including CA pathlen and CA default expiry.
Returns:
(dict) Mapping with two keys:
* private key: (str) a PEM-encoded CA private key.
* certificate: (str) a PEM-encoded self-signed CA certificate.
"""
key = key or ConfigKey()
data = self._clean_mapping({
'hosts': hosts,
'names': names,
'hosts': [
host.to_api() for host in hosts
],
'names': [
name.to_api() for name in names
],
'CN': common_name,
'key': key,
'ca': ca,
'key': key.to_api(),
'ca': ca and ca.to_api() or None,
})
return self.call('init_ca', 'POST', data=data)

def new_key(self, hosts, names, common_name=None, key=None, ca=None):
""" It generates and returns a new private key + CSR.

Args:
hosts: (list) Of SANs (subject alternative names) for the
requested CA certificate.
names: (list) the certificate subject for the requested CA
certificate.
hosts: (iter of cfssl.Host) Subject Alternative Name(s) for the
requested certificate.
names: (iter of cfssl.SubjectInfo) The Subject Info(s) for the
requested certificate.
CN: (str) the common name for the certificate subject in the
requestedrequested CA certificate.
key: the key algorithm and size for the newly generated private key,
default to ECDSA-256.
key: (cfssl.ConfigKey) Cipher and strength to use for certificate.
ca: the CA configuration of the requested CA, including CA pathlen
and CA default expiry.
Returns:
Expand All @@ -208,8 +212,12 @@ def new_key(self, hosts, names, common_name=None, key=None, ca=None):
certificate request
"""
data = self._clean_mapping({
'hosts': hosts,
'names': names,
'hosts': [
host.to_api() for host in hosts
],
'names': [
name.to_api() for name in names
],
'CN': common_name,
'key': key,
'ca': ca,
Expand All @@ -220,7 +228,8 @@ def new_cert(self, request, label=None, profile=None, bundle=None):
""" It generates and returns a new private key and certificate.

Args:
request: (dict) Specifying the certificate request.
request: (cfssl.CertificateRequest) CSR to be used for
certificate creation.
label: (str) Specifying which signer to be appointed to sign
the CSR, useful when interacting with cfssl server that stands
in front of a remote multi-root CA signer.
Expand All @@ -238,7 +247,7 @@ def new_cert(self, request, label=None, profile=None, bundle=None):
if the bundle parameter was set).
"""
data = self._clean_mapping({
'request': request,
'request': request.to_api(),
'label': label,
'profile': profile,
'bundle': bundle,
Expand Down Expand Up @@ -269,12 +278,12 @@ def scan(self, host, ip=None, timeout=None, family=None, scanner=None):
""" It scans servers to determine the quality of their TLS setup.

Args:
host: the hostname (optionally including port) to scan.
ip: IP Address to override DNS lookup of host.
timeout: The amount of time allotted for the scan to complete
host: (cfssl.Host) The host to scan.
ip: (str) IP Address to override DNS lookup of host.
timeout: (str) The amount of time allotted for the scan to complete
(default: 1 minute).
family: regular expression specifying scan famil(ies) to run.
scanner: regular expression specifying scanner(s) to run.
family: (str) regular expression specifying scan famil(ies) to run.
scanner: (str) regular expression specifying scanner(s) to run.
Returns:
(dict) Mapping with keys for each scan family. Each of these
objects contains keys for each scanner run in that family
Expand All @@ -290,7 +299,7 @@ def scan(self, host, ip=None, timeout=None, family=None, scanner=None):
* output: (dict) Arbitrary data retrieved during the scan.
"""
data = self._clean_mapping({
'host': host,
'host': host.to_api(),
'ip': ip,
'timeout': timeout,
'family': family,
Expand All @@ -314,8 +323,8 @@ def sign(self, certificate_request, hosts=None, subject=None,
""" It signs and returns a certificate.

Args:
certificate_request: (str) the CSR bytes to be signed in PEM.
hosts: (iter) of SAN (subject alternative .names)
certificate_request: (str) the CSR bytes to be signed (in PEM).
hosts: (iter of cfssl.Host) of SAN (subject alternative .names)
which overrides the ones in the CSR
subject: (str) The certificate subject which overrides
the ones in the CSR.
Expand All @@ -324,19 +333,22 @@ def sign(self, certificate_request, hosts=None, subject=None,
label: (str) Specifying which signer to be appointed to sign
the CSR, useful when interacting with a remote multi-root CA
signer.
profile: (str) Specifying the signing profile for the signer,
useful when interacting with a remote multi-root CA signer.
profile: (cfssl.ConfigServer) Specifying the signing profile for
the signer, useful when interacting with a remote multi-root
CA signer.
Returns:
(str) A PEM-encoded certificate that has been signed by the
server.
"""
data = self._clean_mapping({
'certificate_request': certificate_request,
'hosts': hosts,
'certificate_request': certificate_request.to_api(),
'hosts': [
host.to_api() for host in hosts
],
'subject': subject,
'serial_sequence': serial_sequence,
'label': label,
'profile': profile,
'profile': profile.to_api(),
})
result = self.call('sign', 'POST', data=data)
return result['certificate']
Expand Down
7 changes: 7 additions & 0 deletions cfssl/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).

DEFAULT_ALGORITHM = 'rsa'
DEFAULT_STRENGTH = 4096
DEFAULT_EXPIRE_MINUTES = 365 * 24 * 60
3 changes: 3 additions & 0 deletions cfssl/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).
30 changes: 30 additions & 0 deletions cfssl/models/certificate_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).

from .host import Host
from .config_key import ConfigKey
from .subject_info import SubjectInfo


class CertificateRequest(object):
""" It provides a Certificate Request compatible with CFSSL. """

def __init__(self, common_name, names=None, hosts=None, key=None):
self.common_name = common_name
self.names = names or []
self.hosts = hosts or []
self.key = key or KeyConfig()

def to_api(self):
""" It returns an object compatible with the API. """
return {
'CN': self.common_name,
'names': [
name.to_api() for name in self.names
],
'hosts': [
host.to_api() for host in self.hosts
],
'key': self.key.to_api(),
}
24 changes: 24 additions & 0 deletions cfssl/models/config_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).

from .config_mixer import ConfigMixer


class ConfigClient(ConfigMixer):
""" It provides a Client Config compatible with CFSSL. """

def __init__(self, sign_policy_default,
sign_policies_add, auth_policies, remotes):
super(ConfigClient, self).__init__(
sign_policy_default, auth_policies, remotes,
)
self.remotes = remotes

def to_api(self):
""" It returns an object compatible with the API. """
res = super(ConfigClient, self).to_api()
res['remotes'] = {
r.name: r.to_api() for r in self.remotes
}
return res
21 changes: 21 additions & 0 deletions cfssl/models/config_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).

from ..defaults import DEFAULT_ALGORITHM, DEFAULT_STRENGTH


class ConfigKey(object):
""" It provides a Key Config compatible with CFSSL. """

def __init__(self, algorithm=DEFAULT_ALGORITHM,
strength=DEFAULT_STRENGTH):
self.algorithm = algorithm
self.strength = strength

def to_api(self):
""" It returns an object compatible with the API. """
return {
'algo': self.algorithm,
'size': self.strength,
}
26 changes: 26 additions & 0 deletions cfssl/models/config_mixer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).


class ConfigMixer(object):
""" It provides a mixer for the Client and Server Configs """

def __init__(self, sign_policy_default, sign_policies_add, auth_policies):
self.sign_policy = sign_policy_default
self.sign_policies = sign_policies_add
self.auth_policies = auth_policies

def to_api(self):
""" It returns an object compatible with the API. """
return {
'signing': {
'default': self.sign_policy.to_api(),
'profiles': {
p.name: p.to_api() for p in self.sign_policies
},
},
'auth_keys': {
k.name: k.to_api() for k in self.auth_policies
},
}
9 changes: 9 additions & 0 deletions cfssl/models/config_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).

from .config_mixer import ConfigMixer


class ConfigServer(ConfigMixer):
""" It provides a Server Config compatible with CFSSL. """
Loading