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];