blob: 1292bd07c4d6c62a371fb7f4595345d26a09a044 [file]
#!/usr/bin/env python
#
# ===================================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ===================================================================
#
# This script creates the private keys and certificates required for
# running the serf test suite.
#
# It should be run from the test/certs folder without arguments.
# Certificates will be created in the test/certs folder, private keys in the
# test/certs/private folder.
#
# You'll need to install 'cryptography' for this script to work.
from datetime import datetime, timedelta, UTC
from ipaddress import IPv4Address, IPv6Address
from cryptography.x509 import (
AccessDescription,
Certificate,
CertificateBuilder,
RevokedCertificateBuilder,
CertificateRevocationListBuilder,
CRLReason, ReasonFlags,
Name, NameAttribute, NameOID,
SubjectKeyIdentifier,
AuthorityKeyIdentifier,
AuthorityInformationAccess,
DNSName, IPAddress,
UniformResourceIdentifier)
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import (
BestAvailableEncryption,
Encoding,
NoEncryption,
PrivateFormat,
pkcs12)
from cryptography.x509.extensions import (
BasicConstraints,
ExtendedKeyUsage,
SubjectAlternativeName)
from cryptography.x509.oid import (
ExtendedKeyUsageOID,
AuthorityInformationAccessOID)
# for serf, update this number every time the certs are updated.
SERIAL_NUMBER=20250711
KEY_ALGO = rsa
KEY_EXPONENT = 65537
KEY_SIZE = 2048
SIGN_ALGO = SHA256()
NOT_BEFORE = datetime(2025, 6, 18, 10, 39, 14, 0, UTC)
NOT_AFTER = datetime(2125, 5, 25, 10, 39, 14, 0, UTC)
def create_key(keyfile, passphrase=None):
key = KEY_ALGO.generate_private_key(KEY_EXPONENT, KEY_SIZE)
if type(passphrase) is type(b''):
encryption = BestAvailableEncryption(passphrase)
else:
encryption = NoEncryption()
with open(keyfile, 'wb') as fd:
fd.write(key.private_bytes(encoding=Encoding.PEM,
format=PrivateFormat.PKCS8,
encryption_algorithm=encryption))
return key
def create_pkcs12(name, key, cert, issuer, pkcs12file, passphrase=None):
if type(passphrase) is type(b''):
encryption = BestAvailableEncryption(passphrase)
else:
encryption = NoEncryption()
with open(pkcs12file, 'wb') as fd:
fd.write(pkcs12.serialize_key_and_certificates(
name, key, cert, [issuer], encryption))
def create_crl(revokedcert, cakey, cacert, crlfile):
revoked = (RevokedCertificateBuilder()
.serial_number(revokedcert.serial_number)
.revocation_date(datetime.now(UTC))
.add_extension(CRLReason(ReasonFlags.unspecified), critical=False)
.build())
crl = (CertificateRevocationListBuilder()
.issuer_name(cacert.subject)
.last_update(NOT_BEFORE)
.next_update(NOT_AFTER)
.add_revoked_certificate(revoked)
.sign(cakey, SIGN_ALGO))
with open(crlfile, 'wb') as fd:
fd.write(crl.public_bytes(Encoding.PEM))
# subjectAltName
def create_cert(subjectkey, certfile, issuer=None, issuerkey=None, country='',
state='', city='', org='', ou='', cn='', email='', ca=False,
not_before=NOT_BEFORE, not_after=NOT_AFTER,
subjectAltName=None, ocsp_responder_url=None, ocsp_signer=False):
'''
Create a X509 signed certificate.
subjectAltName
Array of fully qualified subject alternative names (use OpenSSL syntax):
For a DNS entry, use: ['DNS:localhost']. Other options are 'email', 'URI', 'IP'.
'''
subject = []
if country:
subject.append(NameAttribute(NameOID.COUNTRY_NAME, country))
if state:
subject.append(NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state))
if city:
subject.append(NameAttribute(NameOID.LOCALITY_NAME, city))
if org:
subject.append(NameAttribute(NameOID.ORGANIZATION_NAME, org))
if ou:
subject.append(NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, ou))
if cn:
subject.append(NameAttribute(NameOID.COMMON_NAME, cn))
if email:
subject.append(NameAttribute(NameOID.EMAIL_ADDRESS, email))
pubkey = subjectkey.public_key()
builder = (CertificateBuilder()
.serial_number(SERIAL_NUMBER)
.subject_name(Name(subject))
.public_key(pubkey)
.not_valid_before(not_before)
.not_valid_after(not_after))
if issuer is None:
# self-signed certificate
builder = builder.issuer_name(Name(subject))
issuerkey = subjectkey
else:
builder = builder.issuer_name(issuer.subject)
if ca:
bc = BasicConstraints(ca=True, path_length=None)
ski = SubjectKeyIdentifier.from_public_key(pubkey)
aki = AuthorityKeyIdentifier.from_issuer_public_key(issuerkey.public_key())
builder = (builder
.add_extension(bc, critical=False)
.add_extension(ski, critical=False)
.add_extension(aki, critical=False))
if subjectAltName:
alt = []
for name in subjectAltName:
if name.startswith('DNS:'):
alt.append(DNSName(name[4:]))
if name.startswith('IP4:'):
alt.append(IPAddress(IPv4Address(name[4:])))
if name.startswith('IP6:'):
alt.append(IPAddress(IPv6Address(name[4:])))
if alt:
builder = builder.add_extension(SubjectAlternativeName(alt),
critical=True if not cn else False)
if ocsp_responder_url:
ocsp = AccessDescription(AuthorityInformationAccessOID.OCSP,
UniformResourceIdentifier(ocsp_responder_url))
builder = builder.add_extension(
AuthorityInformationAccess([ocsp]),
critical=False)
if ocsp_signer:
builder = builder.add_extension(
ExtendedKeyUsage([ExtendedKeyUsageOID.OCSP_SIGNING]),
critical=True)
cert = builder.sign(issuerkey, SIGN_ALGO)
with open(certfile, 'wb') as fd:
fd.write(cert.public_bytes(Encoding.PEM))
return cert
if __name__ == '__main__':
# root CA key pair and certificate.
# This key will be used to sign the intermediate CA certificate
rootcakey = create_key('private/serfrootcakey.pem', b'serftest')
rootcacert = create_cert(subjectkey=rootcakey,
certfile='serfrootcacert.pem',
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite Root CA', cn='Serf Root CA',
email='serfrootca@example.com', ca=True)
# intermediate CA key pair and certificate
# This key will be used to sign all server certificates
cakey = create_key('private/serfcakey.pem', b'serftest')
cacert = create_cert(subjectkey=cakey, certfile='serfcacert.pem',
issuer=rootcacert, issuerkey=rootcakey,
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite CA', cn='Serf CA',
email='serfca@example.com', ca=True)
# server key pair
# server certificate, no errors
serverkey = create_key('private/serfserverkey.pem', b'serftest')
servercert = create_cert(subjectkey=serverkey,
certfile='serfservercert.pem',
issuer=cacert, issuerkey=cakey,
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite Server', cn='localhost',
email='serfserver@example.com')
# server certificate that expired a year ago
expiredcert = create_cert(subjectkey=serverkey,
certfile='serfserver_expired_cert.pem',
issuer=cacert, issuerkey=cakey,
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite Server', cn='localhost',
email='serfserver@example.com',
not_before=NOT_BEFORE - timedelta(days=365),
not_after=NOT_BEFORE - timedelta(days=355))
# server certificate that will be valid in 100 years
notyetvalidcert = create_cert(subjectkey=serverkey,
certfile='serfserver_future_cert.pem',
issuer=cacert, issuerkey=cakey,
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite Server', cn='localhost',
email='serfserver@example.com',
not_before=NOT_AFTER,
not_after=NOT_AFTER + timedelta(days=10))
# server certificate with SubjectAltName and empty CN
san_nocncert = create_cert(subjectkey=serverkey,
certfile='serfserver_san_nocn_cert.pem',
issuer=cacert, issuerkey=cakey,
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite Server',
cn='',
email='serfserver@example.com',
subjectAltName=['DNS:localhost'])
# server certificate with OCSP responder URL
ocspcert = create_cert(subjectkey=serverkey,
certfile='serfserver_san_ocsp_cert.pem',
issuer=cacert, issuerkey=cakey,
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite Server',
cn='localhost',
email='serfserver@example.com',
subjectAltName=['DNS:localhost'],
ocsp_responder_url='http://localhost:17080')
# OCSP responder certifi
ocsprspcert = create_cert(subjectkey=serverkey,
certfile='serfocspresponder.pem',
issuer=cacert, issuerkey=cakey,
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite Server',
cn='localhost',
email='serfserver@example.com',
ocsp_signer=True)
# client key pair and certificate
clientkey = create_key('private/serfclientkey.pem', b'serftest')
clientcert = create_cert(subjectkey=clientkey,
certfile='serfclientcert.pem',
issuer=cacert, issuerkey=cakey,
country='BE', state='Antwerp', city='Mechelen',
org='In Serf we trust, Inc.',
ou='Test Suite Client', cn='Serf Client',
email='serfclient@example.com')
clientpkcs12 = create_pkcs12(b'serfclient', clientkey, clientcert, cacert,
'serfclientcert.p12', b'serftest')
# crl
crl = create_crl(servercert, cakey, cacert, 'serfservercrl.pem')