1 |
|
|
/* $Id: acctproc.c,v 1.11 2017/01/24 13:32:55 jsing Exp $ */ |
2 |
|
|
/* |
3 |
|
|
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> |
4 |
|
|
* |
5 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
6 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
7 |
|
|
* copyright notice and this permission notice appear in all copies. |
8 |
|
|
* |
9 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
10 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
11 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
12 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
13 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
14 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
15 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 |
|
|
*/ |
17 |
|
|
|
18 |
|
|
#include <sys/stat.h> |
19 |
|
|
|
20 |
|
|
#include <err.h> |
21 |
|
|
#include <stdio.h> |
22 |
|
|
#include <stdlib.h> |
23 |
|
|
#include <string.h> |
24 |
|
|
#include <unistd.h> |
25 |
|
|
|
26 |
|
|
#include <openssl/pem.h> |
27 |
|
|
#include <openssl/rsa.h> |
28 |
|
|
#include <openssl/rand.h> |
29 |
|
|
#include <openssl/err.h> |
30 |
|
|
|
31 |
|
|
#include "extern.h" |
32 |
|
|
#include "rsa.h" |
33 |
|
|
|
34 |
|
|
/* |
35 |
|
|
* Converts a BIGNUM to the form used in JWK. |
36 |
|
|
* This is essentially a base64-encoded big-endian binary string |
37 |
|
|
* representation of the number. |
38 |
|
|
*/ |
39 |
|
|
static char * |
40 |
|
|
bn2string(const BIGNUM *bn) |
41 |
|
|
{ |
42 |
|
|
int len; |
43 |
|
|
char *buf, *bbuf; |
44 |
|
|
|
45 |
|
|
/* Extract big-endian representation of BIGNUM. */ |
46 |
|
|
|
47 |
|
|
len = BN_num_bytes(bn); |
48 |
|
|
if ((buf = malloc(len)) == NULL) { |
49 |
|
|
warn("malloc"); |
50 |
|
|
return NULL; |
51 |
|
|
} else if (len != BN_bn2bin(bn, (unsigned char *)buf)) { |
52 |
|
|
warnx("BN_bn2bin"); |
53 |
|
|
free(buf); |
54 |
|
|
return NULL; |
55 |
|
|
} |
56 |
|
|
|
57 |
|
|
/* Convert to base64url. */ |
58 |
|
|
|
59 |
|
|
if ((bbuf = base64buf_url(buf, len)) == NULL) { |
60 |
|
|
warnx("base64buf_url"); |
61 |
|
|
free(buf); |
62 |
|
|
return NULL; |
63 |
|
|
} |
64 |
|
|
|
65 |
|
|
free(buf); |
66 |
|
|
return bbuf; |
67 |
|
|
} |
68 |
|
|
|
69 |
|
|
/* |
70 |
|
|
* Extract the relevant RSA components from the key and create the JSON |
71 |
|
|
* thumbprint from them. |
72 |
|
|
*/ |
73 |
|
|
static char * |
74 |
|
|
op_thumb_rsa(EVP_PKEY *pkey) |
75 |
|
|
{ |
76 |
|
|
char *exp = NULL, *mod = NULL, *json = NULL; |
77 |
|
|
RSA *r; |
78 |
|
|
|
79 |
|
|
if ((r = EVP_PKEY_get1_RSA(pkey)) == NULL) |
80 |
|
|
warnx("EVP_PKEY_get1_RSA"); |
81 |
|
|
else if ((mod = bn2string(r->n)) == NULL) |
82 |
|
|
warnx("bn2string"); |
83 |
|
|
else if ((exp = bn2string(r->e)) == NULL) |
84 |
|
|
warnx("bn2string"); |
85 |
|
|
else if ((json = json_fmt_thumb_rsa(exp, mod)) == NULL) |
86 |
|
|
warnx("json_fmt_thumb_rsa"); |
87 |
|
|
|
88 |
|
|
free(exp); |
89 |
|
|
free(mod); |
90 |
|
|
return json; |
91 |
|
|
} |
92 |
|
|
|
93 |
|
|
/* |
94 |
|
|
* The thumbprint operation is used for the challenge sequence. |
95 |
|
|
*/ |
96 |
|
|
static int |
97 |
|
|
op_thumbprint(int fd, EVP_PKEY *pkey) |
98 |
|
|
{ |
99 |
|
|
char *thumb = NULL, *dig64 = NULL; |
100 |
|
|
EVP_MD_CTX *ctx = NULL; |
101 |
|
|
unsigned char *dig = NULL; |
102 |
|
|
unsigned int digsz; |
103 |
|
|
int rc = 0; |
104 |
|
|
|
105 |
|
|
/* Construct the thumbprint input itself. */ |
106 |
|
|
|
107 |
|
|
switch (EVP_PKEY_type(pkey->type)) { |
108 |
|
|
case EVP_PKEY_RSA: |
109 |
|
|
if ((thumb = op_thumb_rsa(pkey)) != NULL) |
110 |
|
|
break; |
111 |
|
|
goto out; |
112 |
|
|
default: |
113 |
|
|
warnx("EVP_PKEY_type: unknown key type"); |
114 |
|
|
goto out; |
115 |
|
|
} |
116 |
|
|
|
117 |
|
|
/* |
118 |
|
|
* Compute the SHA256 digest of the thumbprint then |
119 |
|
|
* base64-encode the digest itself. |
120 |
|
|
* If the reader is closed when we write, ignore it (we'll pick |
121 |
|
|
* it up in the read loop). |
122 |
|
|
*/ |
123 |
|
|
|
124 |
|
|
if ((dig = malloc(EVP_MAX_MD_SIZE)) == NULL) { |
125 |
|
|
warn("malloc"); |
126 |
|
|
goto out; |
127 |
|
|
} else if ((ctx = EVP_MD_CTX_create()) == NULL) { |
128 |
|
|
warnx("EVP_MD_CTX_create"); |
129 |
|
|
goto out; |
130 |
|
|
} else if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) { |
131 |
|
|
warnx("EVP_SignInit_ex"); |
132 |
|
|
goto out; |
133 |
|
|
} else if (!EVP_DigestUpdate(ctx, thumb, strlen(thumb))) { |
134 |
|
|
warnx("EVP_SignUpdate"); |
135 |
|
|
goto out; |
136 |
|
|
} else if (!EVP_DigestFinal_ex(ctx, dig, &digsz)) { |
137 |
|
|
warnx("EVP_SignFinal"); |
138 |
|
|
goto out; |
139 |
|
|
} else if ((dig64 = base64buf_url((char *)dig, digsz)) == NULL) { |
140 |
|
|
warnx("base64buf_url"); |
141 |
|
|
goto out; |
142 |
|
|
} else if (writestr(fd, COMM_THUMB, dig64) < 0) |
143 |
|
|
goto out; |
144 |
|
|
|
145 |
|
|
rc = 1; |
146 |
|
|
out: |
147 |
|
|
if (ctx != NULL) |
148 |
|
|
EVP_MD_CTX_destroy(ctx); |
149 |
|
|
|
150 |
|
|
free(thumb); |
151 |
|
|
free(dig); |
152 |
|
|
free(dig64); |
153 |
|
|
return rc; |
154 |
|
|
} |
155 |
|
|
|
156 |
|
|
static int |
157 |
|
|
op_sign_rsa(char **head, char **prot, EVP_PKEY *pkey, const char *nonce) |
158 |
|
|
{ |
159 |
|
|
char *exp = NULL, *mod = NULL; |
160 |
|
|
int rc = 0; |
161 |
|
|
RSA *r; |
162 |
|
|
|
163 |
|
|
*head = NULL; |
164 |
|
|
*prot = NULL; |
165 |
|
|
|
166 |
|
|
/* |
167 |
|
|
* First, extract relevant portions of our private key. |
168 |
|
|
* Then construct the public header. |
169 |
|
|
* Finally, format the header combined with the nonce. |
170 |
|
|
*/ |
171 |
|
|
|
172 |
|
|
if ((r = EVP_PKEY_get1_RSA(pkey)) == NULL) |
173 |
|
|
warnx("EVP_PKEY_get1_RSA"); |
174 |
|
|
else if ((mod = bn2string(r->n)) == NULL) |
175 |
|
|
warnx("bn2string"); |
176 |
|
|
else if ((exp = bn2string(r->e)) == NULL) |
177 |
|
|
warnx("bn2string"); |
178 |
|
|
else if ((*head = json_fmt_header_rsa(exp, mod)) == NULL) |
179 |
|
|
warnx("json_fmt_header_rsa"); |
180 |
|
|
else if ((*prot = json_fmt_protected_rsa(exp, mod, nonce)) == NULL) |
181 |
|
|
warnx("json_fmt_protected_rsa"); |
182 |
|
|
else |
183 |
|
|
rc = 1; |
184 |
|
|
|
185 |
|
|
free(exp); |
186 |
|
|
free(mod); |
187 |
|
|
return rc; |
188 |
|
|
} |
189 |
|
|
|
190 |
|
|
/* |
191 |
|
|
* Operation to sign a message with the account key. |
192 |
|
|
* This requires the sender ("fd") to provide the payload and a nonce. |
193 |
|
|
*/ |
194 |
|
|
static int |
195 |
|
|
op_sign(int fd, EVP_PKEY *pkey) |
196 |
|
|
{ |
197 |
|
|
char *nonce = NULL, *pay = NULL, *pay64 = NULL; |
198 |
|
|
char *prot = NULL, *prot64 = NULL, *head = NULL; |
199 |
|
|
char *sign = NULL, *dig64 = NULL, *fin = NULL; |
200 |
|
|
unsigned char *dig = NULL; |
201 |
|
|
EVP_MD_CTX *ctx = NULL; |
202 |
|
|
int cc, rc = 0; |
203 |
|
|
unsigned int digsz; |
204 |
|
|
|
205 |
|
|
/* Read our payload and nonce from the requestor. */ |
206 |
|
|
|
207 |
|
|
if ((pay = readstr(fd, COMM_PAY)) == NULL) |
208 |
|
|
goto out; |
209 |
|
|
else if ((nonce = readstr(fd, COMM_NONCE)) == NULL) |
210 |
|
|
goto out; |
211 |
|
|
|
212 |
|
|
/* Base64-encode the payload. */ |
213 |
|
|
|
214 |
|
|
if ((pay64 = base64buf_url(pay, strlen(pay))) == NULL) { |
215 |
|
|
warnx("base64buf_url"); |
216 |
|
|
goto out; |
217 |
|
|
} |
218 |
|
|
|
219 |
|
|
switch (EVP_PKEY_type(pkey->type)) { |
220 |
|
|
case EVP_PKEY_RSA: |
221 |
|
|
if (!op_sign_rsa(&head, &prot, pkey, nonce)) |
222 |
|
|
goto out; |
223 |
|
|
break; |
224 |
|
|
default: |
225 |
|
|
warnx("EVP_PKEY_type"); |
226 |
|
|
goto out; |
227 |
|
|
} |
228 |
|
|
|
229 |
|
|
/* The header combined with the nonce, base64. */ |
230 |
|
|
|
231 |
|
|
if ((prot64 = base64buf_url(prot, strlen(prot))) == NULL) { |
232 |
|
|
warnx("base64buf_url"); |
233 |
|
|
goto out; |
234 |
|
|
} |
235 |
|
|
|
236 |
|
|
/* Now the signature material. */ |
237 |
|
|
|
238 |
|
|
cc = asprintf(&sign, "%s.%s", prot64, pay64); |
239 |
|
|
if (cc == -1) { |
240 |
|
|
warn("asprintf"); |
241 |
|
|
sign = NULL; |
242 |
|
|
goto out; |
243 |
|
|
} |
244 |
|
|
|
245 |
|
|
if ((dig = malloc(EVP_PKEY_size(pkey))) == NULL) { |
246 |
|
|
warn("malloc"); |
247 |
|
|
goto out; |
248 |
|
|
} |
249 |
|
|
|
250 |
|
|
/* |
251 |
|
|
* Here we go: using our RSA key as merged into the envelope, |
252 |
|
|
* sign a SHA256 digest of our message. |
253 |
|
|
*/ |
254 |
|
|
|
255 |
|
|
if ((ctx = EVP_MD_CTX_create()) == NULL) { |
256 |
|
|
warnx("EVP_MD_CTX_create"); |
257 |
|
|
goto out; |
258 |
|
|
} else if (!EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) { |
259 |
|
|
warnx("EVP_SignInit_ex"); |
260 |
|
|
goto out; |
261 |
|
|
} else if (!EVP_SignUpdate(ctx, sign, strlen(sign))) { |
262 |
|
|
warnx("EVP_SignUpdate"); |
263 |
|
|
goto out; |
264 |
|
|
} else if (!EVP_SignFinal(ctx, dig, &digsz, pkey)) { |
265 |
|
|
warnx("EVP_SignFinal"); |
266 |
|
|
goto out; |
267 |
|
|
} else if ((dig64 = base64buf_url((char *)dig, digsz)) == NULL) { |
268 |
|
|
warnx("base64buf_url"); |
269 |
|
|
goto out; |
270 |
|
|
} |
271 |
|
|
|
272 |
|
|
/* |
273 |
|
|
* Write back in the correct JSON format. |
274 |
|
|
* If the reader is closed, just ignore it (we'll pick it up |
275 |
|
|
* when we next enter the read loop). |
276 |
|
|
*/ |
277 |
|
|
|
278 |
|
|
if ((fin = json_fmt_signed(head, prot64, pay64, dig64)) == NULL) { |
279 |
|
|
warnx("json_fmt_signed"); |
280 |
|
|
goto out; |
281 |
|
|
} else if (writestr(fd, COMM_REQ, fin) < 0) |
282 |
|
|
goto out; |
283 |
|
|
|
284 |
|
|
rc = 1; |
285 |
|
|
out: |
286 |
|
|
if (ctx != NULL) |
287 |
|
|
EVP_MD_CTX_destroy(ctx); |
288 |
|
|
|
289 |
|
|
free(pay); |
290 |
|
|
free(sign); |
291 |
|
|
free(pay64); |
292 |
|
|
free(nonce); |
293 |
|
|
free(head); |
294 |
|
|
free(prot); |
295 |
|
|
free(prot64); |
296 |
|
|
free(dig); |
297 |
|
|
free(dig64); |
298 |
|
|
free(fin); |
299 |
|
|
return rc; |
300 |
|
|
} |
301 |
|
|
|
302 |
|
|
int |
303 |
|
|
acctproc(int netsock, const char *acctkey, int newacct) |
304 |
|
|
{ |
305 |
|
|
FILE *f = NULL; |
306 |
|
|
EVP_PKEY *pkey = NULL; |
307 |
|
|
long lval; |
308 |
|
|
enum acctop op; |
309 |
|
|
int rc = 0, cc; |
310 |
|
|
mode_t prev; |
311 |
|
|
|
312 |
|
|
/* |
313 |
|
|
* First, open our private key file read-only or write-only if |
314 |
|
|
* we're creating from scratch. |
315 |
|
|
* Set our umask to be maximally restrictive. |
316 |
|
|
*/ |
317 |
|
|
|
318 |
|
|
prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO); |
319 |
|
|
f = fopen(acctkey, newacct ? "wx" : "r"); |
320 |
|
|
umask(prev); |
321 |
|
|
|
322 |
|
|
if (f == NULL) { |
323 |
|
|
warn("%s", acctkey); |
324 |
|
|
goto out; |
325 |
|
|
} |
326 |
|
|
|
327 |
|
|
/* File-system, user, and sandbox jailing. */ |
328 |
|
|
|
329 |
|
|
ERR_load_crypto_strings(); |
330 |
|
|
|
331 |
|
|
if (pledge("stdio flock rpath cpath wpath", NULL) == -1) { |
332 |
|
|
warn("pledge"); |
333 |
|
|
goto out; |
334 |
|
|
} |
335 |
|
|
|
336 |
|
|
if (newacct) { |
337 |
|
|
if ((pkey = rsa_key_create(f, acctkey)) == NULL) |
338 |
|
|
goto out; |
339 |
|
|
dodbg("%s: generated RSA account key", acctkey); |
340 |
|
|
} else { |
341 |
|
|
if ((pkey = rsa_key_load(f, acctkey)) == NULL) |
342 |
|
|
goto out; |
343 |
|
|
doddbg("%s: loaded RSA account key", acctkey); |
344 |
|
|
} |
345 |
|
|
|
346 |
|
|
fclose(f); |
347 |
|
|
f = NULL; |
348 |
|
|
|
349 |
|
|
/* Notify the netproc that we've started up. */ |
350 |
|
|
|
351 |
|
|
if ((cc = writeop(netsock, COMM_ACCT_STAT, ACCT_READY)) == 0) |
352 |
|
|
rc = 1; |
353 |
|
|
if (cc <= 0) |
354 |
|
|
goto out; |
355 |
|
|
|
356 |
|
|
/* |
357 |
|
|
* Now we wait for requests from the network-facing process. |
358 |
|
|
* It might ask us for our thumbprint, for example, or for us to |
359 |
|
|
* sign a message. |
360 |
|
|
*/ |
361 |
|
|
|
362 |
|
|
for (;;) { |
363 |
|
|
op = ACCT__MAX; |
364 |
|
|
if ((lval = readop(netsock, COMM_ACCT)) == 0) |
365 |
|
|
op = ACCT_STOP; |
366 |
|
|
else if (lval == ACCT_SIGN || lval == ACCT_THUMBPRINT) |
367 |
|
|
op = lval; |
368 |
|
|
|
369 |
|
|
if (ACCT__MAX == op) { |
370 |
|
|
warnx("unknown operation from netproc"); |
371 |
|
|
goto out; |
372 |
|
|
} else if (ACCT_STOP == op) |
373 |
|
|
break; |
374 |
|
|
|
375 |
|
|
switch (op) { |
376 |
|
|
case ACCT_SIGN: |
377 |
|
|
if (op_sign(netsock, pkey)) |
378 |
|
|
break; |
379 |
|
|
warnx("op_sign"); |
380 |
|
|
goto out; |
381 |
|
|
case ACCT_THUMBPRINT: |
382 |
|
|
if (op_thumbprint(netsock, pkey)) |
383 |
|
|
break; |
384 |
|
|
warnx("op_thumbprint"); |
385 |
|
|
goto out; |
386 |
|
|
default: |
387 |
|
|
abort(); |
388 |
|
|
} |
389 |
|
|
} |
390 |
|
|
|
391 |
|
|
rc = 1; |
392 |
|
|
out: |
393 |
|
|
close(netsock); |
394 |
|
|
if (f != NULL) |
395 |
|
|
fclose(f); |
396 |
|
|
if (pkey != NULL) |
397 |
|
|
EVP_PKEY_free(pkey); |
398 |
|
|
ERR_print_errors_fp(stderr); |
399 |
|
|
ERR_free_strings(); |
400 |
|
|
return rc; |
401 |
|
|
} |