Commit ecd07d22 by Vincent Pelletier

all: Major rework.

- Re-evaluate feature set and REST API.
- switch duration units to days, which are more meaningful than sticking to
  ISO units in this context.
- Implement the "cau" half of "caucase".
  As a consequence flask password authentication mechanism is not needed
  anymore. As HTML UI is not required internally to caucase, and as
  sqlalchemy is not used to its full extend, get rid of these
  dependencies altogether.
- Implement REST HTTP/HTTPS stand-alone server as a layer above WSGI
  application, and integrate HTTPS certificate issuance and renewal
  mechanism to simplify deployment: no middleware needed, so from
  gunicorn dependency.
- Use standard python modules for http client needs.
- Re-evaluate data retention options:
  - unsigned CSRs are kept forever
  - CRTs are stored in CSR table, and a 24 hour expiration is set
  - CA CRTs: (unchanged, expire when past validity period)
  - CRLs: (unchanged, expire when past validity period)
- Redispatch housekeeping tasks:
  - CA renewal happens when caucase is used and renewal is needed
  - CRL is flushed when re-generated
  - CSR table (containing CRTs) is cleaned when a new CSR is received
  removing completely the need for these special periodic tasks.
- Storage parameters are not stored persistently anymore, instead their
  effect (time offsets) is applied before storing (to protect against
  transient retention period reconfiguration from wiping data).
- Rework storage schema.
- Implement certificate extension propagation & filtering.
- Implement "Certificate was auto-signed" extension.
- More docstrings.
- Use a CSR as a subject & extensions template instead of only allowing
  to override the subject. Useful when renewing a certificate and when
  authenticated client wants to force (ex) a CommonName in the subject.
- Reorganise cli executable arguments to have more possible actions.
  Especially, make CA renewal systematic on command start (helps
  validating caucase URL).
- Increase the amount of sanity checks against user-provided data (ex:
  do not upload a private key which would be in the same file as the CRT
  to renew).
- Extend package classifiers.
- Get rid of revocation reason, as it seems unlikely to be filled, and
  even less likely to be read later.
- (almost) stop using pyOpenSSL. Use cryptography module instead.
  cryptography has many more features than pyOpenSSL (except for certificate
  validation, sadly), so use it. It completely removes the need to poke
  at ASN.1 ourselves, which significantly simplifies utils module, and
  certificate signature. Code is a bit more verbose when signing, but much
  simpler than before.
- add the possibility to revoke by certificate serial
- update gitignore
- include coverage configuration
- include pylint configuration
- integrate several secondary command:
  - caucase-probe to quickly check server presence and basic
    functionality, so automated deployments can easily auto-check
  - caucase-monitor to automate key initial request and renewal
  - caucase-rerequest to allow full flexibility over certificate request
    content without ever transfering private keys
- add a secure backup generation mechanism
- add a README describing the design
1 parent 0da27fdc
[run]
branch = true
concurrency =
thread
multiprocessing
parallel = true
/htmlcov/
/cover/
/.eggs/
/.coverage
.*.swp
*.pyc
[MESSAGES CONTROL]
disable=C0103,C0330
# C0103 Disable "Invalid name "%s" (should match %s)"
# C0330 Disable "bad-continuation"
[FORMAT]
indent-string=" "
0.2.0 (2017-08-XX)
==================
* implement the "cau" half of "caucase"
* massive rework: removal of flask dependency, removal of HTML UI, rework of
the REST API, rework of the CLI tools, rework of the WGSI application,
incomatible redesign of the database.
0.1.4 (2017-07-21)
==================
* caucase web parameter 'auto-sign-csr-amount' can be used to set how many csr must be signed automatically.
......
include CHANGES.txt
recursive-include caucase/templates *.html
recursive-include caucase/static *.css *.png *.js *.gif
include COPYING
=======
caucase
=======
Certificate Authority for Users, Certificate Authority for SErvices
Overview
========
The goal of caucase is to automate certificate issuance and renewal without
constraining how the certificate will be used.
For example, there is no assumption that the certificate will be used to
secure HTTP, nor to serve anything at all: you may need certificates to
authenticate users, or sign your mails, or secure an SQL server socket.
As an unfortunate consequence, it is not possible for caucase to automatically
validate a signing request content against a service (ex: as one could check
the certificate for an HTTPS service was requested by someone with the ability
to make it serve a special file).
This also means that, while caucase imposes RFC-recommended constraints on many
certificate fields and extensions to be worthy of trust, it imposes no
constraint at all on subject and alternate subject certificate fields.
To still allow certificates to be used, caucase uses itself to authenticate
users (humans or otherwise) who implement the validation procedure: they tell
caucase what certificates to emit. Once done, any certificate can be
prolungated at a simple request of the key holder while the to-renew
certificate is still valid (not expired, not revoked).
Bootstrapping the system (creating the first service certificate for
_`caucased` to operate on HTTPS, and creating the first user certificate to
control further certificate issuance) works by caucase automatically signing a
set number of certificates upon submission.
Vocabulary
==========
Caucase manipulates the following asymetric cryptography concepts.
- Key pair: A private key and corresponding public key. The public key can be
derived from the private key, but not the other way around. As a consequence,
the private key is itself considered to be a key pair.
- Certificate: A certificate is the assurante, by a certificate authority,
that a given public key and set of attributes belong to an authorised entity.
Abbreviated cert or crt. A certificate is by definition signed by a CA.
- Certificate Authority: An entry, arbitrarily trusted (but worthy of trust by
its actions and decision) which can issue certificates. Abbreviated CA.
- Certificate signing request: A document produced by an entity desiring to get
certified, which they send to a certificate authority. The certificate signing
request contains the public key and desired set of attributes that the CA
should pronounce itself on. The CA has all liberty to issue a different set
of attiributes, or to not issue a certificate.
- Certificate revocation list: Lists the certificates which were issued by a CA
but which should not be trusted annymore. This can happen for a variety of
reasons: the private key was compromised, or its owneing entity should not be
trusted anymore (ex: entity's permission to access to protected service was
revoked).
- PEM: A serialisation mechanism commonly used for various cryptographic data
pieces. It relies on base64 so it is 7-bits-safe (unlike DER), and is very
commonly supported. Caucase exclusively uses PEM format.
Validity period
===============
Cryptographic keys wear out as are used and and as they age.
Of course, they do not bit-rot nor become thinner with use. But each time one
uses a key and each minute an attacker had access to a public key, fractions
of the private key bits are inevitably leaked, weakening it overall.
So keys must be renewed periodically to preserve intended security level. So
there is a limited life span to each certificate, including the ones emitted by
caucase.
The unit duration for caucase-emitted certificates is the "normal" certificate
life span. It default to 93 days from the moment the certificate was signed,
or about 3 months.
Then the CA certificate has a default life span of 4 "normal" certificate
validity periods. As CA renewal happens in caucase without x509-level cross
signing (by decision, to avoid relying on intermediate CA support on
certificate presenter side and instead rely on more widespread
multi-CA-certificate support on virifier side), there is a hard lower bound of
3 validity periods, under which the CA certificate cannot be reliably renewed
without risking certificate validation issues for emitted "normal"
certificates. CA certificate renewal is composed of 2 phases:
- Passive distribution phase: current CA certificate has a remaining life span
of less than 2 "normal" certificate life spans: a new CA certificate is
generated and distributed on-demand (on "normal" certificate renewal and
issuance, on CRL retrieval with caucase tools...), but not used to sign
anything.
- Active use phase: new CA certificate is valid for more than one "normal"
certificate life span. This means that all existing certificates which are
still in active use had to be renewed at least once since the new CA
certificate exists. This means all the certificate holders had the
opportunity to learn about the new CA certificate. So the new CA certificate
starts being used to sign new certificates, and the old CA certificate falls
out of use as its signed "normal" certificates expire.
By default, all caucase tools will generate a new private key unrelated to the
previous one on each certificat renewal.
Lastly, there is another limited validity period, although not for the same
reasons: the list of revoked certificates also has a maximum life span. In
caucase, the CRL is re-generated whenever it is requested and:
- there is no previous CRL
- previous CRL expired
- any revocation happened since previous CRL was created
Commands
========
Caucase provides several commands to work with certificates.
caucase
+++++++
Reference caucase "one-shot" client.
This command is intended to be used for isolated actions:
- listing and signing pending certificate signature requests
- revoking certificates
It is also able to submit certificate signing requests, retrieve signed
certificates, requesting certificate renewals and updating both
CA certificates and revocation lists, but you may be interested in using
_`caucase-monitor` for this instead.
caucase-monitor
+++++++++++++++
Reference caucase certificate renewal daemon.
Monitors a key pair, corresponding CA certificate and CRL, and renew them
before expiration.
When the key-pair lacks a signed certificate, issues a pre-existing CSR to
caucase server and waits for the certificate to be issued.
caucase-probe
+++++++++++++
Caucase server availability tester.
Performs minimal checks to verify a caucase server is available at given URL.
caucase-rerequest
+++++++++++++++++
Utility allowing to re-issue a CSR using a locally-generated private key.
Intended to be used in conjunction with _`caucase-monitor` when user cannot
generate the CSR on the system where the certificate is desired (ex: automated
HTTPS server deployment), where user is not the intended audience for
caucase-produced certificate:
- User generates a CSR on their own system, and signs it with any key (it will
not be needed later
- User sends the CSR to the system where the certificate is desired
- User gets caucase-rerequest to run on this CSR, producing a new private key
and a CSR similar to issued one, but signed with this new private key
- From then on, caucase-monitor can take over
This way, no private key left their original system, and user could still
freely customise certificate extensions.
caucase-key-id
++++++++++++++
Utility displaying the identifier of given key, or the identifier of keys
involved in given backup file.
Allows identifying users which hold a private key candidate for restoring a
caucased backup (see _`Restoration procedure`).
caucased
++++++++
Reference caucase server daemon.
This daemon provides access to both CAU and CAS services over both HTTP and
HTTPS.
It handles its own certificate issuance and renewal, so there is no need to use
_`caucase-monitor` for this service.
Backups
-------
Loosing the CA private key prevents issuing any new certificate trusted by
services which trusted the CA. Also, it prevents issuing any new CRL.
Recovering from such total loss requires starting a new CA and rolling it out
to all services which used the previous one. This is very time-costly.
So backups are required.
On the other hand, if someone gets their hand on the CA private key, they can
issue certificates for themselves, allowing them to authenticate with services
trusting the CA managed by caucase - including caucased itself if they issue a
user certificate: they can then revoke existing certificates and cause a lot of
damage.
So backups cannot happen in clear text, they must be encrypted.
But the danger of encrypted backups is that by definition they become worthless
if they cannot be decrypted. So as many (trusted) entities as possible should
be granted the ability to decrypt the backups.
The solution proposed by caucased is to encrypt produced backups in a way which
allows any of the caucase users to decrypt the archive.
As these users are already entrusted with issuing certificates, this puts
only a little more power in their hands than they already have. The little
extra power they get is that by having unrestricted access to the CA private
key they can issue certificates bypassing all caucase restrictions. The
proposed parade is to only make the backups available to a limited subset of
caucase users when there is an actual disaster, and otherwise keep it out of
their reach. This mechanism is not handled by caucase.
As there are few trusted users, caucase can keep their still-valid certificates
in its database for the duration of their validity with minimal size cost.
Backup procedure
----------------
Backups happen periodically as long as caucased is running. See
`--backup-period` and `--backup-directory`.
As discussed above, produced files should be kept out of reach of caucase
users until a disaster happens.
Restoration procedure
---------------------
See `--restore-backup`.
To restore, one of the trusted users must voluntarily compromise their own
private key, providing it to the administrator in charge of the restoration
procedure. Restoration procedure will hence immediately revoke their
certificate. They must also provide a CSR generated with a different private
key, so that caucase can provide them with a new certificate, so they keep
their access only via different credentials.
- admin identifies the list of keys which can decipher a backup, and broadcasts
that list to key holders
- key holders manifest themselves
- admin picks a key holder, requests them to provide their eixsting private key
and to generate a new key and accompanying csr
- key holder provide requested items
- admin initiates restoration with `--restore-backup` and provides key holder
with the csr_id so they can fetch their new certificate using caucase
protocol
Backup file format
------------------
- 64bits: 'caucase\0' magic string
- 32bits LE: header length
- header: json-encoded header (see below)
- encrypted byte stream (aka payload)
Header schema (inspired from s/mime, but s/mime tools available do not
support at least iterative production or iterative generation)::
{
"description": "Caucase backup header",
"required": ["algorithm", "key_list"],
"properties": {
"cipher": {
"description": "Symetric ciher used for payload",
"required": ["name"],
"properties": {
"name":
"enum": ["aes256_cbc_pkcs7_hmac_10M_sha256"],
"type": "string"
},
"parameter": {
"description": "Name-dependend clear cipher parameter (ex: IV)",
"type": "string"
}
}
"type": "object"
},
"key_list": {
"description": "Content key, encrypted with public keys",
"minItems": 1,
"items": {
"required": ["id", "cipher", "key"],
"properties": {
"id": {
"description": "Hex-encoded sha1 hash of the public key",
"type": "string"
},
"cipher": {
"description": "Asymetric cipher used for symetric key",
"required": ["name"],
"properties": {
"name": {
"enum": ["rsa_oaep_sha1_mgf1_sha1"],
"type": "string"
}
},
"type": "object"
}
"key": {
"description": "Hex-encoded encrypted concatenation of signing and symetric encryption keys",
"type": "string"
}
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
Blocker for 1.0
===============
- After pyca/cryptography 21st release: Make is_signature_valid call mandatory in caucase.utils.load_crl .
- After pyca/cryptography later release (code not fixed yet): Enable CRL distribution point extension when it tolerates literal IPv6 in the URL.
Eventually
==========
- Become an OCSP responder (requires support in other libraries - likely pyca/cryptography).
......@@ -15,23 +15,6 @@
#
# You should have received a copy of the GNU General Public License
# along with caucase. If not, see <http://www.gnu.org/licenses/>.
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# Use default value so SQLALCHEMY will not warn because there is not db_uri
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///ca.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
from caucase import web, storage
\ No newline at end of file
"""
Caucase - Certificate Authority for Users, Certificate Authority for SErvices
"""
# -*- coding: utf-8 -*-
# This file is part of caucase
# Copyright (C) 2017 Nexedi
# Alain Takoudjou <alain.takoudjou@nexedi.com>
......@@ -16,541 +15,907 @@
#
# You should have received a copy of the GNU General Public License
# along with caucase. If not, see <http://www.gnu.org/licenses/>.
"""
Caucase - Certificate Authority for Users, Certificate Authority for SErvices
"""
from __future__ import absolute_import
import datetime
import json
import os
import sys
import subprocess
import re
import time
import uuid
import errno
import tempfile
from OpenSSL import crypto, SSL
import traceback
from pyasn1.codec.der import encoder as der_encoder
from pyasn1.type import tag
from pyasn1_modules import rfc2459
from datetime import datetime, timedelta
from caucase.exceptions import (ExpiredCertificate, NotFound,
BadCertificateSigningRequest, CertificateVerificationError)
from caucase import utils
MIN_CA_RENEW_PERIOD = 2
DEFAULT_DIGEST_LIST = ['sha256', 'sha384', 'sha512']
SUBJECT_KEY_LIST = ['C', 'ST', 'L', 'OU', 'O', 'CN', 'emailAddress']
def getX509NameFromDict(**name_dict):
"""
Return a new X509Name with the given attributes.
"""
# XXX There's no other way to get a new X509Name.
name = crypto.X509().get_subject()
for key, value in name_dict.items():
setattr(name, key, value)
return name
import struct
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes, hmac
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.padding import OAEP, MGF1
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from . import utils
from .exceptions import CertificateVerificationError
__all__ = ('CertificateAuthority', )
_cryptography_backend = default_backend()
_AUTO_SIGNED_NO = 0
_AUTO_SIGNED_YES = 1
_AUTO_SIGNED_PASSTHROUGH = 2
_SUBJECT_OID_DICT = {
# pylint: disable=bad-whitespace
'C' : x509.oid.NameOID.COUNTRY_NAME,
'O' : x509.oid.NameOID.ORGANIZATION_NAME,
'OU': x509.oid.NameOID.ORGANIZATIONAL_UNIT_NAME,
'ST': x509.oid.NameOID.STATE_OR_PROVINCE_NAME,
'CN': x509.oid.NameOID.COMMON_NAME,
'L' : x509.oid.NameOID.LOCALITY_NAME,
'SN': x509.oid.NameOID.SURNAME,
'GN': x509.oid.NameOID.GIVEN_NAME,
# pylint: enable=bad-whitespace
}
_BACKUP_MAGIC = 'caucase\0'
_CONFIG_NAME_AUTO_SIGN_CSR_AMOUNT = 'auto_sign_csr_amount'
def Extension(value, critical):
"""
Avoid oid redundant parameter when creating an extension.
"""
return x509.Extension(
oid=value.oid,
critical=critical,
value=value,
)
class CertificateAuthority(object):
def __init__(self, storage, ca_life_period, ca_renew_period,
crt_life_time, crl_renew_period, digest_list=None,
crl_base_url=None, ca_subject='',
max_csr_amount=50, crt_keep_time=0,
auto_sign_csr_amount=0):
self._storage = storage
self.ca_life_period = ca_life_period
self.digest_list = digest_list
self.crt_life_time = crt_life_time
self.crl_renew_period = crl_renew_period
self.ca_renew_period = ca_renew_period
self.default_digest = 'sha256'
self.crl_base_url = crl_base_url
self.auto_sign_csr_amount = auto_sign_csr_amount
self.extension_manager = utils.X509Extension()
self.mandatory_subject_key_list = ['CN']
self.ca_subject_dict = self._getCASubjectDict(ca_subject)
# XXX - ERR_SSL_SERVER_CERT_BAD_FORMAT on browser
# Because if two certificate has the same serial from a CA with the same CN
# self.ca_subject_dict['CN'] = '%s %s' % (self.ca_subject_dict['CN'], int(time.time()))
if not self.digest_list:
self.digest_list = DEFAULT_DIGEST_LIST
if self.ca_life_period < MIN_CA_RENEW_PERIOD:
raise ValueError("'ca_life_period' value should be upper than %s" % MIN_CA_RENEW_PERIOD)
if self.crl_renew_period > 1:
raise ValueError("'crl_renew_period' is too high and should be less than a certificate life time.")
self.crl_life_time = int(self.crt_life_time * self.crl_renew_period)
self.ca_life_time = int(self.crt_life_time * self.ca_life_period)
self.ca_renew_time = int(self.crt_life_time * self.ca_renew_period)
self._ca_key_pairs_list = self._storage.getCAKeyPairList()
if not self._ca_key_pairs_list:
self.createCAKeyPair()
def _getCASubjectDict(self, ca_subject):
"""
Parse CA Subject from provided sting format
Ex: /C=XX/ST=State/L=City/OU=OUnit/O=Company/CN=CA Auth/emailAddress=xx@example.com
This class implements CA policy and lifetime logic:
- how CA key pair is generated
- what x509.3 extensions and attributes are enforced on signed certificates
- CA and CRL automated renewal
"""
def __init__(
self,
storage,
ca_subject_dict=(),
ca_key_size=2048,
crt_life_time=31 * 3, # Approximately 3 months
ca_life_period=4, # Approximately a year
crl_renew_period=0.33, # Approximately a month
crl_base_url=None,
digest_list=utils.DEFAULT_DIGEST_LIST,
auto_sign_csr_amount=0,
lock_auto_sign_csr_amount=False,
):
"""
storage (caucase.storage.Storage)
Persistent storage of certificate authority data.
ca_subject_dict (dict)
Items to use as Certificate Authority certificate subject.
Supported keys are: C, O, OU, ST, CN, L, SN, GN.
ca_key_size (int)
Number of bits to use as Certificate Authority key.
crt_life_time (float)
Validity duration for every issued certificate, in days.
ca_life_period (float)
Number of crt_life_time periods for which Certificate Authority
certificate will be valid.
Must be greater than 3 to allow smooth rollout.
crl_renew_period (float)
Number of crt_life_time periods for which a revocation list is
valid for.
crl_base_url (str)
The CRL distribution URL to include in signed certificates.
None to not declare a CRL distribution point in generated certificates.
Revocations are be functional even if this is None.
digest_list (list of str)
List of digest algorithms considered acceptable for authenticating
renewal and revocation requests, and CA renewal list responses.
The first item will be the one used, others are accepted but not used.
auto_sign_csr_amount (int)
Automatically sign the first <auto_sign_csr_amount> CSRs.
As certificate gets unconditionally emitted and only vital attributes
and extensions are forced during signature, you should choose the
smallest amount possible to get a functional service.
For a typical HTTP(S) caucase service, 1 should be enough for CAS usage
(first service certificate being to serve HTTPS for caucase), and 1 for
CAU usage (first user, which can then sign more user certificate
requests).
To verify nothing accessed the service before intended automated
requests, check issued certificate has an extension with OID:
2.25.285541874270823339875695650038637483517.0
(a message is printed when retrieving the certificate)
This mark is propagated during certificate renewal.
lock_auto_sign_csr_amount (bool)
When given with a true value, auto_sign_csr_amount is stored and the
value given on later instanciation will be ignored.
"""
ca_subject_dict = {}
regex = r"\/([C|ST|L|O|OU|CN|emailAddress]+)=([\w\s\@\.\d\-_\(\)\,\+:']+)"
matches = re.finditer(regex, ca_subject)
for match in matches:
key = match.group(1)
if not key in SUBJECT_KEY_LIST:
raise ValueError("Item %r is not a valid CA Subject key, please" \
"Check that the provided key is in %s" % (key,
SUBJECT_KEY_LIST))
ca_subject_dict[key] = match.group(2)
for key in self.mandatory_subject_key_list:
if key not in ca_subject_dict:
raise ValueError("The subject key '%r' is mandatory." % key)
self._storage = storage
if lock_auto_sign_csr_amount:
storage.setConfigOnce(
_CONFIG_NAME_AUTO_SIGN_CSR_AMOUNT,
auto_sign_csr_amount,
)
self._auto_sign_csr_amount = int(storage.getConfigOnce(
_CONFIG_NAME_AUTO_SIGN_CSR_AMOUNT,
auto_sign_csr_amount,
))
return ca_subject_dict
self._ca_key_size = ca_key_size
self._digest_list = digest_list
self._default_digest_class = getattr(hashes, self.digest_list[0].upper())
self._crt_life_time = datetime.timedelta(crt_life_time, 0)
self._crl_base_url = crl_base_url
self._ca_subject = x509.Name([
x509.NameAttribute(
oid=_SUBJECT_OID_DICT[key],
value=value,
)
for key, value in dict(ca_subject_dict).iteritems()
])
if ca_life_period < 3:
raise ValueError("ca_life_period must be >= 3 to allow CA rollout")
self._crl_life_time = datetime.timedelta(crt_life_time * crl_renew_period, 0)
self._ca_life_time = datetime.timedelta(crt_life_time * ca_life_period, 0)
self._loadCAKeyPairList()
self._renewCAIfNeeded()
@property
def digest_list(self):
"""
Read-only access to digest_list ctor parameter.
"""
return list(self._digest_list)
def _loadCAKeyPairList(self):
ca_key_pair_list = []
for pem_key_pair in self._storage.getCAKeyPairList():
utils.validateCertAndKey(pem_key_pair['crt_pem'], pem_key_pair['key_pem'])
ca_key_pair_list.append({
'crt': utils.load_ca_certificate(pem_key_pair['crt_pem']),
'key': utils.load_privatekey(pem_key_pair['key_pem']),
})
self._ca_key_pairs_list = ca_key_pair_list
def renewCAKeyPair(self):
"""
Refresh instance's knowledge of database content
(as storage house-keeping may/will happen outside our control)
def createCAKeyPair(self):
"""
Create a new CA key pair.
cert = self._ca_key_pairs_list[-1]['crt']
expire_date = datetime.strptime(cert.get_notAfter(), '%Y%m%d%H%M%SZ')
renew_date = expire_date - timedelta(0, self.ca_renew_time)
if renew_date > datetime.now():
# The ca certificat should not be renewed now
return False
self.createCAKeyPair()
return True
def createCAKeyPair(self):
CA certificate renewal normally happens automatically as long as
certificates are getting signed and revocation list downloaded.
"""
Create a new ca key + certificate pair
"""
key_pair = {}
key = crypto.PKey()
# Use 2048 bits key size
key.generate_key(crypto.TYPE_RSA, 2048)
key_pair['key'] = key
ca = crypto.X509()
# 3 = v3
ca.set_version(3)
ca.set_serial_number(int(time.time()))
subject = ca.get_subject()
for name, value in self.ca_subject_dict.items():
setattr(subject, name, value)
ca.gmtime_adj_notBefore(0)
ca.gmtime_adj_notAfter(self.ca_life_time)
ca.set_issuer(ca.get_subject())
ca.set_pubkey(key)
self.extension_manager.setCaExtensions(ca)
ca.sign(key, self.default_digest)
key_pair['crt'] = ca
self._storage.storeCAKeyPair(key_pair)
self._ca_key_pairs_list = self._storage.getCAKeyPairList()
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=self._ca_key_size,
backend=_cryptography_backend,
)
public_key = private_key.public_key()
subject_key_identifier = x509.SubjectKeyIdentifier.from_public_key(
public_key,
)
now = datetime.datetime.utcnow()
certificate = x509.CertificateBuilder(
subject_name=self._ca_subject,
issuer_name=self._ca_subject,
not_valid_before=now,
not_valid_after=now + self._ca_life_time,
serial_number=x509.random_serial_number(),
public_key=public_key,
extensions=[
Extension(
x509.BasicConstraints(
ca=True,
path_length=0,
),
critical=True, # "MUST mark the extension as critical"
),
Extension(
x509.KeyUsage(
# pylint: disable=bad-whitespace
digital_signature =False,
content_commitment=False,
key_encipherment =False,
data_encipherment =False,
key_agreement =False,
key_cert_sign =True,
crl_sign =True,
encipher_only =False,
decipher_only =False,
# pylint: enable=bad-whitespace
),
critical=True, # "SHOULD mark this extension critical"
),
# Should we make use of certificate policies ? If we do, we need to enable
# this extension and fill the values.
# Extension(
# x509.PolicyConstraints(
# require_explicit_policy=,
# inhibit_policy_mapping=,
# ),
# critical=True, # MUST mark this extension as critical
# ),
Extension(
subject_key_identifier,
critical=False, # "MUST mark this extension as non-critical"
),
Extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
# Dummy extension, from_issuer_subject_key_identifier accesses .data directly
Extension(
subject_key_identifier,
critical=False,
),
),
critical=False, # "MUST mark this extension as non-critical"
),
],
).sign(
private_key=private_key,
algorithm=self._default_digest_class(),
backend=_cryptography_backend,
)
self._storage.appendCAKeyPair(
utils.datetime2timestamp(certificate.not_valid_after),
{
'key_pem': utils.dump_privatekey(private_key),
'crt_pem': utils.dump_certificate(certificate),
},
)
self._loadCAKeyPairList()
assert self._ca_key_pairs_list
def getPendingCertificateRequest(self, csr_id):
def getCertificateSigningRequest(self, csr_id):
"""
Retrieve the content of a pending signing request.
Retrieve a PEM-encoded certificate signing request.
@param csr_id: The id of CSR returned by the storage
csr_id (int)
As returned when the CSR was stored.
"""
return self._storage.getPendingCertificateRequest(csr_id)
return self._storage.getCertificateSigningRequest(csr_id)
def createCertificateSigningRequest(self, csr):
def appendCertificateSigningRequest(self, csr_pem, override_limits=False):
"""
Sanity-check CSR, stores it and generates a unique signing request
identifier (crt_id).
Store certificate signing request and return its identifier.
May trigger its signature if the quantity of submitted CSRs is less than
auto_sign_csr_amount (see __init__).
@param csr: CSR string in PEM format
csr_pem (str)
PEM-encoded certificate signing request.
"""
# Check number of already-pending signing requests
# Check if csr is self-signed
# Check it has a CN (?)
# Check its extensions
# more ?
try:
csr_pem = crypto.load_certificate_request(crypto.FILETYPE_PEM, csr)
except crypto.Error, e:
raise BadCertificateSigningRequest(str(e))
if not hasattr(csr_pem.get_subject(), 'CN') or not csr_pem.get_subject().CN:
raise BadCertificateSigningRequest("CSR has no common name set")
# XXX check extensions
csr_id = self._storage.storeCertificateSigningRequest(csr_pem)
if self._storage.getCertificateSigningRequestAmount() <= \
self.auto_sign_csr_amount:
csr = utils.load_certificate_request(csr_pem)
# Note: requested_amount is None when a known CSR is re-submitted
csr_id, requested_amount = self._storage.appendCertificateSigningRequest(
csr_pem=csr_pem,
key_id=x509.SubjectKeyIdentifier.from_public_key(
csr.public_key(),
).digest.encode('hex'),
override_limits=override_limits,
)
if requested_amount is not None and \
requested_amount <= self._auto_sign_csr_amount:
# if allowed to sign this certificate automaticaly
self.createCertificate(csr_id)
self._createCertificate(csr_id, auto_signed=_AUTO_SIGNED_YES)
return csr_id
def deletePendingCertificateRequest(self, csr_id):
def deletePendingCertificateSigningRequest(self, csr_id):
"""
Reject a pending certificate signing request.
@param csr_id: The id of CSR returned by the storage
csr_id (int)
CSR id, as returned when the CSR was stored.
"""
self._storage.deletePendingCertificateRequest(csr_id)
self._storage.deletePendingCertificateSigningRequest(csr_id)
def getPendingCertificateRequestList(self, limit=0, with_data=False):
def getCertificateRequestList(self):
"""
Return list of pending certificate signature request
@param limit: number of element to fetch, 0 is not limit (int)
@param with_data: True or False, say if return csr PEM string associated
to others informations (bool).
Return the list of pending certificate signature requests, individually
PEM-encoded.
"""
return self._storage.getPendingCertificateRequestList(limit, with_data)
return self._storage.getCertificateSigningRequestList()
def createCertificate(self, csr_id, ca_key_pair=None, subject_dict=None):
def createCertificate(self, csr_id, template_csr=None):
"""
Generate new signed certificate. `ca_key_pair` is the CA key_pair to use
if None, use the latest CA key_pair
Sign a pending certificate signing request, storing produced certificate.
@param csr_id: CSR ID returned by storage, csr should be linked to the
new certificate (string).
@param ca_key_pair: The CA key_pair to used for signature. If None, the