]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[tls] Support sending a client certificate
authorMichael Brown <mcb30@ipxe.org>
Tue, 20 Mar 2012 17:09:22 +0000 (17:09 +0000)
committerMichael Brown <mcb30@ipxe.org>
Tue, 20 Mar 2012 20:42:51 +0000 (20:42 +0000)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/tls.h
src/net/tls.c

index 0d1f2d85b0e25612b70d8a803f19a047e28ededa..772233369734953f22c87d6a98ce0a631c87cc1f 100644 (file)
@@ -80,6 +80,14 @@ struct tls_header {
 #define TLS_RSA_WITH_AES_128_CBC_SHA256 0x003c
 #define TLS_RSA_WITH_AES_256_CBC_SHA256 0x003d
 
+/* TLS hash algorithm identifiers */
+#define TLS_MD5_ALGORITHM 1
+#define TLS_SHA1_ALGORITHM 2
+#define TLS_SHA256_ALGORITHM 4
+
+/* TLS signature algorithm identifiers */
+#define TLS_RSA_ALGORITHM 1
+
 /* TLS extension types */
 #define TLS_SERVER_NAME 0
 #define TLS_SERVER_NAME_HOST_NAME 0
@@ -95,8 +103,9 @@ enum tls_tx_pending {
        TLS_TX_CLIENT_HELLO = 0x0001,
        TLS_TX_CERTIFICATE = 0x0002,
        TLS_TX_CLIENT_KEY_EXCHANGE = 0x0004,
-       TLS_TX_CHANGE_CIPHER = 0x0008,
-       TLS_TX_FINISHED = 0x0010,
+       TLS_TX_CERTIFICATE_VERIFY = 0x0008,
+       TLS_TX_CHANGE_CIPHER = 0x0010,
+       TLS_TX_FINISHED = 0x0020,
 };
 
 /** A TLS cipher suite */
@@ -129,6 +138,24 @@ struct tls_cipherspec {
        void *mac_secret;
 };
 
+/** A TLS signature and hash algorithm identifier */
+struct tls_signature_hash_id {
+       /** Hash algorithm */
+       uint8_t hash;
+       /** Signature algorithm */
+       uint8_t signature;
+} __attribute__ (( packed ));
+
+/** A TLS signature algorithm */
+struct tls_signature_hash_algorithm {
+       /** Digest algorithm */
+       struct digest_algorithm *digest;
+       /** Public-key algorithm */
+       struct pubkey_algorithm *pubkey;
+       /** Numeric code */
+       struct tls_signature_hash_id code;
+};
+
 /** TLS pre-master secret */
 struct tls_pre_master_secret {
        /** TLS version */
@@ -205,6 +232,8 @@ struct tls_session {
        struct digest_algorithm *handshake_digest;
        /** Digest algorithm context used for handshake verification */
        uint8_t *handshake_ctx;
+       /** Public-key algorithm used for Certificate Verify (if sent) */
+       struct pubkey_algorithm *verify_pubkey;
 
        /** TX sequence number */
        uint64_t tx_seq;
index 56b01f233d61be55d25740c0b3d1a3781f2da832..4b5891e493dbfbcfb4f3c2f51ebc82b83c50f751 100644 (file)
@@ -41,6 +41,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/xfer.h>
 #include <ipxe/open.h>
 #include <ipxe/x509.h>
+#include <ipxe/clientcert.h>
 #include <ipxe/rbg.h>
 #include <ipxe/tls.h>
 
@@ -75,8 +76,25 @@ static void tls_clear_cipher ( struct tls_session *tls,
  * TLS uses 24-bit integers in several places, which are awkward to
  * parse in C.
  */
-static unsigned long tls_uint24 ( const uint8_t field24[3] ) {
-       return ( ( field24[0] << 16 ) + ( field24[1] << 8 ) + field24[2] );
+static inline __attribute__ (( always_inline )) unsigned long
+tls_uint24 ( const uint8_t field24[3] ) {
+       const uint32_t *field32 __attribute__ (( may_alias )) =
+               ( ( const void * ) field24 );
+       return ( be32_to_cpu ( *field32 ) >> 8 );
+}
+
+/**
+ * Set 24-bit field value
+ *
+ * @v field24          24-bit field
+ * @v value            Field value
+ *
+ * The field must be pre-zeroed.
+ */
+static void tls_set_uint24 ( uint8_t field24[3], unsigned long value ) {
+       uint32_t *field32 __attribute__ (( may_alias )) =
+               ( ( void * ) field24 );
+       *field32 |= cpu_to_be32 ( value << 8 );
 }
 
 /******************************************************************************
@@ -137,6 +155,13 @@ static struct digest_algorithm md5_sha1_algorithm = {
        .final          = md5_sha1_final,
 };
 
+/** RSA digestInfo prefix for MD5+SHA1 algorithm */
+struct rsa_digestinfo_prefix rsa_md5_sha1_prefix __rsa_digestinfo_prefix = {
+       .digest = &md5_sha1_algorithm,
+       .data = NULL, /* MD5+SHA1 signatures have no digestInfo */
+       .len = 0,
+};
+
 /******************************************************************************
  *
  * Cleanup functions
@@ -674,6 +699,59 @@ static int tls_change_cipher ( struct tls_session *tls,
        return 0;
 }
 
+/******************************************************************************
+ *
+ * Signature and hash algorithms
+ *
+ ******************************************************************************
+ */
+
+/** Supported signature and hash algorithms
+ *
+ * Note that the default (TLSv1.1 and earlier) algorithm using
+ * MD5+SHA1 is never explicitly specified.
+ */
+struct tls_signature_hash_algorithm tls_signature_hash_algorithms[] = {
+       {
+               .code = {
+                       .signature = TLS_RSA_ALGORITHM,
+                       .hash = TLS_SHA256_ALGORITHM,
+               },
+               .pubkey = &rsa_algorithm,
+               .digest = &sha256_algorithm,
+       },
+};
+
+/** Number of supported signature and hash algorithms */
+#define TLS_NUM_SIG_HASH_ALGORITHMS                    \
+       ( sizeof ( tls_signature_hash_algorithms ) /    \
+         sizeof ( tls_signature_hash_algorithms[0] ) )
+
+/**
+ * Find TLS signature and hash algorithm
+ *
+ * @v pubkey           Public-key algorithm
+ * @v digest           Digest algorithm
+ * @ret sig_hash       Signature and hash algorithm, or NULL
+ */
+static struct tls_signature_hash_algorithm *
+tls_signature_hash_algorithm ( struct pubkey_algorithm *pubkey,
+                              struct digest_algorithm *digest ) {
+       struct tls_signature_hash_algorithm *sig_hash;
+       unsigned int i;
+
+       /* Identify signature and hash algorithm */
+       for ( i = 0 ; i < TLS_NUM_SIG_HASH_ALGORITHMS ; i++ ) {
+               sig_hash = &tls_signature_hash_algorithms[i];
+               if ( ( sig_hash->pubkey == pubkey ) &&
+                    ( sig_hash->digest == digest ) ) {
+                       return sig_hash;
+               }
+       }
+
+       return NULL;
+}
+
 /******************************************************************************
  *
  * Handshake verification
@@ -812,19 +890,68 @@ static int tls_send_client_hello ( struct tls_session *tls ) {
  * @ret rc             Return status code
  */
 static int tls_send_certificate ( struct tls_session *tls ) {
+       int num_certificates = ( have_client_certificate() ? 1 : 0 );
        struct {
                uint32_t type_length;
                uint8_t length[3];
-       } __attribute__ (( packed )) certificate;
+               struct {
+                       uint8_t length[3];
+                       uint8_t data[ client_certificate.len ];
+               } __attribute__ (( packed )) certificates[num_certificates];
+       } __attribute__ (( packed )) *certificate;
+       struct x509_certificate cert;
+       int rc;
 
-       memset ( &certificate, 0, sizeof ( certificate ) );
-       certificate.type_length = ( cpu_to_le32 ( TLS_CERTIFICATE ) |
-                                   htonl ( sizeof ( certificate ) -
-                                           sizeof ( certificate.type_length)));
+       /* If we have a certificate to send, determine the applicable
+        * public-key algorithm and schedule transmission of
+        * CertificateVerify.
+        */
+       if ( num_certificates ) {
 
-       return tls_send_handshake ( tls, &certificate, sizeof ( certificate ) );
-}
+               /* Parse certificate to determine public-key algorithm */
+               if ( ( rc = x509_parse ( &cert, client_certificate.data,
+                                        client_certificate.len ) ) != 0 ) {
+                       DBGC ( tls, "TLS %p could not parse client "
+                              "certificate: %s\n", tls, strerror ( rc ) );
+                       return rc;
+               }
+               tls->verify_pubkey = cert.signature_algorithm->pubkey;
+
+               /* Schedule CertificateVerify transmission */
+               tls->tx_pending |= TLS_TX_CERTIFICATE_VERIFY;
+               tls_tx_resume ( tls );
+       }
 
+       /* Allocate storage for Certificate record (which may be too
+        * large for the stack).
+        */
+       certificate = zalloc ( sizeof ( *certificate ) );
+       if ( ! certificate )
+               return -ENOMEM;
+
+       /* Populate record */
+       certificate->type_length =
+               ( cpu_to_le32 ( TLS_CERTIFICATE ) |
+                 htonl ( sizeof ( *certificate ) -
+                         sizeof ( certificate->type_length ) ) );
+       tls_set_uint24 ( certificate->length,
+                        sizeof ( certificate->certificates ) );
+       if ( num_certificates ) {
+               tls_set_uint24 ( certificate->certificates[0].length,
+                                sizeof ( certificate->certificates[0].data ) );
+               memcpy ( certificate->certificates[0].data,
+                        client_certificate.data,
+                        sizeof ( certificate->certificates[0].data ) );
+       }
+
+       /* Transmit record */
+       rc = tls_send_handshake ( tls, certificate, sizeof ( *certificate ) );
+
+       /* Free record */
+       free ( certificate );
+
+       return rc;
+}
 
 /**
  * Transmit Client Key Exchange record
@@ -866,7 +993,97 @@ static int tls_send_client_key_exchange ( struct tls_session *tls ) {
                htons ( sizeof ( key_xchg.encrypted_pre_master_secret ) -
                        unused );
 
-       return tls_send_handshake ( tls, &key_xchg, sizeof ( key_xchg ) );
+       return tls_send_handshake ( tls, &key_xchg,
+                                   ( sizeof ( key_xchg ) - unused ) );
+}
+
+/**
+ * Transmit Certificate Verify record
+ *
+ * @v tls              TLS session
+ * @ret rc             Return status code
+ */
+static int tls_send_certificate_verify ( struct tls_session *tls ) {
+       struct digest_algorithm *digest = tls->handshake_digest;
+       struct pubkey_algorithm *pubkey = tls->verify_pubkey;
+       uint8_t digest_out[ digest->digestsize ];
+       uint8_t ctx[ pubkey->ctxsize ];
+       struct tls_signature_hash_algorithm *sig_hash = NULL;
+       int rc;
+
+       /* Generate digest to be signed */
+       tls_verify_handshake ( tls, digest_out );
+
+       /* Initialise public-key algorithm */
+       if ( ( rc = pubkey_init ( pubkey, ctx, client_private_key.data,
+                                 client_private_key.len ) ) != 0 ) {
+               DBGC ( tls, "TLS %p could not initialise %s client private "
+                      "key: %s\n", tls, pubkey->name, strerror ( rc ) );
+               goto err_pubkey_init;
+       }
+
+       /* TLSv1.2 and later use explicit algorithm identifiers */
+       if ( tls->version >= TLS_VERSION_TLS_1_2 ) {
+               sig_hash = tls_signature_hash_algorithm ( pubkey, digest );
+               if ( ! sig_hash ) {
+                       DBGC ( tls, "TLS %p could not identify (%s,%s) "
+                              "signature and hash algorithm\n", tls,
+                              pubkey->name, digest->name );
+                       rc = -ENOTSUP;
+                       goto err_sig_hash;
+               }
+       }
+
+       /* Generate and transmit record */
+       {
+               size_t max_len = pubkey_max_len ( pubkey, ctx );
+               int use_sig_hash = ( ( sig_hash == NULL ) ? 0 : 1 );
+               struct {
+                       uint32_t type_length;
+                       struct tls_signature_hash_id sig_hash[use_sig_hash];
+                       uint16_t signature_len;
+                       uint8_t signature[max_len];
+               } __attribute__ (( packed )) certificate_verify;
+               size_t unused;
+               int len;
+
+               /* Sign digest */
+               len = pubkey_sign ( pubkey, ctx, digest, digest_out,
+                                   certificate_verify.signature );
+               if ( len < 0 ) {
+                       rc = len;
+                       DBGC ( tls, "TLS %p could not sign %s digest using %s "
+                              "client private key: %s\n", tls, digest->name,
+                              pubkey->name, strerror ( rc ) );
+                       goto err_pubkey_sign;
+               }
+               unused = ( max_len - len );
+
+               /* Construct Certificate Verify record */
+               certificate_verify.type_length =
+                       ( cpu_to_le32 ( TLS_CERTIFICATE_VERIFY ) |
+                         htonl ( sizeof ( certificate_verify ) -
+                                 sizeof ( certificate_verify.type_length ) -
+                                 unused ) );
+               if ( use_sig_hash ) {
+                       memcpy ( &certificate_verify.sig_hash[0],
+                                &sig_hash->code,
+                                sizeof ( certificate_verify.sig_hash[0] ) );
+               }
+               certificate_verify.signature_len =
+                       htons ( sizeof ( certificate_verify.signature ) -
+                               unused );
+
+               /* Transmit record */
+               rc = tls_send_handshake ( tls, &certificate_verify,
+                                  ( sizeof ( certificate_verify ) - unused ) );
+       }
+
+ err_pubkey_sign:
+ err_sig_hash:
+       pubkey_final ( pubkey, ctx );
+ err_pubkey_init:
+       return rc;
 }
 
 /**
@@ -1182,9 +1399,8 @@ static int tls_new_certificate_request ( struct tls_session *tls,
                                         const void *data __unused,
                                         size_t len __unused ) {
 
-       /* We can only send an empty certificate (as mandated by
-        * TLSv1.2), so there is no point in parsing the Certificate
-        * Request.
+       /* We can only send a single certificate, so there is no point
+        * in parsing the Certificate Request.
         */
 
        /* Schedule Certificate transmission */
@@ -2002,6 +2218,14 @@ static void tls_tx_step ( struct tls_session *tls ) {
                        goto err;
                }
                tls->tx_pending &= ~TLS_TX_CLIENT_KEY_EXCHANGE;
+       } else if ( tls->tx_pending & TLS_TX_CERTIFICATE_VERIFY ) {
+               /* Send Certificate Verify */
+               if ( ( rc = tls_send_certificate_verify ( tls ) ) != 0 ) {
+                       DBGC ( tls, "TLS %p could not send Certificate "
+                              "Verify: %s\n", tls, strerror ( rc ) );
+                       goto err;
+               }
+               tls->tx_pending &= ~TLS_TX_CERTIFICATE_VERIFY;
        } else if ( tls->tx_pending & TLS_TX_CHANGE_CIPHER ) {
                /* Send Change Cipher, and then change the cipher in use */
                if ( ( rc = tls_send_change_cipher ( tls ) ) != 0 ) {