From 208f6229cd7d4052a1a21b5adabb8b8f85e9c97b 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. --- python/lib/crypto/trc.py | 301 +++++++++++++++++++++++++++++------ python/topology/generator.py | 126 ++++++++++++--- 2 files changed, 360 insertions(+), 67 deletions(-) diff --git a/python/lib/crypto/trc.py b/python/lib/crypto/trc.py index c4a7e7b5cc..7594f837db 100644 --- a/python/lib/crypto/trc.py +++ b/python/lib/crypto/trc.py @@ -19,7 +19,6 @@ import base64 import copy import json -import logging import os import time @@ -29,16 +28,19 @@ # SCION from lib.crypto.asymcrypto import verify, sign from lib.crypto.util import CERT_DIR +from lib.errors import SCIONParseError, SCIONVerificationError from lib.packet.scion_addr import ISD_AS ISDID_STRING = 'ISDID' 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' @@ -49,6 +51,7 @@ ONLINE_KEY_STRING = 'OnlineKey' OFFLINE_KEY_ALG_STRING = 'OfflineKeyAlg' OFFLINE_KEY_STRING = 'OfflineKey' +CERTIFICATE_STRING = 'Certificate' def get_trc_file_path(conf_dir, isd, version): # pragma: no cover @@ -67,10 +70,11 @@ 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 - their addresses and public key certificates + their addresses and public key certificates. :ivar int quroum_eepki: is a threshold number (nonnegative integer) of CAs that have to sign a domain’s policy :ivar str root_rains_key: the RAINS root public key. @@ -88,17 +92,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): """ @@ -110,21 +116,27 @@ 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] - self.core_ases[subject][ONLINE_KEY_STRING] = \ - base64.b64decode(key.encode('utf-8')) + self.core_ases[subject][ONLINE_KEY_STRING] = base64.b64decode(key.encode('utf-8')) key = trc_dict[CORE_ASES_STRING][subject][OFFLINE_KEY_STRING] - self.core_ases[subject][OFFLINE_KEY_STRING] = \ - base64.b64decode(key.encode('utf-8')) + self.core_ases[subject][OFFLINE_KEY_STRING] = base64.b64decode(key.encode('utf-8')) for subject in trc_dict[SIGNATURES_STRING]: sig = trc_dict[SIGNATURES_STRING][subject] - self.signatures[subject] = \ - base64.b64decode(sig.encode('utf-8')) + 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 @@ -160,7 +172,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. @@ -171,11 +183,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, @@ -195,32 +208,22 @@ def verify(self, old_trc): in old TRC. :param: old_trc: the previous TRC which has already been verified. - :returns: True if verification succeeds, false otherwise. - :rtype: bool + :raises: SCIONVerificationError if the verification fails. """ # Only look at signatures which are from core ASes as defined in old TRC signatures = {k: self.signatures[k] for k in old_trc.core_ases.keys()} - # We have more signatures than the number of core ASes in old TRC - if len(signatures) < len(self.signatures): - logging.warning("TRC has more signatures than number of core ASes.") valid_signature_signers = set() # 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 \ - be verified.") # We have fewer valid signatrues for this TRC than quorum_own_trc if len(valid_signature_signers) < old_trc.quorum_own_trc: - logging.error("TRC does not have the number of required valid \ - signatures") - return False - logging.debug("TRC verified.") - return True + raise SCIONVerificationError("TRC does not have the number of required valid" + "signatures: %s" % self) - 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 @@ -240,6 +243,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,8 +252,7 @@ def _encode_dict(self, dict_): encoded_dict = {} for key_ in dict_: if type(dict_[key_]) is str: - encoded_dict[key_] = base64.b64encode( - dict_[key_].encode('utf-8')).decode('utf-8') + encoded_dict[key_] = base64.b64encode(dict_[key_].encode('utf-8')).decode('utf-8') return encoded_dict def to_json(self, with_signatures=True): @@ -256,6 +260,10 @@ 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] @@ -265,8 +273,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 = {} @@ -278,12 +290,87 @@ def to_json(self, with_signatures=True): trc_str = json.dumps(trc_dict, sort_keys=True, indent=4) return trc_str + def get_sigs(self, sig_type): + """ + Returns a list of tuples (isd, ca name, ca signature) for all signatures of + type sig_type on this TRC. ca_name is empty for sig_type AS and RAINS + """ + assert sig_type in ("AS", "CA", "RAINS", ) + sigs = [] + for subject, signature in self.signatures.items(): + try: + res = self._parse_subject_str(subject) + except SCIONParseError: + continue + type_, isd, ca_name = res + if type_ == sig_type: + sigs.append((int(isd), ca_name, signature)) + return sigs + + 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(): + try: + res = self._parse_subject_str(subject) + except SCIONParseError: + 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. + + :raises: SCIONParseError if the subject parse fails. + """ + 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: + raise SCIONParseError("Cannot parse subject: %s" % subject) + 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: + raise SCIONParseError("Cannot parse subject: %s" % subject) + # We have any AS + else: + try: + isd_as = ISD_AS(sub[0]) + return "AS", isd_as, "" + except: + raise SCIONParseError("Cannot parse subject: %s" % subject) + def pack(self, lz4_=False): ret = self.to_json().encode('utf-8') if lz4_: return lz4.dumps(ret) return ret + def short_desc(self): + desc = "TRC(" + for attr in (ISDID_STRING, VERSION_STRING, DESCRIPTION_STRING, + CREATION_TIME_STRING, EXPIRATION_TIME_STRING): + desc += attr + ": " + str(getattr(self, self.FIELDS_MAP[attr][0])) + ", " + desc = desc[:-2] + desc += ")" + return desc + def __str__(self): return self.to_json() @@ -297,25 +384,145 @@ def verify_new_trc(old_trc, new_trc): is correct and checks if the new TRC has enough valid signatures as defined in the current TRC. - :returns: True if update is valid, False otherwise + :raises: SCIONVerificationError if the verification fails. """ # Check if update is correct if old_trc.isd != new_trc.isd: - logging.error("TRC isdid mismatch") - return False + raise SCIONVerificationError("TRC isdid mismatch. Current: %s, New: %s" + % (old_trc.short_desc(), new_trc.short_desc())) if old_trc.version + 1 != new_trc.version: - logging.error("TRC versions mismatch") - return False + raise SCIONVerificationError("TRC versions mismatch. Current: %s, New: %s" + % (old_trc.short_desc(), new_trc.short_desc())) if new_trc.time < old_trc.time: - logging.error("New TRC timestamp is not valid") - return False + raise SCIONVerificationError("New TRC timestamp is not valid. Current: %s, New: %s" + % (old_trc.short_desc(), new_trc.short_desc())) + if old_trc.exp_time >= time.time(): + raise SCIONVerificationError("Current TRC expired: %s" % old_trc.short_desc()) + if new_trc.exp_time >= time.time(): + raise SCIONVerificationError("New TRC expired: %s" % new_trc.short_desc()) if new_trc.quarantine or old_trc.quarantine: - logging.error("Early announcement") - return False + raise SCIONVerificationError("Early announcement. Current: %s, New: %s" + % (old_trc.short_desc(), new_trc.short_desc())) # 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") - return False - logging.debug("New TRC verified") - return True + try: + new_trc.verify(old_trc) + except SCIONVerificationError: + raise SCIONVerificationError("New TRC verification failed, missing or invalid" + "signatures %s" % new_trc.short_desc()) + + +def verify_trc_chain(local_trc, verified_rem_trcs, rem_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 between + local TRC and remote TRC. + + :param TRC local_trc: The local TRC to this ISD. + :param List(TRC) verified_rem_trcs: Already verified remote TRCs. + :param TRC rem_trc: Remote TRC to verify. + :raises: SCIONVerificationError if the verification fails. + """ + # Get neighbors of remote TRC + rem_nbs = rem_trc.get_neighbors() + if local_trc.isd in rem_nbs: + # Try to verify with local TRC + if verify_trc_xsigs(local_trc, rem_trc) and verify_trc_xsigs(rem_trc, local_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: + try: + verify_trc_xsigs(trc, rem_trc) + verify_trc_xsigs(rem_trc, trc) + return + except SCIONVerificationError: + continue + raise SCIONVerificationError("TRC chaiun verification between %s and %s failed." + % (local_trc.short_desc(), rem_trc.short_desc())) + + +def verify_trc_xsigs(src_trc, dst_trc): + """ + Check if dst_trc is signed correctly by the ISD src_trc belongs to. + + :param TRC src_trc: The signing ISD's TRC. + :param TRC dst_trc: The TRC whose signatures need to be checked. + :raises: SCIONVerificationError if the verification fails. + """ + assert isinstance(src_trc, TRC) + assert isinstance(dst_trc, TRC) + if src_trc.isd == dst_trc.isd: + raise SCIONVerificationError("TRCs are from the same ISD(%s)." % src_trc.isd) + try: + verify_core_as_xsigs(src_trc, dst_trc) + verify_rains_xsigs(src_trc, dst_trc) + verify_ca_xsigs(src_trc, dst_trc) + except SCIONVerificationError: + raise SCIONVerificationError("Cross signature verification between %s and %s failded." + % (src_trc.short_desc(), dst_trc.short_desc())) + + +def verify_core_as_xsigs(src_trc, dst_trc): + """ + Checks if dst_trc is signed by a core AS in src_trc. + + :param TRC src_trc: The signing ISD's TRC. + :param TRC dst_trc: The TRC whose signatures need to be checked. + :raises: SCIONVerificationError if the verification fails. + """ + as_sigs = dst_trc.get_sigs("AS") + for isd_as, _, signature in as_sigs: + if ISD_AS(isd_as)[0] != src_trc.isd: + continue + pub_key = src_trc.core_ases[str(ISD_AS(isd_as))][ONLINE_KEY_STRING] + try: + dst_trc.verify_signature(signature, pub_key) + return + except SCIONVerificationError: + continue + raise SCIONVerificationError("%s is not correctly signed by a core AS in %s" + % (src_trc.short_desc(), dst_trc.short_desc())) + + +def verify_rains_xsigs(src_trc, dst_trc): + """ + Checks if dst_trc is signed by RAINS in src_trc. + + :param TRC src_trc: The signing ISD's TRC. + :param TRC dst_trc: The TRC whose signatures need to be checked. + :raises: SCIONVerificationError if the verification fails. + """ + rains_sigs = dst_trc.get_sigs("RAINS") + for isd, _, signature in rains_sigs: + if isd != src_trc.isd: + continue + pub_key = src_trc.rains[ONLINE_KEY_STRING] + try: + dst_trc.verify_signature(signature, pub_key) + return + except SCIONVerificationError: + continue + raise SCIONVerificationError("%s is not correctly signed by RAINS in %s" + % (src_trc.short_desc(), dst_trc.short_desc())) + + +def verify_ca_xsigs(src_trc, dst_trc): + """ + Checks if dst_trc is signed by a CA in src_trc. + + :param TRC src_trc: The signing ISD's TRC. + :param TRC dst_trc: The TRC whose signatures need to be checked. + :raises: SCIONVerificationError if the verification fails. + """ + ca_sigs = dst_trc.get_sigs("CA") + for isd, ca_name, signature in ca_sigs: + if isd != src_trc.isd: + continue + pub_key = src_trc.root_cas[ca_name][ONLINE_KEY_STRING] + try: + dst_trc.verify_signature(signature, pub_key) + return + except SCIONVerificationError: + continue + raise SCIONVerificationError("%s is not correctly signed by a CA in %s" + % (src_trc.short_desc(), dst_trc.short_desc())) diff --git a/python/topology/generator.py b/python/topology/generator.py index ba180f6dd5..41deedc682 100755 --- a/python/topology/generator.py +++ b/python/topology/generator.py @@ -50,10 +50,12 @@ from lib.crypto.certificate_chain import CertificateChain, get_cert_chain_file_path from lib.crypto.trc import ( get_trc_file_path, + CERTIFICATE_STRING, OFFLINE_KEY_ALG_STRING, OFFLINE_KEY_STRING, ONLINE_KEY_ALG_STRING, ONLINE_KEY_STRING, + ROOT_RAINS_KEY_STRING, TRC, ) from lib.crypto.util import ( @@ -185,8 +187,8 @@ 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, prv_networks = self._generate_topology() self._generate_supervisor(topo_dicts, zookeepers) self._generate_zk_conf(zookeepers) @@ -204,8 +206,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): @@ -248,16 +250,14 @@ def _write_conf_policies(self, topo_dicts): Write AS configurations and path policies. """ as_confs = {} - for topo_id, as_topo, base in _srv_iter( - topo_dicts, self.out_dir, common=True): + for topo_id, as_topo, base in _srv_iter(topo_dicts, self.out_dir, common=True): as_confs.setdefault(topo_id, yaml.dump( self._gen_as_conf(as_topo), default_flow_style=False)) conf_file = os.path.join(base, AS_CONF_FILE) write_file(conf_file, as_confs[topo_id]) # Confirm that config parses cleanly. Config.from_file(conf_file) - copy_file(self.path_policy_file, - os.path.join(base, PATH_POLICY_FILE)) + copy_file(self.path_policy_file, os.path.join(base, PATH_POLICY_FILE)) # Confirm that parser actually works on path policy file PathPolicy.from_file(self.path_policy_file) @@ -285,9 +285,14 @@ def _write_networks_conf(self, networks, out_file): 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 = {} @@ -303,15 +308,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] = generate_sign_keypair() @@ -390,22 +410,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] = crypto.dump_certificate(crypto.FILETYPE_ASN1, ca_cert) + 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): @@ -414,6 +444,59 @@ def _sign_trc(self, topo_id, as_conf): trc = self.trcs[topo_id[0]] trc.sign(str(topo_id), SigningKey(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, SigningKey(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), SigningKey(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, SigningKey(self.rains_priv_online_keys[neighbor])) + def _gen_trc_files(self, topo_id, _): trc = self.trcs[topo_id[0]] trc_path = get_trc_file_path("", topo_id[0], INITIAL_TRC_VERSION) @@ -424,16 +507,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(): @@ -443,6 +529,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) @@ -480,18 +569,15 @@ def _gen_ca(self, ca_name, ca_config): def _gen_private_key_files(self, ca_name, ca_config): isd = ca_config["ISD"] - ca_private_key_path = \ - get_ca_private_key_file_path("ISD%s" % isd, ca_name) + ca_private_key_path = get_ca_private_key_file_path("ISD%s" % isd, ca_name) self.ca_private_key_files[isd][ca_private_key_path] = \ - crypto.dump_privatekey(crypto.FILETYPE_PEM, - self.ca_key_pairs[ca_name]) + crypto.dump_privatekey(crypto.FILETYPE_PEM, self.ca_key_pairs[ca_name]) def _gen_cert_files(self, ca_name, ca_config): isd = ca_config["ISD"] ca_cert_path = get_ca_cert_file_path("ISD%s" % isd, ca_name) self.ca_cert_files[isd][ca_cert_path] = \ - crypto.dump_certificate(crypto.FILETYPE_PEM, - self.ca_certs[ca_config["ISD"]][ca_name]) + crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca_certs[ca_config["ISD"]][ca_name]) class TopoGenerator(object):