Add |SSL[_CTX]_set_chain_and_key|.
This allows a caller to configure a serving chain without dealing with
crypto/x509.
Change-Id: Ib42bb2ab9227d32071cf13ab07f92d029643a9a6
Reviewed-on: https://boringssl-review.googlesource.com/14126
Commit-Queue: Adam Langley <alangley@gmail.com>
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c
index 5b9f3de..5013b20 100644
--- a/ssl/ssl_cert.c
+++ b/ssl/ssl_cert.c
@@ -248,18 +248,33 @@
c->cert_cb_arg = arg;
}
-int ssl_set_cert(CERT *cert, CRYPTO_BUFFER *buffer) {
+enum leaf_cert_and_privkey_result_t {
+ leaf_cert_and_privkey_error,
+ leaf_cert_and_privkey_ok,
+ leaf_cert_and_privkey_mismatch,
+};
+
+/* check_leaf_cert_and_privkey checks whether the certificate in |leaf_buffer|
+ * and the private key in |privkey| are suitable and coherent. It returns
+ * |leaf_cert_and_privkey_error| and pushes to the error queue if a problem is
+ * found. If the certificate and private key are valid, but incoherent, it
+ * returns |leaf_cert_and_privkey_mismatch|. Otherwise it returns
+ * |leaf_cert_and_privkey_ok|. */
+static enum leaf_cert_and_privkey_result_t check_leaf_cert_and_privkey(
+ CRYPTO_BUFFER *leaf_buffer, EVP_PKEY *privkey) {
+ enum leaf_cert_and_privkey_result_t ret = leaf_cert_and_privkey_error;
+
CBS cert_cbs;
- CRYPTO_BUFFER_init_CBS(buffer, &cert_cbs);
+ CRYPTO_BUFFER_init_CBS(leaf_buffer, &cert_cbs);
EVP_PKEY *pubkey = ssl_cert_parse_pubkey(&cert_cbs);
if (pubkey == NULL) {
- return 0;
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+ goto out;
}
if (!ssl_is_key_type_supported(pubkey->type)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
- EVP_PKEY_free(pubkey);
- return 0;
+ goto out;
}
/* An ECC certificate may be usable for ECDH or ECDSA. We only support ECDSA
@@ -267,26 +282,102 @@
if (pubkey->type == EVP_PKEY_EC &&
!ssl_cert_check_digital_signature_key_usage(&cert_cbs)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
- EVP_PKEY_free(pubkey);
+ goto out;
+ }
+
+ if (privkey != NULL &&
+ /* Sanity-check that the private key and the certificate match. */
+ !ssl_compare_public_and_private_key(pubkey, privkey)) {
+ ERR_clear_error();
+ ret = leaf_cert_and_privkey_mismatch;
+ goto out;
+ }
+
+ ret = leaf_cert_and_privkey_ok;
+
+out:
+ EVP_PKEY_free(pubkey);
+ return ret;
+}
+
+static int cert_set_chain_and_key(
+ CERT *cert, CRYPTO_BUFFER *const *certs, size_t num_certs,
+ EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+ if (num_certs == 0 ||
+ (privkey == NULL && privkey_method == NULL)) {
+ OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
- if (cert->privatekey != NULL) {
- /* Sanity-check that the private key and the certificate match, unless the
- * key is opaque (in case of, say, a smartcard). */
- if (!EVP_PKEY_is_opaque(cert->privatekey) &&
- !ssl_compare_public_and_private_key(pubkey, cert->privatekey)) {
- /* don't fail for a cert/key mismatch, just free current private key
- * (when switching to a different cert & key, first this function should
- * be used, then ssl_set_pkey */
- EVP_PKEY_free(cert->privatekey);
- cert->privatekey = NULL;
- /* clear error queue */
- ERR_clear_error();
- }
+ if (privkey != NULL && privkey_method != NULL) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD);
+ return 0;
}
- EVP_PKEY_free(pubkey);
+ switch (check_leaf_cert_and_privkey(certs[0], privkey)) {
+ case leaf_cert_and_privkey_error:
+ return 0;
+ case leaf_cert_and_privkey_mismatch:
+ OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_AND_PRIVATE_KEY_MISMATCH);
+ return 0;
+ case leaf_cert_and_privkey_ok:
+ break;
+ }
+
+ STACK_OF(CRYPTO_BUFFER) *certs_sk = sk_CRYPTO_BUFFER_new_null();
+ if (certs_sk == NULL) {
+ return 0;
+ }
+
+ for (size_t i = 0; i < num_certs; i++) {
+ if (!sk_CRYPTO_BUFFER_push(certs_sk, certs[i])) {
+ sk_CRYPTO_BUFFER_pop_free(certs_sk, CRYPTO_BUFFER_free);
+ return 0;
+ }
+ CRYPTO_BUFFER_up_ref(certs[i]);
+ }
+
+ EVP_PKEY_free(cert->privatekey);
+ cert->privatekey = privkey;
+ if (privkey != NULL) {
+ EVP_PKEY_up_ref(privkey);
+ }
+ cert->key_method = privkey_method;
+
+ sk_CRYPTO_BUFFER_pop_free(cert->chain, CRYPTO_BUFFER_free);
+ cert->chain = certs_sk;
+
+ return 1;
+}
+
+int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs,
+ size_t num_certs, EVP_PKEY *privkey,
+ const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+ return cert_set_chain_and_key(ssl->cert, certs, num_certs, privkey,
+ privkey_method);
+}
+
+int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs,
+ size_t num_certs, EVP_PKEY *privkey,
+ const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+ return cert_set_chain_and_key(ctx->cert, certs, num_certs, privkey,
+ privkey_method);
+}
+
+int ssl_set_cert(CERT *cert, CRYPTO_BUFFER *buffer) {
+ switch (check_leaf_cert_and_privkey(buffer, cert->privatekey)) {
+ case leaf_cert_and_privkey_error:
+ return 0;
+ case leaf_cert_and_privkey_mismatch:
+ /* don't fail for a cert/key mismatch, just free current private key
+ * (when switching to a different cert & key, first this function should
+ * be used, then |ssl_set_pkey|. */
+ EVP_PKEY_free(cert->privatekey);
+ cert->privatekey = NULL;
+ break;
+ case leaf_cert_and_privkey_ok:
+ break;
+ }
cert->x509_method->cert_flush_cached_leaf(cert);
@@ -494,6 +585,12 @@
int ssl_compare_public_and_private_key(const EVP_PKEY *pubkey,
const EVP_PKEY *privkey) {
+ if (EVP_PKEY_is_opaque(privkey)) {
+ /* We cannot check an opaque private key and have to trust that it
+ * matches. */
+ return 1;
+ }
+
int ret = 0;
switch (EVP_PKEY_cmp(pubkey, privkey)) {