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 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 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 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 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 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 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 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 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 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 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