import sys as SYS
import sre as RX
import getopt as O
+import shutil as SH
+import filecmp as FC
from cStringIO import StringIO
from errno import *
from stat import *
### Useful regular expressions
-r_comment = RX.compile(r'^\s*(#|$)')
-r_keyval = RX.compile(r'^\s*([-\w]+)(?:\s+(?!=)|\s*=\s*)(|\S|\S.*\S)\s*$')
-r_dollarsubst = RX.compile(r'\$\{([-\w]+)\}')
-r_atsubst = RX.compile(r'@([-\w]+)@')
-r_nonalpha = RX.compile(r'\W')
+rx_comment = RX.compile(r'^\s*(#|$)')
+rx_keyval = RX.compile(r'^\s*([-\w]+)(?:\s+(?!=)|\s*=\s*)(|\S|\S.*\S)\s*$')
+rx_dollarsubst = RX.compile(r'\$\{([-\w]+)\}')
+rx_atsubst = RX.compile(r'@([-\w]+)@')
+rx_nonalpha = RX.compile(r'\W')
+rx_seq = RX.compile(r'\<SEQ\>')
### Utility functions
def rmtree(path):
try:
- st = OS.stat(path)
+ st = OS.lstat(path)
except OSError, err:
if err.errno == ENOENT:
return
class ConfigFileError (Exception): pass
conf = {}
-def conf_subst(s): return subst(s, r_dollarsubst, conf)
+def conf_subst(s): return subst(s, rx_dollarsubst, conf)
## Read the file
def conf_read(f):
lno = 0
for line in file(f):
lno += 1
- if r_comment.match(line): continue
+ if rx_comment.match(line): continue
if line[-1] == '\n': line = line[:-1]
- match = r_keyval.match(line)
+ match = rx_keyval.match(line)
if not match:
raise ConfigFileError, "%s:%d: bad line `%s'" % (f, lno, line)
k, v = match.groups()
## Sift the wreckage
def conf_defaults():
- for k, v in [('sig-url', '${base-url}tripe-keys.sig'),
- ('repos-url', '${base-url}tripe-keys.tar.gz'),
- ('sig-file', '${base-dir}tripe-keys.sig'),
- ('repos-file', '${base-dir}tripe-keys.tar.gz'),
+ for k, v in [('repos-base', 'tripe-keys.tar.gz'),
+ ('sig-base', 'tripe-keys.sig-<SEQ>'),
+ ('repos-url', '${base-url}${repos-base}'),
+ ('sig-url', '${base-url}${sig-base}'),
+ ('sig-file', '${base-dir}${sig-base}'),
+ ('repos-file', '${base-dir}${repos-base}'),
('conf-file', '${base-dir}tripe-keys.conf'),
('kx', 'dh'),
('kx-param', lambda: {'dh': '-LS -b2048 -B256',
func, min, max, help = commands[c]
print '%s %s' % (c, help)
-def cmd_setup(args):
- OS.mkdir('repos')
-
- ## Generate the master key
+def master_keys():
+ if not OS.path.exists('master'):
+ return
+ for k in C.KeyFile('master'):
+ if (k.type != 'tripe-keys-master' or
+ k.expiredp or
+ not k.tag.startswith('master-')):
+ continue #??
+ yield k
+def master_sequence(k):
+ return int(k.tag[7:])
+def max_master_sequence():
+ seq = -1
+ for k in master_keys():
+ q = master_sequence(k)
+ if q > seq: seq = q
+ return seq
+def seqsubst(x, q):
+ return rx_seq.sub(str(q), conf[x])
+
+def cmd_newmaster(args):
+ seq = max_master_sequence() + 1
run('''key -kmaster add
-a${sig-genalg} !${sig-param}
- -e${sig-expire} -l -ttripe-keys-master ccsig
- sig=${sig} hash=${sig-hash}''')
- run('key -kmaster extract -f-secret repos/master.pub tripe-keys-master')
+ -e${sig-expire} -l -tmaster-%d tripe-keys-master
+ sig=${sig} hash=${sig-hash}''' % seq)
+ run('key -kmaster extract -f-secret repos/master.pub')
- ## Generate the parameters key
+def cmd_setup(args):
+ OS.mkdir('repos')
run('''key -krepos/param add
-a${kx}-param !${kx-param}
-eforever -tparam tripe-${kx}-param
cipher=${cipher} hash=${hash} mac=${mac} mgf=${mgf}''')
-
- ## Get fingerprints
- print 'Setup OK: master key = %s' % \
- hexhyphens(fingerprint('repos/master.pub', 'tripe-keys-master'))
+ cmd_newmaster(args)
def cmd_upload(args):
mode = 0666 & ~umask
for f in OS.listdir('repos'):
ff = OS.path.join('repos', f)
- if f.endswith('.old'):
+ if (f.startswith('master') or f.startswith('peer-')) \
+ and f.endswith('.old'):
OS.unlink(ff)
continue
- OS.chmod(OS.path.join('repos', f), mode)
-
- ## Build the configuration file
- v = {'HK-MASTER': hexhyphens(fingerprint('repos/master.pub',
- 'tripe-keys-master'))}
- fin = file('tripe-keys.master')
- fout = file(conf_subst('${conf-file}.new'), 'w')
- for line in fin:
- fout.write(subst(line, r_atsubst, v))
- fin.close(); fout.close()
-
- ## Make and sign the repository archive
- run('tar chozf ${repos-file}.new repos')
- run('''catsign -kmaster sign -abdC -ktripe-keys-master
- -o${sig-file}.new ${repos-file}.new''')
-
- ## Commit the changes
- for i in ['conf-file', 'repos-file', 'sig-file']:
- base = conf[i]
- new = '%s.new' % base
- OS.rename(new, base)
+ OS.chmod(ff, mode)
+
+ rmtree('tmp')
+ OS.mkdir('tmp')
+ OS.symlink('../repos', 'tmp/repos')
+ cwd = OS.getcwd()
+ try:
+
+ ## Build the configuration file
+ seq = max_master_sequence()
+ v = {'MASTER-SEQUENCE': str(seq),
+ 'HK-MASTER': hexhyphens(fingerprint('repos/master.pub',
+ 'master-%d' % seq))}
+ fin = file('tripe-keys.master')
+ fout = file('tmp/tripe-keys.conf', 'w')
+ for line in fin:
+ fout.write(subst(line, rx_atsubst, v))
+ fin.close(); fout.close()
+ SH.copyfile('tmp/tripe-keys.conf', conf_subst('${conf-file}.new'))
+ commit = [conf['repos-file'], conf['conf-file']]
+
+ ## Make and sign the repository archive
+ OS.chdir('tmp')
+ run('tar chozf ${repos-file}.new .')
+ OS.chdir(cwd)
+ for k in master_keys():
+ seq = master_sequence(k)
+ sigfile = seqsubst('sig-file', seq)
+ run('''catsign -kmaster sign -abdC -kmaster-%d
+ -o%s.new ${repos-file}.new''' % (seq, sigfile))
+ commit.append(sigfile)
+
+ ## Commit the changes
+ for base in commit:
+ new = '%s.new' % base
+ OS.rename(new, base)
+ finally:
+ OS.chdir(cwd)
+ rmtree('tmp')
def cmd_update(args):
cwd = OS.getcwd()
## Fetch a new distribution
OS.mkdir('tmp')
OS.chdir('tmp')
+ seq = int(conf['master-sequence'])
run('wget -q -O tripe-keys.tar.gz ${repos-url}')
- run('wget -q -O tripe-keys.sig ${sig-url}')
+ run('wget -q -O tripe-keys.sig %s' % seqsubst('sig-url', seq))
run('tar xfz tripe-keys.tar.gz')
## Verify the signature
- want = C.bytes(r_nonalpha.sub('', conf['hk-master']))
- got = fingerprint('repos/master.pub', 'tripe-keys-master')
+ want = C.bytes(rx_nonalpha.sub('', conf['hk-master']))
+ got = fingerprint('repos/master.pub', 'master-%d' % seq)
if want != got: raise VerifyError
- run('''catsign -krepos/master.pub verify -avC -ktripe-keys-master
- -t${sig-fresh} tripe-keys.sig tripe-keys.tar.gz''')
+ run('''catsign -krepos/master.pub verify -avC -kmaster-%d
+ -t${sig-fresh} tripe-keys.sig tripe-keys.tar.gz''' % seq)
## OK: update our copy
OS.chdir(cwd)
if OS.path.exists('repos'): OS.rename('repos', 'repos.old')
OS.rename('tmp/repos', 'repos')
+ if not FC.cmp('tmp/tripe-keys.conf', 'tripe-keys.conf'):
+ moan('configuration file changed: recommend running another update')
+ OS.rename('tmp/tripe-keys.conf', 'tripe-keys.conf')
rmtree('repos.old')
finally:
zap('keyring'); zap(keyring_pub)
run('key -kkeyring merge repos/param')
run('key -kkeyring add -a${kx} -pparam -e${kx-expire} -t%s tripe-${kx}' %
- (tag,))
- run('key -kkeyring extract -f-secret %s %s' % (keyring_pub, tag))
- print 'Generated %s key = %s' % \
- (tag,
- hexhyphens(fingerprint('repos/master.pub', 'tripe-keys-master')))
-
+ tag)
+ run('key -kkeyring extract -f-secret %s' % keyring_pub)
def cmd_clean(args):
rmtree('repos')
rmtree('tmp')
- for i in 'master', 'keyring.pub':
- zap(i)
- zap('%s.old' % i)
+ for i in OS.listdir('.'):
+ r = i
+ if r.endswith('.old'): r = r[:-4]
+ if (r == 'master' or r == 'param' or
+ r == 'keyring' or r == 'keyring.pub' or r.startswith('peer-')):
+ zap(i)
### Main driver
class UsageError (Exception): pass
commands = {'help': (cmd_help, 0, 1, ''),
+ 'newmaster': (cmd_newmaster, 0, 0, ''),
'setup': (cmd_setup, 0, 0, ''),
'upload': (cmd_upload, 0, 0, ''),
'update': (cmd_update, 0, 0, ''),