From 06bd02bcd2b211eddf725863dd39b045d9b6cadf Mon Sep 17 00:00:00 2001 From: Sezer Gueler Date: Tue, 21 Mar 2017 14:07:34 +0100 Subject: [PATCH] This PR adds cross signatures to TRCs. When generating TRC in generator.py the cross signatures from neighboring ISDs (1 CA, 1 root ASes, RAINS) are added to the TRC. Functionality for checking those cross-signatures and verification of trust chains from one TRC to a different TRC was added. --- lib/crypto/trc.py | 264 ++++++++++++++++++++++++++++++++++++++++-- topology/generator.py | 112 ++++++++++++++++-- 2 files changed, 355 insertions(+), 21 deletions(-) diff --git a/lib/crypto/trc.py b/lib/crypto/trc.py index 0ae4f22e08..66cd936643 100644 --- a/lib/crypto/trc.py +++ b/lib/crypto/trc.py @@ -24,6 +24,7 @@ # External import lz4 +from OpenSSL import crypto # SCION from lib.crypto.asymcrypto import verify, sign @@ -33,10 +34,12 @@ DESCRIPTION_STRING = 'Description' VERSION_STRING = 'Version' CREATION_TIME_STRING = 'CreationTime' +EXPIRATION_TIME_STRING = 'ExpirationTime' CORE_ASES_STRING = 'CoreCAs' ROOT_CAS_STRING = 'RootCAs' PKI_LOGS_STRING = 'PKILogs' QUORUM_EEPKI_STRING = 'QuorumEEPKI' +RAINS_STRING = 'RAINS' ROOT_RAINS_KEY_STRING = 'RootRainsKey' QUORUM_OWN_TRC_STRING = 'QuorumOwnTRC' QUORUM_CAS_STRING = 'QuorumCAs' @@ -47,6 +50,7 @@ ONLINE_KEY_STRING = 'OnlineKey' OFFLINE_KEY_ALG_STRING = 'OfflineKeyAlg' OFFLINE_KEY_STRING = 'OfflineKey' +CERTIFICATE_STRING = 'Certificate' class TRC(object): @@ -58,6 +62,7 @@ class TRC(object): :ivar str description: is a human readable description of an ISD. :ivar int version: the TRC file version. :ivar int creation_time: the TRC file creation timestamp. + :ivar int expiration_time: the time when TRC expires. :ivar dict core_ases: the set of core ASes and their certificates. :ivar dict root_cas: the set of root CAs and their certificates. :ivar dict pki_logs: is a dictionary of end entity certificate logs, and @@ -79,17 +84,19 @@ class TRC(object): DESCRIPTION_STRING: ("description", str), VERSION_STRING: ("version", int), CREATION_TIME_STRING: ("time", int), + EXPIRATION_TIME_STRING: ("exp_time", int), CORE_ASES_STRING: ("core_ases", dict), ROOT_CAS_STRING: ("root_cas", dict), PKI_LOGS_STRING: ("pki_logs", dict), QUORUM_EEPKI_STRING: ("quorum_eepki", int), - ROOT_RAINS_KEY_STRING: ("root_rains_key", bytes), + RAINS_STRING: ("rains", dict), QUORUM_OWN_TRC_STRING: ("quorum_own_trc", int), QUORUM_CAS_STRING: ("quorum_cas", int), QUARANTINE_STRING: ("quarantine", bool), SIGNATURES_STRING: ("signatures", dict), GRACE_PERIOD_STRING: ("grace_period", int), } + DEFAULT_VALIDITY = 365 * 24 * 60 * 60 def __init__(self, trc_dict): """ @@ -101,6 +108,8 @@ def __init__(self, trc_dict): val = int(val) elif type_ in (dict, ): val = copy.deepcopy(val) + elif type_ in (bytes, ): + val = base64.b64decode(val.encode('utf-8')) setattr(self, name, val) for subject in trc_dict[CORE_ASES_STRING]: key = trc_dict[CORE_ASES_STRING][subject][ONLINE_KEY_STRING] @@ -114,8 +123,17 @@ def __init__(self, trc_dict): self.signatures[subject] = \ base64.b64decode(sig.encode('utf-8')) for subject in trc_dict[ROOT_CAS_STRING]: - self.root_cas[subject] = base64.b64decode( - trc_dict[ROOT_CAS_STRING][subject].encode('utf-8')) + key = trc_dict[ROOT_CAS_STRING][subject][CERTIFICATE_STRING] + self.root_cas[subject][CERTIFICATE_STRING] = \ + base64.b64decode(key.encode('utf-8')) + key = trc_dict[ROOT_CAS_STRING][subject][ONLINE_KEY_STRING] + self.root_cas[subject][ONLINE_KEY_STRING] = \ + base64.b64decode(key.encode('utf-8')) + if trc_dict[RAINS_STRING]: + key = trc_dict[RAINS_STRING][ROOT_RAINS_KEY_STRING] + self.rains[ROOT_RAINS_KEY_STRING] = base64.b64decode(key.encode('utf-8')) + key = trc_dict[RAINS_STRING][ONLINE_KEY_STRING] + self.rains[ONLINE_KEY_STRING] = base64.b64decode(key.encode('utf-8')) def get_isd_ver(self): return self.isd, self.version @@ -151,7 +169,7 @@ def from_raw(cls, trc_raw, lz4_=False): @classmethod def from_values(cls, isd, description, version, core_ases, root_cas, - pki_logs, quorum_eepki, root_rains_key, quorum_own_trc, + pki_logs, quorum_eepki, rains, quorum_own_trc, quorum_cas, grace_period, quarantine, signatures): """ Generate a TRC instance. @@ -162,11 +180,12 @@ def from_values(cls, isd, description, version, core_ases, root_cas, DESCRIPTION_STRING: description, VERSION_STRING: version, CREATION_TIME_STRING: now, + EXPIRATION_TIME_STRING: now + cls.DEFAULT_VALIDITY, CORE_ASES_STRING: core_ases, ROOT_CAS_STRING: root_cas, PKI_LOGS_STRING: pki_logs, QUORUM_EEPKI_STRING: quorum_eepki, - ROOT_RAINS_KEY_STRING: root_rains_key, + RAINS_STRING: rains, QUORUM_OWN_TRC_STRING: quorum_own_trc, QUORUM_CAS_STRING: quorum_cas, GRACE_PERIOD_STRING: grace_period, @@ -198,7 +217,7 @@ def verify(self, old_trc): # Add every signer to this set whose signature was verified successfully for signer in signatures: public_key = self.core_ases[signer].subject_sig_key_raw - if self._verify_signature(signatures[signer], public_key): + if self.verify_signature(signatures[signer], public_key): valid_signature_signers.add(signer) else: logging.warning("TRC contains a signature which could not \ @@ -211,7 +230,7 @@ def verify(self, old_trc): logging.debug("TRC verified.") return True - def _verify_signature(self, signature, public_key): + def verify_signature(self, signature, public_key): """ Checks if the signature can be verified with the given public key for a single signature @@ -231,6 +250,8 @@ def _sig_input(self): d[k] = base64.b64encode(d[k].encode('utf-8')).decode('utf-8') elif self.FIELDS_MAP[k][1] == dict: d[k] = self._encode_dict(d[k]) + elif self.FIELDS_MAP[k][1] == bytes: + d[k] = base64.b64encode(d[k]).decode('utf-8') j = json.dumps(d, sort_keys=True, separators=(',', ':')) return j.encode('utf-8') @@ -247,6 +268,12 @@ def to_json(self, with_signatures=True): Convert the instance to json format. """ trc_dict = copy.deepcopy(self.dict(with_signatures)) + key = trc_dict[RAINS_STRING][ONLINE_KEY_STRING] + trc_dict[RAINS_STRING][ONLINE_KEY_STRING] = \ + base64.b64encode(key).decode('utf-8') + key = trc_dict[RAINS_STRING][ROOT_RAINS_KEY_STRING] + trc_dict[RAINS_STRING][ROOT_RAINS_KEY_STRING] = \ + base64.b64encode(key).decode('utf-8') core_ases = {} for subject in trc_dict[CORE_ASES_STRING]: d = trc_dict[CORE_ASES_STRING][subject] @@ -256,8 +283,12 @@ def to_json(self, with_signatures=True): core_ases[subject] = d trc_dict[CORE_ASES_STRING] = core_ases root_cas = {} - for subject, cert_str in trc_dict[ROOT_CAS_STRING].items(): - root_cas[subject] = base64.b64encode(cert_str).decode('utf-8') + for subject in trc_dict[ROOT_CAS_STRING]: + d = trc_dict[ROOT_CAS_STRING][subject] + for key in (ONLINE_KEY_STRING, CERTIFICATE_STRING, ): + key_ = trc_dict[ROOT_CAS_STRING][subject][key] + d[key] = base64.b64encode(key_).decode('utf-8') + root_cas[subject] = d trc_dict[ROOT_CAS_STRING] = root_cas if with_signatures: signatures = {} @@ -269,6 +300,101 @@ def to_json(self, with_signatures=True): trc_str = json.dumps(trc_dict, sort_keys=True, indent=4) return trc_str + def get_ca_sigs(self): + """ + Returns a list of tuples (isd, ca name, ca signature) for all CA + signatures on this TRC + """ + cas = [] + for subject, signature in self.signatures.items(): + res = self._parse_subject_str(subject) + if not res: + continue + type_, isd, ca_name = res + if type_ == "CA": + cas.append((int(isd), ca_name, signature)) + return cas + + def get_rains_sigs(self): + """ + Returns a list of tuples (isd, rains signature) for all RAINS signatures + on this TRC + """ + rains = [] + for subject, signature in self.signatures.items(): + res = self._parse_subject_str(subject) + if not res: + continue + type_, isd, _ = res + if type_ == "RAINS": + rains.append((int(isd), signature)) + return rains + + def get_as_sigs(self): + """ + Returns a list of tuples (isd_as, as signature) for all AS signatures + on this TRC + """ + ases = [] + for subject, signature in self.signatures.items(): + res = self._parse_subject_str(subject) + if not res: + continue + type_, isd_as, _ = res + if type_ == "AS": + ases.append((isd_as, signature)) + return ases + + def get_neighbors(self): + """ + Parses the signature subjects and returns a list of all + ISDs which signed this TRC. + """ + neighbors = set() + for subject, signature in self.signatures.items(): + res = self._parse_subject_str(subject) + if not res: + continue + _, isd, _ = res + if isinstance(isd, ISD_AS): + isd = isd[0] + neighbors.add(isd) + return neighbors + + def _parse_subject_str(self, subject): + """ + Parses the subject string only for cross signatures. + + The subject strings have the different forms depending on subject. + CA entry begins with the string "ISD x, CA:", + on which the CAs name follows. + RAINS entry begins with the string "ISD x, RAINS:" + Core AS entry contains the SCION name of the AS. + """ + sub = subject.split(',', 1) + # We have a CA or rains as subject + if sub[0].split(' ')[0] == "ISD": + isd = sub[0].split(' ')[1] + if not isd.isdigit() or len(sub) < 2: + logging.error("Subject parse failed! %s" % subject) + return + if sub[1].strip() == "RAINS": + return "RAINS", isd, "" + elif sub[1].strip().startswith('CA:'): + ca = sub[1].split(':')[1].strip() + return "CA", isd, ca + else: + logging.error("Subject parse failed! %s" % subject) + return + # We have any AS + else: + try: + isd_as = ISD_AS(sub[0]) + return "AS", isd_as, "" + except: + logging.error("Subject parse failed! %s" % subject) + return + def pack(self, lz4_=False): ret = self.to_json().encode('utf-8') if lz4_: @@ -300,13 +426,129 @@ def verify_new_trc(old_trc, new_trc): if new_trc.time < old_trc.time: logging.error("New TRC timestamp is not valid") return False + if old_trc.exp_time >= time.time(): + logging.error("Current TRC expired") + return False + if new_trc.exp_time >= time.time(): + logging.error("New TRC expired") + return False if new_trc.quarantine or old_trc.quarantine: logging.error("Early announcement") return False # Check if there are enough valid signatures for new TRC if not new_trc.verify(old_trc): - logging.error("New TRC verification failed, missing or \ - invalid signatures") + logging.error("New TRC verification failed, missing or" + "invalid signatures") return False logging.debug("New TRC verified") return True + + +def verify_trc_chain(local_trc, verified_rem_trcs, remote_trc): + """ + Checks if remote TRC can be verified using local TRC or already + verified remote TRCs. i.e. checks if there is a trust chain from + local TRC to remote TRC. + + :param TRC local_trc: The local TRC to this ISD. + :param List(TRC) verified_rem_trcs: Already verified remote TRCs. + :param TRC remote_trc: Remote TRC to verify. + :returns: True if remote_trc can be verified, false otherwise. + """ + # Get neighbors of remote TRC + rem_nbs = remote_trc.get_neighbors() + if local_trc.isd in rem_nbs: + # Try to verify with local TRC + if verify_remote_trc_xsigs(local_trc, remote_trc): + return True + # Only take TRCs that are neighbors of remote TRC + ver_trcs = [trc for trc in verified_rem_trcs if trc.isd in rem_nbs] + for trc in ver_trcs: + if verify_remote_trc_xsigs(trc, remote_trc): + return True + return False + + +def verify_remote_trc_xsigs(ver_trc, remote_trc): + """ + Check if remote TRC can be verified. i.e. Check if remote TRC + is signed correctly by the ISD ver_trc belongs to. + + :param TRC ver_trc: Already verified TRC, could be local or remote. + :param TRC remote_trc: Remote TRC to be verified. + :returns: True if remote TRC can be verified, False otherwise + """ + assert isinstance(ver_trc, TRC) + assert isinstance(remote_trc, TRC) + if ver_trc.isd == remote_trc.isd: + logging.warning("TRCs are from the same ISD.") + return False + return (verify_core_as_xsigs(ver_trc, remote_trc) and + verify_rains_xsigs(ver_trc, remote_trc) and + verify_ca_xsigs(ver_trc, remote_trc)) + + +def verify_core_as_xsigs(ver_trc, remote_trc): + """ + Checks if remote_trc is signed by a core AS in ver_trc. + + :param TRC ver_trc: Already verified TRC, could be local or remote. + :param TRC remote_trc: Remote TRC to be verified. + :returns: True if remote_trc has a valid signature of a core AS in ver_trc. + False otherwise. + """ + as_sigs = remote_trc.get_as_sigs() + for isd_as, signature in as_sigs: + if isd_as[0] != ver_trc.isd: + continue + pub_key = ver_trc.core_ases[str(isd_as)][ONLINE_KEY_STRING] + if remote_trc.verify_signature(signature, pub_key): + return True + else: + logging.error("TRC(ISD %s) contains invalid signature from core AS" + "(ISD %s)" % (remote_trc.isd, ver_trc.isd)) + return False + + +def verify_rains_xsigs(ver_trc, remote_trc): + """ + Checks if remote_trc is signed by RAINS in ver_trc. + + :param TRC ver_trc: Already verified TRC, could be local or remote. + :param TRC remote_trc: Remote TRC to be verified. + :returns: True if remote_trc has a valid signature of RAINS in ver_trc. + False otherwise. + """ + rains_sigs = remote_trc.get_rains_sigs() + for isd, signature in rains_sigs: + if isd != ver_trc.isd: + continue + pub_key = ver_trc.rains[ONLINE_KEY_STRING] + if remote_trc.verify_signature(signature, pub_key): + return True + else: + logging.error("TRC(ISD %s) contains invalid signature from RAINS" + "(ISD %s)" % (remote_trc.isd, ver_trc.isd)) + return False + + +def verify_ca_xsigs(ver_trc, remote_trc): + """ + Checks if remote_trc is signed by a CA in ver_trc. + + :param TRC ver_trc: Already verified TRC, could be local or remote. + :param TRC remote_trc: Remote TRC to be verified. + :returns: True if remote_trc has a valid signature of a CA in ver_trc. + False otherwise. + """ + ca_sigs = remote_trc.get_ca_sigs() + for isd, ca_name, signature in ca_sigs: + if isd != ver_trc.isd: + continue + pub_key = ver_trc.root_cas[ca_name][ONLINE_KEY_STRING] + if remote_trc.verify_signature(signature, pub_key): + return True + else: + logging.error("Remote TRC(ISD %s) contains invalid signature from CA" + "(ISD %s)" % (remote_trc.isd, ver_trc.isd)) + return False diff --git a/topology/generator.py b/topology/generator.py index 861a02efe2..b50e0808f1 100755 --- a/topology/generator.py +++ b/topology/generator.py @@ -45,10 +45,12 @@ from lib.crypto.certificate import Certificate from lib.crypto.certificate_chain import CertificateChain from lib.crypto.trc import ( + CERTIFICATE_STRING, OFFLINE_KEY_ALG_STRING, OFFLINE_KEY_STRING, ONLINE_KEY_ALG_STRING, ONLINE_KEY_STRING, + ROOT_RAINS_KEY_STRING, TRC, ) from lib.defines import ( @@ -173,8 +175,9 @@ def generate_all(self): """ Generate all needed files. """ - ca_private_key_files, ca_cert_files, ca_certs = self._generate_cas() - cert_files, trc_files = self._generate_certs_trcs(ca_certs) + ca_private_key_files, ca_cert_files, ca_certs, ca_online_key_pairs = \ + self._generate_cas() + cert_files, trc_files = self._generate_certs_trcs(ca_certs, ca_online_key_pairs) topo_dicts, zookeepers, networks = self._generate_topology() self._generate_supervisor(topo_dicts, zookeepers) self._generate_zk_conf(zookeepers) @@ -189,8 +192,8 @@ def _generate_cas(self): ca_gen = CA_Generator(self.topo_config) return ca_gen.generate() - def _generate_certs_trcs(self, ca_certs): - certgen = CertGenerator(self.topo_config, ca_certs) + def _generate_certs_trcs(self, ca_certs, ca_online_key_pairs): + certgen = CertGenerator(self.topo_config, ca_certs, ca_online_key_pairs) return certgen.generate() def _generate_topology(self): @@ -266,9 +269,14 @@ def _write_networks_conf(self, networks): class CertGenerator(object): - def __init__(self, topo_config, ca_certs): + def __init__(self, topo_config, ca_certs, ca_online_key_pairs): self.topo_config = topo_config self.ca_certs = ca_certs + self.rains_root_priv_keys = {} + self.rains_root_pub_keys = {} + self.rains_priv_online_keys = {} + self.rains_pub_online_keys = {} + self.ca_online_key_pairs = ca_online_key_pairs self.sig_priv_keys = {} self.sig_pub_keys = {} self.enc_priv_keys = {} @@ -283,15 +291,30 @@ def __init__(self, topo_config, ca_certs): self.trc_files = defaultdict(dict) def generate(self): + self._gen_rains_root_keys() self._self_sign_keys() self._iterate(self._gen_as_keys) self._iterate(self._gen_as_certs) self._build_chains() self._iterate(self._gen_trc_entry) self._iterate(self._sign_trc) + self._gen_xsigs() self._iterate(self._gen_trc_files) return self.cert_files, self.trc_files + def _gen_rains_root_keys(self): + isds = set() + for isd_as, as_config in self.topo_config["ASes"].items(): + isd = ISD_AS(isd_as)[0] + isds.add(isd) + for isd in isds: + pub_key, priv_key = generate_sign_keypair() + self.rains_root_priv_keys[isd] = priv_key + self.rains_root_pub_keys[isd] = pub_key + pub_key, priv_key = generate_sign_keypair() + self.rains_priv_online_keys[isd] = priv_key + self.rains_pub_online_keys[isd] = pub_key + def _self_sign_keys(self): topo_id = TopoID.from_values(0, 0) self.sig_pub_keys[topo_id], self.sig_priv_keys[topo_id] = \ @@ -367,23 +390,32 @@ def _gen_trc_entry(self, topo_id, as_conf): if topo_id[0] not in self.trcs: self._create_trc(topo_id[0]) trc = self.trcs[topo_id[0]] - # Add public root online/offline key to TRC + # Add public root online/offline key for core ASes to TRC core = {} core[ONLINE_KEY_ALG_STRING] = DEFAULT_KEYGEN_ALG core[ONLINE_KEY_STRING] = self.pub_online_root_keys[topo_id] core[OFFLINE_KEY_ALG_STRING] = DEFAULT_KEYGEN_ALG core[OFFLINE_KEY_STRING] = self.priv_online_root_keys[topo_id] trc.core_ases[str(topo_id)] = core - ca_certs = {} + # Add public online key, certificate for CAs to TRC + ca_certs = defaultdict(dict) for ca_name, ca_cert in self.ca_certs[topo_id[0]].items(): - ca_certs[ca_name] = \ + ca_certs[ca_name][CERTIFICATE_STRING] = \ crypto.dump_certificate(crypto.FILETYPE_ASN1, ca_cert) + ca_certs[ca_name][ONLINE_KEY_ALG_STRING] = DEFAULT_KEYGEN_ALG + ca_certs[ca_name][ONLINE_KEY_STRING] = self.ca_online_key_pairs[ca_name][0] trc.root_cas = ca_certs + # Add public online key, root key for RAINS to TRC + rains = {} + rains[ONLINE_KEY_ALG_STRING] = DEFAULT_KEYGEN_ALG + rains[ONLINE_KEY_STRING] = self.rains_pub_online_keys[topo_id[0]] + rains[ROOT_RAINS_KEY_STRING] = self.rains_root_pub_keys[topo_id[0]] + trc.rains = rains def _create_trc(self, isd): self.trcs[isd] = TRC.from_values( isd, "ISD %s" % isd, 0, {}, {}, - {}, 2, 'dns_srv_addr', 2, + {}, 2, {}, 2, 3, 18000, True, {}) def _sign_trc(self, topo_id, as_conf): @@ -392,6 +424,60 @@ def _sign_trc(self, topo_id, as_conf): trc = self.trcs[topo_id[0]] trc.sign(str(topo_id), self.priv_online_root_keys[topo_id]) + def _get_neighbors(self): + """ + Get ISD neighbor information from links contained in topology file + """ + neighbor_isds = defaultdict(set) + for link in self.topo_config["links"]: + a = ISD_AS(link["a"])[0] + b = ISD_AS(link["b"])[0] + ltype = link["ltype"] + if ltype != "CORE": + continue + if a != b: + neighbor_isds[a].add(b) + neighbor_isds[b].add(a) + return neighbor_isds + + def _gen_xsigs(self): + neighbor_isds = self._get_neighbors() + self._rains_xsign_trc(neighbor_isds) + self._ca_xsign_trc(neighbor_isds) + self._core_as_xsign_trc(neighbor_isds) + + def _ca_xsign_trc(self, neighbor_isds): + isd_ca = defaultdict(list) + for ca_name, ca_config in self.topo_config["CAs"].items(): + isd_ca[ca_config["ISD"]].append(ca_name) + for isd, neighbors in neighbor_isds.items(): + for neighbor in neighbors: + ca_name = random.choice(isd_ca[neighbor]) + trc = self.trcs[isd] + subject = "ISD %s, CA: %s" % (neighbor, ca_name) + trc.sign(subject, self.ca_online_key_pairs[ca_name][1]) + + def _core_as_xsign_trc(self, neighbor_isds): + isd_ases = defaultdict(list) + for isd_as, as_config in self.topo_config["ASes"].items(): + if not as_config.get('core', False): + continue + isd = ISD_AS(isd_as)[0] + isd_ases[isd].append(isd_as) + for isd, neighbors in neighbor_isds.items(): + for neighbor in neighbors: + isd_as = random.choice(isd_ases[neighbor]) + trc = self.trcs[isd] + trc.sign(str(isd_as), + self.priv_online_root_keys[ISD_AS(isd_as)]) + + def _rains_xsign_trc(self, neighbor_isds): + for isd, neighbors in neighbor_isds.items(): + for neighbor in neighbors: + trc = self.trcs[isd] + subject = "ISD %s, RAINS" % neighbor + trc.sign(subject, self.rains_priv_online_keys[neighbor]) + def _gen_trc_files(self, topo_id, _): for isd in self.trcs: trc_path = get_trc_file_path("", isd, INITIAL_TRC_VERSION) @@ -402,16 +488,19 @@ class CA_Generator(object): def __init__(self, topo_config): self.topo_config = topo_config self.ca_key_pairs = {} + self.ca_online_key_pairs = {} self.ca_certs = defaultdict(dict) self.ca_private_key_files = defaultdict(dict) self.ca_cert_files = defaultdict(dict) def generate(self): + self._iterate(self._gen_ca_online_key) self._iterate(self._gen_ca_key) self._iterate(self._gen_ca) self._iterate(self._gen_private_key_files) self._iterate(self._gen_cert_files) - return self.ca_private_key_files, self.ca_cert_files, self.ca_certs + return (self.ca_private_key_files, self.ca_cert_files, + self.ca_certs, self.ca_online_key_pairs) def _iterate(self, f): for ca_name, ca_config in self.topo_config["CAs"].items(): @@ -421,6 +510,9 @@ def _gen_ca_key(self, ca_name, ca_config): self.ca_key_pairs[ca_name] = crypto.PKey() self.ca_key_pairs[ca_name].generate_key(crypto.TYPE_RSA, 2048) + def _gen_ca_online_key(self, ca_name, ca_config): + self.ca_online_key_pairs[ca_name] = generate_sign_keypair() + def _gen_ca(self, ca_name, ca_config): ca = crypto.X509() ca.set_version(3)