From 012b2cb9a4329a7e739f91fa4ce48288df6ea705 Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Thu, 2 Aug 2018 18:38:16 -0700 Subject: [PATCH 1/3] cli: split into openssl and minicrypto versions --- CMakeLists.txt | 6 +- README.md | 8 +- t/cli-minicrypto.c | 422 +++++++++++++++++++++++++++++++++++++ t/{cli.c => cli-openssl.c} | 0 t/util.h | 13 ++ 5 files changed, 443 insertions(+), 6 deletions(-) create mode 100644 t/cli-minicrypto.c rename t/{cli.c => cli-openssl.c} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c955782f..eb7a57889 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ SET(MINICRYPTO_LIBRARY_FILES ADD_LIBRARY(picotls-core lib/picotls.c lib/pembase64.c) ADD_LIBRARY(picotls-minicrypto ${MINICRYPTO_LIBRARY_FILES} lib/cifra.c lib/minicrypto-pem.c lib/uecc.c lib/asn1.c) +ADD_EXECUTABLE(cli-minicrypto t/cli-minicrypto.c lib/pembase64.c) +TARGET_LINK_LIBRARIES(cli-minicrypto picotls-minicrypto picotls-core) ADD_EXECUTABLE(test-minicrypto.t ${MINICRYPTO_LIBRARY_FILES} deps/picotest/picotest.c t/picotls.c t/minicrypto.c lib/asn1.c lib/pembase64.c) SET(TEST_EXES test-minicrypto.t) @@ -34,8 +36,8 @@ IF (OPENSSL_FOUND AND NOT (OPENSSL_VERSION VERSION_LESS "1.0.1")) MESSAGE(WARNING "Enabling OpenSSL support") INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) ADD_LIBRARY(picotls-openssl lib/openssl.c) - ADD_EXECUTABLE(cli t/cli.c lib/pembase64.c) - TARGET_LINK_LIBRARIES(cli picotls-openssl picotls-core ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) + ADD_EXECUTABLE(cli-openssl t/cli-openssl.c lib/pembase64.c) + TARGET_LINK_LIBRARIES(cli-openssl picotls-openssl picotls-core ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) ADD_EXECUTABLE(test-openssl.t ${MINICRYPTO_LIBRARY_FILES} lib/cifra.c lib/uecc.c lib/asn1.c lib/pembase64.c deps/picotest/picotest.c t/picotls.c t/openssl.c) SET_TARGET_PROPERTIES(test-openssl.t PROPERTIES COMPILE_FLAGS "-DPTLS_MEMORY_DEBUG=1") TARGET_LINK_LIBRARIES(test-openssl.t ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/README.md b/README.md index 12f587245..32cc4f264 100644 --- a/README.md +++ b/README.md @@ -49,17 +49,17 @@ Using the cli command Run the test server (at 127.0.0.1:8443): ``` -% ./cli -c /path/to/certificate.pem -k /path/to/private-key.pem 127.0.0.1 8443 +% ./cli-openssl -c /path/to/certificate.pem -k /path/to/private-key.pem 127.0.0.1 8443 ``` Connect to the test server: ``` -% ./cli 127.0.0.1 8443 +% ./cli-openssl 127.0.0.1 8443 ``` Using resumption: ``` -% ./cli -s session-file 127.0.0.1 8443 +% ./cli-openssl -s session-file 127.0.0.1 8443 ``` The session-file is read-write. The cli server implements a single-entry session cache. @@ -67,7 +67,7 @@ The cli server sends NewSessionTicket when it first sends application data after Using early-data: ``` -% ./cli -s session-file -e 127.0.0.1 8443 +% ./cli-openssl -s session-file -e 127.0.0.1 8443 ``` When `-e` option is used, client first waits for user input, and then sends CLIENT_HELLO along with the early-data. diff --git a/t/cli-minicrypto.c b/t/cli-minicrypto.c new file mode 100644 index 000000000..3dad1effe --- /dev/null +++ b/t/cli-minicrypto.c @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2016 DeNA Co., Ltd., 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. + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ +#endif + +#define USE_MINICRYPTO + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "picotls.h" +#include "picotls/minicrypto.h" +#include "util.h" + +static void shift_buffer(ptls_buffer_t *buf, size_t delta) +{ + if (delta != 0) { + assert(delta <= buf->off); + if (delta != buf->off) + memmove(buf->base, buf->base + delta, buf->off - delta); + buf->off -= delta; + } +} + +static int handle_connection(int sockfd, ptls_context_t *ctx, const char *server_name, const char *input_file, + ptls_handshake_properties_t *hsprop) +{ + ptls_t *tls = ptls_new(ctx, server_name == NULL); + ptls_buffer_t rbuf, encbuf, ptbuf; + char bytebuf[16384]; + enum { IN_HANDSHAKE, IN_1RTT, IN_SHUTDOWN } state = IN_HANDSHAKE; + int inputfd = 0, ret = 0; + size_t early_bytes_sent = 0; + ssize_t ioret; + + ptls_buffer_init(&rbuf, "", 0); + ptls_buffer_init(&encbuf, "", 0); + ptls_buffer_init(&ptbuf, "", 0); + + fcntl(sockfd, F_SETFL, O_NONBLOCK); + + if (input_file != NULL) { + if ((inputfd = open(input_file, O_RDONLY)) == -1) { + fprintf(stderr, "failed to open file:%s:%s\n", input_file, strerror(errno)); + ret = 1; + goto Exit; + } + } + if (server_name != NULL) { + ptls_set_server_name(tls, server_name, 0); + if ((ret = ptls_handshake(tls, &encbuf, NULL, NULL, hsprop)) != PTLS_ERROR_IN_PROGRESS) { + fprintf(stderr, "ptls_handshake:%d\n", ret); + ret = 1; + goto Exit; + } + } + + while (1) { + /* check if data is available */ + fd_set readfds, writefds, exceptfds; + int maxfd = 0; + struct timeval timeout; + do { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + FD_SET(sockfd, &readfds); + if (encbuf.off != 0) + FD_SET(sockfd, &writefds); + FD_SET(sockfd, &exceptfds); + maxfd = sockfd + 1; + if (inputfd != -1) { + FD_SET(inputfd, &readfds); + FD_SET(inputfd, &exceptfds); + if (maxfd <= inputfd) + maxfd = inputfd + 1; + } + timeout.tv_sec = encbuf.off != 0 ? 0 : 3600; + timeout.tv_usec = 0; + } while (select(maxfd, &readfds, &writefds, &exceptfds, &timeout) == -1); + + /* consume incoming messages */ + if (FD_ISSET(sockfd, &readfds) || FD_ISSET(sockfd, &exceptfds)) { + size_t off = 0, leftlen; + while ((ioret = read(sockfd, bytebuf, sizeof(bytebuf))) == -1 && errno == EINTR) + ; + if (ioret == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) { + /* no data */ + ioret = 0; + } else if (ioret <= 0) { + goto Exit; + } + while ((leftlen = ioret - off) != 0) { + if (state == IN_HANDSHAKE) { + if ((ret = ptls_handshake(tls, &encbuf, bytebuf + off, &leftlen, hsprop)) == 0) { + state = IN_1RTT; + /* release data sent as early-data, if server accepted it */ + if (hsprop->client.early_data_accepted_by_peer) + shift_buffer(&ptbuf, early_bytes_sent); + if (ptbuf.off != 0) { + if ((ret = ptls_send(tls, &encbuf, ptbuf.base, ptbuf.off)) != 0) { + fprintf(stderr, "ptls_send(1rtt):%d\n", ret); + goto Exit; + } + ptbuf.off = 0; + } + } else if (ret == PTLS_ERROR_IN_PROGRESS) { + /* ok */ + } else { + if (encbuf.off != 0) + (void)write(sockfd, encbuf.base, encbuf.off); + fprintf(stderr, "ptls_handshake:%d\n", ret); + goto Exit; + } + } else { + if ((ret = ptls_receive(tls, &rbuf, bytebuf + off, &leftlen)) == 0) { + if (rbuf.off != 0) { + write(1, rbuf.base, rbuf.off); + rbuf.off = 0; + } + } else if (ret == PTLS_ERROR_IN_PROGRESS) { + /* ok */ + } else { + fprintf(stderr, "ptls_receive:%d\n", ret); + goto Exit; + } + } + off += leftlen; + } + } + + /* read input (and send if possible) */ + if (inputfd != -1 && (FD_ISSET(inputfd, &readfds) || FD_ISSET(inputfd, &exceptfds))) { + while ((ioret = read(inputfd, bytebuf, sizeof(bytebuf))) == -1 && errno == EINTR) + ; + if (ioret > 0) { + ptls_buffer_pushv(&ptbuf, bytebuf, ioret); + if (state == IN_HANDSHAKE) { + size_t send_amount = 0; + if (hsprop->client.max_early_data_size != NULL) { + size_t max_can_be_sent = *hsprop->client.max_early_data_size; + if (max_can_be_sent > ptbuf.off) + max_can_be_sent = ptbuf.off; + send_amount = max_can_be_sent - early_bytes_sent; + } + if (send_amount != 0) { + if ((ret = ptls_send(tls, &encbuf, ptbuf.base, send_amount)) != 0) { + fprintf(stderr, "ptls_send(early_data):%d\n", ret); + goto Exit; + } + early_bytes_sent += send_amount; + } + } else { + if ((ret = ptls_send(tls, &encbuf, bytebuf, ioret)) != 0) { + fprintf(stderr, "ptls_send(1rtt):%d\n", ret); + goto Exit; + } + ptbuf.off = 0; + } + } else { + /* closed */ + if (input_file != NULL) + close(inputfd); + inputfd = -1; + } + } + + /* send any data */ + if (encbuf.off != 0) { + while ((ioret = write(sockfd, encbuf.base, encbuf.off)) == -1 && errno == EINTR) + ; + if (ioret == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) { + /* no data */ + } else if (ioret <= 0) { + goto Exit; + } else { + shift_buffer(&encbuf, ioret); + } + } + + /* close the sender side when necessary */ + if (state == IN_1RTT && inputfd == -1) { + /* FIXME send close_alert */ + shutdown(sockfd, SHUT_WR); + state = IN_SHUTDOWN; + } + } + +Exit: + if (sockfd != -1) + close(sockfd); + if (input_file != NULL && inputfd != -1) + close(inputfd); + ptls_buffer_dispose(&rbuf); + ptls_buffer_dispose(&encbuf); + ptls_buffer_dispose(&ptbuf); + ptls_free(tls); + return ret != 0; +} + +static int run_server(struct sockaddr *sa, socklen_t salen, ptls_context_t *ctx, const char *input_file, + ptls_handshake_properties_t *hsprop) +{ + int listen_fd, conn_fd, on = 1; + + if ((listen_fd = socket(sa->sa_family, SOCK_STREAM, 0)) == -1) { + perror("socket(2) failed"); + return 1; + } + if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) { + perror("setsockopt(SO_REUSEADDR) failed"); + return 1; + } + if (bind(listen_fd, sa, salen) != 0) { + perror("bind(2) failed"); + return 1; + } + if (listen(listen_fd, SOMAXCONN) != 0) { + perror("listen(2) failed"); + return 1; + } + + while (1) { + if ((conn_fd = accept(listen_fd, NULL, 0)) != -1) + handle_connection(conn_fd, ctx, NULL, input_file, hsprop); + } + + return 0; +} + +static int run_client(struct sockaddr *sa, socklen_t salen, ptls_context_t *ctx, const char *server_name, const char *input_file, + ptls_handshake_properties_t *hsprop) +{ + int fd; + + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) == 1) { + perror("socket(2) failed"); + return 1; + } + if (connect(fd, sa, salen) != 0) { + perror("connect(2) failed"); + return 1; + } + + return handle_connection(fd, ctx, server_name, input_file, hsprop); +} + +static void usage(const char *cmd) +{ + printf("Usage: %s [options] host port\n" + "\n" + "Options:\n" + " -4 force IPv4\n" + " -6 force IPv6\n" + " -a require client authentication\n" + " -C certificate-file certificate chain used for client authentication\n" + " -c certificate-file certificate chain used for server authentication\n" + " -i file a file to read from and send to the peer (default: stdin)\n" + " -k key-file specifies the credentials for signing the certificate\n" + " -l log-file file to log traffic secrets\n" + " -n negotiates the key exchange method (i.e. wait for HRR)\n" + " -N named-group named group to be used (default: secp256r1)\n" + " -s session-file file to read/write the session ticket\n" + " -S require public key exchange when resuming a session\n" + " -e when resuming a session, send first 8,192 bytes of input\n" + " as early data\n" + " -v verify peer using the default certificates\n" + " -h print this help\n" + "\n" + "Supported named groups: x25519" + "\n\n", + cmd); +} + +ptls_cipher_suite_t *ptls_minicrypto_cipher_suite_chacha20[] = {&ptls_minicrypto_chacha20poly1305sha256, NULL}; + +int main(int argc, char **argv) +{ + ptls_key_exchange_algorithm_t *key_exchanges[128] = {NULL}; + ptls_context_t ctx = {ptls_minicrypto_random_bytes, &ptls_get_time, key_exchanges, ptls_minicrypto_cipher_suite_chacha20}; + ptls_handshake_properties_t hsprop = {{{{NULL}}}}; + const char *host, *port, *file = NULL; + int is_server = 0, use_early_data = 0, ch; + struct sockaddr_storage sa; + socklen_t salen; + int family = 0; + + while ((ch = getopt(argc, argv, "46aC:c:i:k:nN:es:Sl:vh:jJ")) != -1) { + switch (ch) { + case '4': + family = AF_INET; + break; + case '6': + family = AF_INET6; + break; + case 'a': + ctx.require_client_authentication = 1; + break; + case 'C': + case 'c': + if (ctx.certificates.count != 0) { + fprintf(stderr, "-C/-c can only be specified once\n"); + return 1; + } + load_certificate_chain(&ctx, optarg); + is_server = ch == 'c'; + break; + case 'i': + file = optarg; + break; + case 'k': + load_private_key(&ctx, optarg); + break; + case 'n': + hsprop.client.negotiate_before_key_exchange = 1; + break; + case 'e': + use_early_data = 1; + break; + case 's': + setup_session_file(&ctx, &hsprop, optarg); + break; + case 'S': + ctx.require_dhe_on_psk = 1; + break; + case 'l': + setup_log_secret(&ctx, optarg); + break; + case 'v': + setup_verify_certificate(&ctx); + break; + case 'N': { + ptls_key_exchange_algorithm_t *algo = NULL; +#define MATCH(name) \ + if (algo == NULL && strcasecmp(optarg, #name) == 0) \ + algo = (&ptls_minicrypto_##name) + MATCH(secp256r1); + MATCH(x25519); +#undef MATCH + if (algo == NULL) { + fprintf(stderr, "could not find key exchange: %s\n", optarg); + return 1; + } + size_t i; + for (i = 0; key_exchanges[i] != NULL; ++i) + ; + key_exchanges[i++] = algo; + } break; + default: + usage(argv[0]); + exit(1); + } + } + argc -= optind; + argv += optind; + if ((ctx.certificates.count == 0) != (ctx.sign_certificate == NULL)) { + fprintf(stderr, "-C/-c and -k options must be used together\n"); + return 1; + } + if (is_server) { + if (ctx.certificates.count == 0) { + fprintf(stderr, "-c and -k options must be set\n"); + return 1; + } + setup_session_cache(&ctx); + } else { + /* client */ + if (use_early_data) { + static size_t max_early_data_size; + hsprop.client.max_early_data_size = &max_early_data_size; + } + } + if (key_exchanges[0] == NULL) + key_exchanges[0] = &ptls_minicrypto_secp256r1; + if (argc != 2) { + fprintf(stderr, "missing host and port\n"); + return 1; + } + host = (--argc, *argv++); + port = (--argc, *argv++); + + if (resolve_address((struct sockaddr *)&sa, &salen, host, port, family, SOCK_STREAM, IPPROTO_TCP) != 0) + exit(1); + + if (is_server) { + return run_server((struct sockaddr *)&sa, salen, &ctx, file, &hsprop); + } else { + return run_client((struct sockaddr *)&sa, salen, &ctx, host, file, &hsprop); + } +} diff --git a/t/cli.c b/t/cli-openssl.c similarity index 100% rename from t/cli.c rename to t/cli-openssl.c diff --git a/t/util.h b/t/util.h index 78ce87c3c..0d81abea7 100644 --- a/t/util.h +++ b/t/util.h @@ -26,6 +26,7 @@ #define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ #endif +#include #include #include #include @@ -34,8 +35,12 @@ #include #include #include +#ifdef USE_MINICRYPTO +#include "picotls/minicrypto.h" +#else /* openssl */ #include #include "picotls/openssl.h" +#endif static inline void load_certificate_chain(ptls_context_t *ctx, const char *fn) { @@ -47,6 +52,9 @@ static inline void load_certificate_chain(ptls_context_t *ctx, const char *fn) static inline void load_private_key(ptls_context_t *ctx, const char *fn) { +#ifdef USE_MINICRYPTO + ptls_minicrypto_load_private_key(ctx, fn); +#else /* openssl */ static ptls_openssl_sign_certificate_t sc; FILE *fp; EVP_PKEY *pkey; @@ -67,6 +75,7 @@ static inline void load_private_key(ptls_context_t *ctx, const char *fn) EVP_PKEY_free(pkey); ctx->sign_certificate = &sc.super; +#endif } struct st_util_save_ticket_t { @@ -114,9 +123,13 @@ static inline void setup_session_file(ptls_context_t *ctx, ptls_handshake_proper static inline void setup_verify_certificate(ptls_context_t *ctx) { +#ifdef USE_MINICRYPTO + /* stub */ +#else static ptls_openssl_verify_certificate_t vc; ptls_openssl_init_verify_certificate(&vc, NULL); ctx->verify_certificate = &vc.super; +#endif } struct st_util_log_secret_t { From 99351f090eb77657fb8470846ed989fc5c7f0e49 Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Fri, 3 Aug 2018 08:29:03 -0700 Subject: [PATCH 2/3] minicrypto-pem: fix log message --- lib/minicrypto-pem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/minicrypto-pem.c b/lib/minicrypto-pem.c index ea5dece15..7239acc5f 100644 --- a/lib/minicrypto-pem.c +++ b/lib/minicrypto-pem.c @@ -299,10 +299,10 @@ static int ptls_set_ecdsa_private_key(ptls_context_t *ctx, ptls_asn1_pkcs8_priva if (log_ctx != NULL) { /* print the OID value */ - log_ctx->fn(log_ctx->ctx, "Initialized SECP512R1 signing key with %d bytes.\n", ecdsa_key_data_length); + log_ctx->fn(log_ctx->ctx, "Initialized SECP256R1 signing key with %d bytes.\n", ecdsa_key_data_length); } } else if (log_ctx != NULL) { - log_ctx->fn(log_ctx->ctx, "SECP512R1 init with %d bytes returns %d.\n", ecdsa_key_data_length, decode_error); + log_ctx->fn(log_ctx->ctx, "SECP256R1 init with %d bytes returns %d.\n", ecdsa_key_data_length, decode_error); } } } else { From c7120058d43b91b77adc7f6b4e09baa83707a13b Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Sat, 4 Aug 2018 10:40:13 -0700 Subject: [PATCH 3/3] core: basic RawPublicKey support (RFC 7250) --- include/picotls.h | 6 +++ lib/pembase64.c | 3 ++ lib/picotls.c | 107 ++++++++++++++++++++++++++++++++++++++++++++- t/cli-minicrypto.c | 8 +++- 4 files changed, 121 insertions(+), 3 deletions(-) diff --git a/include/picotls.h b/include/picotls.h index 14e24f0ee..0bbf5670d 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -92,6 +92,7 @@ extern "C" { #define PTLS_ALERT_BAD_RECORD_MAC 20 #define PTLS_ALERT_HANDSHAKE_FAILURE 40 #define PTLS_ALERT_BAD_CERTIFICATE 42 +#define PTLS_ALERT_UNSUPPORTED_CERTIFICATE 43 #define PTLS_ALERT_CERTIFICATE_REVOKED 44 #define PTLS_ALERT_CERTIFICATE_EXPIRED 45 #define PTLS_ALERT_CERTIFICATE_UNKNOWN 46 @@ -466,6 +467,11 @@ struct st_ptls_context_t { * to authenticate the client. */ unsigned require_client_authentication : 1; + /* + * Ask for Raw Public Key instead of x509 certificate (RFC 7250) + */ + unsigned server_raw_public_key : 1; + unsigned client_raw_public_key : 1; /** * */ diff --git a/lib/pembase64.c b/lib/pembase64.c index 9db931d46..40b4c66ef 100644 --- a/lib/pembase64.c +++ b/lib/pembase64.c @@ -367,6 +367,9 @@ int ptls_load_certificates(ptls_context_t *ctx, char const *cert_pem_file) } else { ret = ptls_load_pem_objects(cert_pem_file, "CERTIFICATE", ctx->certificates.list, PTLS_MAX_CERTS_IN_CONTEXT, &ctx->certificates.count); + if (ret != 0) + ret = ptls_load_pem_objects(cert_pem_file, "PUBLIC KEY", ctx->certificates.list, PTLS_MAX_CERTS_IN_CONTEXT, + &ctx->certificates.count); } return ret; diff --git a/lib/picotls.c b/lib/picotls.c index 389a0f32b..e43dfb87d 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -56,6 +56,10 @@ #define PTLS_HANDSHAKE_TYPE_KEY_UPDATE 24 #define PTLS_HANDSHAKE_TYPE_MESSAGE_HASH 254 +/* 4.4.2 */ +#define PTLS_CERTIFICATE_TYPE_X509 0 +#define PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY 2 + #define PTLS_PSK_KE_MODE_PSK 0 #define PTLS_PSK_KE_MODE_PSK_DHE 1 @@ -66,6 +70,8 @@ #define PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS 10 #define PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS 13 #define PTLS_EXTENSION_TYPE_ALPN 16 +#define PTLS_EXTENSION_TYPE_CLIENT_CERTIFICATE_TYPE 19 +#define PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE 20 #define PTLS_EXTENSION_TYPE_PRE_SHARED_KEY 41 #define PTLS_EXTENSION_TYPE_EARLY_DATA 42 #define PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS 43 @@ -217,6 +223,11 @@ struct st_ptls_t { unsigned is_psk_handshake : 1; unsigned skip_early_data : 1; /* if early-data is not recognized by the server */ unsigned send_change_cipher_spec : 1; + /* RFC 7250 */ + uint8_t client_certificate_types; /* bit field as should have been all along */ + uint8_t server_certificate_types; /* bit field as should have been all along */ + uint8_t client_certificate_type; + uint8_t server_certificate_type; /** * exporter master secret (either 0rtt or 1rtt) */ @@ -1538,6 +1549,22 @@ static int send_client_hello(ptls_t *tls, struct st_ptls_message_emitter_t *emit } }); }); + /* RFC 7250 RawPublicKey + * TODO make sure all every ptls_key_exchange_algorithm_t implements RawPublicKey + * as per https://tools.ietf.org/html/rfc7250#section-4.1 P 5 and 6 + * TODO allow accepting both RAW_PUBLIC_KEY and X509, and then extracting the public key from the certificate + * ptls_buffer_push(sendbuf, 2, PTLS_CERTIFICATE_TYPE_X509, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); + */ + if (tls->ctx->client_raw_public_key) { + buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_CLIENT_CERTIFICATE_TYPE, { + ptls_buffer_push(sendbuf, 1, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); + }); + } + if (tls->ctx->server_raw_public_key) { + buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, { + ptls_buffer_push(sendbuf, 1, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); + }); + } if (cookie != NULL && cookie->base != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, { ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, cookie->base, cookie->len); }); @@ -1730,12 +1757,43 @@ static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, c goto Exit; } break; - default: + case PTLS_EXTENSION_TYPE_CLIENT_CERTIFICATE_TYPE: + case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE: { + if (src == end) { + ret = PTLS_ALERT_DECODE_ERROR; + goto Exit; + } + uint8_t certificate_type = *src++; + if (certificate_type > PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY || + certificate_type == 1/* OpenSSH, forbidden by TLS 1.3 */) { + ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE; + goto Exit; + } + switch (exttype) { + case PTLS_EXTENSION_TYPE_CLIENT_CERTIFICATE_TYPE: + tls->client_certificate_type = certificate_type; + break; + case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE: + tls->server_certificate_type = certificate_type; + break; + default: + break; + } + break; + } default: src = end; break; } }); + if (((tls->client_certificate_type == PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) != + tls->ctx->client_raw_public_key) || /* TODO handle this case by extracting public key from client certificate */ + ((tls->server_certificate_type == PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) != + tls->ctx->server_raw_public_key)) { + ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE; + goto Exit; + } + if (!is_supported_version(found_version)) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; @@ -2612,13 +2670,50 @@ static int decode_client_hello(ptls_t *tls, struct st_ptls_client_hello_t *ch, c case PTLS_EXTENSION_TYPE_STATUS_REQUEST: ch->status_request = 1; break; - default: + case PTLS_EXTENSION_TYPE_CLIENT_CERTIFICATE_TYPE: + case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE: { + if (src == end) { + ret = PTLS_ALERT_DECODE_ERROR; + goto Exit; + } + uint8_t length = *src++; + if (end - src != length) { + ret = PTLS_ALERT_DECODE_ERROR; + goto Exit; + } + for (;src != end; ++src) { + uint8_t certificate_type = *src; + if (certificate_type > PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY || + certificate_type == 1/* OpenSSH, forbidden by TLS 1.3 */) { + ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE; + goto Exit; + } + if (exttype == PTLS_EXTENSION_TYPE_CLIENT_CERTIFICATE_TYPE) { + tls->client_certificate_types |= (1 << certificate_type); + } else if (exttype == PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE) { + tls->server_certificate_types |= (1 << certificate_type); + } + } + break; + } default: handle_unknown_extension(tls, properties, exttype, src, end, ch->unknown_extensions); break; } src = end; }); + if ((tls->client_certificate_types & (1 << PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY)) && + !tls->ctx->client_raw_public_key ) { + ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE; + goto Exit; + } + /* TODO handle this case by extracting public key from certificate */ + if ((tls->server_certificate_types & (1 << PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) && + !tls->ctx->server_raw_public_key)) { + ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE; + goto Exit; + } + /* check if client hello make sense */ if (is_supported_version(ch->selected_version)) { if (!(ch->compression_methods.count == 1 && ch->compression_methods.ids[0] == 0)) { @@ -3072,6 +3167,14 @@ static int server_handle_hello(ptls_t *tls, struct st_ptls_message_emitter_t *em buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY, { ptls_buffer_push16(sendbuf, (uint16_t)psk_index); }); } + if (tls->ctx->server_raw_public_key) + buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, { + ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); + }); + if (tls->ctx->client_raw_public_key) + buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_CLIENT_CERTIFICATE_TYPE, { + ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); + }); }); if ((ret = push_change_cipher_spec(tls, emitter->buf)) != 0) goto Exit; diff --git a/t/cli-minicrypto.c b/t/cli-minicrypto.c index 3dad1effe..9d2f06948 100644 --- a/t/cli-minicrypto.c +++ b/t/cli-minicrypto.c @@ -317,7 +317,7 @@ int main(int argc, char **argv) socklen_t salen; int family = 0; - while ((ch = getopt(argc, argv, "46aC:c:i:k:nN:es:Sl:vh:jJ")) != -1) { + while ((ch = getopt(argc, argv, "46aC:c:i:k:nN:es:Sl:vh:jJrR")) != -1) { switch (ch) { case '4': family = AF_INET; @@ -337,6 +337,12 @@ int main(int argc, char **argv) load_certificate_chain(&ctx, optarg); is_server = ch == 'c'; break; + case 'r': + ctx.server_raw_public_key = 1; + break; + case 'R': + ctx.client_raw_public_key = 1; + break; case 'i': file = optarg; break;