1# Copyright (C) Internet Systems Consortium, Inc. ("ISC") 2# 3# SPDX-License-Identifier: MPL-2.0 4# 5# This Source Code Form is subject to the terms of the Mozilla Public 6# License, v. 2.0. If a copy of the MPL was not distributed with this 7# file, you can obtain one at https://mozilla.org/MPL/2.0/. 8# 9# See the COPYRIGHT file distributed with this work for additional 10# information regarding copyright ownership. 11 12from collections import defaultdict 13from .dnskey import * 14from .keydict import * 15from .keyevent import * 16from .policy import * 17import time 18 19 20class keyseries: 21 _K = defaultdict(lambda: defaultdict(list)) 22 _Z = defaultdict(lambda: defaultdict(list)) 23 _zones = set() 24 _kdict = None 25 _context = None 26 27 def __init__(self, kdict, now=time.time(), context=None): 28 self._kdict = kdict 29 self._context = context 30 self._zones = set(kdict.missing()) 31 32 for zone in kdict.zones(): 33 self._zones.add(zone) 34 for alg, keys in kdict[zone].items(): 35 for k in keys.values(): 36 if k.sep: 37 if not (k.delete() and k.delete() < now): 38 self._K[zone][alg].append(k) 39 else: 40 if not (k.delete() and k.delete() < now): 41 self._Z[zone][alg].append(k) 42 43 self._K[zone][alg].sort() 44 self._Z[zone][alg].sort() 45 46 def __iter__(self): 47 for zone in self._zones: 48 for collection in [self._K, self._Z]: 49 if zone not in collection: 50 continue 51 for alg, keys in collection[zone].items(): 52 for key in keys: 53 yield key 54 55 def dump(self): 56 for k in self: 57 print("%s" % repr(k)) 58 59 def fixseries(self, keys, policy, now, **kwargs): 60 force = kwargs.get('force', False) 61 if not keys: 62 return 63 64 # handle the first key 65 key = keys[0] 66 if key.sep: 67 rp = policy.ksk_rollperiod 68 prepub = policy.ksk_prepublish or (30 * 86400) 69 postpub = policy.ksk_postpublish or (30 * 86400) 70 else: 71 rp = policy.zsk_rollperiod 72 prepub = policy.zsk_prepublish or (30 * 86400) 73 postpub = policy.zsk_postpublish or (30 * 86400) 74 75 # the first key should be published and active 76 p = key.publish() 77 a = key.activate() 78 if not p or p > now: 79 key.setpublish(now) 80 p = now 81 if not a or a > now: 82 key.setactivate(now) 83 a = now 84 85 i = key.inactive() 86 fudge = 300 87 if not rp: 88 key.setinactive(None, **kwargs) 89 key.setdelete(None, **kwargs) 90 elif not i or a + rp != i: 91 if not i and a + rp > now + prepub + fudge: 92 key.setinactive(a + rp, **kwargs) 93 key.setdelete(a + rp + postpub, **kwargs) 94 elif not i: 95 key.setinactive(now + prepub + fudge, **kwargs) 96 key.setdelete(now + prepub + postpub + fudge, **kwargs) 97 elif i < now: 98 pass 99 elif a + rp > i: 100 key.setinactive(a + rp, **kwargs) 101 key.setdelete(a + rp + postpub, **kwargs) 102 elif a + rp > now + prepub + fudge: 103 key.setinactive(a + rp, **kwargs) 104 key.setdelete(a + rp + postpub, **kwargs) 105 else: 106 key.setinactive(now + prepub + fudge, **kwargs) 107 key.setdelete(now + prepub + postpub + fudge, **kwargs) 108 else: 109 d = key.delete() 110 if not d or i + postpub > now + fudge: 111 key.setdelete(i + postpub, **kwargs) 112 elif not d: 113 key.setdelete(now + postpub + fudge, **kwargs) 114 elif d < now + fudge: 115 pass 116 elif d < i + postpub: 117 key.setdelete(i + postpub, **kwargs) 118 119 if policy.keyttl != key.ttl: 120 key.setttl(policy.keyttl) 121 122 # handle all the subsequent keys 123 prev = key 124 for key in keys[1:]: 125 # if no rollperiod, then all keys after the first in 126 # the series kept inactive. 127 # (XXX: we need to change this to allow standby keys) 128 if not rp: 129 key.setpublish(None, **kwargs) 130 key.setactivate(None, **kwargs) 131 key.setinactive(None, **kwargs) 132 key.setdelete(None, **kwargs) 133 if policy.keyttl != key.ttl: 134 key.setttl(policy.keyttl) 135 continue 136 137 # otherwise, ensure all dates are set correctly based on 138 # the initial key 139 a = prev.inactive() 140 p = a - prepub 141 key.setactivate(a, **kwargs) 142 key.setpublish(p, **kwargs) 143 key.setinactive(a + rp, **kwargs) 144 key.setdelete(a + rp + postpub, **kwargs) 145 prev.setdelete(a + postpub, **kwargs) 146 if policy.keyttl != key.ttl: 147 key.setttl(policy.keyttl) 148 prev = key 149 150 # if we haven't got sufficient coverage, create successor key(s) 151 while rp and prev.inactive() and \ 152 prev.inactive() < now + policy.coverage: 153 # commit changes to predecessor: a successor can only be 154 # generated if Inactive has been set in the predecessor key 155 prev.commit(self._context['settime_path'], **kwargs) 156 key = prev.generate_successor(self._context['keygen_path'], 157 self._context['randomdev'], 158 prepub, **kwargs) 159 160 key.setinactive(key.activate() + rp, **kwargs) 161 key.setdelete(key.inactive() + postpub, **kwargs) 162 keys.append(key) 163 prev = key 164 165 # last key? we already know we have sufficient coverage now, so 166 # disable the inactivation of the final key (if it was set), 167 # ensuring that if dnssec-keymgr isn't run again, the last key 168 # in the series will at least remain usable. 169 prev.setinactive(None, **kwargs) 170 prev.setdelete(None, **kwargs) 171 172 # commit changes 173 for key in keys: 174 key.commit(self._context['settime_path'], **kwargs) 175 176 177 def enforce_policy(self, policies, now=time.time(), **kwargs): 178 # If zones is provided as a parameter, use that list. 179 # If not, use what we have in this object 180 zones = kwargs.get('zones', self._zones) 181 keys_dir = kwargs.get('dir', self._context.get('keys_path', None)) 182 force = kwargs.get('force', False) 183 184 for zone in zones: 185 collections = [] 186 policy = policies.policy(zone) 187 keys_dir = keys_dir or policy.directory or '.' 188 alg = policy.algorithm 189 algnum = dnskey.algnum(alg) 190 if 'ksk' not in kwargs or not kwargs['ksk']: 191 if len(self._Z[zone][algnum]) == 0: 192 k = dnskey.generate(self._context['keygen_path'], 193 self._context['randomdev'], 194 keys_dir, zone, alg, 195 policy.zsk_keysize, False, 196 policy.keyttl or 3600, 197 **kwargs) 198 self._Z[zone][algnum].append(k) 199 collections.append(self._Z[zone]) 200 201 if 'zsk' not in kwargs or not kwargs['zsk']: 202 if len(self._K[zone][algnum]) == 0: 203 k = dnskey.generate(self._context['keygen_path'], 204 self._context['randomdev'], 205 keys_dir, zone, alg, 206 policy.ksk_keysize, True, 207 policy.keyttl or 3600, 208 **kwargs) 209 self._K[zone][algnum].append(k) 210 collections.append(self._K[zone]) 211 212 for collection in collections: 213 for algorithm, keys in collection.items(): 214 if algorithm != algnum: 215 continue 216 try: 217 self.fixseries(keys, policy, now, **kwargs) 218 except Exception as e: 219 raise Exception('%s/%s: %s' % 220 (zone, dnskey.algstr(algnum), str(e))) 221