From 6b552ff9a8bcefee4d082bbf10c394d312730ed8 Mon Sep 17 00:00:00 2001 From: Thomas Fossati Date: Fri, 29 Nov 2019 16:37:09 +0000 Subject: [PATCH] Allow loading PEM credentials from memory Extend pembase64 with two new interfaces: - ptls_load_certificates_from_memory - ptls_minicrypto_load_private_key_from_memory to allow loading keys and certs from PEM-encoded strings. See t/minicrypto.c::test_credentials_from_memory() for working example. --- CMakeLists.txt | 6 +- include/picotls/cred_buffer.h | 30 ++++++++ include/picotls/minicrypto.h | 2 + include/picotls/pembase64.h | 6 ++ lib/cred_buffer.c | 139 ++++++++++++++++++++++++++++++++++ lib/minicrypto-pem.c | 68 ++++++++++++++++- lib/pembase64.c | 62 ++++++++++----- t/e2e.t | 2 +- t/minicrypto.c | 62 +++++++++++++++ t/test.h | 1 + 10 files changed, 356 insertions(+), 22 deletions(-) create mode 100644 include/picotls/cred_buffer.h create mode 100644 lib/cred_buffer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 874b9be4f..c027c76d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ SET(MINICRYPTO_LIBRARY_FILES deps/cifra/src/sha512.c) SET(CORE_FILES lib/picotls.c + lib/cred_buffer.c lib/pembase64.c) SET(CORE_TEST_FILES t/picotls.c) @@ -83,7 +84,9 @@ ADD_EXECUTABLE(test-minicrypto.t ${CORE_TEST_FILES} t/minicrypto.c lib/asn1.c + lib/cred_buffer.c lib/pembase64.c + lib/minicrypto-pem.c lib/ffx.c lib/cifra/x25519.c lib/cifra/chacha20.c @@ -99,7 +102,7 @@ IF (OPENSSL_FOUND AND NOT (OPENSSL_VERSION VERSION_LESS "1.0.1")) INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) ADD_LIBRARY(picotls-openssl lib/openssl.c) TARGET_LINK_LIBRARIES(picotls-openssl ${OPENSSL_LIBRARIES} picotls-core ${CMAKE_DL_LIBS}) - ADD_EXECUTABLE(cli t/cli.c lib/pembase64.c) + ADD_EXECUTABLE(cli t/cli.c lib/cred_buffer.c lib/pembase64.c) TARGET_LINK_LIBRARIES(cli picotls-openssl picotls-core) ADD_EXECUTABLE(picotls-esni src/esni.c) TARGET_LINK_LIBRARIES(picotls-esni picotls-openssl picotls-core ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) @@ -114,6 +117,7 @@ IF (OPENSSL_FOUND AND NOT (OPENSSL_VERSION VERSION_LESS "1.0.1")) lib/cifra/random.c lib/uecc.c lib/asn1.c + lib/cred_buffer.c lib/pembase64.c lib/ffx.c deps/picotest/picotest.c diff --git a/include/picotls/cred_buffer.h b/include/picotls/cred_buffer.h new file mode 100644 index 000000000..92c2cd779 --- /dev/null +++ b/include/picotls/cred_buffer.h @@ -0,0 +1,30 @@ +#ifndef PTLS_CRED_BUFFER_H +#define PTLS_CRED_BUFFER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "picotls.h" + +typedef struct ptls_cred_buffer_s { + char *base; + size_t len; + size_t off; + int owns_base; +#define PTLS_CRED_BUFFER_RPOS(buf) ((buf)->base + (buf)->off) +#define PTLS_CRED_BUFFER_REND(buf) ((buf)->base + (buf)->len) +#define PTLS_CRED_BUFFER_LEFT(buf) ((buf)->len - (buf)->off) +} ptls_cred_buffer_t; + +int ptls_cred_buffer_set_from_file(ptls_cred_buffer_t *buf, const char *fname); +int ptls_cred_buffer_set_from_string(ptls_cred_buffer_t *buf, char *s); +void ptls_cred_buffer_dispose(ptls_cred_buffer_t *buf); +void ptls_cred_buffer_rewind(ptls_cred_buffer_t *buf); +char *ptls_cred_buffer_gets(char *s, int n, ptls_cred_buffer_t *buf); + +#ifdef __cplusplus +} +#endif + +#endif /* !PTLS_CRED_BUFFER_H */ \ No newline at end of file diff --git a/include/picotls/minicrypto.h b/include/picotls/minicrypto.h index af0c71466..91a2b49b9 100644 --- a/include/picotls/minicrypto.h +++ b/include/picotls/minicrypto.h @@ -27,6 +27,7 @@ extern "C" { #endif #include "picotls.h" +#include "picotls/cred_buffer.h" #define SECP256R1_PRIVATE_KEY_SIZE 32 #define SECP256R1_PUBLIC_KEY_SIZE 65 /* including the header */ @@ -62,6 +63,7 @@ typedef struct st_ptls_asn1_pkcs8_private_key_t { } ptls_asn1_pkcs8_private_key_t; int ptls_minicrypto_load_private_key(ptls_context_t *ctx, char const *pem_fname); +int ptls_minicrypto_load_private_key_from_memory(ptls_context_t *ctx, ptls_cred_buffer_t *mem); #ifdef __cplusplus } diff --git a/include/picotls/pembase64.h b/include/picotls/pembase64.h index 1b59910b4..922911976 100644 --- a/include/picotls/pembase64.h +++ b/include/picotls/pembase64.h @@ -17,6 +17,8 @@ #ifndef PTLS_PEMBASE64_H #define PTLS_PEMBASE64_H +#include "picotls/cred_buffer.h" + /* * Base64 functions used in encoding and decoding of PEM files */ @@ -41,4 +43,8 @@ int ptls_base64_decode(const char *base64_text, ptls_base64_decode_state_t *stat int ptls_load_pem_objects(char const *pem_fname, const char *label, ptls_iovec_t *list, size_t list_max, size_t *nb_objects); +int ptls_load_pem_objects_from_memory(ptls_cred_buffer_t *mem, const char *label, ptls_iovec_t *list, size_t list_max, size_t *nb_objects); + +int ptls_load_certificates_from_memory(ptls_context_t *ctx, ptls_cred_buffer_t *mem); + #endif /* PTLS_PEMBASE64_H */ diff --git a/lib/cred_buffer.c b/lib/cred_buffer.c new file mode 100644 index 000000000..5c88368c2 --- /dev/null +++ b/lib/cred_buffer.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include "picotls/cred_buffer.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static int cred_buffer_getc(ptls_cred_buffer_t *buf) +{ + return PTLS_CRED_BUFFER_LEFT(buf) > 0 ? buf->base[buf->off++] : -1; +} + +static ssize_t fsize(FILE *fp) +{ + long sz; + + if (fseek(fp, 0, SEEK_END) == -1 || (sz = ftell(fp)) == -1) { + return -1; + } + + rewind(fp); + + return (ssize_t) sz; +} + +/* The caller owns 'mem' and must have called ptls_buffer_init prior to + * invoking this function */ +int ptls_cred_buffer_set_from_file(ptls_cred_buffer_t *buf, const char *fname) +{ + FILE *fp = NULL; + ssize_t sz; + char *m = NULL; + +#ifdef _WINDOWS + errno_t err = fopen_s(&fp, fname, "r"); + if (err != 0) { + return -1; + } +#else + fp = fopen(fname, "r"); + if (fp == NULL) { + return -1; + } +#endif + + if ((sz = fsize(fp)) == -1 || + (m = malloc(sz)) == NULL || + fread(m, sz, 1, fp) != 1) { + goto err; + } + + (void) fclose(fp); + + buf->base = m; + buf->len = sz; + buf->off = 0; + buf->owns_base = 1; + + return 0; +err: + if (m) + free(m); + if (fp != NULL) + (void) fclose(fp); + + return -1; +} + +int ptls_cred_buffer_set_from_string(ptls_cred_buffer_t *buf, char *s) +{ + buf->base = s; + buf->len = strlen(s); + buf->off = 0; + buf->owns_base = 0; + + return 0; +} + +void ptls_cred_buffer_dispose(ptls_cred_buffer_t *buf) +{ + if (buf->owns_base) { + if (buf->base) { + free(buf->base); + buf->base = NULL; + } + buf->len = buf->off = 0; + buf->owns_base = 0; + } + + return; +} + +void ptls_cred_buffer_rewind(ptls_cred_buffer_t *buf) +{ + buf->off = 0; + return; +} + +/* z -> nlptr */ +char *ptls_cred_buffer_gets(char *s, int n, ptls_cred_buffer_t *buf) +{ + char *p = s; + char *z; + size_t k; + int c; + + if (n-- <= 1) { + if (n) return NULL; + *s = '\0'; + return s; + } + + while (n) { + if (PTLS_CRED_BUFFER_RPOS(buf) != PTLS_CRED_BUFFER_REND(buf)) { + z = memchr(PTLS_CRED_BUFFER_RPOS(buf), '\n', PTLS_CRED_BUFFER_LEFT(buf)); + k = z ? z - PTLS_CRED_BUFFER_RPOS(buf) + 1 : PTLS_CRED_BUFFER_LEFT(buf); + k = MIN(k, n); + memcpy(p, PTLS_CRED_BUFFER_RPOS(buf), k); + buf->off += k; + p += k; + n -= k; + if (z || !n) break; + } + + if ((c = cred_buffer_getc(buf)) < 0) { + if (p == s || PTLS_CRED_BUFFER_LEFT(buf) > 0) s = NULL; + break; + } + + n--; + + if ((*p++ = c) == '\n') break; + } + + if (s) *p = '\0'; + + return s; +} diff --git a/lib/minicrypto-pem.c b/lib/minicrypto-pem.c index 9d6c4996a..8581b2b1f 100644 --- a/lib/minicrypto-pem.c +++ b/lib/minicrypto-pem.c @@ -165,12 +165,47 @@ size_t ptls_minicrypto_asn1_decode_private_key(ptls_asn1_pkcs8_private_key_t *pk return byte_index; } +static int ptls_pem_parse_private_key_from_memory(ptls_cred_buffer_t *mem, ptls_asn1_pkcs8_private_key_t *pkey, + ptls_minicrypto_log_ctx_t *log_ctx) +{ + size_t nb_keys = 0; + int ret = ptls_load_pem_objects_from_memory(mem, "PRIVATE KEY", &pkey->vec, 1, &nb_keys); + + if (ret == 0) { + if (nb_keys != 1) { + ret = PTLS_ERROR_PEM_LABEL_NOT_FOUND; + } + } + + if (ret == 0 && nb_keys == 1) { + int decode_error = 0; + + if (log_ctx != NULL) { + log_ctx->fn(log_ctx->ctx, "\nFound PRIVATE KEY, length = %d bytes\n", (int)pkey->vec.len); + } + + (void)ptls_minicrypto_asn1_decode_private_key(pkey, &decode_error, log_ctx); + + if (decode_error != 0) { + ret = decode_error; + } + } + + return ret; +} + static int ptls_pem_parse_private_key(char const *pem_fname, ptls_asn1_pkcs8_private_key_t *pkey, ptls_minicrypto_log_ctx_t *log_ctx) { size_t nb_keys = 0; - int ret = ptls_load_pem_objects(pem_fname, "PRIVATE KEY", &pkey->vec, 1, &nb_keys); + int ret; + ptls_cred_buffer_t mem; + if ((ret = ptls_cred_buffer_set_from_file(&mem, pem_fname)) != 0) { + goto end; + } + + ret = ptls_load_pem_objects_from_memory(&mem, "PRIVATE KEY", &pkey->vec, 1, &nb_keys); if (ret == 0) { if (nb_keys != 1) { ret = PTLS_ERROR_PEM_LABEL_NOT_FOUND; @@ -191,6 +226,9 @@ static int ptls_pem_parse_private_key(char const *pem_fname, ptls_asn1_pkcs8_pri } } + /* Fallthrough */ +end: + ptls_cred_buffer_dispose(&mem); return ret; } @@ -321,6 +359,34 @@ static int ptls_set_ecdsa_private_key(ptls_context_t *ctx, ptls_asn1_pkcs8_priva return decode_error; } +int ptls_minicrypto_load_private_key_from_memory(ptls_context_t *ctx, ptls_cred_buffer_t *mem) +{ + ptls_asn1_pkcs8_private_key_t pkey = {{0}}; + int ret = ptls_pem_parse_private_key_from_memory(mem, &pkey, NULL); + + if (ret != 0) + goto err; + + /* Check that this is the expected key type. + * At this point, the minicrypto library only supports ECDSA keys. + * In theory, we could add support for RSA keys at some point. + */ + if (pkey.algorithm_length != sizeof(ptls_asn1_algorithm_ecdsa) || + memcmp(pkey.vec.base + pkey.algorithm_index, ptls_asn1_algorithm_ecdsa, sizeof(ptls_asn1_algorithm_ecdsa)) != 0) { + ret = -1; + goto err; + } + + ret = ptls_set_ecdsa_private_key(ctx, &pkey, NULL); + +err: + if (pkey.vec.base) { + ptls_clear_memory(pkey.vec.base, pkey.vec.len); + free(pkey.vec.base); + } + return ret; +} + int ptls_minicrypto_load_private_key(ptls_context_t *ctx, char const *pem_fname) { ptls_asn1_pkcs8_private_key_t pkey = {{0}}; diff --git a/lib/pembase64.c b/lib/pembase64.c index 70197efb3..65266f4f9 100644 --- a/lib/pembase64.c +++ b/lib/pembase64.c @@ -27,8 +27,10 @@ #include #include #include "picotls.h" +#include "picotls/cred_buffer.h" #include "picotls/pembase64.h" + static char ptls_base64_alphabet[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', @@ -265,14 +267,14 @@ static int ptls_compare_separator_line(const char *line, const char *begin_or_en return ret; } -static int ptls_get_pem_object(FILE *F, const char *label, ptls_buffer_t *buf) +static int ptls_get_pem_object_from_memory(ptls_cred_buffer_t *mem, const char *label, ptls_buffer_t *buf) { int ret = PTLS_ERROR_PEM_LABEL_NOT_FOUND; char line[256]; ptls_base64_decode_state_t state; /* Get the label on a line by itself */ - while (fgets(line, 256, F)) { + while (ptls_cred_buffer_gets(line, 256, mem)) { if (ptls_compare_separator_line(line, "BEGIN", label) == 0) { ret = 0; ptls_base64_decode_init(&state); @@ -280,7 +282,7 @@ static int ptls_get_pem_object(FILE *F, const char *label, ptls_buffer_t *buf) } } /* Get the data in the buffer */ - while (ret == 0 && fgets(line, 256, F)) { + while (ret == 0 && ptls_cred_buffer_gets(line, 256, mem)) { if (ptls_compare_separator_line(line, "END", label) == 0) { if (state.status == PTLS_BASE64_DECODE_DONE || (state.status == PTLS_BASE64_DECODE_IN_PROGRESS && state.nbc == 0)) { ret = 0; @@ -296,22 +298,12 @@ static int ptls_get_pem_object(FILE *F, const char *label, ptls_buffer_t *buf) return ret; } -int ptls_load_pem_objects(char const *pem_fname, const char *label, ptls_iovec_t *list, size_t list_max, size_t *nb_objects) + + +int ptls_load_pem_objects_from_memory(ptls_cred_buffer_t *mem, const char *label, ptls_iovec_t *list, size_t list_max, size_t *nb_objects) { - FILE *F; int ret = 0; size_t count = 0; -#ifdef _WINDOWS - errno_t err = fopen_s(&F, pem_fname, "r"); - if (err != 0) { - ret = -1; - } -#else - F = fopen(pem_fname, "r"); - if (F == NULL) { - ret = -1; - } -#endif *nb_objects = 0; @@ -321,7 +313,7 @@ int ptls_load_pem_objects(char const *pem_fname, const char *label, ptls_iovec_t ptls_buffer_init(&buf, "", 0); - ret = ptls_get_pem_object(F, label, &buf); + ret = ptls_get_pem_object_from_memory(mem, label, &buf); if (ret == 0) { if (buf.off > 0 && buf.is_allocated) { @@ -344,10 +336,26 @@ int ptls_load_pem_objects(char const *pem_fname, const char *label, ptls_iovec_t *nb_objects = count; - if (F != NULL) { - fclose(F); + return ret; +} + +int ptls_load_pem_objects(char const *pem_fname, const char *label, ptls_iovec_t *list, size_t list_max, size_t *nb_objects) +{ + int ret = 0; + ptls_cred_buffer_t mem = {0}; + + if ((ret = ptls_cred_buffer_set_from_file(&mem, pem_fname)) != 0) { + goto err; } + ret = ptls_load_pem_objects_from_memory(&mem, label, list, list_max, nb_objects); + + ptls_cred_buffer_dispose(&mem); + + return ret; +err: + ptls_cred_buffer_dispose(&mem); + return ret; } @@ -368,3 +376,19 @@ int ptls_load_certificates(ptls_context_t *ctx, char const *cert_pem_file) return ret; } + +int ptls_load_certificates_from_memory(ptls_context_t *ctx, ptls_cred_buffer_t *mem) +{ + int ret = 0; + + ctx->certificates.list = (ptls_iovec_t *)malloc(PTLS_MAX_CERTS_IN_CONTEXT * sizeof(ptls_iovec_t)); + + if (ctx->certificates.list == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + } else { + ret = ptls_load_pem_objects_from_memory(mem, "CERTIFICATE", ctx->certificates.list, PTLS_MAX_CERTS_IN_CONTEXT, + &ctx->certificates.count); + } + + return ret; +} diff --git a/t/e2e.t b/t/e2e.t index f7f1562bf..bf26813b2 100755 --- a/t/e2e.t +++ b/t/e2e.t @@ -1,4 +1,4 @@ -#! /usr/bin/perl +#!/usr/bin/env perl use strict; use warnings; diff --git a/t/minicrypto.c b/t/minicrypto.c index 4b0d73cf3..8afaf397c 100644 --- a/t/minicrypto.c +++ b/t/minicrypto.c @@ -138,11 +138,73 @@ static void test_hrr(void) DEFINE_FFX_AES128_ALGORITHMS(minicrypto); DEFINE_FFX_CHACHA20_ALGORITHMS(minicrypto); +static void test_credentials_from_memory() +{ + int ret; + ptls_context_t ctx = {0}; + ptls_cred_buffer_t mem = {0}; + + char *blob = + /* random CA */ + "-----BEGIN CERTIFICATE-----\n" + "MIIDMjCCAhqgAwIBAgIJAPh4W88oNy7tMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV\n" + "BAMTD3BpY290bHMgdGVzdCBjYTAeFw0xODAyMjMwMjI0NDJaFw0yODAyMjEwMjI0\n" + "NDJaMBoxGDAWBgNVBAMTD3BpY290bHMgdGVzdCBjYTCCASIwDQYJKoZIhvcNAQEB\n" + "BQADggEPADCCAQoCggEBAMewDZVDfz1PFT4TfGvG4uF27Tv7w64/bxFB0ZK4Wjpj\n" + "eMxdiWBrw7dyZ9KAqxrcIw0KLBqeVRUvokTNSLGLM7j6CVMNFtH0dKqQ7hef9xB8\n" + "NSPoNkTMs/Cf2te79ifOCd0+QHlIWi7Qzt2Ito+sKzuACFP+8zXIkksxHWGLLNSz\n" + "Q0PfmDHNp+WnoTmTDIcwjhfhb3PUZVNZONFhVjXgrkCqgbutna96InsN/7TWGotT\n" + "xSjb2xOuSSvoueCYGSFFb5a9UVMwWbAmquc8KnhTAvqwCa8QbaiOVujUWCL2k0H4\n" + "EVlkzn+QfIiDNRk28SvwazcOtz7HPj795XwMYXPXiKcCAwEAAaN7MHkwHQYDVR0O\n" + "BBYEFL95ypeyYHgglqpGV5zfp7Ij9SVjMEoGA1UdIwRDMEGAFL95ypeyYHgglqpG\n" + "V5zfp7Ij9SVjoR6kHDAaMRgwFgYDVQQDEw9waWNvdGxzIHRlc3QgY2GCCQD4eFvP\n" + "KDcu7TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBQ9EyGzIm8uX8U\n" + "MIYkvGyQiSAl4v7Y9PZhtJIbuSn/hV8rutKs550AMFpPL5tijNpyUvZyR+Wpuvs9\n" + "TGrOPIFhetcBF3tVUsg4lVvhMcxojUKysv0UwfEJQVbu1yoZmRdXOnKGiVnqvpI8\n" + "ZjcgNtMacoBViQV44cR805bu6zBNWLaac3Q1wgT9NQSdBuQp0tAzVFQkE3ZRigfT\n" + "LdQMb73jddaWZG8wnDfebK0klZo2oif2kGq53OOBooN/QUWKinMPPWdQVcY5Texa\n" + "TmOVYk7HnWQEQ+Wr+9/o8EUs+3B/Af7lV7q9redWIiyYdyKPKmx090XHBy6HTPyO\n" + "o9citOWg\n" + "-----END CERTIFICATE-----\n" + /* dummy EE cert */ + "-----BEGIN CERTIFICATE-----\n" + "MIIBbjCCAROgAwIBAgIUDScD73HL2clgD6LjeIEULc8yn9gwCgYIKoZIzj0EAwIw\n" + "ADAeFw0xOTEwMTYyMTE2MTBaFw00NTA2MDYyMTE2MTBaMAAwWTATBgcqhkjOPQIB\n" + "BggqhkjOPQMBBwNCAATxmX/xuK+SXT0WRXCk+91TmoKsAl05fFXXSSMp5Ce/5M2r\n" + "HIdxNF+VtNHDmAgku//c6+5Ty+9ut9sbvR518yOmo2swaTAdBgNVHQ4EFgQUIkin\n" + "nTmpGPTfdbs3xEsDwEVdXt0wHwYDVR0jBBgwFoAUIkinnTmpGPTfdbs3xEsDwEVd\n" + "Xt0wDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREEDzANggtleGFtcGxlLm9yZzAKBggq\n" + "hkjOPQQDAgNJADBGAiEAkKG1XfORc4yBAlt/7OYBtRhATeMFp6mpUDs5hIv+B3gC\n" + "IQD7+uewwBal+cvFevwsbpIKEHapodaqeNp96AfJNKUayA==\n" + "-----END CERTIFICATE-----\n" + /* dummy private key */ + "-----BEGIN PRIVATE KEY-----\n" + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgv6Cda8Tc6fSyyKW2\n" + "H2MDhbmkc3E0j1r4G9lnmbsD7uyhRANCAATxmX/xuK+SXT0WRXCk+91TmoKsAl05\n" + "fFXXSSMp5Ce/5M2rHIdxNF+VtNHDmAgku//c6+5Ty+9ut9sbvR518yOm\n" + "-----END PRIVATE KEY-----\n" + ; + + ret = ptls_cred_buffer_set_from_string(&mem, blob); + ok(ret == 0); + + ret = ptls_load_certificates_from_memory(&ctx, &mem); + ok(ret == 0); + + ptls_cred_buffer_rewind(&mem); + + ret = ptls_minicrypto_load_private_key_from_memory(&ctx, &mem); + ok(ret == 0); + + ptls_cred_buffer_dispose(&mem); +} + int main(int argc, char **argv) { subtest("secp256r1", test_secp256r1_key_exchange); subtest("x25519", test_x25519_key_exchange); subtest("secp256r1-sign", test_secp256r1_sign); + subtest("credentials-from-memory", test_credentials_from_memory); ptls_iovec_t cert = ptls_iovec_init(SECP256R1_CERTIFICATE, sizeof(SECP256R1_CERTIFICATE) - 1); diff --git a/t/test.h b/t/test.h index 0d3583214..b804f4acc 100644 --- a/t/test.h +++ b/t/test.h @@ -24,6 +24,7 @@ #include "picotls.h" #include "picotls/ffx.h" +#include "picotls/pembase64.h" /* raw private key and certificate using secp256v1 */ #define SECP256R1_PRIVATE_KEY \