diff --git a/lib/crypto/trc.py b/lib/crypto/trc.py index 0ae4f22e08..31362eebac 100644 --- a/lib/crypto/trc.py +++ b/lib/crypto/trc.py @@ -33,10 +33,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 +49,7 @@ ONLINE_KEY_STRING = 'OnlineKey' OFFLINE_KEY_ALG_STRING = 'OfflineKeyAlg' OFFLINE_KEY_STRING = 'OfflineKey' +CERTIFICATE_STRING = 'Certificate' class TRC(object): @@ -58,6 +61,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 +83,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,21 +107,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 @@ -151,7 +163,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 +174,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,20 +211,18 @@ 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 \ - be verified.") + 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") + logging.error("TRC does not have the number of required valid signatures") return False 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 +242,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') @@ -238,8 +251,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): @@ -247,6 +259,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] @@ -256,8 +272,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 +289,100 @@ 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("Cannot parse subject: %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("Cannot parse subject: %s" % subject) + return + # We have any AS + else: + try: + isd_as = ISD_AS(sub[0]) + return "AS", isd_as, "" + except: + logging.error("Cannot parse subject: %s" % subject) + return + def pack(self, lz4_=False): ret = self.to_json().encode('utf-8') if lz4_: @@ -300,13 +414,127 @@ 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, 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. + :returns: True if rem_trc can be verified, false otherwise. + """ + # 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: + if verify_trc_xsigs(trc, rem_trc) and verify_trc_xsigs(rem_trc, trc): + return True + return False + + +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. + :returns: True if dst_trc is signed correctly by src_trc, False otherwise. + """ + assert isinstance(src_trc, TRC) + assert isinstance(dst_trc, TRC) + if src_trc.isd == dst_trc.isd: + logging.warning("TRCs are from the same ISD.") + return False + return (verify_core_as_xsigs(src_trc, dst_trc) and + verify_rains_xsigs(src_trc, dst_trc) and + verify_ca_xsigs(src_trc, dst_trc)) + + +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. + :returns: True if dst_trc has a valid signature of a core AS in src_trc. + False otherwise. + """ + as_sigs = dst_trc.get_as_sigs() + for isd_as, signature in as_sigs: + if isd_as[0] != src_trc.isd: + continue + pub_key = src_trc.core_ases[str(isd_as)][ONLINE_KEY_STRING] + if dst_trc.verify_signature(signature, pub_key): + return True + else: + logging.error("TRC(ISD %s) contains invalid signature from core AS (ISD %s)" + % (dst_trc.isd, src_trc.isd)) + return False + + +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. + :returns: True if dst_trc has a valid signature of RAINS in src_trc. + False otherwise. + """ + rains_sigs = dst_trc.get_rains_sigs() + for isd, signature in rains_sigs: + if isd != src_trc.isd: + continue + pub_key = src_trc.rains[ONLINE_KEY_STRING] + if dst_trc.verify_signature(signature, pub_key): + return True + else: + logging.error("TRC(ISD %s) contains invalid signature from RAINS (ISD %s)" + % (dst_trc.isd, src_trc.isd)) + return False + + +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. + :returns: True if dst_trc has a valid signature of a CA in src_trc. + False otherwise. + """ + ca_sigs = dst_trc.get_ca_sigs() + 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] + if dst_trc.verify_signature(signature, pub_key): + return True + else: + logging.error("Remote TRC(ISD %s) contains invalid signature from CA (ISD %s)" + % (dst_trc.isd, src_trc.isd)) + return False diff --git a/topology/generator.py b/topology/generator.py index 741ec51f6c..ca8b042c56 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 ( @@ -181,9 +183,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) - topo_dicts, zookeepers, networks, prv_networks = self._generate_topology() + 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) self._generate_prom_conf(topo_dicts) @@ -200,8 +202,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): @@ -244,16 +246,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) @@ -281,9 +281,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 = {} @@ -298,21 +303,34 @@ 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() - self.enc_pub_keys[topo_id], self.enc_priv_keys[topo_id] = \ - generate_enc_keypair() + self.sig_pub_keys[topo_id], self.sig_priv_keys[topo_id] = generate_sign_keypair() + self.enc_pub_keys[topo_id], self.enc_priv_keys[topo_id] = generate_enc_keypair() def _iterate(self, f): for isd_as, as_conf in self.topo_config["ASes"].items(): @@ -339,10 +357,8 @@ def _gen_as_keys(self, topo_id, as_conf): self.priv_offline_root_keys[topo_id] = off_root_priv online_key_path = get_online_key_file_path("") offline_key_path = get_offline_key_file_path("") - self.cert_files[topo_id][online_key_path] = \ - base64.b64encode(on_root_priv).decode() - self.cert_files[topo_id][offline_key_path] = \ - base64.b64encode(off_root_priv).decode() + self.cert_files[topo_id][online_key_path] = base64.b64encode(on_root_priv).decode() + self.cert_files[topo_id][offline_key_path] = base64.b64encode(off_root_priv).decode() def _gen_as_certs(self, topo_id, as_conf): # Self-signed if cert_issuer is missing. @@ -368,10 +384,8 @@ def _build_chains(self): break chain.append(cert) issuer = TopoID(cert.issuer) - cert_path = get_cert_chain_file_path( - "", topo_id, INITIAL_CERT_VERSION) - self.cert_files[topo_id][cert_path] = \ - CertificateChain(chain).to_json() + cert_path = get_cert_chain_file_path("", topo_id, INITIAL_CERT_VERSION) + self.cert_files[topo_id][cert_path] = CertificateChain(chain).to_json() def is_core(self, as_conf): return as_conf.get("core") @@ -382,23 +396,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): @@ -407,6 +430,59 @@ 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, _): trc = self.trcs[topo_id[0]] trc_path = get_trc_file_path("", topo_id[0], INITIAL_TRC_VERSION) @@ -417,16 +493,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(): @@ -436,6 +515,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) @@ -473,18 +555,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):