Merge branch 'kazuho/pr/335' into kazuho/server-cipher-preference
diff --git a/.travis.yml b/.travis.yml
index 49feef0..2a9d1c6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,19 +4,29 @@
 
 matrix:
   include:
-    - name: Linux (gcc-8)
+    - name: Linux (gcc)
       os: linux
-      compiler: gcc-8
-      addons:
-        apt:
-          sources: ['ubuntu-toolchain-r-test']
-          packages: ['gcc-8']
+      dist: focal
+      compiler: gcc
       before_install: &bs_linux
         - sudo apt-get install faketime libscope-guard-perl libtest-tcp-perl
     - name: Linux (clang)
       os: linux
+      dist: focal
       compiler: clang
       before_install: *bs_linux
+    - name: Linux (OpenSSL 1.1.0)
+      os: linux
+      before_install:
+        - sudo apt-get install faketime libscope-guard-perl libtest-tcp-perl
+        - curl https://www.openssl.org/source/old/1.1.0/openssl-1.1.0l.tar.gz | tar xzf -
+        - cd openssl-1.1.0l
+        - ./config --prefix=/usr/local/openssl-1.1.0
+        - make
+        - sudo make install
+        - cd ..
+      env:
+        - PKG_CONFIG_PATH=/usr/local/openssl-1.1.0/lib/pkgconfig
     - name: Linux (OpenSSL 1.0.2)
       os: linux
       before_install:
@@ -29,6 +39,18 @@
         - cd ..
       env:
         - PKG_CONFIG_PATH=/usr/local/openssl-1.0.2/lib/pkgconfig
+    - name: Linux (libressl 3.2)
+      os: linux
+      before_install:
+        - sudo apt-get install faketime libscope-guard-perl libtest-tcp-perl
+        - curl https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-3.2.5.tar.gz | tar xzf -
+        - cd libressl-3.2.5
+        - ./configure --prefix=/usr/local/libressl-3.2
+        - make
+        - sudo make install
+        - cd ..
+      env:
+        - PKG_CONFIG_PATH=/usr/local/libressl-3.2/lib/pkgconfig
     - name: macOS (Xcode)
       os: osx
       addons: &addons_macos
diff --git a/README.md b/README.md
index afbc60e..d7c5dbc 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,10 @@
   * ["fusion" AES-GCM engine, optimized for QUIC and other protocols that use short AEAD blocks](https://github.com/h2o/picotls/pull/310)
 * support for PSK, PSK-DHE resumption using 0-RTT
 * API for dealing directly with TLS handshake messages (essential for QUIC)
-* support for new extensions: Encrypted SNI (wg-draft-02), Certificate Compression (wg-draft-10)
+* supported extensions:
+  * RFC 7250 (raw public keys)
+  * RFC 8879 (certificate compression)
+  * Encrypted SNI (wg-draft-02)
 
 Primary goal of the project is to create a fast, tiny, low-latency TLS 1.3 implementation that can be used with the HTTP/2 protocol stack and the upcoming QUIC stack of the [H2O HTTP/2 server](https://h2o.examp1e.net).
 
@@ -20,8 +23,8 @@
 
 | Binding | License | Key Exchange | Certificate | AEAD cipher |
 |:-----:|:-----:|:-----:|:-----:|:-----:|
-| minicrypto | [CC0](https://github.com/ctz/cifra/) / [2-clause BSD](https://github.com/kmackay/micro-ecc) | secp256r1, x25519 | ECDSA (P256)<sup>1</sup> | AES-128-GCM, chacha20-poly1305 |
-| OpenSSL | OpenSSL | secp256r1, secp384r1, secp521r1, x25519 | RSA, ECDSA (P256) | AES-128-GCM, AES-256-GCM, chacha20-poly1305 |
+| minicrypto | [CC0](https://github.com/ctz/cifra/) / [2-clause BSD](https://github.com/kmackay/micro-ecc) | secp256r1, x25519 | ECDSA (secp256r1)<sup>1</sup> | AES-128-GCM, chacha20-poly1305 |
+| OpenSSL | OpenSSL | secp256r1, secp384r1, secp521r1, x25519 | RSA, ECDSA (secp256r1, secp384r1, secp521r1), ed25519 | AES-128-GCM, AES-256-GCM, chacha20-poly1305 |
 
 Note 1: Minicrypto binding is capable of signing a handshake using the certificate's key, but cannot verify a signature sent by the peer.
 
diff --git a/include/picotls.h b/include/picotls.h
index b61a5e8..b7f13d2 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -124,6 +124,7 @@
 #define PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256 0x0804
 #define PTLS_SIGNATURE_RSA_PSS_RSAE_SHA384 0x0805
 #define PTLS_SIGNATURE_RSA_PSS_RSAE_SHA512 0x0806
+#define PTLS_SIGNATURE_ED25519 0x0807
 
 /* ESNI */
 #define PTLS_ESNI_VERSION_DRAFT03 0xff02
@@ -153,6 +154,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 +212,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,    \
@@ -531,6 +536,10 @@
         const uint16_t *list;
         size_t count;
     } cipher_suites;
+    struct {
+        const uint8_t *list;
+        size_t count;
+    } server_certificate_types;
     /**
      * if ESNI was used
      */
@@ -567,9 +576,15 @@
  * The implementor of the callback should use that as the opportunity to free any temporary data allocated for the verify_sign
  * callback.
  */
-PTLS_CALLBACK_TYPE(int, verify_certificate, ptls_t *tls,
-                   int (**verify_sign)(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t sign), void **verify_data,
-                   ptls_iovec_t *certs, size_t num_certs);
+typedef struct st_ptls_verify_certificate_t {
+    int (*cb)(struct st_ptls_verify_certificate_t *self, ptls_t *tls,
+              int (**verify_sign)(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t sign), void **verify_data,
+              ptls_iovec_t *certs, size_t num_certs);
+    /**
+     * list of signature algorithms being supported, terminated by UINT16_MAX
+     */
+    const uint16_t *algos;
+} ptls_verify_certificate_t;
 /**
  * Encrypt-and-signs (or verify-and-decrypts) a ticket (server-only).
  * When used for encryption (i.e., is_encrypt being set), the function should return 0 if successful, or else a non-zero value.
@@ -708,7 +723,20 @@
      */
     unsigned omit_end_of_early_data : 1;
     /**
-     * server cipher preference
+     * This option turns on support for Raw Public Keys (RFC 7250).
+     *
+     * When running as a client, this option instructs the client to request the server to send raw public keys in place of X.509
+     * certificate chain. The client should set its `certificate_verify` callback to one that is capable of validating the raw
+     * public key that will be sent by the server.
+     *
+     * When running as a server, this option instructs the server to only handle clients requesting the use of raw public keys. If
+     * the client does not, the handshake is rejected. Note however that the rejection happens only after the `on_client_hello`
+     * callback is being called. Therefore, applications can support both X.509 and raw public keys by swapping `ptls_context_t` to
+     * the correct one when that callback is being called (like handling swapping the contexts based on the value of SNI).
+     */
+    unsigned use_raw_public_keys : 1;
+    /**
+     * boolean indicating if the cipher-suite should be chosen based on server's preference
      */
     unsigned server_cipher_preference : 1;
     /**
diff --git a/include/picotls/openssl.h b/include/picotls/openssl.h
index 2bfe1b9..8e26cf8 100644
--- a/include/picotls/openssl.h
+++ b/include/picotls/openssl.h
@@ -50,9 +50,12 @@
 #define PTLS_OPENSSL_HAS_SECP521R1 1 /* deprecated; use HAVE_ */
 extern ptls_key_exchange_algorithm_t ptls_openssl_secp521r1;
 #endif
+#ifdef EVP_PKEY_ED25519
+#define PTLS_OPENSSL_HAVE_ED25519 1
+#endif
 #if defined(NID_X25519) && !defined(LIBRESSL_VERSION_NUMBER)
 #define PTLS_OPENSSL_HAVE_X25519 1
-#define PTLS_OPENSSL_HAS_X25519 1  /* deprecated; use HAVE_ */
+#define PTLS_OPENSSL_HAS_X25519 1 /* deprecated; use HAVE_ */
 extern ptls_key_exchange_algorithm_t ptls_openssl_x25519;
 #endif
 #ifndef OPENSSL_NO_BF
@@ -91,19 +94,24 @@
 
 struct st_ptls_openssl_signature_scheme_t {
     uint16_t scheme_id;
-    const EVP_MD *scheme_md;
+    const EVP_MD *(*scheme_md)(void);
 };
 
 typedef struct st_ptls_openssl_sign_certificate_t {
     ptls_sign_certificate_t super;
     EVP_PKEY *key;
-    struct st_ptls_openssl_signature_scheme_t schemes[4]; /* terminated by .scheme_id == UINT16_MAX */
+    const struct st_ptls_openssl_signature_scheme_t *schemes; /* terminated by .scheme_id == UINT16_MAX */
 } ptls_openssl_sign_certificate_t;
 
 int ptls_openssl_init_sign_certificate(ptls_openssl_sign_certificate_t *self, EVP_PKEY *key);
 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_openssl_raw_pubkey_verify_certificate_t {
+    ptls_verify_certificate_t super;
+    EVP_PKEY *expected_pubkey;
+} ptls_openssl_raw_pubkey_verify_certificate_t;
+
 typedef struct st_ptls_openssl_verify_certificate_t {
     ptls_verify_certificate_t super;
     X509_STORE *cert_store;
@@ -113,6 +121,9 @@
 void ptls_openssl_dispose_verify_certificate(ptls_openssl_verify_certificate_t *self);
 X509_STORE *ptls_openssl_create_default_certificate_store(void);
 
+int ptls_openssl_raw_pubkey_init_verify_certificate(ptls_openssl_raw_pubkey_verify_certificate_t *self, EVP_PKEY *pubkey);
+void ptls_openssl_raw_pubkey_dispose_verify_certificate(ptls_openssl_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/openssl.c b/lib/openssl.c
index 00c7777..80613bb 100644
--- a/lib/openssl.c
+++ b/lib/openssl.c
@@ -88,6 +88,85 @@
 
 #endif
 
+static const struct st_ptls_openssl_signature_scheme_t rsa_signature_schemes[] = {{PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, EVP_sha256},
+                                                                                  {PTLS_SIGNATURE_RSA_PSS_RSAE_SHA384, EVP_sha384},
+                                                                                  {PTLS_SIGNATURE_RSA_PSS_RSAE_SHA512, EVP_sha512},
+                                                                                  {UINT16_MAX, NULL}};
+static const struct st_ptls_openssl_signature_scheme_t secp256r1_signature_schemes[] = {
+    {PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256, EVP_sha256}, {UINT16_MAX, NULL}};
+#if PTLS_OPENSSL_HAVE_SECP384R1
+static const struct st_ptls_openssl_signature_scheme_t secp384r1_signature_schemes[] = {
+    {PTLS_SIGNATURE_ECDSA_SECP384R1_SHA384, EVP_sha384}, {UINT16_MAX, NULL}};
+#endif
+#if PTLS_OPENSSL_HAVE_SECP521R1
+static const struct st_ptls_openssl_signature_scheme_t secp521r1_signature_schemes[] = {
+    {PTLS_SIGNATURE_ECDSA_SECP521R1_SHA512, EVP_sha512}, {UINT16_MAX, NULL}};
+#endif
+#if PTLS_OPENSSL_HAVE_ED25519
+static const struct st_ptls_openssl_signature_scheme_t ed25519_signature_schemes[] = {{PTLS_SIGNATURE_ED25519, NULL},
+                                                                                      {UINT16_MAX, NULL}};
+#endif
+
+/**
+ * The default list sent in ClientHello.signature_algorithms. ECDSA certificates are preferred.
+ */
+static const uint16_t default_signature_schemes[] = {
+#if PTLS_OPENSSL_HAVE_ED25519
+    PTLS_SIGNATURE_ED25519,
+#endif
+    PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256,
+#if PTLS_OPENSSL_HAVE_SECP384R1
+    PTLS_SIGNATURE_ECDSA_SECP384R1_SHA384,
+#endif
+#if PTLS_OPENSSL_HAVE_SECP521R1
+    PTLS_SIGNATURE_ECDSA_SECP521R1_SHA512,
+#endif
+    PTLS_SIGNATURE_RSA_PSS_RSAE_SHA512,
+    PTLS_SIGNATURE_RSA_PSS_RSAE_SHA384,
+    PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256,
+    UINT16_MAX};
+
+static const struct st_ptls_openssl_signature_scheme_t *lookup_signature_schemes(EVP_PKEY *key)
+{
+    const struct st_ptls_openssl_signature_scheme_t *schemes = NULL;
+
+    switch (EVP_PKEY_id(key)) {
+    case EVP_PKEY_RSA:
+        schemes = rsa_signature_schemes;
+        break;
+    case EVP_PKEY_EC: {
+        EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(key);
+        switch (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))) {
+        case NID_X9_62_prime256v1:
+            schemes = secp256r1_signature_schemes;
+            break;
+#if PTLS_OPENSSL_HAVE_SECP384R1
+        case NID_secp384r1:
+            schemes = secp384r1_signature_schemes;
+            break;
+#endif
+#if PTLS_OPENSSL_HAVE_SECP521R1
+        case NID_secp521r1:
+            schemes = secp521r1_signature_schemes;
+            break;
+#endif
+        default:
+            break;
+        }
+        EC_KEY_free(eckey);
+    } break;
+#if PTLS_OPENSSL_HAVE_ED25519
+    case EVP_PKEY_ED25519:
+        schemes = ed25519_signature_schemes;
+        break;
+#endif
+    default:
+        break;
+    }
+
+    return schemes;
+}
+
 void ptls_openssl_random_bytes(void *buf, size_t len)
 {
     int ret = RAND_bytes(buf, (int)len);
@@ -613,9 +692,11 @@
     }
 }
 
-static int do_sign(EVP_PKEY *key, ptls_buffer_t *outbuf, ptls_iovec_t input, const EVP_MD *md)
+static int do_sign(EVP_PKEY *key, const struct st_ptls_openssl_signature_scheme_t *scheme, ptls_buffer_t *outbuf,
+                   ptls_iovec_t input)
 {
     EVP_MD_CTX *ctx = NULL;
+    const EVP_MD *md = scheme->scheme_md != NULL ? scheme->scheme_md() : NULL;
     EVP_PKEY_CTX *pkey_ctx;
     size_t siglen;
     int ret;
@@ -624,38 +705,58 @@
         ret = PTLS_ERROR_NO_MEMORY;
         goto Exit;
     }
+
     if (EVP_DigestSignInit(ctx, &pkey_ctx, md, NULL, key) != 1) {
         ret = PTLS_ERROR_LIBRARY;
         goto Exit;
     }
-    if (EVP_PKEY_id(key) == EVP_PKEY_RSA) {
-        if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) {
+
+#if PTLS_OPENSSL_HAVE_ED25519
+    if (EVP_PKEY_id(key) == EVP_PKEY_ED25519) {
+        /* ED25519 requires the use of the all-at-once function that appeared in OpenSSL 1.1.1, hence different path */
+        if (EVP_DigestSign(ctx, NULL, &siglen, input.base, input.len) != 1) {
             ret = PTLS_ERROR_LIBRARY;
             goto Exit;
         }
-        if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) {
+        if ((ret = ptls_buffer_reserve(outbuf, siglen)) != 0)
+            goto Exit;
+        if (EVP_DigestSign(ctx, outbuf->base + outbuf->off, &siglen, input.base, input.len) != 1) {
             ret = PTLS_ERROR_LIBRARY;
             goto Exit;
         }
-        if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, EVP_sha256()) != 1) {
+    } else
+#endif
+    {
+        if (EVP_PKEY_id(key) == EVP_PKEY_RSA) {
+            if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) {
+                ret = PTLS_ERROR_LIBRARY;
+                goto Exit;
+            }
+            if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) {
+                ret = PTLS_ERROR_LIBRARY;
+                goto Exit;
+            }
+            if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, md) != 1) {
+                ret = PTLS_ERROR_LIBRARY;
+                goto Exit;
+            }
+        }
+        if (EVP_DigestSignUpdate(ctx, input.base, input.len) != 1) {
+            ret = PTLS_ERROR_LIBRARY;
+            goto Exit;
+        }
+        if (EVP_DigestSignFinal(ctx, NULL, &siglen) != 1) {
+            ret = PTLS_ERROR_LIBRARY;
+            goto Exit;
+        }
+        if ((ret = ptls_buffer_reserve(outbuf, siglen)) != 0)
+            goto Exit;
+        if (EVP_DigestSignFinal(ctx, outbuf->base + outbuf->off, &siglen) != 1) {
             ret = PTLS_ERROR_LIBRARY;
             goto Exit;
         }
     }
-    if (EVP_DigestSignUpdate(ctx, input.base, input.len) != 1) {
-        ret = PTLS_ERROR_LIBRARY;
-        goto Exit;
-    }
-    if (EVP_DigestSignFinal(ctx, NULL, &siglen) != 1) {
-        ret = PTLS_ERROR_LIBRARY;
-        goto Exit;
-    }
-    if ((ret = ptls_buffer_reserve(outbuf, siglen)) != 0)
-        goto Exit;
-    if (EVP_DigestSignFinal(ctx, outbuf->base + outbuf->off, &siglen) != 1) {
-        ret = PTLS_ERROR_LIBRARY;
-        goto Exit;
-    }
+
     outbuf->off += siglen;
 
     ret = 0;
@@ -944,7 +1045,7 @@
     ptls_openssl_sign_certificate_t *self = (ptls_openssl_sign_certificate_t *)_self;
     const struct st_ptls_openssl_signature_scheme_t *scheme;
 
-    /* select the algorithm */
+    /* select the algorithm (driven by server-side preference of `self->schemes`) */
     for (scheme = self->schemes; scheme->scheme_id != UINT16_MAX; ++scheme) {
         size_t i;
         for (i = 0; i != num_algorithms; ++i)
@@ -955,7 +1056,7 @@
 
 Found:
     *selected_algorithm = scheme->scheme_id;
-    return do_sign(self->key, outbuf, input, scheme->scheme_md);
+    return do_sign(self->key, scheme, outbuf, input);
 }
 
 static X509 *to_x509(ptls_iovec_t vec)
@@ -964,9 +1065,10 @@
     return d2i_X509(NULL, &p, (long)vec.len);
 }
 
-static int verify_sign(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t signature)
+static int verify_sign(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t signature)
 {
     EVP_PKEY *key = verify_ctx;
+    const struct st_ptls_openssl_signature_scheme_t *scheme;
     EVP_MD_CTX *ctx = NULL;
     EVP_PKEY_CTX *pkey_ctx = NULL;
     int ret = 0;
@@ -974,36 +1076,65 @@
     if (data.base == NULL)
         goto Exit;
 
+    if ((scheme = lookup_signature_schemes(key)) == NULL) {
+        ret = PTLS_ERROR_LIBRARY;
+        goto Exit;
+    }
+    for (; scheme->scheme_id != UINT16_MAX; ++scheme)
+        if (scheme->scheme_id == algo)
+            goto SchemeFound;
+    ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+    goto Exit;
+
+SchemeFound:
     if ((ctx = EVP_MD_CTX_create()) == NULL) {
         ret = PTLS_ERROR_NO_MEMORY;
         goto Exit;
     }
-    if (EVP_DigestVerifyInit(ctx, &pkey_ctx, EVP_sha256(), NULL, key) != 1) {
-        ret = PTLS_ERROR_LIBRARY;
-        goto Exit;
-    }
-    if (EVP_PKEY_id(key) == EVP_PKEY_RSA) {
-        if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) {
+
+#if PTLS_OPENSSL_HAVE_ED25519
+    if (EVP_PKEY_id(key) == EVP_PKEY_ED25519) {
+        /* ED25519 requires the use of the all-at-once function that appeared in OpenSSL 1.1.1, hence different path */
+        if (EVP_DigestVerifyInit(ctx, &pkey_ctx, NULL, NULL, key) != 1) {
             ret = PTLS_ERROR_LIBRARY;
             goto Exit;
         }
-        if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) {
+        if (EVP_DigestVerify(ctx, signature.base, signature.len, data.base, data.len) != 1) {
             ret = PTLS_ERROR_LIBRARY;
             goto Exit;
         }
-        if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, EVP_sha256()) != 1) {
+    } else
+#endif
+    {
+        if (EVP_DigestVerifyInit(ctx, &pkey_ctx, scheme->scheme_md(), NULL, key) != 1) {
             ret = PTLS_ERROR_LIBRARY;
             goto Exit;
         }
+
+        if (EVP_PKEY_id(key) == EVP_PKEY_RSA) {
+            if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) {
+                ret = PTLS_ERROR_LIBRARY;
+                goto Exit;
+            }
+            if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, -1) != 1) {
+                ret = PTLS_ERROR_LIBRARY;
+                goto Exit;
+            }
+            if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, scheme->scheme_md()) != 1) {
+                ret = PTLS_ERROR_LIBRARY;
+                goto Exit;
+            }
+        }
+        if (EVP_DigestVerifyUpdate(ctx, data.base, data.len) != 1) {
+            ret = PTLS_ERROR_LIBRARY;
+            goto Exit;
+        }
+        if (EVP_DigestVerifyFinal(ctx, signature.base, signature.len) != 1) {
+            ret = PTLS_ALERT_DECRYPT_ERROR;
+            goto Exit;
+        }
     }
-    if (EVP_DigestVerifyUpdate(ctx, data.base, data.len) != 1) {
-        ret = PTLS_ERROR_LIBRARY;
-        goto Exit;
-    }
-    if (EVP_DigestVerifyFinal(ctx, signature.base, signature.len) != 1) {
-        ret = PTLS_ALERT_DECRYPT_ERROR;
-        goto Exit;
-    }
+
     ret = 0;
 
 Exit:
@@ -1016,50 +1147,9 @@
 int ptls_openssl_init_sign_certificate(ptls_openssl_sign_certificate_t *self, EVP_PKEY *key)
 {
     *self = (ptls_openssl_sign_certificate_t){{sign_certificate}};
-    size_t scheme_index = 0;
 
-#define PUSH_SCHEME(id, md)                                                                                                        \
-    self->schemes[scheme_index++] = (struct st_ptls_openssl_signature_scheme_t)                                                    \
-    {                                                                                                                              \
-        id, md                                                                                                                     \
-    }
-
-    switch (EVP_PKEY_id(key)) {
-    case EVP_PKEY_RSA:
-        PUSH_SCHEME(PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, EVP_sha256());
-        PUSH_SCHEME(PTLS_SIGNATURE_RSA_PSS_RSAE_SHA384, EVP_sha384());
-        PUSH_SCHEME(PTLS_SIGNATURE_RSA_PSS_RSAE_SHA512, EVP_sha512());
-        break;
-    case EVP_PKEY_EC: {
-        EC_KEY *eckey = EVP_PKEY_get1_EC_KEY(key);
-        switch (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey))) {
-        case NID_X9_62_prime256v1:
-            PUSH_SCHEME(PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256, EVP_sha256());
-            break;
-#if defined(NID_secp384r1) && !OPENSSL_NO_SHA384
-        case NID_secp384r1:
-            PUSH_SCHEME(PTLS_SIGNATURE_ECDSA_SECP384R1_SHA384, EVP_sha384());
-            break;
-#endif
-#if defined(NID_secp384r1) && !OPENSSL_NO_SHA512
-        case NID_secp521r1:
-            PUSH_SCHEME(PTLS_SIGNATURE_ECDSA_SECP521R1_SHA512, EVP_sha512());
-            break;
-#endif
-        default:
-            EC_KEY_free(eckey);
-            return PTLS_ERROR_INCOMPATIBLE_KEY;
-        }
-        EC_KEY_free(eckey);
-    } break;
-    default:
+    if ((self->schemes = lookup_signature_schemes(key)) == NULL)
         return PTLS_ERROR_INCOMPATIBLE_KEY;
-    }
-    PUSH_SCHEME(UINT16_MAX, NULL);
-    assert(scheme_index <= PTLS_ELEMENTSOF(self->schemes));
-
-#undef PUSH_SCHEME
-
     EVP_PKEY_up_ref(key);
     self->key = key;
 
@@ -1200,8 +1290,9 @@
     return ret;
 }
 
-static int verify_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)
+static int verify_cert(ptls_verify_certificate_t *_self, ptls_t *tls,
+                       int (**verifier)(void *, uint16_t, ptls_iovec_t, ptls_iovec_t), void **verify_data, ptls_iovec_t *certs,
+                       size_t num_certs)
 {
     ptls_openssl_verify_certificate_t *self = (ptls_openssl_verify_certificate_t *)_self;
     X509 *cert = NULL;
@@ -1246,7 +1337,7 @@
 
 int ptls_openssl_init_verify_certificate(ptls_openssl_verify_certificate_t *self, X509_STORE *store)
 {
-    *self = (ptls_openssl_verify_certificate_t){{verify_cert}};
+    *self = (ptls_openssl_verify_certificate_t){{verify_cert, default_signature_schemes}};
 
     if (store != NULL) {
         X509_STORE_up_ref(store);
@@ -1286,6 +1377,52 @@
     return NULL;
 }
 
+static int verify_raw_cert(ptls_verify_certificate_t *_self, ptls_t *tls,
+                           int (**verifier)(void *, uint16_t algo, ptls_iovec_t, ptls_iovec_t), void **verify_data,
+                           ptls_iovec_t *certs, size_t num_certs)
+{
+    ptls_openssl_raw_pubkey_verify_certificate_t *self = (ptls_openssl_raw_pubkey_verify_certificate_t *)_self;
+    int ret = PTLS_ALERT_BAD_CERTIFICATE;
+    ptls_iovec_t expected_pubkey = {};
+
+    assert(num_certs != 0);
+
+    if (num_certs != 1)
+        goto Exit;
+
+    int r = i2d_PUBKEY(self->expected_pubkey, &expected_pubkey.base);
+    if (r <= 0) {
+        ret = PTLS_ALERT_BAD_CERTIFICATE;
+        goto Exit;
+    }
+
+    expected_pubkey.len = r;
+    if (certs[0].len != expected_pubkey.len)
+        goto Exit;
+
+    if (!ptls_mem_equal(expected_pubkey.base, certs[0].base, certs[0].len))
+        goto Exit;
+
+    EVP_PKEY_up_ref(self->expected_pubkey);
+    *verify_data = self->expected_pubkey;
+    *verifier = verify_sign;
+    ret = 0;
+Exit:
+    free(expected_pubkey.base);
+    return ret;
+}
+
+int ptls_openssl_raw_pubkey_init_verify_certificate(ptls_openssl_raw_pubkey_verify_certificate_t *self, EVP_PKEY *expected_pubkey)
+{
+    EVP_PKEY_up_ref(expected_pubkey);
+    *self = (ptls_openssl_raw_pubkey_verify_certificate_t){{verify_raw_cert, default_signature_schemes}, expected_pubkey};
+    return 0;
+}
+void ptls_openssl_raw_pubkey_dispose_verify_certificate(ptls_openssl_raw_pubkey_verify_certificate_t *self)
+{
+    EVP_PKEY_free(self->expected_pubkey);
+}
+
 #define TICKET_LABEL_SIZE 16
 #define TICKET_IV_SIZE EVP_MAX_IV_LENGTH
 
diff --git a/lib/picotls.c b/lib/picotls.c
index 2428239..e2894fb 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
@@ -256,7 +257,7 @@
      * will be used by the client and the server (if require_client_authentication is set).
      */
     struct {
-        int (*cb)(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t signature);
+        int (*cb)(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t signature);
         void *verify_ctx;
     } certificate_verify;
     /**
@@ -284,6 +285,7 @@
 
 #define MAX_UNKNOWN_EXTENSIONS 16
 #define MAX_CLIENT_CIPHERS 32
+#define MAX_CERTIFICATE_TYPES 8
 
 struct st_ptls_client_hello_t {
     uint16_t legacy_version;
@@ -335,6 +337,10 @@
         unsigned early_data_indication : 1;
         unsigned is_last_extension : 1;
     } psk;
+    struct {
+        uint8_t list[MAX_CERTIFICATE_TYPES];
+        size_t count;
+    } server_certificate_types;
     ptls_raw_extension_t unknown_extensions[MAX_UNKNOWN_EXTENSIONS + 1];
     unsigned status_request : 1;
 };
@@ -462,6 +468,10 @@
         ALLOW(CLIENT_HELLO);
         ALLOW(SERVER_HELLO);
     });
+    EXT(SERVER_CERTIFICATE_TYPE, {
+        ALLOW(CLIENT_HELLO);
+        ALLOW(ENCRYPTED_EXTENSIONS);
+    });
 
 #undef ALLOW
 #undef EXT
@@ -1509,15 +1519,16 @@
     return ret;
 }
 
-static int push_signature_algorithms(ptls_buffer_t *sendbuf)
+static int push_signature_algorithms(ptls_verify_certificate_t *vc, ptls_buffer_t *sendbuf)
 {
+    /* The list sent when verify callback is not registered */
+    static const uint16_t default_algos[] = {PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256,
+                                             PTLS_SIGNATURE_RSA_PKCS1_SHA256, PTLS_SIGNATURE_RSA_PKCS1_SHA1, UINT16_MAX};
     int ret;
 
     ptls_buffer_push_block(sendbuf, 2, {
-        ptls_buffer_push16(sendbuf, PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256);
-        ptls_buffer_push16(sendbuf, PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256);
-        ptls_buffer_push16(sendbuf, PTLS_SIGNATURE_RSA_PKCS1_SHA256);
-        ptls_buffer_push16(sendbuf, PTLS_SIGNATURE_RSA_PKCS1_SHA1);
+        for (const uint16_t *p = vc != NULL ? vc->algos : default_algos; *p != UINT16_MAX; ++p)
+            ptls_buffer_push16(sendbuf, *p);
     });
 
     ret = 0;
@@ -2064,7 +2075,7 @@
                 });
             });
             buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS, {
-                if ((ret = push_signature_algorithms(sendbuf)) != 0)
+                if ((ret = push_signature_algorithms(tls->ctx->verify_certificate, sendbuf)) != 0)
                     goto Exit;
             });
             buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS, {
@@ -2079,6 +2090,11 @@
                     ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, cookie->base, cookie->len); });
                 });
             }
+            if (tls->ctx->use_raw_public_keys) {
+                buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, {
+                    ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); });
+                });
+            }
             if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
                 goto Exit;
             if (tls->ctx->save_ticket != NULL || resumption_secret.base != NULL) {
@@ -2439,6 +2455,7 @@
     static const ptls_raw_extension_t no_unknown_extensions = {UINT16_MAX};
     ptls_raw_extension_t *unknown_extensions = (ptls_raw_extension_t *)&no_unknown_extensions;
     int ret, skip_early_data = 1;
+    uint8_t server_offered_cert_type = PTLS_CERTIFICATE_TYPE_X509;
 
     decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, &type, {
         if (tls->ctx->on_extension != NULL &&
@@ -2493,6 +2510,14 @@
             }
             skip_early_data = 0;
             break;
+        case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE:
+            if (end - src != 1) {
+                ret = PTLS_ALERT_DECODE_ERROR;
+                goto Exit;
+            }
+            server_offered_cert_type = *src;
+            src = end;
+            break;
         default:
             if (should_collect_unknown_extension(tls, properties, type)) {
                 if (unknown_extensions == &no_unknown_extensions) {
@@ -2510,6 +2535,12 @@
         src = end;
     });
 
+    if (server_offered_cert_type !=
+        (tls->ctx->use_raw_public_keys ? PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY : PTLS_CERTIFICATE_TYPE_X509)) {
+        ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE;
+        goto Exit;
+    }
+
     if (tls->esni != NULL) {
         if (esni_nonce == NULL || !ptls_mem_equal(esni_nonce, tls->esni->nonce, PTLS_ESNI_NONCE_SIZE)) {
             ret = PTLS_ALERT_ILLEGAL_PARAMETER;
@@ -2852,19 +2883,10 @@
         src = end;
     });
 
-    /* validate */
-    switch (algo) {
-    case PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256:
-    case PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256:
-        /* ok */
-        break;
-    default:
-        ret = PTLS_ALERT_ILLEGAL_PARAMETER;
-        goto Exit;
-    }
     signdata_size = build_certificate_verify_signdata(signdata, tls->key_schedule, context_string);
     if (tls->certificate_verify.cb != NULL) {
-        ret = tls->certificate_verify.cb(tls->certificate_verify.verify_ctx, ptls_iovec_init(signdata, signdata_size), signature);
+        ret = tls->certificate_verify.cb(tls->certificate_verify.verify_ctx, algo, ptls_iovec_init(signdata, signdata_size),
+                                         signature);
     } else {
         ret = 0;
     }
@@ -3300,6 +3322,23 @@
                 } while (src != end);
             });
             break;
+        case PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE:
+            ptls_decode_block(src, end, 1, {
+                size_t list_size = end - src;
+
+                /* RFC7250 4.1: No empty list, no list with single x509 element */
+                if (list_size == 0 || (list_size == 1 && *src == PTLS_CERTIFICATE_TYPE_X509)) {
+                    ret = PTLS_ALERT_DECODE_ERROR;
+                    goto Exit;
+                }
+
+                do {
+                    if (ch->server_certificate_types.count < PTLS_ELEMENTSOF(ch->server_certificate_types.list))
+                        ch->server_certificate_types.list[ch->server_certificate_types.count++] = *src;
+                    src++;
+                } while (src != end);
+            });
+            break;
         case PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE:
             ptls_decode_block(src, end, 1, {
                 do {
@@ -3489,7 +3528,7 @@
             int64_t delta = (now - issue_at) - (identity->obfuscated_ticket_age - age_add);
             if (delta < 0)
                 delta = -delta;
-            if (delta <= PTLS_EARLY_DATA_MAX_DELAY)
+            if (tls->ctx->max_early_data_size != 0 && delta <= PTLS_EARLY_DATA_MAX_DELAY)
                 *accept_early_data = 1;
         }
         /* check server-name */
@@ -3598,6 +3637,18 @@
     return 0;
 }
 
+static int certificate_type_exists(uint8_t *list, size_t count, uint8_t desired_type)
+{
+    /* empty type list means that we default to x509 */
+    if (desired_type == PTLS_CERTIFICATE_TYPE_X509 && count == 0)
+        return 1;
+    for (size_t i = 0; i < count; i++) {
+        if (list[i] == desired_type)
+            return 1;
+    }
+    return 0;
+}
+
 static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message,
                                ptls_handshake_properties_t *properties)
 {
@@ -3649,8 +3700,9 @@
         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){0,      NULL,   {NULL},     {NULL}, 0,     {NULL},   {NULL}, {NULL}, {{0}},
+                                          {NULL}, {NULL}, {{{NULL}}}, {{0}},  {{0}}, {{NULL}}, {NULL}, {{0}},  {{UINT16_MAX}}};
 
     /* decode ClientHello */
     if ((ret = decode_client_hello(tls, ch, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len, properties)) !=
@@ -3736,15 +3788,24 @@
                                                         {ch->signature_algorithms.list, ch->signature_algorithms.count},
                                                         {ch->cert_compression_algos.list, ch->cert_compression_algos.count},
                                                         {ch->client_ciphers.list, ch->client_ciphers.count},
+                                                        {ch->server_certificate_types.list, ch->server_certificate_types.count},
                                                         is_esni};
             ret = tls->ctx->on_client_hello->cb(tls->ctx->on_client_hello, tls, &params);
         } else {
             ret = 0;
         }
+
         if (is_esni)
             free(server_name.base);
         if (ret != 0)
             goto Exit;
+
+        if (!certificate_type_exists(ch->server_certificate_types.list, ch->server_certificate_types.count,
+                                     tls->ctx->use_raw_public_keys ? PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY
+                                                                   : PTLS_CERTIFICATE_TYPE_X509)) {
+            ret = PTLS_ALERT_UNSUPPORTED_CERTIFICATE;
+            goto Exit;
+        }
     } else {
         if (ch->psk.early_data_indication) {
             ret = PTLS_ALERT_DECODE_ERROR;
@@ -4008,6 +4069,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 (tls->ctx->use_raw_public_keys) {
+                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, {
@@ -4036,7 +4101,7 @@
                 /* extensions */
                 ptls_buffer_push_block(sendbuf, 2, {
                     buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS, {
-                        if ((ret = push_signature_algorithms(sendbuf)) != 0)
+                        if ((ret = push_signature_algorithms(tls->ctx->verify_certificate, sendbuf)) != 0)
                             goto Exit;
                     });
                 });
@@ -4329,7 +4394,7 @@
             free(tls->client.certificate_request.context.base);
     }
     if (tls->certificate_verify.cb != NULL) {
-        tls->certificate_verify.cb(tls->certificate_verify.verify_ctx, ptls_iovec_init(NULL, 0), ptls_iovec_init(NULL, 0));
+        tls->certificate_verify.cb(tls->certificate_verify.verify_ctx, 0, ptls_iovec_init(NULL, 0), ptls_iovec_init(NULL, 0));
     }
     if (tls->pending_handshake_secret != NULL) {
         ptls_clear_memory(tls->pending_handshake_secret, PTLS_MAX_DIGEST_SIZE);
diff --git a/picotls.xcodeproj/project.pbxproj b/picotls.xcodeproj/project.pbxproj
index 3fe64e9..27453aa 100644
--- a/picotls.xcodeproj/project.pbxproj
+++ b/picotls.xcodeproj/project.pbxproj
@@ -1019,10 +1019,10 @@
 				EXECUTABLE_PREFIX = lib;
 				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
-					"/usr/local/openssl-1.1.0/include",
+					/usr/local/opt/openssl/include,
 					include,
 				);
-				LIBRARY_SEARCH_PATHS = "/usr/local/openssl-1.1.0/lib";
+				LIBRARY_SEARCH_PATHS = /usr/local/opt/openssl/lib;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
 			name = Debug;
@@ -1033,10 +1033,10 @@
 				EXECUTABLE_PREFIX = lib;
 				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
-					"/usr/local/openssl-1.1.0/include",
+					/usr/local/opt/openssl/include,
 					include,
 				);
-				LIBRARY_SEARCH_PATHS = "/usr/local/openssl-1.1.0/lib";
+				LIBRARY_SEARCH_PATHS = /usr/local/opt/openssl/lib;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
 			name = Release;
@@ -1143,10 +1143,10 @@
 			buildSettings = {
 				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
-					"/usr/local/openssl-1.1.0/include",
+					/usr/local/opt/openssl/include,
 					include,
 				);
-				LIBRARY_SEARCH_PATHS = "/usr/local/openssl-1.1.0/lib";
+				LIBRARY_SEARCH_PATHS = /usr/local/opt/openssl/lib;
 				OTHER_LDFLAGS = "-lcrypto";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
@@ -1157,10 +1157,10 @@
 			buildSettings = {
 				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
-					"/usr/local/openssl-1.1.0/include",
+					/usr/local/opt/openssl/include,
 					include,
 				);
-				LIBRARY_SEARCH_PATHS = "/usr/local/openssl-1.1.0/lib";
+				LIBRARY_SEARCH_PATHS = /usr/local/opt/openssl/lib;
 				OTHER_LDFLAGS = "-lcrypto";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
@@ -1193,11 +1193,11 @@
 				);
 				HEADER_SEARCH_PATHS = (
 					include,
-					"/usr/local/openssl-1.1.0/include",
+					/usr/local/opt/openssl/include,
 					/usr/local/include,
 				);
 				LIBRARY_SEARCH_PATHS = (
-					"/usr/local/openssl-1.1.0/lib",
+					/usr/local/opt/openssl/lib,
 					/usr/local/lib,
 				);
 				OTHER_LDFLAGS = (
@@ -1219,11 +1219,11 @@
 				);
 				HEADER_SEARCH_PATHS = (
 					include,
-					"/usr/local/openssl-1.1.0/include",
+					/usr/local/opt/openssl/include,
 					/usr/local/include,
 				);
 				LIBRARY_SEARCH_PATHS = (
-					"/usr/local/openssl-1.1.0/lib",
+					/usr/local/opt/openssl/lib,
 					/usr/local/lib,
 				);
 				OTHER_LDFLAGS = (
@@ -1259,10 +1259,10 @@
 			buildSettings = {
 				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
-					"/usr/local/openssl-1.1.0/include",
+					/usr/local/opt/openssl/include,
 					include,
 				);
-				LIBRARY_SEARCH_PATHS = "/usr/local/openssl-1.1.0/lib";
+				LIBRARY_SEARCH_PATHS = /usr/local/opt/openssl/lib;
 				OTHER_LDFLAGS = "-lcrypto";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
@@ -1273,10 +1273,10 @@
 			buildSettings = {
 				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
-					"/usr/local/openssl-1.1.0/include",
+					/usr/local/opt/openssl/include,
 					include,
 				);
-				LIBRARY_SEARCH_PATHS = "/usr/local/openssl-1.1.0/lib";
+				LIBRARY_SEARCH_PATHS = /usr/local/opt/openssl/lib;
 				OTHER_LDFLAGS = "-lcrypto";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
diff --git a/t/assets/ed25519/cert.pem b/t/assets/ed25519/cert.pem
new file mode 100644
index 0000000..56e0cf9
--- /dev/null
+++ b/t/assets/ed25519/cert.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICOTCCASGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9waWNv
+dGxzIHRlc3QgY2EwHhcNMjEwMzIxMjMxNjU2WhcNMzEwMzE5MjMxNjU2WjAjMSEw
+HwYDVQQDDBhlZDI1NTE5LnRlc3QuZXhhbXBsZS5jb20wKjAFBgMrZXADIQDKBvBk
+m3KyTPvBdVJTrtmR5V7/OtlJTmZmPg972cKuEaN7MHkwCQYDVR0TBAIwADAsBglg
+hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
+BBYEFD2F2ds0fQ6SbWUHViucoIl/kY8BMB8GA1UdIwQYMBaAFL95ypeyYHgglqpG
+V5zfp7Ij9SVjMA0GCSqGSIb3DQEBCwUAA4IBAQBN6/tmJrdkPDxGQ+kbGuh6KlaT
+FzywvfyaArpOoNWRHuDgzyOYOJ7XCaChW4GeVk+zRLxC1ZVrbn9kL5LDP9oKdnTn
+dPOsj4Zmn3er9zPlRgauvGAd5DuHk1n3fdIKhw/zusB3MK5iQWIsmSx9jFEx+8Sf
+CMe5aFqzZO1JHRaR/yrKLxxhabhqjP7Xad6Dz9uMQpOPEddOi93iun589yTI40d3
+UrB81XJW9Ll1SnCs8qYl99D9Kcq8OWjFjxrKiEFHw0z8oWag+M8bgqnjvYZOZln4
+Mnm9mOJtsGjtuQ+wvUMt3AWhJ4iI0eEebXxDjnZFMoipXa/L3B+h/DnnH2GN
+-----END CERTIFICATE-----
diff --git a/t/assets/ed25519/key.pem b/t/assets/ed25519/key.pem
new file mode 100644
index 0000000..6461749
--- /dev/null
+++ b/t/assets/ed25519/key.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIFh3irfeqAxMuC/lkrh12Q5Qz/h5JfkvSDhgncoYF1t8
+-----END PRIVATE KEY-----
diff --git a/t/assets/ed25519/pub.pem b/t/assets/ed25519/pub.pem
new file mode 100644
index 0000000..04b8564
--- /dev/null
+++ b/t/assets/ed25519/pub.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEAygbwZJtyskz7wXVSU67ZkeVe/zrZSU5mZj4Pe9nCrhE=
+-----END PUBLIC KEY-----
diff --git a/t/assets/server.crt b/t/assets/rsa/cert.pem
similarity index 100%
rename from t/assets/server.crt
rename to t/assets/rsa/cert.pem
diff --git a/t/assets/server.key b/t/assets/rsa/key.pem
similarity index 100%
rename from t/assets/server.key
rename to t/assets/rsa/key.pem
diff --git a/t/assets/rsa/pub.pem b/t/assets/rsa/pub.pem
new file mode 100644
index 0000000..52b7731
--- /dev/null
+++ b/t/assets/rsa/pub.pem
@@ -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/assets/secp256r1/cert.pem b/t/assets/secp256r1/cert.pem
new file mode 100644
index 0000000..ef2ae2c
--- /dev/null
+++ b/t/assets/secp256r1/cert.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICYDCCAUigAwIBAgIBATANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9waWNv
+dGxzIHRlc3QgY2EwHhcNMTgwMjIzMDUzMTA0WhcNMjgwMjIxMDUzMTA0WjAbMRkw
+FwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
+QgAE2silQFS6M9oYqUF/SVPfYOamPbaOUzqf3RkUXqsDz7z7NpgWJI8HKW0V2E8w
+6Alk+xT8hnzUBsL9neiZP0iMK6N7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0E
+HxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFO4whhah
+0mmtZOTXd2uy/VxPAaK1MB8GA1UdIwQYMBaAFL95ypeyYHgglqpGV5zfp7Ij9SVj
+MA0GCSqGSIb3DQEBCwUAA4IBAQCPrJwBbYGqjK5dtRZ06ujrJluxZtVr1E15DW2H
+qba/dC3Bsi5StkvKDQFFOFga0mptIJhaUbBvLD8PEojtfAmldAAhPUvSLVSqU4tk
++R7qpYrnYV5WklI2PqBoWZx9s+hcS3du3ijtGJGpnDnSlsyYBYx03B4SWzi9Vsuj
+6OEqWivSMkXBEIUgbGs06maRDi64ZIefB7wjTyOtvonfCphH6WMC00H0LaTO3ePY
+QQj+30fA52OOH/BLxa6rwLo4PuOQnAi9dRy5uFRDHZlC4KK3dbsUA3ma9gfYpasr
+OnCLd4Vwipg4mzUJ9mJrKUqnp/k73tjIkFfydiojCwFoxpry
+-----END CERTIFICATE-----
diff --git a/t/assets/secp256r1/key.pem b/t/assets/secp256r1/key.pem
new file mode 100644
index 0000000..71ba2ed
--- /dev/null
+++ b/t/assets/secp256r1/key.pem
@@ -0,0 +1,8 @@
+-----BEGIN EC PARAMETERS-----
+BggqhkjOPQMBBw==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMF0tPle/noBDr5K6DOyNhP8Zellkag5npqA+6vR/7o6oAoGCCqGSM49
+AwEHoUQDQgAE2silQFS6M9oYqUF/SVPfYOamPbaOUzqf3RkUXqsDz7z7NpgWJI8H
+KW0V2E8w6Alk+xT8hnzUBsL9neiZP0iMKw==
+-----END EC PRIVATE KEY-----
diff --git a/t/assets/secp256r1/pub.pem b/t/assets/secp256r1/pub.pem
new file mode 100644
index 0000000..0402946
--- /dev/null
+++ b/t/assets/secp256r1/pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2silQFS6M9oYqUF/SVPfYOamPbaO
+Uzqf3RkUXqsDz7z7NpgWJI8HKW0V2E8w6Alk+xT8hnzUBsL9neiZP0iMKw==
+-----END PUBLIC KEY-----
diff --git a/t/assets/secp384r1/cert.pem b/t/assets/secp384r1/cert.pem
new file mode 100644
index 0000000..8c03b51
--- /dev/null
+++ b/t/assets/secp384r1/cert.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIIChzCCAW+gAwIBAgIBAzANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9waWNv
+dGxzIHRlc3QgY2EwHhcNMjEwMzIyMDAxNDM0WhcNMzEwMzIwMDAxNDM0WjAlMSMw
+IQYDVQQDDBpzZWNwMzg0cjEudGVzdC5leGFtcGxlLmNvbTB2MBAGByqGSM49AgEG
+BSuBBAAiA2IABAEP8gHosiRYm0qAIUQsVhmG2EHz8X4AsgGU/zjF1imDNj5t3DiI
+k1/CEZ/eBGW9Qc+KPPZvC1sHUWzxWSCbFnk10g32fD+Gj8IdVC0r9SVMiahisZ+A
+H/HC3J5LprjFWqN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT
+TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFMPSpIyQI1TYn+1ZlIMJ
+DdUig6DxMB8GA1UdIwQYMBaAFL95ypeyYHgglqpGV5zfp7Ij9SVjMA0GCSqGSIb3
+DQEBCwUAA4IBAQA3yL5Ip2hXTqqSuRkN+IRLnZQyJrrUHU4fiGmgaNdPSAXHgCoE
+2Wd2TMBJk2kbIFjORIumVIpUoQCTUtGjraDP5hgwZCCpTIztwnp7esOMwGVPFnqx
+mz4DEGyfbDP8LdWgdZN4xtshXSMgFUY9u3cWQUNxWN7WDL61byeHw5GZPKf+SD99
+fQOstYzO8XAeT3xCKNiVmO1rFztRrP/uy3nBbC45TKv1yaJCa6gcOBdNSXi6EH8l
+XWz0ASAuF7/jQg97QwG31IUtDvI7yIJ2Q8qLS68cBRjv2nw8rTCQ/Uv3ABG9mX+f
+bWcOgMQI6c8vOOPZ/61+mVo5R3vBSrevVODF
+-----END CERTIFICATE-----
diff --git a/t/assets/secp384r1/key.pem b/t/assets/secp384r1/key.pem
new file mode 100644
index 0000000..849b07d
--- /dev/null
+++ b/t/assets/secp384r1/key.pem
@@ -0,0 +1,9 @@
+-----BEGIN EC PARAMETERS-----
+BgUrgQQAIg==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDDKRvwSCQcP5gx5uH0nUkITtNjhXTyWQOBxixOqvBviKE+8ypVluHbg
+Ya/evmF1A52gBwYFK4EEACKhZANiAAQBD/IB6LIkWJtKgCFELFYZhthB8/F+ALIB
+lP84xdYpgzY+bdw4iJNfwhGf3gRlvUHPijz2bwtbB1Fs8VkgmxZ5NdIN9nw/ho/C
+HVQtK/UlTImoYrGfgB/xwtyeS6a4xVo=
+-----END EC PRIVATE KEY-----
diff --git a/t/assets/secp384r1/pub.pem b/t/assets/secp384r1/pub.pem
new file mode 100644
index 0000000..2238c41
--- /dev/null
+++ b/t/assets/secp384r1/pub.pem
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAQ/yAeiyJFibSoAhRCxWGYbYQfPxfgCy
+AZT/OMXWKYM2Pm3cOIiTX8IRn94EZb1Bz4o89m8LWwdRbPFZIJsWeTXSDfZ8P4aP
+wh1ULSv1JUyJqGKxn4Af8cLcnkumuMVa
+-----END PUBLIC KEY-----
diff --git a/t/assets/secp521r1/cert.pem b/t/assets/secp521r1/cert.pem
new file mode 100644
index 0000000..28a76c1
--- /dev/null
+++ b/t/assets/secp521r1/cert.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICrTCCAZWgAwIBAgIBBDANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDEw9waWNv
+dGxzIHRlc3QgY2EwHhcNMjEwMzIyMDAzNDQ1WhcNMzEwMzIwMDAzNDQ1WjAlMSMw
+IQYDVQQDDBpzZWNwNTIxcjEudGVzdC5leGFtcGxlLmNvbTCBmzAQBgcqhkjOPQIB
+BgUrgQQAIwOBhgAEACyVCnKujpMyv58r9L/3HL0pM9Pxgx/07c5/f+SC1ukqYo8l
+IuLuC78nB3mn4wAB8PsT8pz8XQZoYBI552lj+HBiAHsTki9Y+amzcDo5QClzoqO6
+GtXN7HOILsqBgFUblf+av6k19OX185X+qnO8Pb1KwwviEWXDoku4QTIWoQjj4VGS
+o3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRl
+ZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUv1vbGGuBcMs5zEdTGHWLkJ5mWwgwHwYD
+VR0jBBgwFoAUv3nKl7JgeCCWqkZXnN+nsiP1JWMwDQYJKoZIhvcNAQELBQADggEB
+ABLBvD2LuyS84AGroVEVCp6qijpJWwzQ7Sk8AZQYr0mPUK+mdX/44/8OC2bKlUye
+LtfRBurbQjLXtn4UC8qGJVcBdnYkvKKna+GaPgD8qduzqeyJot3oMD3lZOvg1cZg
+yUynOmFpDQMR+WGufhaHf68Q2ggR11ShX5fxOniMWgz4ogz6rhfVjBqE1Xe3/cFf
++Vadeb66XaEXGhCU5wlJSOYI1+5oZBkXxZGwsOANQmfenDxfNNcd9ZioZimHZf62
+FatHHkIQYt3Lhg9oPl1RCiLDBz3ly0QriZwz3Y/biLQKjNfaoRlVGIcmwOIu7rcc
+ZDEnl40J122rLwtMlw9a0+s=
+-----END CERTIFICATE-----
diff --git a/t/assets/secp521r1/key.pem b/t/assets/secp521r1/key.pem
new file mode 100644
index 0000000..8ec1749
--- /dev/null
+++ b/t/assets/secp521r1/key.pem
@@ -0,0 +1,10 @@
+-----BEGIN EC PARAMETERS-----
+BgUrgQQAIw==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIA2fGi1/ysuxTrmnDf6rdqCDzvPomZKLbOCNAckjas5FDIYvpYoYI9
+0gUpNTZWhL/50Wckg1kxmUNR10lRDb4bNKmgBwYFK4EEACOhgYkDgYYABAAslQpy
+ro6TMr+fK/S/9xy9KTPT8YMf9O3Of3/kgtbpKmKPJSLi7gu/Jwd5p+MAAfD7E/Kc
+/F0GaGASOedpY/hwYgB7E5IvWPmps3A6OUApc6KjuhrVzexziC7KgYBVG5X/mr+p
+NfTl9fOV/qpzvD29SsML4hFlw6JLuEEyFqEI4+FRkg==
+-----END EC PRIVATE KEY-----
diff --git a/t/assets/secp521r1/pub.pem b/t/assets/secp521r1/pub.pem
new file mode 100644
index 0000000..656e591
--- /dev/null
+++ b/t/assets/secp521r1/pub.pem
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQALJUKcq6OkzK/nyv0v/ccvSkz0/GD
+H/Ttzn9/5ILW6SpijyUi4u4LvycHeafjAAHw+xPynPxdBmhgEjnnaWP4cGIAexOS
+L1j5qbNwOjlAKXOio7oa1c3sc4guyoGAVRuV/5q/qTX05fXzlf6qc7w9vUrDC+IR
+ZcOiS7hBMhahCOPhUZI=
+-----END PUBLIC KEY-----
diff --git a/t/cli.c b/t/cli.c
index bce9a65..db6bf82 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,10 @@
            "  -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 public-key-file   use raw public keys (RFC 7250). When set and running as a\n"
+           "                       client, the argument specifies the public keys that the\n"
+           "                       server is expected to use. When running as a server, the\n"
+           "                       argument is ignored.\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"
@@ -374,6 +379,17 @@
 #if PTLS_OPENSSL_HAVE_X25519
            ", X25519"
 #endif
+           "\n"
+           "Supported signature algorithms: rsa, secp256r1"
+#if PTLS_OPENSSL_HAVE_SECP384R1
+           ", secp384r1"
+#endif
+#if PTLS_OPENSSL_HAVE_SECP521R1
+           ", secp521r1"
+#endif
+#if PTLS_OPENSSL_HAVE_ED25519
+           ", ed25519"
+#endif
            "\n\n",
            cmd);
 }
@@ -404,8 +420,9 @@
     struct sockaddr_storage sa;
     socklen_t salen;
     int family = 0;
+    const char *raw_pub_key_file = NULL, *cert_location = NULL;
 
-    while ((ch = getopt(argc, argv, "46abBC:c:i:Ik:nN:es:SE:K:l:y:vh")) != -1) {
+    while ((ch = getopt(argc, argv, "46abBC:c:i:Ik:nN:es:Sr:E:K:l:y:vh")) != -1) {
         switch (ch) {
         case '4':
             family = AF_INET;
@@ -429,11 +446,11 @@
             break;
         case 'C':
         case 'c':
-            if (ctx.certificates.count != 0) {
+            if (cert_location != NULL) {
                 fprintf(stderr, "-C/-c can only be specified once\n");
                 return 1;
             }
-            load_certificate_chain(&ctx, optarg);
+            cert_location = optarg;
             is_server = ch == 'c';
             break;
         case 'i':
@@ -451,6 +468,9 @@
         case 'e':
             use_early_data = 1;
             break;
+        case 'r':
+            raw_pub_key_file = optarg;
+            break;
         case 's':
             setup_session_file(&ctx, &hsprop, optarg);
             break;
@@ -540,10 +560,36 @@
     }
     argc -= optind;
     argv += optind;
+
+    if (raw_pub_key_file != NULL) {
+        int is_dash = !strcmp(raw_pub_key_file, "-");
+        if (is_server) {
+            ctx.certificates.list = malloc(sizeof(*ctx.certificates.list));
+            load_raw_public_key(ctx.certificates.list, cert_location);
+            ctx.certificates.count = 1;
+        } else if (!is_dash) {
+            ptls_iovec_t raw_pub_key;
+            EVP_PKEY *pubkey;
+            load_raw_public_key(&raw_pub_key, raw_pub_key_file);
+            pubkey = d2i_PUBKEY(NULL, (const unsigned char **)&raw_pub_key.base, raw_pub_key.len);
+            if (pubkey == NULL) {
+                fprintf(stderr, "Failed to create an EVP_PKEY from the key found in %s\n", raw_pub_key_file);
+                return 1;
+            }
+            setup_raw_pubkey_verify_certificate(&ctx, pubkey);
+            EVP_PKEY_free(pubkey);
+        }
+        ctx.use_raw_public_keys = 1;
+    } else {
+        if (cert_location)
+            load_certificate_chain(&ctx, cert_location);
+    }
+
     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");
diff --git a/t/e2e.t b/t/e2e.t
index 577bc20..ada3109 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("rsa", 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("rsa", 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("rsa", 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";
 };
 
+# This test acts as an end-to-end testing of the certificate verifier of the OpenSSL backend.
+subtest "raw-public-keys" => sub {
+    my @key_types = do {
+        my $help = `$cli -h`;
+        $help =~ /^Supported signature algorithms:\s*(.*)\s*$/m
+            or die "failed to extract list of supported signature algorithms from $cli -h";
+        split /,\s*/, $1;
+    };
+    die "unexpected list of supported signature algorithms: @key_types"
+        unless grep /^rsa$/, @key_types;
+    for my $key_type (@key_types) {
+        subtest $key_type => sub {
+            my $guard = spawn_server($key_type, qw(-r - -i t/assets/hello.txt));
+            my $resp = `$cli -v -r t/assets/$key_type/pub.pem 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 $key_type = shift;
+    my $cert_type = grep(/^-r$/, @_) ? "pub" : "cert";
+    my @cmd = ($cli, "-k", "t/assets/$key_type/key.pem", "-c", "t/assets/$key_type/$cert_type.pem", @_, "127.0.0.1", $port);
     my $pid = fork;
     die "fork failed:$!"
         unless defined $pid;
diff --git a/t/openssl.c b/t/openssl.c
index fb52775..ff47eee 100644
--- a/t/openssl.c
+++ b/t/openssl.c
@@ -129,47 +129,74 @@
 #endif
 }
 
+static void test_sign_verify(EVP_PKEY *key, const struct st_ptls_openssl_signature_scheme_t *schemes)
+{
+    for (size_t i = 0; schemes[i].scheme_id != UINT16_MAX; ++i) {
+        note("scheme 0x%04x", schemes[i].scheme_id);
+        const void *message = "hello world";
+        ptls_buffer_t sigbuf;
+        uint8_t sigbuf_small[1024];
+
+        ptls_buffer_init(&sigbuf, sigbuf_small, sizeof(sigbuf_small));
+        ok(do_sign(key, schemes + i, &sigbuf, ptls_iovec_init(message, strlen(message))) == 0);
+        EVP_PKEY_up_ref(key);
+        ok(verify_sign(key, schemes[i].scheme_id, ptls_iovec_init(message, strlen(message)),
+                       ptls_iovec_init(sigbuf.base, sigbuf.off)) == 0);
+
+        ptls_buffer_dispose(&sigbuf);
+    }
+}
+
 static void test_rsa_sign(void)
 {
     ptls_openssl_sign_certificate_t *sc = (ptls_openssl_sign_certificate_t *)ctx->sign_certificate;
-
-    const void *message = "hello world";
-    ptls_buffer_t sigbuf;
-    uint8_t sigbuf_small[1024];
-
-    ptls_buffer_init(&sigbuf, sigbuf_small, sizeof(sigbuf_small));
-    ok(do_sign(sc->key, &sigbuf, ptls_iovec_init(message, strlen(message)), EVP_sha256()) == 0);
-    EVP_PKEY_up_ref(sc->key);
-    ok(verify_sign(sc->key, ptls_iovec_init(message, strlen(message)), ptls_iovec_init(sigbuf.base, sigbuf.off)) == 0);
-
-    ptls_buffer_dispose(&sigbuf);
+    test_sign_verify(sc->key, sc->schemes);
 }
 
-static void test_ecdsa_sign(void)
+static void do_test_ecdsa_sign(int nid, const struct st_ptls_openssl_signature_scheme_t *schemes)
 {
     EVP_PKEY *pkey;
 
     { /* create pkey */
-        EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+        EC_KEY *eckey = EC_KEY_new_by_curve_name(nid);
         EC_KEY_generate_key(eckey);
         pkey = EVP_PKEY_new();
         EVP_PKEY_set1_EC_KEY(pkey, eckey);
         EC_KEY_free(eckey);
     }
 
-    const char *message = "hello world";
-    ptls_buffer_t sigbuf;
-    uint8_t sigbuf_small[1024];
-
-    ptls_buffer_init(&sigbuf, sigbuf_small, sizeof(sigbuf_small));
-    ok(do_sign(pkey, &sigbuf, ptls_iovec_init(message, strlen(message)), EVP_sha256()) == 0);
-    EVP_PKEY_up_ref(pkey);
-    ok(verify_sign(pkey, ptls_iovec_init(message, strlen(message)), ptls_iovec_init(sigbuf.base, sigbuf.off)) == 0);
-
-    ptls_buffer_dispose(&sigbuf);
+    test_sign_verify(pkey, schemes);
     EVP_PKEY_free(pkey);
 }
 
+static void test_ecdsa_sign(void)
+{
+    do_test_ecdsa_sign(NID_X9_62_prime256v1, secp256r1_signature_schemes);
+#if PTLS_OPENSSL_HAVE_SECP384R1
+    do_test_ecdsa_sign(NID_secp384r1, secp384r1_signature_schemes);
+#endif
+#if PTLS_OPENSSL_HAVE_SECP521R1
+    do_test_ecdsa_sign(NID_secp521r1, secp521r1_signature_schemes);
+#endif
+}
+
+static void test_ed25519_sign(void)
+{
+#if PTLS_OPENSSL_HAVE_ED25519
+    EVP_PKEY *pkey = NULL;
+
+    { /* create pkey */
+        EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
+        EVP_PKEY_keygen_init(pctx);
+        EVP_PKEY_keygen(pctx, &pkey);
+        EVP_PKEY_CTX_free(pctx);
+    }
+
+    test_sign_verify(pkey, ed25519_signature_schemes);
+    EVP_PKEY_free(pkey);
+#endif
+}
+
 static X509 *x509_from_pem(const char *pem)
 {
     BIO *bio = BIO_new_mem_buf((void *)pem, (int)strlen(pem));
@@ -308,6 +335,7 @@
 
     subtest("rsa-sign", test_rsa_sign);
     subtest("ecdsa-sign", test_ecdsa_sign);
+    subtest("ed25519-sign", test_ed25519_sign);
     subtest("cert-verify", test_cert_verify);
     subtest("picotls", test_picotls);
     test_picotls_esni(esni_private_keys);
diff --git a/t/util.h b/t/util.h
index db86416..3890737 100644
--- a/t/util.h
+++ b/t/util.h
@@ -48,6 +48,15 @@
     }
 }
 
+static inline void load_raw_public_key(ptls_iovec_t *raw_public_key, char const *cert_pem_file)
+{
+    size_t count;
+    if (ptls_load_pem_objects(cert_pem_file, "PUBLIC KEY", raw_public_key, 1, &count) != 0) {
+        fprintf(stderr, "failed to load public key:%s:%s\n", cert_pem_file, strerror(errno));
+        exit(1);
+    }
+}
+
 static inline void load_private_key(ptls_context_t *ctx, const char *fn)
 {
     static ptls_openssl_sign_certificate_t sc;
@@ -122,6 +131,13 @@
     ctx->verify_certificate = &vc.super;
 }
 
+static inline void setup_raw_pubkey_verify_certificate(ptls_context_t *ctx, EVP_PKEY *pubkey)
+{
+    static ptls_openssl_raw_pubkey_verify_certificate_t vc;
+    ptls_openssl_raw_pubkey_init_verify_certificate(&vc, pubkey);
+    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];