blob: 5c5c04726e6dbce25194232562f10fa41ef9586e [file] [log] [blame]
/*
* Copyright (c) 2018 Fastly, Kazuho Oku
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <assert.h>
#include <getopt.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#ifdef _WINDOWS
#include "..\picotls\wincompat.h"
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#pragma warning(disable : 4996)
#else
#include <strings.h>
#endif
#include <time.h>
#include <openssl/err.h>
#include <openssl/engine.h>
#include <openssl/pem.h>
#include "picotls.h"
#include "picotls/pembase64.h"
#include "picotls/openssl.h"
static int emit_esni(ptls_key_exchange_context_t **key_exchanges, ptls_cipher_suite_t **cipher_suites, uint16_t padded_length,
uint64_t not_before, uint64_t lifetime, char const *published_sni, char const *file_output)
{
ptls_buffer_t buf;
ptls_key_exchange_context_t *ctx[256] = {NULL};
int ret;
ptls_buffer_init(&buf, "", 0);
ptls_buffer_push16(&buf, PTLS_ESNI_VERSION_DRAFT03);
ptls_buffer_push(&buf, 0, 0, 0, 0); /* checksum, filled later */
if (published_sni != NULL) {
ptls_buffer_push_block(&buf, 2, { ptls_buffer_pushv(&buf, published_sni, strlen(published_sni)); });
} else {
ptls_buffer_push16(&buf, 0);
}
ptls_buffer_push_block(&buf, 2, {
size_t i;
for (i = 0; key_exchanges[i] != NULL; ++i) {
ptls_buffer_push16(&buf, key_exchanges[i]->algo->id);
ptls_buffer_push_block(&buf, 2,
{ ptls_buffer_pushv(&buf, key_exchanges[i]->pubkey.base, key_exchanges[i]->pubkey.len); });
}
});
ptls_buffer_push_block(&buf, 2, {
size_t i;
for (i = 0; cipher_suites[i] != NULL; ++i)
ptls_buffer_push16(&buf, cipher_suites[i]->id);
});
ptls_buffer_push16(&buf, padded_length);
ptls_buffer_push64(&buf, not_before);
ptls_buffer_push64(&buf, not_before + lifetime - 1);
ptls_buffer_push_block(&buf, 2, {});
{ /* fill checksum */
uint8_t d[PTLS_SHA256_DIGEST_SIZE];
ptls_calc_hash(&ptls_openssl_sha256, d, buf.base, buf.off);
memcpy(buf.base + 2, d, 4);
}
if (file_output != NULL) {
FILE *fo = fopen(file_output, "wb");
if (fo == NULL) {
fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno));
goto Exit;
} else {
fwrite(buf.base, 1, buf.off, fo);
fclose(fo);
}
} else {
/* emit the structure to stdout */
fwrite(buf.base, 1, buf.off, stdout);
fflush(stdout);
}
ret = 0;
Exit : {
size_t i;
for (i = 0; ctx[i] != NULL; ++i)
ctx[i]->on_exchange(ctx + i, 1, NULL, ptls_iovec_init(NULL, 0));
}
ptls_buffer_dispose(&buf);
return ret;
}
static void usage(const char *cmd, int status)
{
printf("picotls-esni - generates an ESNI Resource Record\n"
"\n"
"Usage: %s [options]\n"
"Options:\n"
" -n <published-sni> published sni value\n"
" -K <key-file> private key files (repeat the option to include multiple\n"
" keys)\n"
" -c <cipher-suite> aes128-gcm, chacha20-poly1305, ...\n"
" -d <days> number of days until expiration (default: 90)\n"
" -p <padded-length> padded length (default: 260)\n"
" -o <output-file> write output to specified file instead of stdout\n"
" (use on Windows as stdout is not binary there)\n"
" -h prints this help\n"
"\n"
"-c and -x can be used multiple times.\n"
"\n",
cmd);
exit(status);
}
int main(int argc, char **argv)
{
char const *published_sni = NULL;
char const *file_output = NULL;
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
#if !defined(OPENSSL_NO_ENGINE)
/* Load all compiled-in ENGINEs */
ENGINE_load_builtin_engines();
ENGINE_register_all_ciphers();
ENGINE_register_all_digests();
#endif
struct {
ptls_key_exchange_context_t *elements[256];
size_t count;
} key_exchanges = {{NULL}, 0};
struct {
ptls_cipher_suite_t *elements[256];
size_t count;
} cipher_suites = {{NULL}, 0};
uint16_t padded_length = 260;
uint64_t lifetime = 90 * 86400;
int ch;
while ((ch = getopt(argc, argv, "n:K:c:d:p:o:h")) != -1) {
switch (ch) {
case 'n':
published_sni = optarg;
break;
case 'K': {
FILE *fp;
EVP_PKEY *pkey;
if ((fp = fopen(optarg, "rt")) == NULL) {
fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno));
exit(1);
}
if ((pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) {
fprintf(stderr, "failed to read private key from file:%s:%s\n", optarg, strerror(errno));
exit(1);
}
fclose(fp);
if (ptls_openssl_create_key_exchange(key_exchanges.elements + key_exchanges.count++, pkey) != 0) {
fprintf(stderr, "unknown type of private key found in file:%s\n", optarg);
exit(1);
}
EVP_PKEY_free(pkey);
} break;
case 'c': {
size_t i;
for (i = 0; ptls_openssl_cipher_suites[i] != NULL; ++i)
if (strcasecmp(ptls_openssl_cipher_suites[i]->aead->name, optarg) == 0)
break;
if (ptls_openssl_cipher_suites[i] == NULL) {
fprintf(stderr, "unknown cipher-suite: %s\n", optarg);
exit(1);
}
cipher_suites.elements[cipher_suites.count++] = ptls_openssl_cipher_suites[i];
} break;
case 'd':
if (sscanf(optarg, "%" SCNu64, &lifetime) != 1 || lifetime == 0) {
fprintf(stderr, "lifetime must be a positive integer\n");
exit(1);
}
lifetime *= 86400; /* convert to seconds */
break;
case 'p':
#ifdef _WINDOWS
if (sscanf_s(optarg, "%" SCNu16, &padded_length) != 1 || padded_length == 0) {
fprintf(stderr, "padded length must be a positive integer\n");
exit(1);
}
#else
if (sscanf(optarg, "%" SCNu16, &padded_length) != 1 || padded_length == 0) {
fprintf(stderr, "padded length must be a positive integer\n");
exit(1);
}
#endif
break;
case 'o':
file_output = optarg;
break;
case 'h':
usage(argv[0], 0);
break;
default:
usage(argv[0], 1);
break;
}
}
if (cipher_suites.count == 0)
cipher_suites.elements[cipher_suites.count++] = &ptls_openssl_aes128gcmsha256;
if (key_exchanges.count == 0) {
fprintf(stderr, "no private key specified\n");
exit(1);
}
argc -= optind;
argv += optind;
if (emit_esni(key_exchanges.elements, cipher_suites.elements, padded_length, time(NULL), lifetime, published_sni,
file_output) != 0) {
fprintf(stderr, "failed to generate ESNI private structure.\n");
exit(1);
}
return 0;
}