| 1 | #! @PYTHON@ |
| 2 | ### -*-python-*- |
| 3 | ### |
| 4 | ### Key management and distribution |
| 5 | ### |
| 6 | ### (c) 2006 Straylight/Edgeware |
| 7 | ### |
| 8 | |
| 9 | ###----- Licensing notice --------------------------------------------------- |
| 10 | ### |
| 11 | ### This file is part of Trivial IP Encryption (TrIPE). |
| 12 | ### |
| 13 | ### TrIPE is free software; you can redistribute it and/or modify |
| 14 | ### it under the terms of the GNU General Public License as published by |
| 15 | ### the Free Software Foundation; either version 2 of the License, or |
| 16 | ### (at your option) any later version. |
| 17 | ### |
| 18 | ### TrIPE is distributed in the hope that it will be useful, |
| 19 | ### but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 20 | ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 21 | ### GNU General Public License for more details. |
| 22 | ### |
| 23 | ### You should have received a copy of the GNU General Public License |
| 24 | ### along with TrIPE; if not, write to the Free Software Foundation, |
| 25 | ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 26 | |
| 27 | ###-------------------------------------------------------------------------- |
| 28 | ### External dependencies. |
| 29 | |
| 30 | import catacomb as C |
| 31 | import os as OS |
| 32 | import sys as SYS |
| 33 | import re as RX |
| 34 | import getopt as O |
| 35 | import shutil as SH |
| 36 | import time as T |
| 37 | import filecmp as FC |
| 38 | from cStringIO import StringIO |
| 39 | from errno import * |
| 40 | from stat import * |
| 41 | |
| 42 | ###-------------------------------------------------------------------------- |
| 43 | ### Useful regular expressions |
| 44 | |
| 45 | ## Match a comment or blank line. |
| 46 | rx_comment = RX.compile(r'^\s*(#|$)') |
| 47 | |
| 48 | ## Match a KEY = VALUE assignment. |
| 49 | rx_keyval = RX.compile(r'^\s*([-\w]+)(?:\s+(?!=)|\s*=\s*)(|\S|\S.*\S)\s*$') |
| 50 | |
| 51 | ## Match a ${KEY} substitution. |
| 52 | rx_dollarsubst = RX.compile(r'\$\{([-\w]+)\}') |
| 53 | |
| 54 | ## Match a @TAG@ substitution. |
| 55 | rx_atsubst = RX.compile(r'@([-\w]+)@') |
| 56 | |
| 57 | ## Match a single non-alphanumeric character. |
| 58 | rx_nonalpha = RX.compile(r'\W') |
| 59 | |
| 60 | ## Match the literal string "<SEQ>". |
| 61 | rx_seq = RX.compile(r'\<SEQ\>') |
| 62 | |
| 63 | ## Match a shell metacharacter. |
| 64 | rx_shmeta = RX.compile('[\\s`!"#$&*()\\[\\];\'|<>?\\\\]') |
| 65 | |
| 66 | ## Match a character which needs escaping in a shell double-quoted string. |
| 67 | rx_shquote = RX.compile(r'["`$\\]') |
| 68 | |
| 69 | ###-------------------------------------------------------------------------- |
| 70 | ### Utility functions. |
| 71 | |
| 72 | ## Exceptions. |
| 73 | class SubprocessError (Exception): pass |
| 74 | class VerifyError (Exception): pass |
| 75 | |
| 76 | ## Program name and identification. |
| 77 | quis = OS.path.basename(SYS.argv[0]) |
| 78 | PACKAGE = "@PACKAGE@" |
| 79 | VERSION = "@VERSION@" |
| 80 | |
| 81 | def moan(msg): |
| 82 | """Report MSG to standard error.""" |
| 83 | SYS.stderr.write('%s: %s\n' % (quis, msg)) |
| 84 | |
| 85 | def die(msg, rc = 1): |
| 86 | """Report MSG to standard error, and exit with code RC.""" |
| 87 | moan(msg) |
| 88 | SYS.exit(rc) |
| 89 | |
| 90 | def subst(s, rx, map): |
| 91 | """ |
| 92 | Substitute values into a string. |
| 93 | |
| 94 | Repeatedly match RX (a compiled regular expression) against the string S. |
| 95 | For each match, extract group 1, and use it as a key to index the MAP; |
| 96 | replace the match by the result. Finally, return the fully-substituted |
| 97 | string. |
| 98 | """ |
| 99 | out = StringIO() |
| 100 | i = 0 |
| 101 | for m in rx.finditer(s): |
| 102 | out.write(s[i:m.start()] + map[m.group(1)]) |
| 103 | i = m.end() |
| 104 | out.write(s[i:]) |
| 105 | return out.getvalue() |
| 106 | |
| 107 | def shell_quotify(arg): |
| 108 | """ |
| 109 | Quotify ARG to keep the shell happy. |
| 110 | |
| 111 | This isn't actually used for invoking commands, just for presentation |
| 112 | purposes; but correctness is still nice. |
| 113 | """ |
| 114 | if not rx_shmeta.search(arg): |
| 115 | return arg |
| 116 | elif arg.find("'") == -1: |
| 117 | return "'%s'" % arg |
| 118 | else: |
| 119 | return '"%s"' % rx_shquote.sub(lambda m: '\\' + m.group(0), arg) |
| 120 | |
| 121 | def rmtree(path): |
| 122 | """Delete the directory tree given by PATH.""" |
| 123 | try: |
| 124 | st = OS.lstat(path) |
| 125 | except OSError, err: |
| 126 | if err.errno == ENOENT: |
| 127 | return |
| 128 | raise |
| 129 | if not S_ISDIR(st.st_mode): |
| 130 | OS.unlink(path) |
| 131 | else: |
| 132 | cwd = OS.getcwd() |
| 133 | try: |
| 134 | OS.chdir(path) |
| 135 | for i in OS.listdir('.'): |
| 136 | rmtree(i) |
| 137 | finally: |
| 138 | OS.chdir(cwd) |
| 139 | OS.rmdir(path) |
| 140 | |
| 141 | def zap(file): |
| 142 | """Delete the named FILE if it exists; otherwise do nothing.""" |
| 143 | try: |
| 144 | OS.unlink(file) |
| 145 | except OSError, err: |
| 146 | if err.errno == ENOENT: return |
| 147 | raise |
| 148 | |
| 149 | def run(args): |
| 150 | """ |
| 151 | Run a subprocess whose arguments are given by the string ARGS. |
| 152 | |
| 153 | The ARGS are split at word boundaries, and then subjected to configuration |
| 154 | variable substitution (see conf_subst). Individual argument elements |
| 155 | beginning with `!' are split again into multiple arguments at word |
| 156 | boundaries. |
| 157 | """ |
| 158 | args = map(conf_subst, args.split()) |
| 159 | nargs = [] |
| 160 | for a in args: |
| 161 | if len(a) > 0 and a[0] != '!': |
| 162 | nargs += [a] |
| 163 | else: |
| 164 | nargs += a[1:].split() |
| 165 | args = nargs |
| 166 | print '+ %s' % ' '.join([shell_quotify(arg) for arg in args]) |
| 167 | SYS.stdout.flush() |
| 168 | rc = OS.spawnvp(OS.P_WAIT, args[0], args) |
| 169 | if rc != 0: |
| 170 | raise SubprocessError, rc |
| 171 | |
| 172 | def hexhyphens(bytes): |
| 173 | """ |
| 174 | Convert a byte string BYTES into hex, with hyphens at each 4-byte boundary. |
| 175 | """ |
| 176 | out = StringIO() |
| 177 | for i in xrange(0, len(bytes)): |
| 178 | if i > 0 and i % 4 == 0: out.write('-') |
| 179 | out.write('%02x' % ord(bytes[i])) |
| 180 | return out.getvalue() |
| 181 | |
| 182 | def fingerprint(kf, ktag): |
| 183 | """ |
| 184 | Compute the fingerprint of a key, using the user's selected hash. |
| 185 | |
| 186 | KF is the name of a keyfile; KTAG is the tag of the key. |
| 187 | """ |
| 188 | h = C.gchashes[conf['fingerprint-hash']]() |
| 189 | k = C.KeyFile(kf)[ktag].fingerprint(h, '-secret') |
| 190 | return h.done() |
| 191 | |
| 192 | ###-------------------------------------------------------------------------- |
| 193 | ### The configuration file. |
| 194 | |
| 195 | ## Exceptions. |
| 196 | class ConfigFileError (Exception): pass |
| 197 | |
| 198 | ## The configuration dictionary. |
| 199 | conf = {} |
| 200 | |
| 201 | def conf_subst(s): |
| 202 | """ |
| 203 | Apply configuration substitutions to S. |
| 204 | |
| 205 | That is, for each ${KEY} in S, replace it with the current value of the |
| 206 | configuration variable KEY. |
| 207 | """ |
| 208 | return subst(s, rx_dollarsubst, conf) |
| 209 | |
| 210 | def conf_read(f): |
| 211 | """ |
| 212 | Read the file F and insert assignments into the configuration dictionary. |
| 213 | """ |
| 214 | lno = 0 |
| 215 | for line in file(f): |
| 216 | lno += 1 |
| 217 | if rx_comment.match(line): continue |
| 218 | if line[-1] == '\n': line = line[:-1] |
| 219 | match = rx_keyval.match(line) |
| 220 | if not match: |
| 221 | raise ConfigFileError, "%s:%d: bad line `%s'" % (f, lno, line) |
| 222 | k, v = match.groups() |
| 223 | conf[k] = conf_subst(v) |
| 224 | |
| 225 | def conf_defaults(): |
| 226 | """ |
| 227 | Apply defaults to the configuration dictionary. |
| 228 | |
| 229 | Fill in all the interesting configuration variables based on the existing |
| 230 | contents, as described in the manual. |
| 231 | """ |
| 232 | for k, v in [('repos-base', 'tripe-keys.tar.gz'), |
| 233 | ('sig-base', 'tripe-keys.sig-<SEQ>'), |
| 234 | ('repos-url', '${base-url}${repos-base}'), |
| 235 | ('sig-url', '${base-url}${sig-base}'), |
| 236 | ('sig-file', '${base-dir}${sig-base}'), |
| 237 | ('repos-file', '${base-dir}${repos-base}'), |
| 238 | ('conf-file', '${base-dir}tripe-keys.conf'), |
| 239 | ('upload-hook', ': run upload hook'), |
| 240 | ('kx', 'dh'), |
| 241 | ('kx-genalg', lambda: {'dh': 'dh', |
| 242 | 'ec': 'ec'}[conf['kx']]), |
| 243 | ('kx-param-genalg', lambda: {'dh': 'dh-param', |
| 244 | 'ec': 'ec-param'}[conf['kx']]), |
| 245 | ('kx-param', lambda: {'dh': '-LS -b3072 -B256', |
| 246 | 'ec': '-Cnist-p256'}[conf['kx']]), |
| 247 | ('kx-attrs', 'serialization=constlen'), |
| 248 | ('kx-expire', 'now + 1 year'), |
| 249 | ('kx-warn-days', '28'), |
| 250 | ('bulk', 'iiv'), |
| 251 | ('cipher', lambda: conf['bulk'] == 'naclbox' |
| 252 | and 'salsa20' or 'rijndael-cbc'), |
| 253 | ('hash', 'sha256'), |
| 254 | ('master-keygen-flags', '-l'), |
| 255 | ('master-attrs', ''), |
| 256 | ('mgf', '${hash}-mgf'), |
| 257 | ('mac', lambda: conf['bulk'] == 'naclbox' |
| 258 | and 'poly1305/128' |
| 259 | or '%s-hmac/%d' % |
| 260 | (conf['hash'], |
| 261 | C.gchashes[conf['hash']].hashsz * 4)), |
| 262 | ('sig', lambda: {'dh': 'dsa', 'ec': 'ecdsa'}[conf['kx']]), |
| 263 | ('sig-fresh', 'always'), |
| 264 | ('sig-genalg', lambda: {'kcdsa': 'dh', |
| 265 | 'dsa': 'dsa', |
| 266 | 'rsapkcs1': 'rsa', |
| 267 | 'rsapss': 'rsa', |
| 268 | 'ecdsa': 'ec', |
| 269 | 'eckcdsa': 'ec', |
| 270 | 'ed25519': 'ed25519', |
| 271 | 'ed448': 'ed448'}[conf['sig']]), |
| 272 | ('sig-param', lambda: {'dh': '-LS -b3072 -B256', |
| 273 | 'dsa': '-b3072 -B256', |
| 274 | 'ec': '-Cnist-p256', |
| 275 | 'rsa': '-b3072', |
| 276 | 'ed25519': '', |
| 277 | 'ed448': ''}[conf['sig-genalg']]), |
| 278 | ('sig-hash', '${hash}'), |
| 279 | ('sig-expire', 'forever'), |
| 280 | ('fingerprint-hash', '${hash}')]: |
| 281 | try: |
| 282 | if k in conf: continue |
| 283 | if type(v) == str: |
| 284 | conf[k] = conf_subst(v) |
| 285 | else: |
| 286 | conf[k] = v() |
| 287 | except KeyError, exc: |
| 288 | if len(exc.args) == 0: raise |
| 289 | conf[k] = '<missing-var %s>' % exc.args[0] |
| 290 | |
| 291 | ###-------------------------------------------------------------------------- |
| 292 | ### Key-management utilities. |
| 293 | |
| 294 | def master_keys(): |
| 295 | """ |
| 296 | Iterate over the master keys. |
| 297 | """ |
| 298 | if not OS.path.exists('master'): |
| 299 | return |
| 300 | for k in C.KeyFile('master').itervalues(): |
| 301 | if (k.type != 'tripe-keys-master' or |
| 302 | k.expiredp or |
| 303 | not k.tag.startswith('master-')): |
| 304 | continue #?? |
| 305 | yield k |
| 306 | |
| 307 | def master_sequence(k): |
| 308 | """ |
| 309 | Return the sequence number of the given master key as an integer. |
| 310 | |
| 311 | No checking is done that K is really a master key. |
| 312 | """ |
| 313 | return int(k.tag[7:]) |
| 314 | |
| 315 | def max_master_sequence(): |
| 316 | """ |
| 317 | Find the master key with the highest sequence number and return this |
| 318 | sequence number. |
| 319 | """ |
| 320 | seq = -1 |
| 321 | for k in master_keys(): |
| 322 | q = master_sequence(k) |
| 323 | if q > seq: seq = q |
| 324 | return seq |
| 325 | |
| 326 | def seqsubst(x, q): |
| 327 | """ |
| 328 | Return the value of the configuration variable X, with <SEQ> replaced by |
| 329 | the value Q. |
| 330 | """ |
| 331 | return rx_seq.sub(str(q), conf[x]) |
| 332 | |
| 333 | ###-------------------------------------------------------------------------- |
| 334 | ### Commands: help [COMMAND...] |
| 335 | |
| 336 | def version(fp = SYS.stdout): |
| 337 | fp.write('%s, %s version %s\n' % (quis, PACKAGE, VERSION)) |
| 338 | |
| 339 | def usage(fp): |
| 340 | fp.write('Usage: %s SUBCOMMAND [ARGS...]\n' % quis) |
| 341 | |
| 342 | def cmd_help(args): |
| 343 | if len(args) == 0: |
| 344 | version(SYS.stdout) |
| 345 | print |
| 346 | usage(SYS.stdout) |
| 347 | print """ |
| 348 | Key management utility for TrIPE. |
| 349 | |
| 350 | Options supported: |
| 351 | |
| 352 | -h, --help Show this help message. |
| 353 | -v, --version Show the version number. |
| 354 | -u, --usage Show pointlessly short usage string. |
| 355 | |
| 356 | Subcommands available: |
| 357 | """ |
| 358 | args = commands.keys() |
| 359 | args.sort() |
| 360 | for c in args: |
| 361 | try: func, min, max, help = commands[c] |
| 362 | except KeyError: die("unknown command `%s'" % c) |
| 363 | print '%s%s%s' % (c, help and ' ', help) |
| 364 | |
| 365 | ###-------------------------------------------------------------------------- |
| 366 | ### Commands: newmaster |
| 367 | |
| 368 | def cmd_newmaster(args): |
| 369 | seq = max_master_sequence() + 1 |
| 370 | run('''key -kmaster add |
| 371 | -a${sig-genalg} !${sig-param} |
| 372 | -e${sig-expire} !${master-keygen-flags} -tmaster-%d tripe-keys-master |
| 373 | sig=${sig} hash=${sig-hash} !${master-attrs}''' % seq) |
| 374 | run('key -kmaster extract -f-secret repos/master.pub') |
| 375 | |
| 376 | ###-------------------------------------------------------------------------- |
| 377 | ### Commands: setup |
| 378 | |
| 379 | def cmd_setup(args): |
| 380 | OS.mkdir('repos') |
| 381 | run('''key -krepos/param add |
| 382 | -a${kx-param-genalg} !${kx-param} |
| 383 | -eforever -tparam tripe-param |
| 384 | kx-group=${kx} mgf=${mgf} mac=${mac} |
| 385 | bulk=${bulk} cipher=${cipher} hash=${hash} ${kx-attrs}''') |
| 386 | cmd_newmaster(args) |
| 387 | |
| 388 | ###-------------------------------------------------------------------------- |
| 389 | ### Commands: upload |
| 390 | |
| 391 | def cmd_upload(args): |
| 392 | |
| 393 | ## Sanitize the repository directory |
| 394 | umask = OS.umask(0); OS.umask(umask) |
| 395 | mode = 0666 & ~umask |
| 396 | for f in OS.listdir('repos'): |
| 397 | ff = OS.path.join('repos', f) |
| 398 | if (f.startswith('master') or f.startswith('peer-')) \ |
| 399 | and f.endswith('.old'): |
| 400 | OS.unlink(ff) |
| 401 | continue |
| 402 | OS.chmod(ff, mode) |
| 403 | |
| 404 | rmtree('tmp') |
| 405 | OS.mkdir('tmp') |
| 406 | OS.symlink('../repos', 'tmp/repos') |
| 407 | cwd = OS.getcwd() |
| 408 | try: |
| 409 | |
| 410 | ## Build the configuration file |
| 411 | seq = max_master_sequence() |
| 412 | v = {'MASTER-SEQUENCE': str(seq), |
| 413 | 'HK-MASTER': hexhyphens(fingerprint('repos/master.pub', |
| 414 | 'master-%d' % seq))} |
| 415 | fin = file('tripe-keys.master') |
| 416 | fout = file('tmp/tripe-keys.conf', 'w') |
| 417 | for line in fin: |
| 418 | fout.write(subst(line, rx_atsubst, v)) |
| 419 | fin.close(); fout.close() |
| 420 | SH.copyfile('tmp/tripe-keys.conf', conf_subst('${conf-file}.new')) |
| 421 | commit = [conf['repos-file'], conf['conf-file']] |
| 422 | |
| 423 | ## Make and sign the repository archive |
| 424 | OS.chdir('tmp') |
| 425 | run('tar chozf ${repos-file}.new .') |
| 426 | OS.chdir(cwd) |
| 427 | for k in master_keys(): |
| 428 | seq = master_sequence(k) |
| 429 | sigfile = seqsubst('sig-file', seq) |
| 430 | run('''catsign -kmaster sign -abdC -kmaster-%d |
| 431 | -o%s.new ${repos-file}.new''' % (seq, sigfile)) |
| 432 | commit.append(sigfile) |
| 433 | |
| 434 | ## Commit the changes |
| 435 | for base in commit: |
| 436 | new = '%s.new' % base |
| 437 | OS.rename(new, base) |
| 438 | |
| 439 | ## Remove files in the base-dir which don't correspond to ones we just |
| 440 | ## committed |
| 441 | allow = {} |
| 442 | basedir = conf['base-dir'] |
| 443 | bdl = len(basedir) |
| 444 | for base in commit: |
| 445 | if base.startswith(basedir): allow[base[bdl:]] = 1 |
| 446 | for found in OS.listdir(basedir): |
| 447 | if found not in allow: OS.remove(OS.path.join(basedir, found)) |
| 448 | finally: |
| 449 | OS.chdir(cwd) |
| 450 | rmtree('tmp') |
| 451 | run('sh -c ${upload-hook}') |
| 452 | |
| 453 | ###-------------------------------------------------------------------------- |
| 454 | ### Commands: rebuild |
| 455 | |
| 456 | def cmd_rebuild(args): |
| 457 | zap('keyring.pub') |
| 458 | for i in OS.listdir('repos'): |
| 459 | if i.startswith('peer-') and i.endswith('.pub'): |
| 460 | run('key -kkeyring.pub merge %s' % OS.path.join('repos', i)) |
| 461 | |
| 462 | ###-------------------------------------------------------------------------- |
| 463 | ### Commands: update |
| 464 | |
| 465 | def cmd_update(args): |
| 466 | cwd = OS.getcwd() |
| 467 | rmtree('tmp') |
| 468 | try: |
| 469 | |
| 470 | ## Fetch a new distribution |
| 471 | OS.mkdir('tmp') |
| 472 | OS.chdir('tmp') |
| 473 | seq = int(conf['master-sequence']) |
| 474 | run('curl -s -o tripe-keys.tar.gz ${repos-url}') |
| 475 | run('curl -s -o tripe-keys.sig %s' % seqsubst('sig-url', seq)) |
| 476 | run('tar xfz tripe-keys.tar.gz') |
| 477 | |
| 478 | ## Verify the signature |
| 479 | want = C.bytes(rx_nonalpha.sub('', conf['hk-master'])) |
| 480 | got = fingerprint('repos/master.pub', 'master-%d' % seq) |
| 481 | if want != got: raise VerifyError |
| 482 | run('''catsign -krepos/master.pub verify -avC -kmaster-%d |
| 483 | -t${sig-fresh} tripe-keys.sig tripe-keys.tar.gz''' % seq) |
| 484 | |
| 485 | ## OK: update our copy |
| 486 | OS.chdir(cwd) |
| 487 | if OS.path.exists('repos'): OS.rename('repos', 'repos.old') |
| 488 | OS.rename('tmp/repos', 'repos') |
| 489 | if not FC.cmp('tmp/tripe-keys.conf', 'tripe-keys.conf', False): |
| 490 | moan('configuration file changed: recommend running another update') |
| 491 | OS.rename('tmp/tripe-keys.conf', 'tripe-keys.conf') |
| 492 | rmtree('repos.old') |
| 493 | |
| 494 | finally: |
| 495 | OS.chdir(cwd) |
| 496 | rmtree('tmp') |
| 497 | cmd_rebuild(args) |
| 498 | |
| 499 | ###-------------------------------------------------------------------------- |
| 500 | ### Commands: generate TAG |
| 501 | |
| 502 | def cmd_generate(args): |
| 503 | tag, = args |
| 504 | keyring_pub = 'peer-%s.pub' % tag |
| 505 | zap('keyring'); zap(keyring_pub) |
| 506 | run('key -kkeyring merge repos/param') |
| 507 | run('key -kkeyring add -a${kx-genalg} -pparam -e${kx-expire} -t%s tripe' % |
| 508 | tag) |
| 509 | run('key -kkeyring extract -f-secret %s %s' % (keyring_pub, tag)) |
| 510 | |
| 511 | ###-------------------------------------------------------------------------- |
| 512 | ### Commands: clean |
| 513 | |
| 514 | def cmd_clean(args): |
| 515 | rmtree('repos') |
| 516 | rmtree('tmp') |
| 517 | for i in OS.listdir('.'): |
| 518 | r = i |
| 519 | if r.endswith('.old'): r = r[:-4] |
| 520 | if (r == 'master' or r == 'param' or |
| 521 | r == 'keyring' or r == 'keyring.pub' or r.startswith('peer-')): |
| 522 | zap(i) |
| 523 | |
| 524 | ###-------------------------------------------------------------------------- |
| 525 | ### Commands: check |
| 526 | |
| 527 | def check_key(k): |
| 528 | now = T.time() |
| 529 | thresh = int(conf['kx-warn-days']) * 86400 |
| 530 | if k.exptime == C.KEXP_FOREVER: return None |
| 531 | elif k.exptime == C.KEXP_EXPIRE: left = -1 |
| 532 | else: left = k.exptime - now |
| 533 | if left < 0: |
| 534 | return "key `%s' HAS EXPIRED" % k.tag |
| 535 | elif left < thresh: |
| 536 | if left >= 86400: n, u, uu = left // 86400, 'day', 'days' |
| 537 | else: n, u, uu = left // 3600, 'hour', 'hours' |
| 538 | return "key `%s' EXPIRES in %d %s" % (k.tag, n, n == 1 and u or uu) |
| 539 | else: |
| 540 | return None |
| 541 | |
| 542 | def cmd_check(args): |
| 543 | if OS.path.exists('keyring.pub'): |
| 544 | for k in C.KeyFile('keyring.pub').itervalues(): |
| 545 | whinge = check_key(k) |
| 546 | if whinge is not None: print whinge |
| 547 | if OS.path.exists('master'): |
| 548 | whinges = [] |
| 549 | for k in C.KeyFile('master').itervalues(): |
| 550 | whinge = check_key(k) |
| 551 | if whinge is None: break |
| 552 | whinges.append(whinge) |
| 553 | else: |
| 554 | for whinge in whinges: print whinge |
| 555 | |
| 556 | ###-------------------------------------------------------------------------- |
| 557 | ### Commands: mtu |
| 558 | |
| 559 | def mac_tagsz(): |
| 560 | macname = conf['mac'] |
| 561 | index = macname.rindex('/') |
| 562 | if index == -1: tagsz = C.gcmacs[macname].tagsz |
| 563 | else: tagsz = int(macname[index + 1:])/8 |
| 564 | return tagsz |
| 565 | |
| 566 | def cmd_mtu(args): |
| 567 | mtu, = (lambda mtu = '1500': (mtu,))(*args) |
| 568 | mtu = int(mtu) |
| 569 | |
| 570 | mtu -= 20 # Minimum IP header |
| 571 | mtu -= 8 # UDP header |
| 572 | mtu -= 1 # TrIPE packet type octet |
| 573 | |
| 574 | bulk = conf['bulk'] |
| 575 | |
| 576 | if bulk == 'v0': |
| 577 | blksz = C.gcciphers[conf['cipher']].blksz |
| 578 | mtu -= mac_tagsz() # MAC tag |
| 579 | mtu -= 4 # Sequence number |
| 580 | mtu -= blksz # Initialization vector |
| 581 | |
| 582 | elif bulk == 'iiv': |
| 583 | mtu -= mac_tagsz() # MAC tag |
| 584 | mtu -= 4 # Sequence number |
| 585 | |
| 586 | elif bulk == 'naclbox': |
| 587 | mtu -= 16 # MAC tag |
| 588 | mtu -= 4 # Sequence number |
| 589 | |
| 590 | else: |
| 591 | die("Unknown bulk transform `%s'" % bulk) |
| 592 | |
| 593 | print mtu |
| 594 | |
| 595 | ###-------------------------------------------------------------------------- |
| 596 | ### Main driver. |
| 597 | |
| 598 | commands = {'help': (cmd_help, 0, 1, ''), |
| 599 | 'newmaster': (cmd_newmaster, 0, 0, ''), |
| 600 | 'setup': (cmd_setup, 0, 0, ''), |
| 601 | 'upload': (cmd_upload, 0, 0, ''), |
| 602 | 'update': (cmd_update, 0, 0, ''), |
| 603 | 'clean': (cmd_clean, 0, 0, ''), |
| 604 | 'mtu': (cmd_mtu, 0, 1, '[PATH-MTU]'), |
| 605 | 'check': (cmd_check, 0, 0, ''), |
| 606 | 'generate': (cmd_generate, 1, 1, 'TAG'), |
| 607 | 'rebuild': (cmd_rebuild, 0, 0, '')} |
| 608 | |
| 609 | def init(): |
| 610 | """ |
| 611 | Load the appropriate configuration file and set up the configuration |
| 612 | dictionary. |
| 613 | """ |
| 614 | for f in ['tripe-keys.master', 'tripe-keys.conf']: |
| 615 | if OS.path.exists(f): |
| 616 | conf_read(f) |
| 617 | break |
| 618 | conf_defaults() |
| 619 | |
| 620 | def main(argv): |
| 621 | """ |
| 622 | Main program: parse options and dispatch to appropriate command handler. |
| 623 | """ |
| 624 | try: |
| 625 | opts, args = O.getopt(argv[1:], 'hvu', |
| 626 | ['help', 'version', 'usage']) |
| 627 | except O.GetoptError, exc: |
| 628 | moan(exc) |
| 629 | usage(SYS.stderr) |
| 630 | SYS.exit(1) |
| 631 | for o, v in opts: |
| 632 | if o in ('-h', '--help'): |
| 633 | cmd_help([]) |
| 634 | SYS.exit(0) |
| 635 | elif o in ('-v', '--version'): |
| 636 | version(SYS.stdout) |
| 637 | SYS.exit(0) |
| 638 | elif o in ('-u', '--usage'): |
| 639 | usage(SYS.stdout) |
| 640 | SYS.exit(0) |
| 641 | if len(argv) < 2: |
| 642 | cmd_help([]) |
| 643 | else: |
| 644 | c = argv[1] |
| 645 | try: func, min, max, help = commands[c] |
| 646 | except KeyError: die("unknown command `%s'" % c) |
| 647 | args = argv[2:] |
| 648 | if len(args) < min or (max is not None and len(args) > max): |
| 649 | SYS.stderr.write('Usage: %s %s%s%s\n' % (quis, c, help and ' ', help)) |
| 650 | SYS.exit(1) |
| 651 | func(args) |
| 652 | |
| 653 | ###----- That's all, folks -------------------------------------------------- |
| 654 | |
| 655 | if __name__ == '__main__': |
| 656 | init() |
| 657 | main(SYS.argv) |