/*- * Copyright (c) 2017, 2018 Taylor R. Campbell * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include "crypto/crypto_sign_ed25519.h" #include "crypto/randombytes.h" #include "crypto/sha3/sha3.h" #include "attribute.h" #include "base32enc.h" #include "base64enc.h" #include "explicit_memset.h" #include "progname.h" #include "strlcpy.h" /* Forward declarations. */ static void ignoresignal(int); static void parseargs(int, char **); static void usage(void) attr_noreturn; static void generate_rsa1024(void); static void generate_ed25519_v3(void); static void writefile(const char *, const char *, mode_t); static void wipe(void); const char default_keytype[] = "BEST"; const char default_keytype_v2[] = "RSA1024"; const char default_keytype_v3[] = "ED25519-V3"; /* Parameters. */ const char * keytype; const char * path_onionsecret; const char * path_onion; /* State. */ uint8_t buf[1024]; char onionsecret[1024]; char onion[64 + sizeof(".onion")]; /* max dns label + .onion + 0 */ /* * Main. */ int main(int argc, char **argv) { setprogname(argv[0]); if (signal(SIGPIPE, &ignoresignal) == SIG_ERR) err(1, "ignore SIGPIPE"); parseargs(argc, argv); if (strcmp(keytype, "BEST") == 0 || strcmp(keytype, "RSA1024") == 0) generate_rsa1024(); else if (strcmp(keytype, "ED25519-V3") == 0) generate_ed25519_v3(); else errx(1, "unsupported key type"); writefile(onionsecret, path_onionsecret, 0600); writefile(onion, path_onion, 0644); wipe(); return 0; } /* * ignoresignal(signo) * * Do nothing. We use this rather than setting SIG_IGN to avoid * perturbing the child's environment -- SIG_IGN is inherited but * a signal handler is reset to the default action. */ static void ignoresignal(int signo attr_unused) { } /* * parseargs(argc, argv) * * Parse command-line arguments argc/argv. */ static void parseargs(int argc, char **argv) { extern char *optarg; extern int optind; int ch; while ((ch = getopt(argc, argv, "t:V:")) != -1) { switch (ch) { case 't': if (strcmp(optarg, "BEST") != 0 && strcmp(optarg, "RSA1024") != 0 && strcmp(optarg, "ED25519-V3") != 0) warnx("unknown key type"); keytype = optarg; break; case 'V': if (keytype != NULL) { warnx("key type overridden, ignoring version"); break; } if (strcmp(optarg, "2") == 0) keytype = default_keytype_v2; else if (strcmp(optarg, "3") == 0) keytype = default_keytype_v3; else errx(1, "unknown onion version"); break; case '?': default: usage(); } } argc -= optind; argv += optind; if (argc != 2) usage(); argc--, path_onionsecret = *argv++; argc--, path_onion = *argv++; assert(argc == 0); assert(*argv == NULL); /* Apply default values. */ if (keytype == NULL) keytype = default_keytype; } /* * usage() * * Print usage message and exit. */ static void attr_noreturn usage(void) { fprintf(stderr, "Usage: %s", getprogname()); fprintf(stderr, " \n"); exit(1); } /* * generate_rsa1024() * * Generate an RSA1024 key and format the onion secret and .onion * address. */ static void generate_rsa1024(void) { RSA *key; BIGNUM *e; uint8_t *derp; int ret; unsigned nder; uint8_t sha1[20]; size_t i; /* Set e := 65537. */ if ((e = BN_new()) == NULL) errx(1, "BN_new"); if (!BN_set_word(e, 65537)) errx(1, "BN_set_word"); /* Generate an RSA key pair with 1024-bit modulus and e = 65537. */ if ((key = RSA_new()) == NULL) errx(1, "RSA_new"); if (!RSA_generate_key_ex(key, 1024, e, NULL)) errx(1, "generate key"); /* Encode the private key in ASN.1 DER. */ if ((unsigned)i2d_RSAPrivateKey(key, NULL) > sizeof buf) errx(1, "private key encoding too large"); derp = buf; ret = i2d_RSAPrivateKey(key, &derp); assert(0 < ret); nder = (unsigned)ret; assert(nder <= sizeof buf); assert(derp == &buf[nder]); /* Format it as `RSA1024:', NUL-terminated. */ i = strlcpy(onionsecret, "RSA1024:", sizeof onionsecret); assert(i <= sizeof onionsecret); assert(base64enc_size(nder) + 1 <= sizeof(onionsecret) - i); base64enc(onionsecret + i, sizeof(onionsecret) - i, buf, nder); i += base64enc_size(nder); assert(1 <= sizeof(onionsecret) - i); onionsecret[i++] = '\0'; /* Encode the public key in ASN.1 DER. */ if ((unsigned)i2d_RSAPublicKey(key, NULL) > sizeof buf) errx(1, "public key encoding too large"); derp = buf; ret = i2d_RSAPublicKey(key, &derp); assert(0 < ret); nder = (unsigned)ret; assert(nder <= sizeof buf); assert(derp == &buf[nder]); /* Encode it as `.onion'. */ (void)SHA1(buf, nder, sha1); i = 0; assert(base32enc_size(10) + strlen(".onion") + 1 <= sizeof(onion) - i); base32enc(onion, sizeof onion, sha1, 10); i += base32enc_size(10); assert(strlen(".onion") + 1 <= sizeof(onion) - i); i += strlcpy(onion + i, ".onion", sizeof(onion) - i); assert(i < sizeof(onion)); /* Wipe. */ RSA_free(key); BN_free(e); explicit_memset(buf, 0, sizeof buf); explicit_memset(sha1, 0, sizeof sha1); } /* * generate_ed25519_v3(void) * * Generate an ED25519-V3 key and format the onion secret and * .onion address. */ static void generate_ed25519_v3(void) { uint8_t seed[32], sk[64], pk[32], h[32], raw[35]; const uint8_t prefix[] = ".onion checksum"; const uint8_t version = '\003'; SHA3_256_CTX ctx; size_t i; /* Generate a 32-byte seed. */ randombytes(seed, sizeof seed); /* Generate a key pair from the 32-byte seed. */ if (crypto_sign_ed25519_keypair_seed(pk, sk, seed) != 0) errx(1, "generate key"); /* Format the secret key as `ED25519-V3:', NUL-terminated. */ i = strlcpy(onionsecret, "ED25519-V3:", sizeof onionsecret); assert(i <= sizeof onionsecret); assert(base64enc_size(sizeof sk) + 1 <= sizeof(onionsecret) - i); base64enc(onionsecret + i, sizeof(onionsecret) - i, sk, sizeof sk); i += base64enc_size(sizeof sk); assert(1 <= sizeof(onionsecret) - 1); onionsecret[i++] = '\0'; /* Compute the checksum. */ SHA3_256_Init(&ctx); SHA3_256_Update(&ctx, prefix, 15); SHA3_256_Update(&ctx, pk, 32); SHA3_256_Update(&ctx, &version, 1); SHA3_256_Final(h, &ctx); /* Concatenate the public key, checksum, and version. */ memcpy(raw, pk, 32); memcpy(raw + 32, h, 2); memcpy(raw + 34, &version, 1); /* Encode it all and stick `.onion' on the end. */ i = 0; assert(base32enc_size(35) + strlen(".onion") + 1 <= sizeof(onion) - i); base32enc(onion, sizeof onion, raw, 35); i += base32enc_size(35); assert(strlen(".onion") + 1 <= sizeof(onion) - i); i += strlcpy(onion + i, ".onion", sizeof(onion) - i); assert(i <= sizeof(onion)); /* Wipe. */ explicit_memset(seed, 0, sizeof seed); explicit_memset(sk, 0, sizeof sk); explicit_memset(pk, 0, sizeof pk); explicit_memset(h, 0, sizeof h); explicit_memset(raw, 0, sizeof raw); } /* * writefile(s, path) * * Write the NUL-terminated string s, followed by a newline (but * excluding the NUL terminator), to the file path. */ static void writefile(const char *s, const char *path, mode_t perms) { struct iovec iov[2] = { { .iov_base = UNCONST(s), .iov_len = strlen(s) }, { .iov_base = "\n", .iov_len = 1 }, }; int fd; ssize_t nwrit; if ((fd = open(path, O_WRONLY|O_CREAT|O_EXCL, perms)) == -1) err(1, "open"); if ((nwrit = writev(fd, iov, 2)) == -1) err(1, "write"); if ((size_t)nwrit != strlen(s) + 1) err(1, "bad write"); if (close(fd) == -1) warn("close"); } /* * wipe() * * Wipe temporary data. */ static void wipe(void) { explicit_memset(buf, 0, sizeof buf); explicit_memset(onionsecret, 0, sizeof onionsecret); explicit_memset(onion, 0, sizeof onion); }