-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathfutord.py
183 lines (164 loc) · 7.12 KB
/
futord.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
##
# This is the core of a trust-based cryptographic primitive for encrypting a
# message with a key that will not be released until a certian time. Key pairs
# are generated by the server at intervals. Pubkeys are released immediately,
# while privkeys are held for a period before being published.
#
# Requires GPG, OpenSSL, or some other key-generating tool.
#
# Files (relative to datadir provided in argv[1]):
# keys/ contains keygen directories
# (anything in a keygen dir that ends in .pub is immediately public)
# openssl/ openssl keygen's keys
# 2014-02-12_14-49-10_UTC.pub pubkey (timestamp of release)
# 2014-02-12_14-49-10_UTC.priv privkey (timestamp of release)
# root.pub e.g. CA cert (optional, name not magic)
# root.priv e.g. CA key (optional, name not magic)
# README.txt encrypt/decrypt notes (optional, public)
# pub/ contains published keys
# openssl/ published openssl keys
# 2014-02-12_14-49-10_UTC.pub
# root.pub
# README.txt
# conf config file -- we trust data from this file!
# [timing] section with release timing stuff
# period = 60 seconds between pubkey and privkey release
# interval = 10 seconds between keypairs
# [generation] section with key generation module stuff
# modules = openssl semicolon-separated keygen module list
# [generate_openssl] keygen-specific configurations
# bits = 2048 key strength in bits
# instancename = My Futor Name to use in certificates
#
# TODO: Time source is very important. Use GPS.
# TODO: Entropy source is important.
import configparser, time, os, datetime, shutil, sys
tsformat = "%Y-%m-%d_%H-%M-%S_UTC"
tslen = 23
#this should probably move to its own file once it's properly implemented
def getcurtime():
return time.time() #yes, this is cheating
def getkeytime(fname):
st = time.strptime(fname[:tslen], tsformat)
return time.mktime(st) - time.timezone
def maketimestamp(t):
return time.strftime(tsformat, time.gmtime(t))
def touchdir(path):
try:
os.makedirs(path)
except OSError as e:
if e.errno == 17: #File exists
pass
else:
raise e
##
# This object represents a keygen module and holds some useful data for it. The
# init method is called automatically when the class is instantiated.
class KeyGen():
def __init__(self, name, conf, keydir):
touchdir(keydir)
module = __import__(name)
sect = "generate_" + name
args = {key: conf.get(sect, key) for key in conf.options(sect)}
args.update({"keydir": keydir})
self.name, self.module, self.args = name, module, args
self.init() #call module init method
def init(self):
self.module.init(**self.args)
def generate(self, pubfname, privfname):
args = self.args.copy()
args.update({
"pubfname": pubfname,
"privfname": privfname,
})
print("keygen gen", args)
self.module.generate(**args)
def loadconfig():
datadir = sys.argv[1] if len(sys.argv) > 1 else "."
sys.path.insert(0, datadir) #we import modules from here
dd = lambda path: os.path.join(datadir, path)
conf = configparser.ConfigParser()
conf.read([dd("conf")])
pubdir = dd("pub")
keydir = dd("keys")
return {"datadir": datadir, "conf": conf,
"pubdir": pubdir, "keydir": keydir}
if __name__ == "__main__":
c = loadconfig()
conf = c["conf"]
pubdir = c["pubdir"]
keydir = c["keydir"]
touchdir(pubdir)
touchdir(keydir)
curtime = getcurtime()
#init keygens
keygens = conf.get("generation", "modules").split(";")
keygens = [k.strip(" ") for k in keygens]
keygendict = {}
for keygen in keygens:
keysubdir = os.path.join(keydir, keygen)
keygendict[keygen] = KeyGen(keygen, conf, keysubdir)
#update contents of pub directory
def updatepub(curtime):
for keysubdir in keygens:
pubsubdir = os.path.join(pubdir, keysubdir)
keysubdir = os.path.join(keydir, keysubdir)
touchdir(pubsubdir)
for fname in os.listdir(keysubdir):
#copy public files to pub directory
if fname.endswith(".pub") or fname == "README.txt":
shutil.copy(os.path.join(keysubdir, fname),
pubsubdir)
elif not os.path.exists(os.path.join(pubsubdir, fname)):
#copy any ready and missing privkeys to pub directory
if fname.endswith(".priv"):
try:
t = getkeytime(fname)
except ValueError: #no timestamp: not to be public
continue
if t <= curtime:
shutil.copy(os.path.join(keysubdir, fname),
pubsubdir)
#TODO: maybe remove really old keys from keydir
updatepub(curtime)
#calculate timestamps of keys to generate.
#find latest (most-future) existing key and work forwards.
#if no key newer than now, start at now
#TODO: align to round minutes/hours
#NOTE: This will not generate missing keys if some but not all of the keys
# for the latest interval exist. There isn't a lot we can do about
# this, so we just assume that the intervals are generated in a
# single step and won't have partially-present keys. The extent of
# the breakage is permanently skipped keys for the incomplete
# interval. The solution is to run this loop once for every keygen,
# and due to the minimal chance of this situation and the minor
# nature of the breakage, I will not make this change this at this
# time for performance and code neatness reasons.
period = int(conf.get("timing", "period"))
interval = int(conf.get("timing", "interval"))
latestkey = curtime
for keysubdir in keygens:
for fname in os.listdir(os.path.join(keydir, keysubdir)):
#NOTE: make sure both pub and privkeys are present for each pair?
try:
keytime = getkeytime(fname)
except ValueError: #file that doesn't have a timestamp
keytime = latestkey #no change this iteration
if keytime > latestkey:
latestkey = keytime
#make a list of new keys to generate
togenerate = []
while latestkey+interval <= curtime+period:
togenerate.append(latestkey + interval)
latestkey += interval
#starting with soonest key, generate new key pairs,
#between each pair, updatepub()
#be careful as this approach exposes approximate key generation duration!
for keytime in togenerate:
for keygen in keygens:
ts = maketimestamp(keytime)
pubfname = "{}.pub".format(ts)
privfname = "{}.priv".format(ts)
keygendict[keygen].generate(pubfname, privfname)
curtime = getcurtime()
updatepub(curtime)