Make key exchange strength available.

This change stores the size of the group/modulus (for RSA/DHE) or curve
ID (for ECDHE) in the |SSL_SESSION|. This makes it available for UIs
where desired.

Change-Id: I354141da432a08f71704c9683f298b361362483d
Reviewed-on: https://boringssl-review.googlesource.com/5280
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 82f2cb0..9a18032 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -1051,6 +1051,18 @@
 
   const SSL_CIPHER *cipher;
 
+  /* key_exchange_info contains an indication of the size of the asymmetric
+   * primitive used in the handshake that created this session. In the event
+   * that two asymmetric operations are used, this value applies to the one
+   * that controls the confidentiality of the connection. Its interpretation
+   * depends on the primitive that was used; as specified by the cipher suite:
+   *   DHE: the size, in bits, of the multiplicative group.
+   *   RSA: the size, in bits, of the modulus.
+   *   ECDHE: the TLS id for the curve.
+   *
+   * A zero indicates that the value is unknown. */
+  uint32_t key_exchange_info;
+
   CRYPTO_EX_DATA ex_data; /* application specific data */
 
   /* These are used to make removal of session-ids more efficient and to
@@ -2152,6 +2164,13 @@
 OPENSSL_EXPORT long SSL_SESSION_set_time(SSL_SESSION *s, long t);
 OPENSSL_EXPORT long SSL_SESSION_get_timeout(const SSL_SESSION *s);
 OPENSSL_EXPORT long SSL_SESSION_set_timeout(SSL_SESSION *s, long t);
+
+/* SSL_SESSION_get_key_exchange_info returns a value that describes the
+ * strength of the asymmetric operation that provides confidentiality to
+ * |session|. Its interpretation depends on the operation used. See the
+ * documentation for this value in the |SSL_SESSION| structure. */
+OPENSSL_EXPORT uint32_t SSL_SESSION_get_key_exchange_info(SSL_SESSION *session);
+
 OPENSSL_EXPORT X509 *SSL_SESSION_get0_peer(SSL_SESSION *s);
 OPENSSL_EXPORT int SSL_SESSION_set1_id_context(SSL_SESSION *s,
                                                const uint8_t *sid_ctx,
@@ -2271,6 +2290,10 @@
  * |sess|. For example, "TLSv1.2" or "SSLv3". */
 OPENSSL_EXPORT const char *SSL_SESSION_get_version(const SSL_SESSION *sess);
 
+/* SSL_get_curve_name returns a human-readable name for the elliptic curve
+ * specified by the given TLS curve id, or NULL if the curve if unknown. */
+OPENSSL_EXPORT const char* SSL_get_curve_name(uint16_t curve_id);
+
 OPENSSL_EXPORT STACK_OF(SSL_CIPHER) *SSL_get_ciphers(const SSL *s);
 
 OPENSSL_EXPORT int SSL_do_handshake(SSL *s);
diff --git a/ssl/internal.h b/ssl/internal.h
index 7d4214d..63046e8 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1072,6 +1072,11 @@
 int tls1_ec_curve_id2nid(uint16_t curve_id);
 int tls1_ec_nid2curve_id(uint16_t *out_curve_id, int nid);
 
+/* tls1_ec_curve_id2name returns a human-readable name for the
+ * curve specified by the TLS curve id in |curve_id|. If the
+ * curve is unknown, it returns NULL. */
+const char* tls1_ec_curve_id2name(uint16_t curve_id);
+
 /* tls1_check_curve parses ECParameters out of |cbs|, modifying it. It
  * checks the curve is one of our preferences and writes the
  * NamedCurve value to |*out_curve_id|. It returns one on success and
diff --git a/ssl/s3_clnt.c b/ssl/s3_clnt.c
index ef24316..1ed1507 100644
--- a/ssl/s3_clnt.c
+++ b/ssl/s3_clnt.c
@@ -1186,7 +1186,8 @@
       goto err;
     }
 
-    if (DH_num_bits(dh) < 1024) {
+    s->session->key_exchange_info = DH_num_bits(dh);
+    if (s->session->key_exchange_info < 1024) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_DH_P_LENGTH);
       goto err;
     }
@@ -1215,6 +1216,7 @@
     }
 
     ecdh = EC_KEY_new_by_curve_name(curve_nid);
+    s->session->key_exchange_info = curve_id;
     if (ecdh == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_EC_LIB);
       goto err;
@@ -1696,6 +1698,7 @@
         goto err;
       }
 
+      s->session->key_exchange_info = EVP_PKEY_bits(pkey);
       rsa = pkey->pkey.rsa;
       EVP_PKEY_free(pkey);
 
diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c
index a2b6879..e516a45 100644
--- a/ssl/ssl_asn1.c
+++ b/ssl/ssl_asn1.c
@@ -116,6 +116,7 @@
  *     ocspResponse            [16] OCTET STRING OPTIONAL,
  *                                   -- stapled OCSP response from the server
  *     extendedMasterSecret    [17] BOOLEAN OPTIONAL,
+ *     keyExchangeInfo         [18] INTEGER OPTIONAL,
  * }
  *
  * Note: historically this serialization has included other optional
@@ -154,6 +155,8 @@
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 16;
 static const int kExtendedMasterSecretTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 17;
+static const int kKeyExchangeInfoTag =
+    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 18;
 
 static int SSL_SESSION_to_bytes_full(SSL_SESSION *in, uint8_t **out_data,
                                      size_t *out_len, int for_ticket) {
@@ -315,6 +318,13 @@
     }
   }
 
+  if (in->key_exchange_info > 0 &&
+      (!CBB_add_asn1(&session, &child, kKeyExchangeInfoTag) ||
+       !CBB_add_asn1_uint64(&child, in->key_exchange_info))) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    goto err;
+  }
+
   if (!CBB_finish(&cbb, out_data, out_len)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     goto err;
@@ -413,7 +423,8 @@
   CBS peer, sid_ctx, peer_sha256, original_handshake_hash;
   int has_peer, has_peer_sha256, extended_master_secret;
   uint64_t version, ssl_version;
-  uint64_t session_time, timeout, verify_result, ticket_lifetime_hint;
+  uint64_t session_time, timeout, verify_result, ticket_lifetime_hint,
+      key_exchange_info;
 
   ret = SSL_SESSION_new();
   if (ret == NULL) {
@@ -470,13 +481,22 @@
   }
   if (!CBS_get_optional_asn1_bool(&session, &extended_master_secret,
                                   kExtendedMasterSecretTag,
-                                  0 /* default to false */) ||
-      CBS_len(&session) != 0) {
+                                  0 /* default to false */)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     goto err;
   }
   ret->extended_master_secret = extended_master_secret;
 
+  if (!CBS_get_optional_asn1_uint64(&session, &key_exchange_info,
+                                    kKeyExchangeInfoTag, 0) ||
+      CBS_len(&session) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
+    goto err;
+  }
+  if (key_exchange_info <= 0xffffffff) {
+    ret->key_exchange_info = key_exchange_info;
+  }
+
   if (version != SSL_SESSION_ASN1_VERSION) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     goto err;
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index be3a2b9..6af0040 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -2099,6 +2099,10 @@
   return ssl_get_version(sess->ssl_version);
 }
 
+const char* SSL_get_curve_name(uint16_t curve_id) {
+  return tls1_ec_curve_id2name(curve_id);
+}
+
 void ssl_clear_cipher_ctx(SSL *s) {
   SSL_AEAD_CTX_free(s->aead_read_ctx);
   s->aead_read_ctx = NULL;
diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c
index 2c5dfe6..1c644fd 100644
--- a/ssl/ssl_sess.c
+++ b/ssl/ssl_sess.c
@@ -639,20 +639,12 @@
   return 1;
 }
 
-long SSL_SESSION_get_timeout(const SSL_SESSION *s) {
-  if (s == NULL) {
-    return 0;
-  }
-
-  return s->timeout;
+long SSL_SESSION_get_timeout(const SSL_SESSION *session) {
+  return session->timeout;
 }
 
-long SSL_SESSION_get_time(const SSL_SESSION *s) {
-  if (s == NULL) {
-    return 0;
-  }
-
-  return s->time;
+long SSL_SESSION_get_time(const SSL_SESSION *session) {
+  return session->time;
 }
 
 long SSL_SESSION_set_time(SSL_SESSION *s, long t) {
@@ -664,6 +656,10 @@
   return t;
 }
 
+uint32_t SSL_SESSION_get_key_exchange_info(SSL_SESSION *session) {
+  return session->key_exchange_info;
+}
+
 X509 *SSL_SESSION_get0_peer(SSL_SESSION *s) { return s->peer; }
 
 int SSL_SESSION_set1_id_context(SSL_SESSION *s, const uint8_t *sid_ctx,
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index f31e5a1..bfb6b50 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -24,6 +24,8 @@
 #include <openssl/ssl.h>
 
 #include "test/scoped_types.h"
+#include "../crypto/test/test_util.h"
+
 
 struct ExpectedCipher {
   unsigned long id;
@@ -418,6 +420,8 @@
   if (encoded_len != input.size() ||
       memcmp(bssl::vector_data(&input), encoded.get(), input.size()) != 0) {
     fprintf(stderr, "SSL_SESSION_to_bytes did not round-trip\n");
+    hexdump(stderr, "Before: ", input.data(), input.size());
+    hexdump(stderr, "After:  ", encoded_raw, encoded_len);
     return false;
   }
 
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index d2f8983..7a13f8e 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -336,14 +336,15 @@
 struct tls_curve {
   uint16_t curve_id;
   int nid;
+  const char curve_name[8];
 };
 
 /* ECC curves from RFC4492. */
 static const struct tls_curve tls_curves[] = {
-    {21, NID_secp224r1},
-    {23, NID_X9_62_prime256v1},
-    {24, NID_secp384r1},
-    {25, NID_secp521r1},
+    {21, NID_secp224r1, "P-224"},
+    {23, NID_X9_62_prime256v1, "P-256"},
+    {24, NID_secp384r1, "P-384"},
+    {25, NID_secp521r1, "P-521"},
 };
 
 static const uint16_t eccurves_default[] = {
@@ -372,6 +373,16 @@
   return 0;
 }
 
+const char* tls1_ec_curve_id2name(uint16_t curve_id) {
+  size_t i;
+  for (i = 0; i < sizeof(tls_curves) / sizeof(tls_curves[0]); i++) {
+    if (curve_id == tls_curves[i].curve_id) {
+      return tls_curves[i].curve_name;
+    }
+  }
+  return NULL;
+}
+
 /* tls1_get_curvelist sets |*out_curve_ids| and |*out_curve_ids_len| to the
  * list of allowed curve IDs. If |get_peer_curves| is non-zero, return the
  * peer's curve list. Otherwise, return the preferred list. */