Raw public keys (RFC 7250) support
This PR adds RFC 7250 support for _server_ certificate raw certificates
(client certificate types aren't added in this PR).
It interops succesfully with this PR for BoringSSL:
https://boringssl-review.googlesource.com/c/boringssl/+/45444
It adds three additional options to `cli`:
- `-R` on the server side to add a raw cert
- `-V` on the client side to verify raw certs
- `-v` on the client side to express support for different certificate types
diff --git a/include/picotls.h b/include/picotls.h
index 0162fe8..6ff0d0e 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -153,6 +153,7 @@
#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
@@ -210,6 +211,9 @@
#define PTLS_HANDSHAKE_TYPE_COMPRESSED_CERTIFICATE 25
#define PTLS_HANDSHAKE_TYPE_MESSAGE_HASH 254
+#define PTLS_CERTIFICATE_TYPE_X509 0
+#define PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY 2
+
#define PTLS_ZERO_DIGEST_SHA256 \
{ \
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, \
@@ -554,7 +558,7 @@
* callback to generate the certificate message. `ptls_context::certificates` are set when the callback is set to NULL.
*/
PTLS_CALLBACK_TYPE(int, emit_certificate, ptls_t *tls, ptls_message_emitter_t *emitter, ptls_key_schedule_t *key_sched,
- ptls_iovec_t context, int push_status_request, const uint16_t *compress_algos, size_t num_compress_algos);
+ ptls_iovec_t context, int push_status_request, const uint16_t *compress_algos, size_t num_compress_algos, int send_raw_cert);
/**
* when gerenating CertificateVerify, the core calls the callback to sign the handshake context using the certificate.
*/
@@ -739,6 +743,10 @@
*
*/
ptls_on_extension_t *on_extension;
+ /**
+ * An optional certificate in raw format - RFC7250
+ */
+ ptls_iovec_t raw_certificate;
};
typedef struct st_ptls_raw_extension_t {
@@ -794,6 +802,10 @@
* ESNIKeys (the value of the TXT record, after being base64-"decoded")
*/
ptls_iovec_t esni_keys;
+ struct {
+ const uint8_t *list;
+ size_t count;
+ } supported_certificate_types;
} client;
struct {
/**
@@ -825,6 +837,7 @@
* if retry should be stateless (cookie.key MUST be set when this option is used)
*/
unsigned retry_uses_cookie : 1;
+ uint8_t server_certificate_type;
} server;
};
/**
@@ -1168,7 +1181,7 @@
* build the body of a Certificate message. Can be called with tls set to NULL in order to create a precompressed message.
*/
int ptls_build_certificate_message(ptls_buffer_t *buf, ptls_iovec_t request_context, ptls_iovec_t *certificates,
- size_t num_certificates, ptls_iovec_t ocsp_status);
+ size_t num_certificates, ptls_iovec_t ocsp_status, ptls_iovec_t raw_cert);
/**
*
*/
diff --git a/include/picotls/openssl.h b/include/picotls/openssl.h
index 2bfe1b9..60fb69f 100644
--- a/include/picotls/openssl.h
+++ b/include/picotls/openssl.h
@@ -104,6 +104,11 @@
void ptls_openssl_dispose_sign_certificate(ptls_openssl_sign_certificate_t *self);
int ptls_openssl_load_certificates(ptls_context_t *ctx, X509 *cert, STACK_OF(X509) * chain);
+typedef struct st_ptls_raw_pubkey_verify_certificate_t {
+ ptls_verify_certificate_t super;
+ ptls_iovec_t expected_pubkey;
+} ptls_raw_pubkey_verify_certificate_t;
+
typedef struct st_ptls_openssl_verify_certificate_t {
ptls_verify_certificate_t super;
X509_STORE *cert_store;
@@ -113,6 +118,8 @@
void ptls_openssl_dispose_verify_certificate(ptls_openssl_verify_certificate_t *self);
X509_STORE *ptls_openssl_create_default_certificate_store(void);
+int ptls_raw_pubkey_init_verify_certificate(ptls_raw_pubkey_verify_certificate_t *self);
+
int ptls_openssl_encrypt_ticket(ptls_buffer_t *dst, ptls_iovec_t src,
int (*cb)(unsigned char *, unsigned char *, EVP_CIPHER_CTX *, HMAC_CTX *, int));
int ptls_openssl_decrypt_ticket(ptls_buffer_t *dst, ptls_iovec_t src,
diff --git a/lib/certificate_compression.c b/lib/certificate_compression.c
index 36e77fd..32ba172 100644
--- a/lib/certificate_compression.c
+++ b/lib/certificate_compression.c
@@ -49,7 +49,7 @@
static int emit_compressed_certificate(ptls_emit_certificate_t *_self, ptls_t *tls, ptls_message_emitter_t *emitter,
ptls_key_schedule_t *key_sched, ptls_iovec_t context, int push_status_request,
- const uint16_t *compress_algos, size_t num_compress_algos)
+ const uint16_t *compress_algos, size_t num_compress_algos, int send_raw_cert)
{
ptls_emit_compressed_certificate_t *self = (void *)_self;
struct st_ptls_compressed_certificate_entry_t *entry;
@@ -91,8 +91,8 @@
ptls_buffer_init(&uncompressed, "", 0);
/* build uncompressed */
- if ((ret = ptls_build_certificate_message(&uncompressed, ptls_iovec_init(NULL, 0), certificates, num_certificates,
- ocsp_status)) != 0)
+ if ((ret = ptls_build_certificate_message(&uncompressed, ptls_iovec_init(NULL, 0), certificates, num_certificates, ocsp_status,
+ ptls_iovec_init(NULL, 0))) != 0)
goto Exit;
entry->uncompressed_length = (uint32_t)uncompressed.off;
diff --git a/lib/openssl.c b/lib/openssl.c
index 00c7777..4287e2c 100644
--- a/lib/openssl.c
+++ b/lib/openssl.c
@@ -1286,6 +1286,34 @@
return NULL;
}
+static int verify_raw_cert(ptls_verify_certificate_t *_self, ptls_t *tls, int (**verifier)(void *, ptls_iovec_t, ptls_iovec_t),
+ void **verify_data, ptls_iovec_t *certs, size_t num_certs)
+{
+ ptls_raw_pubkey_verify_certificate_t *self = (ptls_raw_pubkey_verify_certificate_t *)_self;
+ int ret = PTLS_ALERT_BAD_CERTIFICATE;
+
+ assert(num_certs != 0);
+
+ if (num_certs != 1)
+ goto Exit;
+
+ if (certs[0].len != self->expected_pubkey.len)
+ goto Exit;
+
+ if (!ptls_mem_equal(self->expected_pubkey.base, certs[0].base, certs[0].len))
+ goto Exit;
+
+ ret = 0;
+Exit:
+ return ret;
+}
+
+int ptls_raw_pubkey_init_verify_certificate(ptls_raw_pubkey_verify_certificate_t *self)
+{
+ *self = (ptls_raw_pubkey_verify_certificate_t){{verify_raw_cert}};
+ return 0;
+}
+
#define TICKET_LABEL_SIZE 16
#define TICKET_IV_SIZE EVP_MAX_IV_LENGTH
diff --git a/lib/picotls.c b/lib/picotls.c
index 9f84120..c496bcc 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -56,6 +56,7 @@
#define PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS 10
#define PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS 13
#define PTLS_EXTENSION_TYPE_ALPN 16
+#define PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE 20
#define PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE 27
#define PTLS_EXTENSION_TYPE_PRE_SHARED_KEY 41
#define PTLS_EXTENSION_TYPE_EARLY_DATA 42
@@ -335,6 +336,10 @@
unsigned early_data_indication : 1;
unsigned is_last_extension : 1;
} psk;
+ struct {
+ const uint8_t *list;
+ size_t count;
+ } supported_server_certificate_types;
ptls_raw_extension_t unknown_extensions[MAX_UNKNOWN_EXTENSIONS + 1];
unsigned status_request : 1;
};
@@ -350,6 +355,7 @@
ptls_iovec_t cookie;
} retry_request;
};
+ const uint8_t *certtype;
};
struct st_ptls_key_schedule_t {
@@ -462,6 +468,10 @@
ALLOW(CLIENT_HELLO);
ALLOW(SERVER_HELLO);
});
+ EXT(SERVER_CERTIFICATE_TYPE, {
+ ALLOW(CLIENT_HELLO);
+ ALLOW(ENCRYPTED_EXTENSIONS);
+ });
#undef ALLOW
#undef EXT
@@ -2064,6 +2074,14 @@
ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, cookie->base, cookie->len); });
});
}
+ if (properties && properties->client.supported_certificate_types.list != NULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, {
+ ptls_buffer_push_block(sendbuf, 1, {
+ ptls_buffer_pushv(sendbuf, properties->client.supported_certificate_types.list,
+ properties->client.supported_certificate_types.count);
+ });
+ });
+ }
if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
goto Exit;
if (tls->ctx->save_ticket != NULL || resumption_secret.base != NULL) {
@@ -2478,6 +2496,14 @@
}
skip_early_data = 0;
break;
+ case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE:
+ if (end - src != 1) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ properties->server.server_certificate_type = *src;
+ src = end;
+ break;
default:
if (should_collect_unknown_extension(tls, properties, type)) {
if (unknown_extensions == &no_unknown_extensions) {
@@ -2576,25 +2602,34 @@
}
int ptls_build_certificate_message(ptls_buffer_t *buf, ptls_iovec_t context, ptls_iovec_t *certificates, size_t num_certificates,
- ptls_iovec_t ocsp_status)
+ ptls_iovec_t ocsp_status, ptls_iovec_t raw_cert)
{
int ret;
ptls_buffer_push_block(buf, 1, { ptls_buffer_pushv(buf, context.base, context.len); });
- ptls_buffer_push_block(buf, 3, {
- size_t i;
- for (i = 0; i != num_certificates; ++i) {
- ptls_buffer_push_block(buf, 3, { ptls_buffer_pushv(buf, certificates[i].base, certificates[i].len); });
- ptls_buffer_push_block(buf, 2, {
- if (i == 0 && ocsp_status.len != 0) {
- buffer_push_extension(buf, PTLS_EXTENSION_TYPE_STATUS_REQUEST, {
- ptls_buffer_push(buf, 1); /* status_type == ocsp */
- ptls_buffer_push_block(buf, 3, { ptls_buffer_pushv(buf, ocsp_status.base, ocsp_status.len); });
- });
- }
- });
- }
- });
+ if (raw_cert.base) {
+ ptls_buffer_push_block(buf, 3, {
+ char *debug = alloca(raw_cert.len * 2 + 1);
+ ptls_hexdump(debug, raw_cert.base, raw_cert.len);
+ ptls_buffer_push_block(buf, 3, { ptls_buffer_pushv(buf, raw_cert.base, raw_cert.len); });
+ ptls_buffer_push_block(buf, 2, { } );
+ });
+ } else {
+ ptls_buffer_push_block(buf, 3, {
+ size_t i;
+ for (i = 0; i != num_certificates; ++i) {
+ ptls_buffer_push_block(buf, 3, { ptls_buffer_pushv(buf, certificates[i].base, certificates[i].len); });
+ ptls_buffer_push_block(buf, 2, {
+ if (i == 0 && ocsp_status.len != 0) {
+ buffer_push_extension(buf, PTLS_EXTENSION_TYPE_STATUS_REQUEST, {
+ ptls_buffer_push(buf, 1); /* status_type == ocsp */
+ ptls_buffer_push_block(buf, 3, { ptls_buffer_pushv(buf, ocsp_status.base, ocsp_status.len); });
+ });
+ }
+ });
+ }
+ });
+ }
ret = 0;
Exit:
@@ -2603,13 +2638,14 @@
static int default_emit_certificate_cb(ptls_emit_certificate_t *_self, ptls_t *tls, ptls_message_emitter_t *emitter,
ptls_key_schedule_t *key_sched, ptls_iovec_t context, int push_status_request,
- const uint16_t *compress_algos, size_t num_compress_algos)
+ const uint16_t *compress_algos, size_t num_compress_algos, int send_raw_cert)
{
int ret;
ptls_push_message(emitter, key_sched, PTLS_HANDSHAKE_TYPE_CERTIFICATE, {
- if ((ret = ptls_build_certificate_message(emitter->buf, context, tls->ctx->certificates.list, tls->ctx->certificates.count,
- ptls_iovec_init(NULL, 0))) != 0)
+ if ((ret = ptls_build_certificate_message(
+ emitter->buf, context, tls->ctx->certificates.list, tls->ctx->certificates.count, ptls_iovec_init(NULL, 0),
+ send_raw_cert ? tls->ctx->raw_certificate : ptls_iovec_init(NULL, 0))) != 0)
goto Exit;
});
@@ -2621,7 +2657,7 @@
static int send_certificate_and_certificate_verify(ptls_t *tls, ptls_message_emitter_t *emitter,
struct st_ptls_signature_algorithms_t *signature_algorithms,
ptls_iovec_t context, const char *context_string, int push_status_request,
- const uint16_t *compress_algos, size_t num_compress_algos)
+ const uint16_t *compress_algos, size_t num_compress_algos, int send_raw_cert)
{
int ret;
@@ -2636,7 +2672,7 @@
tls->ctx->emit_certificate != NULL ? tls->ctx->emit_certificate : &default_emit_certificate;
Redo:
if ((ret = emit_certificate->cb(emit_certificate, tls, emitter, tls->key_schedule, context, push_status_request,
- compress_algos, num_compress_algos)) != 0) {
+ compress_algos, num_compress_algos, send_raw_cert)) != 0) {
if (ret == PTLS_ERROR_DELEGATE) {
assert(emit_certificate != &default_emit_certificate);
emit_certificate = &default_emit_certificate;
@@ -2710,6 +2746,8 @@
certs[num_certs++] = ptls_iovec_init(src, end - src);
src = end;
});
+ if (src == end)
+ break;
uint16_t type;
decode_open_extensions(src, end, PTLS_HANDSHAKE_TYPE_CERTIFICATE, &type, {
if (tls->ctx->on_extension != NULL &&
@@ -2929,7 +2967,7 @@
}
ret = send_certificate_and_certificate_verify(tls, emitter, &tls->client.certificate_request.signature_algorithms,
tls->client.certificate_request.context,
- PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING, 0, NULL, 0);
+ PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING, 0, NULL, 0, 0);
free(tls->client.certificate_request.context.base);
tls->client.certificate_request.context = ptls_iovec_init(NULL, 0);
if (ret != 0)
@@ -3285,6 +3323,10 @@
} while (src != end);
});
break;
+ case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE:
+ ch->supported_server_certificate_types.list = src;
+ ch->supported_server_certificate_types.count = end - src;
+ break;
case PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE:
ptls_decode_block(src, end, 1, {
do {
@@ -3634,8 +3676,25 @@
ret = PTLS_ERROR_NO_MEMORY;
goto Exit;
}
- *ch = (struct st_ptls_client_hello_t){0, NULL, {NULL}, {NULL}, 0, {NULL}, {NULL}, {NULL}, {{0}},
- {NULL}, {NULL}, {{{NULL}}}, {{0}}, {{0}}, {{NULL}}, {NULL}, {{UINT16_MAX}}};
+
+ *ch = (struct st_ptls_client_hello_t){.legacy_version = 0,
+ .random_bytes = NULL,
+ .legacy_session_id = {NULL},
+ .compression_methods = {NULL},
+ .selected_version = 0,
+ .cipher_suites = {NULL},
+ .negotiated_groups = {NULL},
+ .key_shares = {NULL},
+ .signature_algorithms = {{0}},
+ .server_name = {NULL},
+ .esni = {NULL},
+ .alpn = {{{NULL}}},
+ .cert_compression_algos = {{0}},
+ .client_ciphers = {{0}},
+ .cookie = {{NULL}},
+ .psk = {NULL},
+ .supported_server_certificate_types = {NULL},
+ .unknown_extensions = {{UINT16_MAX}}};
/* decode ClientHello */
if ((ret = decode_client_hello(tls, ch, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len, properties)) !=
@@ -3937,23 +3996,45 @@
goto Exit;
tls->key_share = key_share.algorithm;
}
+ int send_raw_cert = 0;
+ if (mode == HANDSHAKE_MODE_FULL) {
+ if (ch->supported_server_certificate_types.list) {
+ int x509_ok = 0;
+ int raw_ok = 0;
+ for (size_t i = 0; i < ch->supported_server_certificate_types.count; i++) {
+ if (ch->supported_server_certificate_types.list[i] == PTLS_CERTIFICATE_TYPE_X509) {
+ x509_ok = 1;
+ } else if (ch->supported_server_certificate_types.list[i] == PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY) {
+ raw_ok = 1;
+ }
+ }
+ if ((!x509_ok && !raw_ok) || (!x509_ok && !tls->ctx->raw_certificate.base) ||
+ (!raw_ok && !tls->ctx->certificates.count)) {
+ ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE;
+ goto Exit;
+ }
+ if (raw_ok && tls->ctx->raw_certificate.base) {
+ send_raw_cert = 1;
+ }
+ }
+ }
/* send ServerHello */
- EMIT_SERVER_HELLO(tls->key_schedule,
- { tls->ctx->random_bytes(emitter->buf->base + emitter->buf->off, PTLS_HELLO_RANDOM_SIZE); },
- {
- ptls_buffer_t *sendbuf = emitter->buf;
- if (mode != HANDSHAKE_MODE_PSK) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, {
- ptls_buffer_push16(sendbuf, key_share.algorithm->id);
- ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, pubkey.base, pubkey.len); });
- });
- }
- if (mode != HANDSHAKE_MODE_FULL) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY,
- { ptls_buffer_push16(sendbuf, (uint16_t)psk_index); });
- }
- });
+ EMIT_SERVER_HELLO(
+ tls->key_schedule, { tls->ctx->random_bytes(emitter->buf->base + emitter->buf->off, PTLS_HELLO_RANDOM_SIZE); },
+ {
+ ptls_buffer_t *sendbuf = emitter->buf;
+ if (mode != HANDSHAKE_MODE_PSK) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, {
+ ptls_buffer_push16(sendbuf, key_share.algorithm->id);
+ ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, pubkey.base, pubkey.len); });
+ });
+ }
+ if (mode != HANDSHAKE_MODE_FULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY,
+ { ptls_buffer_push16(sendbuf, (uint16_t)psk_index); });
+ }
+ });
if ((ret = push_change_cipher_spec(tls, emitter)) != 0)
goto Exit;
@@ -3993,6 +4074,10 @@
* The "extension_data" field of this extension SHALL be empty. (RFC 6066 section 3) */
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {});
}
+ if (send_raw_cert) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE,
+ { ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); });
+ }
if (tls->negotiated_protocol != NULL) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ALPN, {
ptls_buffer_push_block(sendbuf, 2, {
@@ -4034,7 +4119,7 @@
ret = send_certificate_and_certificate_verify(tls, emitter, &ch->signature_algorithms, ptls_iovec_init(NULL, 0),
PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING, ch->status_request,
- ch->cert_compression_algos.list, ch->cert_compression_algos.count);
+ ch->cert_compression_algos.list, ch->cert_compression_algos.count, send_raw_cert);
if (ret != 0) {
goto Exit;
diff --git a/t/assets/server.pub b/t/assets/server.pub
new file mode 100644
index 0000000..52b7731
--- /dev/null
+++ b/t/assets/server.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7zZheZ4ph98JaedBNv9k
+qsVA9CSmhd69kBc9ZAfVFMA4VQwprOj3ZGrxf20HB3FkvqGvew9ZogUF6NjbPume
+iUObGpP21Y5wcYlPL4aojlrwMB/eOxOCpuRyQTRSSe1hDPvdJABQdmshDP5ZSEBL
+dUSgrNn4KWhIDjFj1AHXIMqeqTXetFuRgNzHdtbXQx+UWBis2B6qZJuqSArb2msV
+OC8D5gNznPPlQw7FbdPCaLNXSb6GnI0E0uj6QmYlAw9s6nkgP/zxjfFldqPNUprG
+cEqTwmAb8VVtd7XbANYrzubZ4Nn6/WXrCrVxWUmh/7Spgdwa/I4Nr1JHv9HHyL2z
+/wIDAQAB
+-----END PUBLIC KEY-----
diff --git a/t/cli.c b/t/cli.c
index bce9a65..aab4aac 100644
--- a/t/cli.c
+++ b/t/cli.c
@@ -97,6 +97,7 @@
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) {
@@ -358,6 +359,8 @@
" -E esni-file file that stores ESNI data generated by picotls-esni\n"
" -e when resuming a session, send first 8,192 bytes of input\n"
" as early data\n"
+ " -r Have the client ask for a raw certificate\n"
+ " -R raw-cert-key specifies a file to load for raw certificates\n"
" -u update the traffic key when handshake is complete\n"
" -v verify peer using the default certificates\n"
" -y cipher-suite cipher-suite to be used, e.g., aes128gcmsha256 (default:\n"
@@ -404,8 +407,14 @@
struct sockaddr_storage sa;
socklen_t salen;
int family = 0;
+ enum cert_types {
+ CERT_TYPES_NONE,
+ CERT_TYPES_X509 = 1,
+ CERT_TYPES_RAW = 2,
+ } cert_types_conf = CERT_TYPES_NONE;
+ const char *ca_file = NULL;
- while ((ch = getopt(argc, argv, "46abBC:c:i:Ik:nN:es:SE:K:l:y:vh")) != -1) {
+ while ((ch = getopt(argc, argv, "46aA:bBC:c:i:Ik:nN:es:Sr:R:E:K:l:y:vV:h")) != -1) {
switch (ch) {
case '4':
family = AF_INET;
@@ -416,6 +425,9 @@
case 'a':
ctx.require_client_authentication = 1;
break;
+ case 'A':
+ ca_file = optarg;
+ break;
case 'b':
#if PICOTLS_USE_BROTLI
ctx.decompress_certificate = &ptls_decompress_certificate;
@@ -451,6 +463,13 @@
case 'e':
use_early_data = 1;
break;
+ case 'r':
+ cert_types_conf = atoi(optarg);
+ break;
+ case 'R':
+ setup_raw_cert_file(&ctx, optarg);
+ is_server = 1;
+ break;
case 's':
setup_session_file(&ctx, &hsprop, optarg);
break;
@@ -483,7 +502,10 @@
setup_log_event(&ctx, optarg);
break;
case 'v':
- setup_verify_certificate(&ctx);
+ setup_verify_certificate(&ctx, ca_file);
+ break;
+ case 'V':
+ setup_raw_pubkey_verify_certificate(&ctx, optarg);
break;
case 'N': {
ptls_key_exchange_algorithm_t *algo = NULL;
@@ -540,13 +562,13 @@
}
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");
+ if ((ctx.certificates.count == 0 && ctx.raw_certificate.base == NULL) != (ctx.sign_certificate == NULL)) {
+ fprintf(stderr, "-C/-c or -R options must be used together with -k\n");
return 1;
}
if (is_server) {
- if (ctx.certificates.count == 0) {
- fprintf(stderr, "-c and -k options must be set\n");
+ if (ctx.certificates.count == 0 && ctx.raw_certificate.base == NULL) {
+ fprintf(stderr, "-c or -R options must be set with -k\n");
return 1;
}
#if PICOTLS_USE_BROTLI
@@ -569,6 +591,18 @@
}
ctx.send_change_cipher_spec = 1;
}
+ if (cert_types_conf != CERT_TYPES_NONE) {
+ static uint8_t cert_types[2];
+ size_t cert_types_count = 0;
+ if (cert_types_conf & CERT_TYPES_X509) {
+ cert_types[cert_types_count++] = 0;
+ }
+ if (cert_types_conf & CERT_TYPES_RAW) {
+ cert_types[cert_types_count++] = 2;
+ }
+ hsprop.client.supported_certificate_types.list = cert_types;
+ hsprop.client.supported_certificate_types.count = cert_types_count;
+ }
if (key_exchanges[0] == NULL)
key_exchanges[0] = &ptls_openssl_secp256r1;
if (cipher_suites[0] == NULL) {
diff --git a/t/e2e.t b/t/e2e.t
index 577bc20..0f07baf 100755
--- a/t/e2e.t
+++ b/t/e2e.t
@@ -16,7 +16,7 @@
my $tempdir = tempdir(CLEANUP => 1);
subtest "hello" => sub {
- my $guard = spawn_server(qw(-i t/assets/hello.txt));
+ my $guard = spawn_server("-c", qw(-i t/assets/hello.txt));
subtest "full-handshake" => sub {
my $resp = `$cli 127.0.0.1 $port 2> /dev/null`;
is $resp, "hello";
@@ -35,7 +35,7 @@
subtest "success" => sub {
plan skip_all => "faketime not found"
unless system("which faketime > /dev/null 2>&1") == 0;
- my $guard = spawn_server(qw(-i t/assets/hello.txt -l), "$tempdir/events");
+ my $guard = spawn_server("-c", qw(-i t/assets/hello.txt -l), "$tempdir/events");
my $resp = `$cli -s $tempdir/session 127.0.0.1 $port`;
is $resp, "hello";
$resp = `$cli -e -s $tempdir/session 127.0.0.1 $port`;
@@ -63,17 +63,38 @@
subtest "certificate-compression" => sub {
plan skip_all => "feature disabled"
unless system("$cli -b -h > /dev/null 2>&1") == 0;
- my $guard = spawn_server(qw(-i t/assets/hello.txt -b));
+ my $guard = spawn_server("-c", qw(-i t/assets/hello.txt -b));
my $resp = `$cli 127.0.0.1 $port 2> /dev/null`;
is $resp, "hello";
$resp = `$cli -b 127.0.0.1 $port 2> /dev/null`;
is $resp, "hello";
};
+subtest "certificate-compression" => sub {
+ plan skip_all => "feature disabled"
+ unless system("$cli -b -h > /dev/null 2>&1") == 0;
+ my $guard = spawn_server("-c", qw(-i t/assets/hello.txt -b));
+ my $resp = `$cli 127.0.0.1 $port 2> /dev/null`;
+ is $resp, "hello";
+ $resp = `$cli -b 127.0.0.1 $port 2> /dev/null`;
+ is $resp, "hello";
+};
+
+subtest "raw-certificates" => sub {
+ my $guard = spawn_server("-R", qw(-i t/assets/hello.txt));
+ my $resp = `$cli -r 2 -V t/assets/server.pub 127.0.0.1 $port 2> /dev/null`;
+ is $resp, "hello";
+};
+
done_testing;
sub spawn_server {
- my @cmd = ($cli, "-k", "t/assets/server.key", "-c", "t/assets/server.crt", @_, "127.0.0.1", $port);
+ my $cert_option = shift;
+ my $pub = "t/assets/server.crt";
+ if ($cert_option eq "-R") {
+ $pub = "t/assets/server.pub";
+ }
+ my @cmd = ($cli, "-k", "t/assets/server.key", $cert_option, $pub, @_, "127.0.0.1", $port);
my $pid = fork;
die "fork failed:$!"
unless defined $pid;
diff --git a/t/util.h b/t/util.h
index db86416..130bf0c 100644
--- a/t/util.h
+++ b/t/util.h
@@ -115,13 +115,69 @@
}
}
-static inline void setup_verify_certificate(ptls_context_t *ctx)
+
+static ptls_iovec_t raw_cert_from_file(const char *fn)
{
+ FILE *fp;
+
+ fp = fopen(fn, "rb");
+ if (fp == NULL)
+ goto Err;
+
+ RSA *pubkey = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
+ if (pubkey == NULL) {
+ ERR_print_errors_fp(stderr);
+ goto Err;
+ }
+
+ unsigned char *out = NULL;
+ int outlen;
+ outlen = i2d_RSA_PUBKEY(pubkey, &out);
+
+ if (outlen == 0) {
+ ERR_print_errors_fp(stderr);
+ goto Err;
+ }
+ fclose(fp);
+
+ return ptls_iovec_init(out, outlen);
+Err:
+ fprintf(stderr, "failed to load raw cert from file:%s\n", fn);
+ exit(1);
+ return ptls_iovec_init(NULL, 0);
+}
+
+static void setup_raw_cert_file(ptls_context_t *ctx, const char *fn)
+{
+ ctx->raw_certificate = raw_cert_from_file(fn);
+}
+
+static inline void setup_verify_certificate(ptls_context_t *ctx, const char *cafile)
+{
+ X509_STORE *store = NULL;
+
+ if (cafile != NULL) {
+ store = X509_STORE_new();
+ int ret = X509_STORE_load_locations(store, cafile, NULL);
+ if (ret != 1) {
+ fprintf(stderr, "failed to load CA from file: %s\n", cafile);
+ exit(1);
+ }
+ }
static ptls_openssl_verify_certificate_t vc;
- ptls_openssl_init_verify_certificate(&vc, NULL);
+ ptls_openssl_init_verify_certificate(&vc, store);
ctx->verify_certificate = &vc.super;
}
+static inline void setup_raw_pubkey_verify_certificate(ptls_context_t *ctx, const char *fn)
+{
+ static ptls_raw_pubkey_verify_certificate_t vc;
+ ptls_raw_pubkey_init_verify_certificate(&vc);
+ vc.expected_pubkey = raw_cert_from_file(fn);
+ ctx->verify_certificate = &vc.super;
+
+}
+
static inline void setup_esni(ptls_context_t *ctx, const char *esni_fn, ptls_key_exchange_context_t **key_exchanges)
{
uint8_t esnikeys[65536];