forked from tonyg/js-nacl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimport.py
202 lines (183 loc) · 8.16 KB
/
import.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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env python
# A tool to import a NaCl source tree into subnacl/, fixing symbol names.
# Written by Brian Warner https://github.com/warner as part of PyNaCl.
# PyNaCl is released under version 2.0 of the Apache license.
# Modified by [email protected] to construct a Javascript API as well.
import os, sys, re, shutil, codecs
open = codecs.open
from os.path import abspath, exists, join, isfile
assert abspath(sys.argv[0]) == join(abspath(os.getcwd()), "import.py")
OUTPUT = join(abspath(os.getcwd()), "subnacl")
os.mkdir(OUTPUT)
INCLUDE = join(OUTPUT, "include")
os.mkdir(INCLUDE)
NACL = sys.argv[1]
def lines(*path):
return [line.strip() for line in open(join(NACL, *path),"r")]
MACROS = lines("MACROS")
class JSApi:
def __init__(self):
self.constants = {}
self.functions = set()
self.aliases = {}
self.version = open(join(NACL, "version"), "r").read().strip()
self._realfunctions = None
def realfunctions(self):
if self._realfunctions is None:
self._realfunctions = set((f for f in self.functions
if not f in self.constants and
not f.endswith('_BEFORENMBYTES')))
# ^ The _BEFORENMBYTES thing seems like a bug; missing from api.h
self._realfunctions.add("malloc")
self._realfunctions.add("free")
self._realfunctions.add("randombytes")
self._realfunctions.add("crypto_sign_keypair_from_raw_sk")
self._realfunctions.add("crypto_scalarmult_curve25519_base")
return self._realfunctions
jsapi = JSApi()
for op in lines("OPERATIONS"):
assert op.startswith("crypto_")
opname = op[len("crypto_"):]
op_dir = join(NACL, op)
#op_macros = [m[len(op+"_"):] for m in MACROS if m.startswith(op)]
op_macros = [m for m in MACROS if m == op or m.startswith("%s_" % op)]
# each operation includes several primitives. Sometimes you care about
# using a specific primitive, sometimes you just want to use the default
for prim in os.listdir(op_dir):
prim_dir = join(op_dir, prim)
if not exists(join(prim_dir, "used")):
continue # nacl might not mark everything as used
# 'selected' means this is a default. E.g. the presence of
# crypto_hash/sha512/selected means that the generic crypto_hash()
# should invoke crypto_hash_sha512(), and that crypto_hash_BYTES is
# #defined to crypto_hash_sha512_BYTES.
selected = exists(join(prim_dir, "selected"))
# now which implementations are available? nacl files these by
# looking for "api.h" files. We cheat and assume they're all in
# immediate subdirectories (nacl/do can find them in arbitrarily-deep
# subdirectories)
implementations = []
for impl in os.listdir(prim_dir):
if not exists(join(prim_dir, impl, "api.h")):
continue
implementations.append(impl)
# which implementation should we use? Prefer the 'ref' or 'portable'
# ones for now.
if "ref" in implementations:
preferred = "ref"
elif "portable" in implementations:
preferred = "portable"
else:
print("unable to find acceptable impl of %s/%s in %s" %
(op, prim, ",".join(implementations)))
continue
print("%s %s %s" % (op, prim, preferred))
outdir = join(OUTPUT, "%s_%s" % (opname, prim))
os.mkdir(outdir)
impldir = join(prim_dir, preferred)
for fn in os.listdir(impldir):
absfn = join(impldir, fn)
assert isfile(absfn), "cannot handle subdirs: %s/%s" % (impldir, fn)
if fn.endswith(".c"):
# need to modify it
out = open(join(outdir, fn), "w", encoding="utf-8")
for line in open(absfn, "r", encoding="utf-8").readlines():
if line.strip().startswith("#include"):
old = "%s.h" % op
if old in line:
new = "%s_%s.h" % (op, prim)
line = line.replace(old, new)
else:
for m in op_macros:
# when processing crypto_hash, replace
# crypto_hash() with crypto_hash_sha256() and
# crypto_hash_BYTES with
# crypto_hash_sha256_BYTES, but leave
# crypto_hashblocks_OTHER alone
new = m.replace(op, "%s_%s" % (op, prim))
line = re.sub(r'\b%s\b' % m, new, line)
out.write(line)
out.close()
else:
# don't try to modify it
shutil.copy(absfn, outdir)
out = open(join(INCLUDE, "%s_%s.h" % (op,prim)), "w")
out.write("#ifndef %s_%s_H\n" % (op, prim))
out.write("#define %s_%s_H\n" % (op, prim))
out.write("\n")
for line in open(join(impldir, "api.h"), "r").readlines():
line = line.replace("CRYPTO_", "%s_%s_" % (op, prim))
out.write(line)
(constantname, constantstr) = line.replace("#define ", "").strip().split()
jsapi.constants[constantname] = int(constantstr)
for line in open(join(NACL, "PROTOTYPES.c"), "r").readlines():
if ("%s(" % op) in line or ("%s_" % op) in line:
line = line.replace(op, "%s_%s" % (op, prim))
out.write(line)
out.write('#define %s_%s_IMPLEMENTATION "%s/%s/%s"\n'
% (op, prim, op, prim, preferred))
out.write('#define %s_%s_VERSION "-"\n' % (op, prim))
out.write("\n")
out.write("#endif\n")
out.close()
# record exports even if this primitive is not selected
for m in op_macros:
m_prim = m.replace(op, "%s_%s" % (op,prim))
jsapi.functions.add(m_prim)
# if this primitive is selected as a default, create a generic header
# file for the operation
if selected:
out = open(join(INCLUDE, "%s.h" % op), "w")
out.write("#ifndef %s_H\n" % op)
out.write("#define %s_H\n" % op)
out.write("\n")
# this is the "selected" default
out.write('#include "%s_%s.h"\n' % (op, prim))
out.write("\n")
for m in op_macros:
m_prim = m.replace(op, "%s_%s" % (op,prim))
out.write('#define %s %s\n' % (m, m_prim))
jsapi.aliases[m] = m_prim
out.write('#define %s_PRIMITIVE "%s"\n' % (op, prim))
out.write('#define %s_IMPLEMENTATION %s_%s_IMPLEMENTATION\n'
% (op, op, prim))
out.write('#define %s_VERSION %s_%s_VERSION\n' % (op, op, prim))
out.write("\n")
out.write("#endif\n")
out.close()
# create a few more include files
out = open(join(INCLUDE, "randombytes.h"), "w")
out.write("#ifndef randombytes_H\n")
out.write("#define randombytes_H\n")
out.write("\n")
out.write("extern void randombytes(unsigned char *,unsigned long long);\n")
out.write("#endif\n")
out.close()
out = open(join(INCLUDE, "crypto_uint32.h"), "w")
out.write("#ifndef crypto_uint32_H\n")
out.write("#define crypto_uint32_H\n")
out.write("#include <stdint.h>\n")
out.write("typedef uint32_t crypto_uint32;\n")
out.write("#endif\n")
out.close()
out = open(join(INCLUDE, "crypto_uint64.h"), "w")
out.write("#ifndef crypto_uint64_H\n")
out.write("#define crypto_uint64_H\n")
out.write("#include <stdint.h>\n")
out.write("typedef uint64_t crypto_uint64;\n")
out.write("#endif\n")
out.close()
# Emit the JS stuff from jsapi
out = open(join(OUTPUT, "naclapi.js"), "w")
for (k, v) in jsapi.constants.items():
out.write("Module['_%s'] = %d;\n" % (k, v))
for (k, v) in jsapi.aliases.items():
out.write("Module['_%s'] = Module['_%s'];\n" % (k, v))
out.close()
out = open(join(OUTPUT, "naclexports"), "w")
for v in jsapi.realfunctions():
out.write(v + "\n")
out.close()
out = open(join(OUTPUT, "naclexports.sh"), "w")
out.write("['_" + "','_".join(jsapi.realfunctions()) + "']")
out.close()