Merge pull request #457 from h2o/kazuho/expose-sigschemes

alternative signers: support callback-based method, add utility functions
diff --git a/include/picotls.h b/include/picotls.h
index 9762dad..c98a053 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -650,10 +650,23 @@
 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);
 /**
- * context object of an async operation (e.g., RSA signature generation)
+ * An object that represents an asynchronous task (e.g., RSA signature generation).
+ * When `ptls_handshake` returns `PTLS_ERROR_ASYNC_OPERATION`, it has an associated task in flight. The user should obtain the
+ * reference to the associated task by calling `ptls_get_async_job`, then either wait for the file descriptor obtained from
+ * the `get_fd` callback to become readable, or set a completion callback via `set_completion_callback` and wait for its
+ * invocation. Once notified, the user should invoke `ptls_handshake` again.
+ * Async jobs typically provide support for only one of the two methods.
  */
 typedef struct st_ptls_async_job_t {
     void (*destroy_)(struct st_ptls_async_job_t *self);
+    /**
+     * optional callback returning a file descriptor that becomes readable when the job is complete
+     */
+    int (*get_fd)(struct st_ptls_async_job_t *self);
+    /**
+     * optional callback for setting a completion callback
+     */
+    void (*set_completion_callback)(struct st_ptls_async_job_t *self, void (*cb)(void *), void *cbdata);
 } ptls_async_job_t;
 /**
  * When gerenating CertificateVerify, the core calls the callback to sign the handshake context using the certificate. This callback
diff --git a/include/picotls/openssl.h b/include/picotls/openssl.h
index 3a7c3b5..a892f9b 100644
--- a/include/picotls/openssl.h
+++ b/include/picotls/openssl.h
@@ -125,22 +125,25 @@
  */
 int ptls_openssl_create_key_exchange(ptls_key_exchange_context_t **ctx, EVP_PKEY *pkey);
 
-#if PTLS_OPENSSL_HAVE_ASYNC
-/**
- * Returns the file descriptor of the asynchronous operation in flight.
- */
-OSSL_ASYNC_FD ptls_openssl_get_async_fd(ptls_t *ptls);
-#endif
-
-struct st_ptls_openssl_signature_scheme_t {
+typedef struct st_ptls_openssl_signature_scheme_t {
     uint16_t scheme_id;
     const EVP_MD *(*scheme_md)(void);
-};
+} ptls_openssl_signature_scheme_t;
+
+/**
+ * Given a private key, returns a list of compatible signature schemes. This list is terminated by scheme_id of UINT16_MAX.
+ */
+const ptls_openssl_signature_scheme_t *ptls_openssl_lookup_signature_schemes(EVP_PKEY *key);
+/**
+ * Given available schemes and input, choses one, or returns NULL if none is available.
+ */
+const ptls_openssl_signature_scheme_t *ptls_openssl_select_signature_scheme(const ptls_openssl_signature_scheme_t *available,
+                                                                            const uint16_t *algorithms, size_t num_algorithms);
 
 typedef struct st_ptls_openssl_sign_certificate_t {
     ptls_sign_certificate_t super;
     EVP_PKEY *key;
-    const struct st_ptls_openssl_signature_scheme_t *schemes; /* terminated by .scheme_id == UINT16_MAX */
+    const ptls_openssl_signature_scheme_t *schemes; /* terminated by .scheme_id == UINT16_MAX */
     /**
      * When set to true, indicates to the backend that the signature can be generated asynchronously. When the backend decides to
      * generate the signature asynchronously, `ptls_handshake` will return PTLS_ERROR_ASYNC_OPERATION. When receiving that error
diff --git a/lib/openssl.c b/lib/openssl.c
index 511315e..71b33ae 100644
--- a/lib/openssl.c
+++ b/lib/openssl.c
@@ -89,22 +89,22 @@
 
 #endif
 
-static const struct st_ptls_openssl_signature_scheme_t rsa_signature_schemes[] = {{PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, EVP_sha256},
+static const 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[] = {
+static const 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[] = {
+static const 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[] = {
+static const 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},
+static const ptls_openssl_signature_scheme_t ed25519_signature_schemes[] = {{PTLS_SIGNATURE_ED25519, NULL},
                                                                                       {UINT16_MAX, NULL}};
 #endif
 
@@ -127,9 +127,9 @@
     PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256,
     UINT16_MAX};
 
-static const struct st_ptls_openssl_signature_scheme_t *lookup_signature_schemes(EVP_PKEY *key)
+const ptls_openssl_signature_scheme_t *ptls_openssl_lookup_signature_schemes(EVP_PKEY *key)
 {
-    const struct st_ptls_openssl_signature_scheme_t *schemes = NULL;
+    const ptls_openssl_signature_scheme_t *schemes = NULL;
 
     switch (EVP_PKEY_id(key)) {
     case EVP_PKEY_RSA:
@@ -168,6 +168,20 @@
     return schemes;
 }
 
+const ptls_openssl_signature_scheme_t *ptls_openssl_select_signature_scheme(const ptls_openssl_signature_scheme_t *available,
+                                                                            const uint16_t *algorithms, size_t num_algorithms)
+{
+    const ptls_openssl_signature_scheme_t *scheme;
+
+    /* select the algorithm, driven by server-isde preference of `available` */
+    for (scheme = available; scheme->scheme_id != UINT16_MAX; ++scheme)
+        for (size_t i = 0; i != num_algorithms; ++i)
+            if (algorithms[i] == scheme->scheme_id)
+                return scheme;
+
+    return NULL;
+}
+
 void ptls_openssl_random_bytes(void *buf, size_t len)
 {
     int ret = RAND_bytes(buf, (int)len);
@@ -698,7 +712,7 @@
 
 struct async_sign_ctx {
     ptls_async_job_t super;
-    const struct st_ptls_openssl_signature_scheme_t *scheme;
+    const ptls_openssl_signature_scheme_t *scheme;
     EVP_MD_CTX *ctx;
     ASYNC_WAIT_CTX *waitctx;
     ASYNC_JOB *job;
@@ -724,14 +738,26 @@
     free(self);
 }
 
-static ptls_async_job_t *async_sign_ctx_new(const struct st_ptls_openssl_signature_scheme_t *scheme, EVP_MD_CTX *ctx, size_t siglen)
+int async_sign_ctx_get_fd(ptls_async_job_t *_self)
+{
+    struct async_sign_ctx *self = (void *)_self;
+    OSSL_ASYNC_FD fds[1];
+    size_t numfds;
+
+    ASYNC_WAIT_CTX_get_all_fds(self->waitctx, NULL, &numfds);
+    assert(numfds == 1);
+    ASYNC_WAIT_CTX_get_all_fds(self->waitctx, fds, &numfds);
+    return (int)fds[0];
+}
+
+static ptls_async_job_t *async_sign_ctx_new(const ptls_openssl_signature_scheme_t *scheme, EVP_MD_CTX *ctx, size_t siglen)
 {
     struct async_sign_ctx *self;
 
     if ((self = malloc(offsetof(struct async_sign_ctx, sig) + siglen)) == NULL)
         return NULL;
 
-    self->super = (ptls_async_job_t){async_sign_ctx_free};
+    self->super = (ptls_async_job_t){async_sign_ctx_free, async_sign_ctx_get_fd};
     self->scheme = scheme;
     self->ctx = ctx;
     self->waitctx = ASYNC_WAIT_CTX_new();
@@ -742,18 +768,6 @@
     return &self->super;
 }
 
-OSSL_ASYNC_FD ptls_openssl_get_async_fd(ptls_t *ptls)
-{
-    OSSL_ASYNC_FD fds[1];
-    size_t numfds;
-    struct async_sign_ctx *async = (void *)ptls_get_async_job(ptls);
-    assert(async != NULL);
-    ASYNC_WAIT_CTX_get_all_fds(async->waitctx, NULL, &numfds);
-    assert(numfds == 1);
-    ASYNC_WAIT_CTX_get_all_fds(async->waitctx, fds, &numfds);
-    return fds[0];
-}
-
 static int do_sign_async_job(void *_async)
 {
     struct async_sign_ctx *async = *(struct async_sign_ctx **)_async;
@@ -792,7 +806,7 @@
 
 #endif
 
-static int do_sign(EVP_PKEY *key, const struct st_ptls_openssl_signature_scheme_t *scheme, ptls_buffer_t *outbuf,
+static int do_sign(EVP_PKEY *key, const ptls_openssl_signature_scheme_t *scheme, ptls_buffer_t *outbuf,
                    ptls_iovec_t input, ptls_async_job_t **async)
 {
     EVP_MD_CTX *ctx = NULL;
@@ -1161,7 +1175,7 @@
                             ptls_buffer_t *outbuf, ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms)
 {
     ptls_openssl_sign_certificate_t *self = (ptls_openssl_sign_certificate_t *)_self;
-    const struct st_ptls_openssl_signature_scheme_t *scheme;
+    const ptls_openssl_signature_scheme_t *scheme;
 
     /* Just resume the asynchronous operation, if one is in flight. */
 #if PTLS_OPENSSL_HAVE_ASYNC
@@ -1172,17 +1186,11 @@
     }
 #endif
 
-    /* Select the algorithm (driven by server-side preference of `self->schemes`), or return failure if none found. */
-    for (scheme = self->schemes; scheme->scheme_id != UINT16_MAX; ++scheme) {
-        size_t i;
-        for (i = 0; i != num_algorithms; ++i)
-            if (algorithms[i] == scheme->scheme_id)
-                goto Found;
-    }
-    return PTLS_ALERT_HANDSHAKE_FAILURE;
-
-Found:
+    /* Select the algorithm or return failure if none found. */
+    if ((scheme = ptls_openssl_select_signature_scheme(self->schemes, algorithms, num_algorithms)) == NULL)
+        return PTLS_ALERT_HANDSHAKE_FAILURE;
     *selected_algorithm = scheme->scheme_id;
+
 #if PTLS_OPENSSL_HAVE_ASYNC
     if (!self->async && async != NULL) {
         /* indicate to `do_sign` that async mode is disabled for this operation */
@@ -1202,7 +1210,7 @@
 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;
+    const ptls_openssl_signature_scheme_t *scheme;
     EVP_MD_CTX *ctx = NULL;
     EVP_PKEY_CTX *pkey_ctx = NULL;
     int ret = 0;
@@ -1210,7 +1218,7 @@
     if (data.base == NULL)
         goto Exit;
 
-    if ((scheme = lookup_signature_schemes(key)) == NULL) {
+    if ((scheme = ptls_openssl_lookup_signature_schemes(key)) == NULL) {
         ret = PTLS_ERROR_LIBRARY;
         goto Exit;
     }
@@ -1282,7 +1290,7 @@
 {
     *self = (ptls_openssl_sign_certificate_t){.super = {sign_certificate}, .async = 0 /* libssl has it off by default too */};
 
-    if ((self->schemes = lookup_signature_schemes(key)) == NULL)
+    if ((self->schemes = ptls_openssl_lookup_signature_schemes(key)) == NULL)
         return PTLS_ERROR_INCOMPATIBLE_KEY;
     EVP_PKEY_up_ref(key);
     self->key = key;
diff --git a/t/openssl.c b/t/openssl.c
index b4c73b0..4ca49aa 100644
--- a/t/openssl.c
+++ b/t/openssl.c
@@ -143,7 +143,7 @@
 #endif
 }
 
-static void test_sign_verify(EVP_PKEY *key, const struct st_ptls_openssl_signature_scheme_t *schemes)
+static void test_sign_verify(EVP_PKEY *key, const 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);
@@ -198,7 +198,7 @@
     test_sign_verify(sc->key, sc->schemes);
 }
 
-static void do_test_ecdsa_sign(int nid, const struct st_ptls_openssl_signature_scheme_t *schemes)
+static void do_test_ecdsa_sign(int nid, const ptls_openssl_signature_scheme_t *schemes)
 {
     EVP_PKEY *pkey;
 
@@ -464,10 +464,12 @@
                 if (num_issued < num_total)
                     qat_set_pending(offending);
                 break;
-            case PTLS_ERROR_ASYNC_OPERATION:
-                qat.conns[offending].wait_fd = ptls_openssl_get_async_fd(qat.conns[offending].tls);
+            case PTLS_ERROR_ASYNC_OPERATION: {
+                ptls_async_job_t *job = ptls_get_async_job(qat.conns[offending].tls);
+                assert(job->get_fd != NULL);
+                qat.conns[offending].wait_fd = job->get_fd(job);
                 assert(qat.conns[offending].wait_fd != -1);
-                break;
+            } break;
             default:
                 fprintf(stderr, "ptls_handshake returned %d\n", hsret);
                 abort();