/*- * Copyright (c) 2010 Alistair Crooks * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libpaa.h" #include "b64.h" enum { MAX_DIGEST_SIZE = 128 }; /* create an area of random memory */ static int randomise(char *s, size_t size) { uint32_t r; size_t i; for (i = 0 ; i < size ; i += sizeof(r)) { r = random(); (void) memcpy(&s[i], &r, sizeof(r)); } return i; } /* generate a challenge */ static int genchallenge(paa_challenge_t *challenge, paa_server_info_t *server) { time_t t; char digest[MAX_DIGEST_SIZE]; char raw[PAA_CHALLENGE_SIZE * 2]; int cc; t = time(NULL); cc = snprintf(raw, sizeof(raw), "%s;%s;%lld;", challenge->realm, server->hostaddress, (int64_t)t); cc += randomise(&raw[cc], 64); /* 64 is arbitrary */ /* raw now has the raw-challenge in it */ challenge->encc = b64encode(raw, (const unsigned)cc, challenge->encoded_challenge, sizeof(challenge->encoded_challenge), 0); cc += snprintf(&raw[cc], sizeof(raw) - cc, ";%.*s", server->secretc, server->secret); (void) SHA1Data((uint8_t *)raw, (unsigned)cc, digest); server->server_signaturec = b64encode(digest, (const unsigned)strlen(digest), server->server_signature, sizeof(server->server_signature), (int)0); /* raw has raw-challenge ; server-secret-value, i.e. raw-server-signature */ challenge->challengec = snprintf(challenge->challenge, sizeof(challenge->challenge), "%.*s;%.*s", server->server_signaturec, server->server_signature, challenge->encc, challenge->encoded_challenge); return challenge->challengec; } /* fill in the identity information in the response */ static int fill_identity(paa_identity_t *id, char *response, char *raw_challenge) { regmatch_t matches[10]; regex_t response_re; regex_t id_re; char t[32]; /* id="userid" */ (void) regcomp(&id_re, "id=\"([^\"]+)\"", REG_EXTENDED); if (regexec(&id_re, response, 10, matches, 0) != 0) { (void) fprintf(stderr, "No identity information found\n"); return 0; } (void) snprintf(id->userid, sizeof(id->userid), "%.*s", (int)(matches[1].rm_eo - matches[1].rm_so), &response[(int)matches[1].rm_so]); /* realm;ip;timestamp;seed */ (void) regcomp(&response_re, "([^;]+);([^;]+);([^;]+);(.*)", REG_EXTENDED); if (regexec(&response_re, raw_challenge, 10, matches, 0) != 0) { (void) fprintf(stderr, "No identity information found\n"); return 0; } (void) snprintf(id->realm, sizeof(id->realm), "%.*s", (int)(matches[1].rm_eo - matches[1].rm_so), &raw_challenge[(int)matches[1].rm_so]); (void) snprintf(id->client, sizeof(id->client), "%.*s", (int)(matches[2].rm_eo - matches[2].rm_so), &raw_challenge[(int)matches[2].rm_so]); (void) snprintf(t, sizeof(t), "%.*s", (int)(matches[3].rm_eo - matches[3].rm_so), &raw_challenge[(int)matches[3].rm_so]); id->timestamp = strtoll(t, NULL, 10); return 1; } /***************************************************************************/ /* exported functions start here */ /***************************************************************************/ /* initialise the server info */ int paa_server_init(paa_server_info_t *server, unsigned secretsize) { struct sockaddr_in6 *sin6; struct sockaddr_in *sin; struct ifaddrs *addrs; char host[512]; if (getifaddrs(&addrs) < 0) { (void) fprintf(stderr, "can't getifaddrs\n"); return 0; } for ( ; addrs ; addrs = addrs->ifa_next) { if (addrs->ifa_addr->sa_family == AF_INET) { sin = (struct sockaddr_in *)(void *)addrs->ifa_addr; (void) snprintf(server->hostaddress, sizeof(server->hostaddress), "%s", inet_ntoa(sin->sin_addr)); break; } if (addrs->ifa_addr->sa_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)(void *)addrs->ifa_addr; (void) getnameinfo((const struct sockaddr *)(void *)sin6, (unsigned)sin6->sin6_len, server->hostaddress, sizeof(server->hostaddress), NULL, 0, NI_NUMERICHOST); break; } } if (addrs == NULL) { if (gethostname(host, sizeof(host)) < 0) { (void) fprintf(stderr, "can't get hostname\n"); return 0; } (void) snprintf(server->hostaddress, sizeof(server->hostaddress), "%s", host); } if ((server->secret = calloc(1, server->secretc = secretsize)) == NULL) { (void) fprintf(stderr, "can't allocate server secret\n"); return 0; } server->secretc = randomise(server->secret, secretsize); return 1; } /* challenge = "PubKey.v1" pubkey-challenge pubkey-challenge = 1#( realm | [domain] | challenge ) realm = "realm" "=" quoted-string domain = "domain" "=" <"> URI ( 1*SP URI ) <"> URI = absoluteURI | abs_path challenge = "challenge" "=" quoted-string */ /* called from server to send the challenge */ int paa_format_challenge(paa_challenge_t *challenge, paa_server_info_t *server, char *buf, size_t size) { int cc; if (challenge->realm == NULL) { (void) fprintf(stderr, "paa_format_challenge: no realm information\n"); return 0; } cc = snprintf(buf, size, "401 Unauthorized\r\nWWW-Authenticate: PubKey.v1\r\n"); (void) genchallenge(challenge, server); cc += snprintf(&buf[cc], size - cc, " challenge=\"%s\"", challenge->challenge); if (challenge->realm) { cc += snprintf(&buf[cc], size - cc, ",\r\n realm=\"%s\"", challenge->realm); } if (challenge->domain) { cc += snprintf(&buf[cc], size - cc, ",\r\n domain=\"%s\"", challenge->domain); } cc += snprintf(&buf[cc], size - cc, "\r\n"); return cc; } /* credentials = "PubKey.v1" privkey-credentials privkey-credentials = 1#( identifier | realm | challenge | signature ) identifier = "id" "=" identifier-value identifier-value = quoted-string challenge = "challenge" "=" challenge-value challenge-value = quoted-string signature = "signature" "=" signature-value signature-value = quoted-string */ /* called from client to respond to the challenge */ int paa_format_response(paa_response_t *response, netpgp_t *netpgp, char *in, char *out, size_t outsize) { regmatch_t matches[10]; regex_t r; char challenge[2048 * 2]; char base64_signature[2048 * 2]; char sig[2048]; int challengec; int sig64c; int sigc; int outc; if (response->realm == NULL) { (void) fprintf(stderr, "paa_format_response: no realm information\n"); return 0; } (void) regcomp(&r, "challenge=\"([^\"]+)\"", REG_EXTENDED); if (regexec(&r, in, 10, matches, 0) != 0) { (void) fprintf(stderr, "no signature found\n"); return 0; } challengec = snprintf(challenge, sizeof(challenge), "%.*s", (int)(matches[1].rm_eo - matches[1].rm_so), &in[(int)matches[1].rm_so]); /* read challenge string */ outc = snprintf(out, outsize, "Authorization: PubKey.v1\r\n"); response->userid = netpgp_getvar(netpgp, "userid"); outc += snprintf(&out[outc], outsize - outc, " id=\"%s\"", response->userid); outc += snprintf(&out[outc], outsize - outc, ",\r\n challenge=\"%s\"", challenge); outc += snprintf(&out[outc], outsize - outc, ",\r\n realm=\"%s\"", response->realm); /* set up response */ (void) memset(sig, 0x0, sizeof(sig)); (void) snprintf(sig, sizeof(sig), "%s;%s;%s;", response->userid, response->realm, challenge); sigc = netpgp_sign_memory(netpgp, response->userid, challenge, (unsigned)challengec, sig, sizeof(sig), 0, 0); sig64c = b64encode(sig, (const unsigned)sigc, base64_signature, sizeof(base64_signature), (int)0); outc += snprintf(&out[outc], outsize - outc, ",\r\n signature=\"%.*s\"", sig64c, base64_signature); return outc; } /* called from server to check the response to the challenge */ int paa_check_response(paa_challenge_t *challenge, paa_identity_t *id, netpgp_t *netpgp, char *response) { regmatch_t matches[10]; regex_t challenge_regex; regex_t signature_regex; regex_t realm_regex; time_t t; char encoded_challenge[512]; char raw_challenge[512]; char verified[2048]; char realm[128]; char buf[2048]; int bufc; /* grab the signed text from the response */ (void) regcomp(&signature_regex, "signature=\"([^\"]+)\"", REG_EXTENDED); if (regexec(&signature_regex, response, 10, matches, 0) != 0) { (void) fprintf(stderr, "paa_check: no signature found\n"); return 0; } /* atob the signature itself */ bufc = b64decode(&response[(int)matches[1].rm_so], (size_t)(matches[1].rm_eo - matches[1].rm_so), buf, sizeof(buf)); /* verify the signature */ (void) memset(verified, 0x0, sizeof(verified)); if (netpgp_verify_memory(netpgp, buf, (const unsigned)bufc, verified, sizeof(verified), 0) <= 0) { (void) fprintf(stderr, "paa_check: signature cannot be verified\n"); return 0; } /* we check the complete signed text against our challenge */ if (strcmp(challenge->challenge, verified) != 0) { (void) fprintf(stderr, "paa_check: signature does not match\n"); return 0; } (void) regcomp(&challenge_regex, "^([^;]+);(.+)", REG_EXTENDED); if (regexec(&challenge_regex, verified, 10, matches, 0) != 0) { (void) fprintf(stderr, "paa_check: no 2 parts to challenge\n"); return 0; } /* we know server signature matches from comparison on whole challenge above */ (void) snprintf(encoded_challenge, sizeof(encoded_challenge), "%.*s", (int)(matches[2].rm_eo - matches[2].rm_so), &verified[(int)matches[2].rm_so]); (void) b64decode(&verified[(int)matches[2].rm_so], (const unsigned)(matches[2].rm_eo - matches[2].rm_so), raw_challenge, sizeof(raw_challenge)); if (!fill_identity(id, response, raw_challenge)) { (void) fprintf(stderr, "paa_check: identity problems\n"); return 0; } /* check realm info in authentication header matches signed realm */ (void) regcomp(&realm_regex, "realm=\"([^\"]+)\"", REG_EXTENDED); if (regexec(&realm_regex, response, 10, matches, 0) != 0) { (void) fprintf(stderr, "paa_check: no realm found\n"); return 0; } (void) snprintf(realm, sizeof(realm), "%.*s", (int)(matches[1].rm_eo - matches[1].rm_so), &response[(int)matches[1].rm_so]); if (strcmp(id->realm, realm) != 0) { (void) fprintf(stderr, "paa_check: realm mismatch: signed realm '%s' vs '%s'\n", id->realm, realm); return 0; } /* check timestamp is within bounds */ t = time(NULL); if (id->timestamp < t - (3 * 60)) { (void) fprintf(stderr, "paa_check: timestamp check: %lld seconds ago\n", t - id->timestamp); return 0; } if (id->timestamp > t + (3 * 60)) { (void) fprintf(stderr, "paa_check: timestamp check: %lld seconds in future\n", id->timestamp - t); return 0; } return 1; } /* print identity details on a stream */ int paa_print_identity(FILE *fp, paa_identity_t *id) { (void) fprintf(fp, "\tuserid\t%s\n\tclient\t%s\n\trealm\t%s\n\ttime\t%.24s\n", id->userid, id->client, id->realm, ctime(&id->timestamp)); return 1; } /* utility function to write a string to a file */ int paa_write_file(const char *f, char *s, unsigned cc) { FILE *fp; if ((fp = fopen(f, "w")) == NULL) { (void) fprintf(stderr, "can't write file '%s'\n", f); return 0; } write(fileno(fp), s, cc); (void) fclose(fp); return 1; } /* utility function to read a string from a file */ int paa_read_file(const char *f, char *s, size_t size) { FILE *fp; int cc; if ((fp = fopen(f, "r")) == NULL) { (void) fprintf(stderr, "can't write '%s'\n", f); return 0; } cc = read(fileno(fp), s, size); (void) fclose(fp); return cc; }