-
Notifications
You must be signed in to change notification settings - Fork 11
/
generate.clj
343 lines (286 loc) · 10.8 KB
/
generate.clj
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
(ns clj-pgp.generate
"This namespace provides functionality to generate PGP keypairs and keyrings
using a convenient macro syntax.
The two primary supported key types are RSA keys (via `rsa-keypair-generator`)
and Elliptic Curve keys (via `ec-keypair-generator`). A set of the supported
named curves can be found in `elliptic-curve-names`.
To generate PGP keyrings with designater master keys and subkeys with specific
roles and restrictions, use the `generate-keys` macro. This returns a map with
`:public` and `:secret` entries containing the respective keyrings."
(:require
[clj-pgp.core :as pgp]
[clj-pgp.tags :as tags]
[clj-pgp.util :refer [arg-seq]]
[clojure.string :as str])
(:import
java.security.SecureRandom
java.util.Date
(org.bouncycastle.asn1.sec
SECNamedCurves)
(org.bouncycastle.asn1.x9
X9ECParameters)
(org.bouncycastle.bcpg.sig
Features
KeyFlags)
(org.bouncycastle.crypto
AsymmetricCipherKeyPairGenerator)
(org.bouncycastle.crypto.generators
ECKeyPairGenerator
RSAKeyPairGenerator)
(org.bouncycastle.crypto.params
ECKeyGenerationParameters
ECNamedDomainParameters
RSAKeyGenerationParameters)
(org.bouncycastle.openpgp
PGPKeyPair
PGPKeyRingGenerator
PGPSignature
PGPSignatureSubpacketGenerator)
(org.bouncycastle.openpgp.operator
PGPDigestCalculator)
(org.bouncycastle.openpgp.operator.bc
BcPBESecretKeyEncryptorBuilder
BcPGPContentSignerBuilder
BcPGPDigestCalculatorProvider
BcPGPKeyPair)))
;; ## KeyPair Generation
(defn generate-keypair
"Builds a new PGP keypair from a generator."
^PGPKeyPair
[^AsymmetricCipherKeyPairGenerator generator
algorithm]
(BcPGPKeyPair.
(tags/public-key-algorithm-code algorithm)
(.generateKeyPair generator)
(Date.)))
(defn rsa-keypair-generator
"Constructs a new generator for RSA keypairs with the given bit strength.
Other parameters may be customized with keyword options.
Opts may include:
- `:random` a custom random number generator
- `:exponent` RSA algorithm public exponent
- `:certainty` threshold for probabilistic prime generation"
{:arglists '([strength & opts])}
^RSAKeyPairGenerator
[strength & {:keys [random exponent certainty]
:or {random (SecureRandom/getInstance "SHA1PRNG")
exponent (BigInteger/valueOf 0x10001)
certainty 80}}]
(doto (RSAKeyPairGenerator.)
(.init (RSAKeyGenerationParameters.
exponent
random
strength
certainty))))
(def elliptic-curve-names
"Set of supported elliptic curves."
(set (iterator-seq (SECNamedCurves/getNames))))
(defn ec-keypair-generator
"Constructs a new generator for keypairs on the named elliptic curve.
Opts may include:
- `:random` a custom random number generator"
{:arglists '([curve & opts])}
[curve
& {:keys [^SecureRandom random]
:or {random (SecureRandom/getInstance "SHA1PRNG")}}]
(let [^X9ECParameters params (SECNamedCurves/getByName curve)]
(doto (ECKeyPairGenerator.)
(.init (ECKeyGenerationParameters.
(ECNamedDomainParameters.
(SECNamedCurves/getOID curve)
(.getCurve params)
(.getG params)
(.getN params)
(.getH params)
(.getSeed params))
random)))))
;; ## Key Signatures
(defn signature-subpacket-generator
"Constructs a new generator for key signature subpackets. The given flags
will be applied to the key."
^PGPSignatureSubpacketGenerator
[& flags]
(let [generator (PGPSignatureSubpacketGenerator.)]
(when (seq flags)
(.setKeyFlags generator false (if (< 1 (count flags))
(apply bit-or flags)
(first flags))))
generator))
(defn signature-generator
"Constructs a signature subpacket generator with a preset mode. This can be
one of `:master`, `:signing`, or `:encryption`."
[mode]
(case mode
:master
(doto (signature-subpacket-generator
KeyFlags/SIGN_DATA
KeyFlags/CERTIFY_OTHER)
; Request senders add additional checksums to the message (useful
; when verifying unsigned messages).
(.setFeature false Features/FEATURE_MODIFICATION_DETECTION))
:signing
(signature-subpacket-generator
KeyFlags/ENCRYPT_COMMS
KeyFlags/ENCRYPT_STORAGE)
:encryption
(signature-subpacket-generator
KeyFlags/SIGN_DATA)))
(defmacro ^:private defpreference
"Builds a function which sets preferences on a signature generator for
secondary cryptographic algorithms to prefer."
[pref-type tag->code]
`(defn ~(symbol (str "prefer-" (str/lower-case pref-type) "-algorithms!"))
"Sets the list of preferred algorithms on a signature generator for
use when sending messages to the key."
[generator# & algorithms#]
(when-let [prefs# (arg-seq algorithms#)]
(~(symbol (str ".setPreferred" pref-type "Algorithms"))
^PGPSignatureSubpacketGenerator generator#
false
(int-array (map ~tag->code prefs#))))))
(defpreference Symmetric tags/symmetric-key-algorithm-code)
(defpreference Hash tags/hash-algorithm-code)
(defpreference Compression tags/compression-algorithm-code)
(defn set-key-expiration!
"Sets a key expiration time on a signature generator. The lifetime is
expressed as a number of seconds since the key creation time."
[^PGPSignatureSubpacketGenerator generator
^long lifetime]
(.setKeyExpirationTime generator true lifetime))
;; ## Keyring Construction
(defn- digest-calculator
"Constructs a new digest calculator for the given hash algorithm."
^PGPDigestCalculator
[algorithm]
(.get (BcPGPDigestCalculatorProvider.)
(tags/hash-algorithm-code algorithm)))
(defn- secret-key-encryptor
"Constructs a new encryptor which will lock secret keys with the given
passphrase. The encryption algorithm and passphrase hash algorithm may be
specified as optional keyword arguments."
[^String passphrase
& {:keys [enc-algo pass-algo]
:or {enc-algo :aes-256
pass-algo :sha256}}]
(.build (BcPBESecretKeyEncryptorBuilder.
(tags/symmetric-key-algorithm-code enc-algo)
(digest-calculator pass-algo))
(.toCharArray passphrase)))
(defn keyring-generator
"Constructs a new generator for a keyring for a user-id, encrypted with the
given passphrase. The provided keypair will become the master key with any
options specified in the signature subpacket."
[^String user-id
^String passphrase
^PGPKeyPair master-key
^PGPSignatureSubpacketGenerator master-sig-gen]
(PGPKeyRingGenerator.
PGPSignature/POSITIVE_CERTIFICATION
master-key
user-id
(digest-calculator :sha1)
(.generate master-sig-gen)
nil
(BcPGPContentSignerBuilder.
(tags/public-key-algorithm-code (pgp/key-algorithm master-key))
(tags/hash-algorithm-code :sha1))
(secret-key-encryptor passphrase)))
(defn add-subkey!
"Adds a subkey and signature packet to a keyring genrator."
[^PGPKeyRingGenerator generator
^PGPKeyPair subkey
^PGPSignatureSubpacketGenerator sig-gen]
(.addSubKey generator subkey (.generate sig-gen) nil))
(defn generate-keyrings
"Generates both the public and secret keyrings from the given generator."
[^PGPKeyRingGenerator keyring-gen]
{:public (.generatePublicKeyRing keyring-gen)
:secret (.generateSecretKeyRing keyring-gen)})
;; ## Keyring Specification
(defn- group-key-spec
"Checks a single key specification, updating the map with either a master-key
or subkey entry."
[spec-map spec]
(when-not (list? spec)
(throw (IllegalArgumentException.
(str "Key specifications must be lists: " spec))))
(if (= 'master-key (first spec))
(if (:master spec-map)
(throw (IllegalArgumentException.
(str "Cannot specify multiple master-key specs: " spec)))
(assoc spec-map :master spec))
(update-in spec-map [:subkeys] conj spec)))
(defn- subpacket->fn
[packet]
(when-not (list? packet)
(throw (IllegalArgumentException.
(str "Signature subpacket forms must be lists: " packet))))
(let [fns {'prefer-symmetric `prefer-symmetric-algorithms!
'prefer-hash `prefer-hash-algorithms!
'prefer-compression `prefer-compression-algorithms!
'expires `set-key-expiration!}
[packet-type & args] packet]
(when-not (contains? fns packet-type)
(throw (IllegalArgumentException.
(str "Unknown signature subpacket type: " packet-type))))
(cons (fns packet-type) args)))
(defn- keypair-with-signature-subpackets
"Standard form to create a list of keypair with a doto block around the
signature generator to apply the subpackets."
[sig-generator keypair sig-subpackets]
[(if (and (list? keypair) (= 'keypair (first keypair)))
(cons `generate-keypair (rest keypair))
keypair)
(if (seq sig-subpackets)
`(doto ~sig-generator
~@(map subpacket->fn sig-subpackets))
sig-generator)])
(defn- master-keyring-generator
[user-id passphrase key-spec]
(let [[keypair & sig-subpackets] (rest key-spec)]
`(keyring-generator
~user-id ~passphrase
~@(keypair-with-signature-subpackets
`(signature-generator :master)
keypair
sig-subpackets))))
(defn- add-keyring-subkey
[[key-type keypair & sig-subpackets]]
(cons
`add-subkey!
(keypair-with-signature-subpackets
(case key-type
encryption-key `(signature-generator :encryption)
signing-key `(signature-generator :signing)
(throw (IllegalArgumentException.
(str "Unknown subkey type " key-type))))
keypair
sig-subpackets)))
(defmacro generate-keys
"Macro to generate keys with a mini-language to specify preferences and
subkeys.
An example invocation that creates a master key with signing and encryption
subkeys:
```
(pgp-gen/generate-keys
\"test user\" \"test passphrase\"
(master-key
(keypair rsa :rsa-general)
(prefer-symmetric :aes-256 :aes-128)
(prefer-hash :sha512 :sha256 :sha1)
(prefer-compression :zlib :bzip2))
(signing-key
(keypair rsa :rsa-general)
(expires 36000))
(encryption-key
(keypair rsa :rsa-general)))
```"
[user-id passphrase & key-specs]
(let [spec-map (reduce group-key-spec {:subkeys []} key-specs)]
(when-not (:master spec-map)
(throw (IllegalArgumentException.
(str "No master-key specification provided in key-specs: " key-specs))))
`(generate-keyrings
(doto
~(master-keyring-generator user-id passphrase (:master spec-map))
~@(map add-keyring-subkey (:subkeys spec-map))))))