Overview
The External Signing CA plugin's purpose is to generate certificate and private key pairs signed by a Certificate Authority. By using this type of plugin the certificate signing can be tailored to fit any custom requirement.
Details
The plugin is a .zip file containing a MANIFEST and a main.py file.
The MANIFEST file
The MANIFEST file is a YAML file, and should conform to version 1.2 of the YAML specification. It should contain the following information about the plugin:
api: 1.0 type: signingca name: MySigningCaPlugin version: 1.0 description: My custom Signing CA
The type of the plugin must be signingca.
The main.py file
A Plugin class containing the following methods must be defined in the main.py file:
-
generate_for_addresses: generates a key/certificate pair for the given addresses (IP/DNS)
-
generate_for_username: generates a key/certificate pair for the given username
-
generate_for_subject: generates a key/certificate pair for the given subject values
Method arguments
Each method must take the following arguments:
-
generate_for_addresses:
-
addresses: {list of str} - contains either IP or DNS addresses for which the certificate shall be issued
-
keytype: {str} - contains a fixed value of 'RSA' or 'DSS' indicating the requested key type for the certificate
-
-
generate_for_username:
-
username {str} - contains the username for which the certificate shall be issued
-
keytype: {str} - contains a fixed value of 'RSA' or 'DSS' indicating the requested key type for the certificate
-
-
generate_for_subject:
-
subject {list of (str, str)} - contains the certificate subject as type-value pairs (tuples). Valid types are the following:
-
'C' country name
-
'ST' state or province name
-
'L' locality name
-
'O' orangization name
-
'OU' organizational unit
-
'CN' common name
-
'emailAddress' email address
-
-
keytype: {str} - contains a fixed value of 'RSA' or 'DSS' indicating the requested key type for the certificate
-
Method return values
Each method returns a {dict} that must have the following keys:
-
key: {str} the generated key
-
chain: {list of str} a PEM encoded certificate chain containing the generated certificate as the first element
Example
The code below demonstrates a simple plugin that can sign certificates with a built in CA. By default it uses a pre-generated CA certificate and key to complete signing requests. To use a custom certificate, provide a certificate and a key in a python dict format in the configuration field.
NOTE: If you wish to try this sample code, you will need to provide a MANIFEST file (see below) and the following package dependencies in the .zip file alongside the plugin:
-
asn1crypto
-
certbuilder
-
oscrypto
main.py:
#!/usr/bin/env pluginwrapper3
#
# Copyright (c) One Identity
# All Rights Reserved.
#
from ast import literal_eval
from certbuilder import CertificateBuilder, pem_armor_certificate
from ipaddress import ip_address
from oscrypto import asymmetric
class Plugin(object):
plugin_root_ca = """-----BEGIN CERTIFICATE-----
MIIDhjCCAm6gAwIBAgIIW+mOlk1Cu4swDQYJKoZIhvcNAQELBQAwYTELMAkGA1UE
BhMCSFUxETAPBgNVBAgMCEJ1ZGFwZXN0MREwDwYDVQQHDAhCdWRhcGVzdDEQMA4G
A1UECgwHQmFsYWJpdDEaMBgGA1UEAwwRQmFsYWJpdCBQbHVnaW4gQ0EwHhcNMTgx
MTEyMTQzMDQ2WhcNMTkxMTEyMTQzMDQ2WjBhMQswCQYDVQQGEwJIVTERMA8GA1UE
CAwIQnVkYXBlc3QxETAPBgNVBAcMCEJ1ZGFwZXN0MRAwDgYDVQQKDAdCYWxhYml0
MRowGAYDVQQDDBFCYWxhYml0IFBsdWdpbiBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAO73W0ONVwIaBJas+qUe0VBZ4rtk6PtzRNenZcBkTCkITuuF
DAQ3T1qLUsCyQ4uHMo+yKZUqR3HxbWGxS2l4IaHP6Hbna2kNEyYEsg16mGVUz6tc
D6bxFu3EpB7eU/OXh8RS8URIfZbLNrql1sKe7k1hpXUDS74Ra/avUIYKIpZ5sCjs
F6MBZWz5u3tNUa53xVmqgpnQ6pozN+OQ6k74DjK4xqWqJgTWcN6rxZ9k2voQYE3s
H66jl53q+Zl0D4w/AEW5W3OYNHJtx3tsc36sD2i0doqBCAAvflcSDEs7TXhfXSkC
qCBKyx8ics5EL9h49MDPGwDTehzwvXusz8LlxeMCAwEAAaNCMEAwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUyFWUMJli0q5CtJOp25IqK2M70oAwDgYDVR0PAQH/
BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQCWoQCJPqfM4Sjg0R2O42yrE2GtQsXf
Qb3Dur+CefWLcvjI28t1xuj31khDgpNTwk4IVYrvarNX33C3tjYKgcimwWRMijbA
p8kZzFajOZSWC32CQtkWL79LLkJCTJB7b/4E41oNQPHtOoNCqFY+uQogP90qZ1w1
xlFX8ie/W3cuqhfzW6+/M3iCIwdjhBquvOo6mE3t2/lUcGXE20GayFsKnEmgpDJa
nxoG1+m+s5zCwDuukX8Lr1O7maTMwNVhm5P5QWeEPbGRN7yw+CfzcvPIbFYwnZ5x
XeC9Vtoj2Jbom8RV9uus8R5LfYBJ+HZh74wbGhIC2Kf9LrJTK7r92uVA
-----END CERTIFICATE-----"""
plugin_root_ca_key = """-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDu91tDjVcCGgSW
rPqlHtFQWeK7ZOj7c0TXp2XAZEwpCE7rhQwEN09ai1LAskOLhzKPsimVKkdx8W1h
sUtpeCGhz+h252tpDRMmBLINephlVM+rXA+m8RbtxKQe3lPzl4fEUvFESH2Wyza6
pdbCnu5NYaV1A0u+EWv2r1CGCiKWebAo7BejAWVs+bt7TVGud8VZqoKZ0OqaMzfj
kOpO+A4yuMalqiYE1nDeq8WfZNr6EGBN7B+uo5ed6vmZdA+MPwBFuVtzmDRybcd7
bHN+rA9otHaKgQgAL35XEgxLO014X10pAqggSssfInLORC/YePTAzxsA03oc8L17
rM/C5cXjAgMBAAECggEBAIucxpw76naW3tFtNG7eB2pLaZUUSq4F1VWtPlxd/MUI
Tpt5OuEHs3vx5CIixCWzkk2zyGmWrvEaHU6zN5ziC7wu7ODzKaTRd7uBiMkpM/oX
x9CU06w0NLIrbbt/J0ss36xKzRyYwY8lIM+Bbmx8UDuzbehkSY89PHd+S6xUJYsF
YmOVM00wx1N6yZGKHUV9GLRnysHb+DBbjGIcjDqmlsdyuAzlB7/DAeToLFNkZvzx
Qzza6whMILXS9Qp39dzn7nJuJywfoOAX2q5LgOrPise2QY1FuAy0GTfqvBDR1eGd
NwFW5YtH89l347AIDPgklKvKaii2iIw1ZEMf1AlX7mECgYEA+0JM9sToMgLhHJDj
cUsznVn3xzjDT/4O95LdAq1fVrn10wh/SwGCBBPMHQkV1nS70d//1aGctZLWk5+F
K3aPGV9Eas70mOGNcXdU1ITpFNuVfbKK8uH5NF/oEuoD4zRabunrj/zEk0Qu/D9v
pN4qEwJoV1SD/9HpfpaUG/xuBLECgYEA83mtE34zYTY2TLBr4HvoiWrFYoEpPldN
64oD+w1/D0Wd9hxCyzO3y2SmrBmmbzoawTckxD/VKndeRdV5dLlEnAV4F35bPsQl
dRJJEAAQPqqc1z4x6c2my27WPSbm4mIcvfTc65UFu4ovm/koywc96fwvpTX6JIN1
X8zHZ/tQaNMCgYEAl3yk3I9hk22K/ecZSiBWEUPCETpW/66kpX3FhKy085wQ61iP
LtDM69pn0QW+RduBtgsAu3PCAPN0LfManlbP9jMrE96NOHOdDNEusycjRHET04JH
JiM6VeqRCH5RM7ZH4+FjJh/3APc2AN3aWSOdaHKmKCkLoLyVs73jtG/ggTECgYBp
reCf22E1yrAa7WCFmYK/UqbGMMXUF1Ts7YT4zUzfNhpwHqgnRxV5pQBrJt8E3DWM
tACzZfmCazlyGkyTi27qQb10hRXZ0o1nmT45Qa3LZYaaLpa/otHI7xzyghYpIOjU
0pmpb4+DbWFo0+cO6N/I1ftgPGOMwbqKkHnk+kJWnQKBgQDQwITXna8OVjn86AQP
m36JHXi0RNVO/x4+b8T7nurU6XCPIzE0PxfVVSXXsbKTWlq48GIw9ZNpPKPSTCQy
fnYC+Pcu+4A+bfUwFk21khnN/fP5vyFlFhTrGneZeKWhUxv5iOqASEaizfOePmtj
et/4B9LUf9KQFstlhuIR4AP2OA==
-----END PRIVATE KEY-----"""
def __init__(self, configuration=""):
self.cert_x509name = {
'country_name': 'HU',
'state_or_province_name': 'Budapest',
'locality_name': 'Budapest',
'organization_name': 'One Identity',
'common_name': '',
}
self.cert_alt_domains = []
self.cert_alt_ips = []
if configuration is not "":
try:
configdict = literal_eval(configuration)
self.plugin_root_ca = configdict['ca']
self.plugin_root_ca_key = configdict['key']
except Exception:
pass
self.root_ca_certificate = asymmetric.load_certificate(bytes(self.plugin_root_ca, 'utf-8'))
self.root_ca_key = asymmetric.load_private_key(bytes(self.plugin_root_ca_key, 'utf-8'))
def _sign_certificate(self):
end_entity_public_key, end_entity_private_key = asymmetric.generate_pair(self.cert_keytype, bit_size=2048)
end_entity_private_key = asymmetric.dump_private_key(end_entity_private_key, None)
end_entity_subject = self.cert_x509name
builder = CertificateBuilder(
end_entity_subject,
end_entity_public_key
)
builder.subject_alt_domains = self.cert_alt_domains
builder.subject_alt_ips = self.cert_alt_ips
builder.issuer = self.root_ca_certificate
end_entity_certificate = builder.build(self.root_ca_key)
end_entity_certificate = pem_armor_certificate(end_entity_certificate)
return end_entity_certificate, end_entity_private_key
_subject_type_mapping = {
'C': 'country_name',
'ST': 'state_or_province_name',
'L': 'locality_name',
'O': 'organization_name',
'OU': 'organizational_unit_name',
'CN': 'common_name',
'emailAddress': 'email_address',
}
def _set_certificate_x509_name(self, x509name):
for (t, v) in x509name:
self.cert_x509name[self._subject_type_mapping[t]] = v
def _set_certificate_keytype(self, keytype):
# Certbuilder lists DSS key as DSA so we have to translate the string here
if keytype == "dss": keytype = "dsa"
if keytype not in ['dsa', 'rsa']:
raise ValueError('Certificate type should be either \'rsa\' or \'dss\'')
else:
self.cert_keytype = keytype
def _set_certificate_cn(self, cn):
self.cert_x509name['common_name'] = cn
def _set_certificate_addresses(self, addresses):
for address in addresses:
try:
ip_address(address)
self.cert_alt_ips.append(address)
except ValueError:
self.cert_alt_domains.append(address)
def _build_response(self, cert, key):
return {'key': key.decode('ascii'), 'chain': [cert.decode('ascii'), self.plugin_root_ca]}
def generate_for_addresses(self, addresses: list, keytype: str):
self._set_certificate_keytype(keytype)
self._set_certificate_addresses(addresses)
self._set_certificate_cn(addresses[0])
return self._build_response(
*self._sign_certificate()
)
def generate_for_username(self, username: str, keytype: str):
self._set_certificate_keytype(keytype)
self._set_certificate_cn(username)
return self._build_response(
*self._sign_certificate()
)
def generate_for_subject(self, x509name: list, keytype: str):
self._set_certificate_keytype(keytype)
self._set_certificate_x509_name(x509name)
return self._build_response(
*self._sign_certificate()
)
Use the following snippet as the MANIFEST file:
# Name of the plugin, may contain [a-zA-Z0-9] name: HelloSigningCaPlugin # Version of the plugin, only for display purposes version: 0.1 # Type of the plugin - this is a signingca plugin type: signingca # API version of the SCB the plugin was written for, in major.minor format api: 1.0 # Free form description. description: This is an example plugin used for testing. # Entry point for the plugin (also for running with python3) entry_point: main.py