1 /*-
2 * Copyright (c) 2010 Alistair Crooks <agc@NetBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25 #include <sys/types.h>
26 #include <sys/socket.h>
27
28 #include <arpa/inet.h>
29
30 #include <netinet6/in6.h>
31
32 #include <netdb.h>
33
34 #include <ifaddrs.h>
35 #include <netpgp.h>
36 #include <regex.h>
37 #include <sha1.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <time.h>
42 #include <unistd.h>
43
44 #include "libpaa.h"
45 #include "b64.h"
46
47 enum {
48 MAX_DIGEST_SIZE = 128
49 };
50
51 /* create an area of random memory */
52 static int
randomise(char * s,size_t size)53 randomise(char *s, size_t size)
54 {
55 uint32_t r;
56 size_t i;
57
58 for (i = 0 ; i < size ; i += sizeof(r)) {
59 r = random();
60 (void) memcpy(&s[i], &r, sizeof(r));
61 }
62 return i;
63 }
64
65 /* generate a challenge */
66 static int
genchallenge(paa_challenge_t * challenge,paa_server_info_t * server)67 genchallenge(paa_challenge_t *challenge, paa_server_info_t *server)
68 {
69 time_t t;
70 char digest[MAX_DIGEST_SIZE];
71 char raw[PAA_CHALLENGE_SIZE * 2];
72 int cc;
73
74 t = time(NULL);
75 cc = snprintf(raw, sizeof(raw), "%s;%s;%lld;", challenge->realm, server->hostaddress, (int64_t)t);
76 cc += randomise(&raw[cc], 64); /* 64 is arbitrary */
77 /* raw now has the raw-challenge in it */
78 challenge->encc = b64encode(raw, (const unsigned)cc, challenge->encoded_challenge,
79 sizeof(challenge->encoded_challenge), 0);
80 cc += snprintf(&raw[cc], sizeof(raw) - cc, ";%.*s", server->secretc, server->secret);
81 (void) SHA1Data((uint8_t *)raw, (unsigned)cc, digest);
82 server->server_signaturec = b64encode(digest, (const unsigned)strlen(digest),
83 server->server_signature, sizeof(server->server_signature), (int)0);
84 /* raw has raw-challenge ; server-secret-value, i.e. raw-server-signature */
85 challenge->challengec = snprintf(challenge->challenge, sizeof(challenge->challenge),
86 "%.*s;%.*s", server->server_signaturec, server->server_signature,
87 challenge->encc, challenge->encoded_challenge);
88 return challenge->challengec;
89 }
90
91 /* fill in the identity information in the response */
92 static int
fill_identity(paa_identity_t * id,char * response,char * raw_challenge)93 fill_identity(paa_identity_t *id, char *response, char *raw_challenge)
94 {
95 regmatch_t matches[10];
96 regex_t response_re;
97 regex_t id_re;
98 char t[32];
99
100 /* id="userid" */
101 (void) regcomp(&id_re, "id=\"([^\"]+)\"", REG_EXTENDED);
102 if (regexec(&id_re, response, 10, matches, 0) != 0) {
103 (void) fprintf(stderr, "No identity information found\n");
104 return 0;
105 }
106 (void) snprintf(id->userid, sizeof(id->userid), "%.*s",
107 (int)(matches[1].rm_eo - matches[1].rm_so),
108 &response[(int)matches[1].rm_so]);
109 /* realm;ip;timestamp;seed */
110 (void) regcomp(&response_re, "([^;]+);([^;]+);([^;]+);(.*)", REG_EXTENDED);
111 if (regexec(&response_re, raw_challenge, 10, matches, 0) != 0) {
112 (void) fprintf(stderr, "No identity information found\n");
113 return 0;
114 }
115 (void) snprintf(id->realm, sizeof(id->realm), "%.*s",
116 (int)(matches[1].rm_eo - matches[1].rm_so),
117 &raw_challenge[(int)matches[1].rm_so]);
118 (void) snprintf(id->client, sizeof(id->client), "%.*s",
119 (int)(matches[2].rm_eo - matches[2].rm_so),
120 &raw_challenge[(int)matches[2].rm_so]);
121 (void) snprintf(t, sizeof(t), "%.*s",
122 (int)(matches[3].rm_eo - matches[3].rm_so),
123 &raw_challenge[(int)matches[3].rm_so]);
124 id->timestamp = strtoll(t, NULL, 10);
125 return 1;
126 }
127
128 /***************************************************************************/
129 /* exported functions start here */
130 /***************************************************************************/
131
132 /* initialise the server info */
133 int
paa_server_init(paa_server_info_t * server,unsigned secretsize)134 paa_server_init(paa_server_info_t *server, unsigned secretsize)
135 {
136 struct sockaddr_in6 *sin6;
137 struct sockaddr_in *sin;
138 struct ifaddrs *addrs;
139 char host[512];
140
141 if (getifaddrs(&addrs) < 0) {
142 (void) fprintf(stderr, "can't getifaddrs\n");
143 return 0;
144 }
145 for ( ; addrs ; addrs = addrs->ifa_next) {
146 if (addrs->ifa_addr->sa_family == AF_INET) {
147 sin = (struct sockaddr_in *)(void *)addrs->ifa_addr;
148 (void) snprintf(server->hostaddress, sizeof(server->hostaddress), "%s",
149 inet_ntoa(sin->sin_addr));
150 break;
151 }
152 if (addrs->ifa_addr->sa_family == AF_INET6) {
153 sin6 = (struct sockaddr_in6 *)(void *)addrs->ifa_addr;
154 (void) getnameinfo((const struct sockaddr *)(void *)sin6,
155 (unsigned)sin6->sin6_len,
156 server->hostaddress, sizeof(server->hostaddress),
157 NULL, 0, NI_NUMERICHOST);
158 break;
159 }
160 }
161 if (addrs == NULL) {
162 if (gethostname(host, sizeof(host)) < 0) {
163 (void) fprintf(stderr, "can't get hostname\n");
164 return 0;
165 }
166 (void) snprintf(server->hostaddress, sizeof(server->hostaddress), "%s", host);
167 }
168 if ((server->secret = calloc(1, server->secretc = secretsize)) == NULL) {
169 (void) fprintf(stderr, "can't allocate server secret\n");
170 return 0;
171 }
172 server->secretc = randomise(server->secret, secretsize);
173 return 1;
174 }
175
176 /*
177 challenge = "PubKey.v1" pubkey-challenge
178
179 pubkey-challenge = 1#( realm | [domain] | challenge )
180
181 realm = "realm" "=" quoted-string
182 domain = "domain" "=" <"> URI ( 1*SP URI ) <">
183 URI = absoluteURI | abs_path
184 challenge = "challenge" "=" quoted-string
185 */
186
187 /* called from server to send the challenge */
188 int
paa_format_challenge(paa_challenge_t * challenge,paa_server_info_t * server,char * buf,size_t size)189 paa_format_challenge(paa_challenge_t *challenge, paa_server_info_t *server, char *buf, size_t size)
190 {
191 int cc;
192
193 if (challenge->realm == NULL) {
194 (void) fprintf(stderr, "paa_format_challenge: no realm information\n");
195 return 0;
196 }
197 cc = snprintf(buf, size, "401 Unauthorized\r\nWWW-Authenticate: PubKey.v1\r\n");
198 (void) genchallenge(challenge, server);
199 cc += snprintf(&buf[cc], size - cc, " challenge=\"%s\"", challenge->challenge);
200 if (challenge->realm) {
201 cc += snprintf(&buf[cc], size - cc, ",\r\n realm=\"%s\"", challenge->realm);
202 }
203 if (challenge->domain) {
204 cc += snprintf(&buf[cc], size - cc, ",\r\n domain=\"%s\"", challenge->domain);
205 }
206 cc += snprintf(&buf[cc], size - cc, "\r\n");
207 return cc;
208 }
209
210 /*
211 credentials = "PubKey.v1" privkey-credentials
212
213 privkey-credentials = 1#( identifier | realm | challenge | signature )
214
215 identifier = "id" "=" identifier-value
216 identifier-value = quoted-string
217 challenge = "challenge" "=" challenge-value
218 challenge-value = quoted-string
219 signature = "signature" "=" signature-value
220 signature-value = quoted-string
221 */
222
223 /* called from client to respond to the challenge */
224 int
paa_format_response(paa_response_t * response,netpgp_t * netpgp,char * in,char * out,size_t outsize)225 paa_format_response(paa_response_t *response, netpgp_t *netpgp, char *in, char *out, size_t outsize)
226 {
227 regmatch_t matches[10];
228 regex_t r;
229 char challenge[2048 * 2];
230 char base64_signature[2048 * 2];
231 char sig[2048];
232 int challengec;
233 int sig64c;
234 int sigc;
235 int outc;
236
237 if (response->realm == NULL) {
238 (void) fprintf(stderr, "paa_format_response: no realm information\n");
239 return 0;
240 }
241 (void) regcomp(&r, "challenge=\"([^\"]+)\"", REG_EXTENDED);
242 if (regexec(&r, in, 10, matches, 0) != 0) {
243 (void) fprintf(stderr, "no signature found\n");
244 return 0;
245 }
246 challengec = snprintf(challenge, sizeof(challenge), "%.*s",
247 (int)(matches[1].rm_eo - matches[1].rm_so), &in[(int)matches[1].rm_so]);
248 /* read challenge string */
249 outc = snprintf(out, outsize, "Authorization: PubKey.v1\r\n");
250 response->userid = netpgp_getvar(netpgp, "userid");
251 outc += snprintf(&out[outc], outsize - outc, " id=\"%s\"", response->userid);
252 outc += snprintf(&out[outc], outsize - outc, ",\r\n challenge=\"%s\"", challenge);
253 outc += snprintf(&out[outc], outsize - outc, ",\r\n realm=\"%s\"", response->realm);
254 /* set up response */
255 (void) memset(sig, 0x0, sizeof(sig));
256 (void) snprintf(sig, sizeof(sig), "%s;%s;%s;", response->userid, response->realm, challenge);
257 sigc = netpgp_sign_memory(netpgp, response->userid, challenge,
258 (unsigned)challengec, sig, sizeof(sig), 0, 0);
259 sig64c = b64encode(sig, (const unsigned)sigc, base64_signature,
260 sizeof(base64_signature), (int)0);
261 outc += snprintf(&out[outc], outsize - outc, ",\r\n signature=\"%.*s\"", sig64c, base64_signature);
262 return outc;
263 }
264
265 /* called from server to check the response to the challenge */
266 int
paa_check_response(paa_challenge_t * challenge,paa_identity_t * id,netpgp_t * netpgp,char * response)267 paa_check_response(paa_challenge_t *challenge, paa_identity_t *id, netpgp_t *netpgp, char *response)
268 {
269 regmatch_t matches[10];
270 regex_t challenge_regex;
271 regex_t signature_regex;
272 regex_t realm_regex;
273 time_t t;
274 char encoded_challenge[512];
275 char raw_challenge[512];
276 char verified[2048];
277 char realm[128];
278 char buf[2048];
279 int bufc;
280
281 /* grab the signed text from the response */
282 (void) regcomp(&signature_regex, "signature=\"([^\"]+)\"", REG_EXTENDED);
283 if (regexec(&signature_regex, response, 10, matches, 0) != 0) {
284 (void) fprintf(stderr, "paa_check: no signature found\n");
285 return 0;
286 }
287 /* atob the signature itself */
288 bufc = b64decode(&response[(int)matches[1].rm_so],
289 (size_t)(matches[1].rm_eo - matches[1].rm_so), buf, sizeof(buf));
290 /* verify the signature */
291 (void) memset(verified, 0x0, sizeof(verified));
292 if (netpgp_verify_memory(netpgp, buf, (const unsigned)bufc, verified, sizeof(verified), 0) <= 0) {
293 (void) fprintf(stderr, "paa_check: signature cannot be verified\n");
294 return 0;
295 }
296 /* we check the complete signed text against our challenge */
297 if (strcmp(challenge->challenge, verified) != 0) {
298 (void) fprintf(stderr, "paa_check: signature does not match\n");
299 return 0;
300 }
301 (void) regcomp(&challenge_regex, "^([^;]+);(.+)", REG_EXTENDED);
302 if (regexec(&challenge_regex, verified, 10, matches, 0) != 0) {
303 (void) fprintf(stderr, "paa_check: no 2 parts to challenge\n");
304 return 0;
305 }
306 /* we know server signature matches from comparison on whole challenge above */
307 (void) snprintf(encoded_challenge, sizeof(encoded_challenge), "%.*s",
308 (int)(matches[2].rm_eo - matches[2].rm_so), &verified[(int)matches[2].rm_so]);
309 (void) b64decode(&verified[(int)matches[2].rm_so],
310 (const unsigned)(matches[2].rm_eo - matches[2].rm_so),
311 raw_challenge, sizeof(raw_challenge));
312 if (!fill_identity(id, response, raw_challenge)) {
313 (void) fprintf(stderr, "paa_check: identity problems\n");
314 return 0;
315
316 }
317 /* check realm info in authentication header matches signed realm */
318 (void) regcomp(&realm_regex, "realm=\"([^\"]+)\"", REG_EXTENDED);
319 if (regexec(&realm_regex, response, 10, matches, 0) != 0) {
320 (void) fprintf(stderr, "paa_check: no realm found\n");
321 return 0;
322 }
323 (void) snprintf(realm, sizeof(realm), "%.*s",
324 (int)(matches[1].rm_eo - matches[1].rm_so),
325 &response[(int)matches[1].rm_so]);
326 if (strcmp(id->realm, realm) != 0) {
327 (void) fprintf(stderr, "paa_check: realm mismatch: signed realm '%s' vs '%s'\n",
328 id->realm, realm);
329 return 0;
330 }
331 /* check timestamp is within bounds */
332 t = time(NULL);
333 if (id->timestamp < t - (3 * 60)) {
334 (void) fprintf(stderr, "paa_check: timestamp check: %lld seconds ago\n",
335 t - id->timestamp);
336 return 0;
337 }
338 if (id->timestamp > t + (3 * 60)) {
339 (void) fprintf(stderr, "paa_check: timestamp check: %lld seconds in future\n",
340 id->timestamp - t);
341 return 0;
342 }
343 return 1;
344 }
345
346 /* print identity details on a stream */
347 int
paa_print_identity(FILE * fp,paa_identity_t * id)348 paa_print_identity(FILE *fp, paa_identity_t *id)
349 {
350 (void) fprintf(fp, "\tuserid\t%s\n\tclient\t%s\n\trealm\t%s\n\ttime\t%.24s\n",
351 id->userid,
352 id->client,
353 id->realm,
354 ctime(&id->timestamp));
355 return 1;
356 }
357
358 /* utility function to write a string to a file */
359 int
paa_write_file(const char * f,char * s,unsigned cc)360 paa_write_file(const char *f, char *s, unsigned cc)
361 {
362 FILE *fp;
363
364 if ((fp = fopen(f, "w")) == NULL) {
365 (void) fprintf(stderr, "can't write file '%s'\n", f);
366 return 0;
367 }
368 write(fileno(fp), s, cc);
369 (void) fclose(fp);
370 return 1;
371 }
372
373 /* utility function to read a string from a file */
374 int
paa_read_file(const char * f,char * s,size_t size)375 paa_read_file(const char *f, char *s, size_t size)
376 {
377 FILE *fp;
378 int cc;
379
380 if ((fp = fopen(f, "r")) == NULL) {
381 (void) fprintf(stderr, "can't write '%s'\n", f);
382 return 0;
383 }
384 cc = read(fileno(fp), s, size);
385 (void) fclose(fp);
386 return cc;
387 }
388