Remove SSL 3.0 implementation.

Update-Note: SSL_CTX_set_min_proto_version(SSL3_VERSION) now fails.
   SSL_OP_NO_SSLv3 is now zero. Internal SSL3-specific "AEAD"s are gone.

Change-Id: I34edb160be40a5eea3e2e0fdea562c6e2adda229
Reviewed-on: https://boringssl-review.googlesource.com/29444
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/handoff.cc b/ssl/handoff.cc
index c4acc21..2ad6e4d 100644
--- a/ssl/handoff.cc
+++ b/ssl/handoff.cc
@@ -100,7 +100,7 @@
 }
 
 bool SSL_serialize_handback(const SSL *ssl, CBB *out) {
-  if (!ssl->server || ssl->method->is_dtls || ssl->version < TLS1_VERSION) {
+  if (!ssl->server || ssl->method->is_dtls) {
     return false;
   }
   handback_t type;
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 1f3f29b..cfba839 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -433,20 +433,18 @@
   }
 
   // Copy the Finished so we can use it for renegotiation checks.
-  if (ssl->version != SSL3_VERSION) {
-    if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
-        finished_len > sizeof(ssl->s3->previous_server_finished)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return ssl_hs_error;
-    }
+  if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
+      finished_len > sizeof(ssl->s3->previous_server_finished)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return ssl_hs_error;
+  }
 
-    if (ssl->server) {
-      OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
-      ssl->s3->previous_client_finished_len = finished_len;
-    } else {
-      OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
-      ssl->s3->previous_server_finished_len = finished_len;
-    }
+  if (ssl->server) {
+    OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
+    ssl->s3->previous_client_finished_len = finished_len;
+  } else {
+    OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
+    ssl->s3->previous_server_finished_len = finished_len;
   }
 
   ssl->method->next_message(ssl);
@@ -472,20 +470,18 @@
   }
 
   // Copy the Finished so we can use it for renegotiation checks.
-  if (ssl->version != SSL3_VERSION) {
-    if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
-        finished_len > sizeof(ssl->s3->previous_server_finished)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return 0;
-    }
+  if (finished_len > sizeof(ssl->s3->previous_client_finished) ||
+      finished_len > sizeof(ssl->s3->previous_server_finished)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
 
-    if (ssl->server) {
-      OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
-      ssl->s3->previous_server_finished_len = finished_len;
-    } else {
-      OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
-      ssl->s3->previous_client_finished_len = finished_len;
-    }
+  if (ssl->server) {
+    OPENSSL_memcpy(ssl->s3->previous_server_finished, finished, finished_len);
+    ssl->s3->previous_server_finished_len = finished_len;
+  } else {
+    OPENSSL_memcpy(ssl->s3->previous_client_finished, finished, finished_len);
+    ssl->s3->previous_client_finished_len = finished_len;
   }
 
   ScopedCBB cbb;
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index e9b0eed..391aa29 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -269,15 +269,6 @@
     }
   }
 
-  // For SSLv3, the SCSV is added. Otherwise the renegotiation extension is
-  // added.
-  if (hs->max_version == SSL3_VERSION &&
-      !ssl->s3->initial_handshake_complete) {
-    if (!CBB_add_u16(&child, SSL3_CK_SCSV & 0xffff)) {
-      return 0;
-    }
-  }
-
   if (ssl->mode & SSL_MODE_SEND_FALLBACK_SCSV) {
     if (!CBB_add_u16(&child, SSL3_CK_FALLBACK_SCSV & 0xffff)) {
       return 0;
@@ -394,12 +385,6 @@
     return ssl_hs_error;
   }
 
-  // SSL 3.0 ClientHellos should use SSL 3.0 not TLS 1.0, for the record-layer
-  // version.
-  if (hs->max_version == SSL3_VERSION) {
-    ssl->s3->aead_write_ctx->SetVersionIfNullCipher(SSL3_VERSION);
-  }
-
   // Always advertise the ClientHello version from the original maximum version,
   // even on renegotiation. The static RSA key exchange uses this field, and
   // some servers fail when it changes across handshakes.
@@ -1192,16 +1177,6 @@
   if (!ssl_has_certificate(hs->config)) {
     // Without a client certificate, the handshake buffer may be released.
     hs->transcript.FreeBuffer();
-
-    // In SSL 3.0, the Certificate message is replaced with a warning alert.
-    if (ssl->version == SSL3_VERSION) {
-      if (!ssl->method->add_alert(ssl, SSL3_AL_WARNING,
-                                  SSL_AD_NO_CERTIFICATE)) {
-        return ssl_hs_error;
-      }
-      hs->state = state_send_client_key_exchange;
-      return ssl_hs_ok;
-    }
   }
 
   if (!ssl_on_certificate_selected(hs) ||
@@ -1286,21 +1261,14 @@
       return ssl_hs_error;
     }
 
-    CBB child, *enc_pms = &body;
-    size_t enc_pms_len;
-    // In TLS, there is a length prefix.
-    if (ssl->version > SSL3_VERSION) {
-      if (!CBB_add_u16_length_prefixed(&body, &child)) {
-        return ssl_hs_error;
-      }
-      enc_pms = &child;
-    }
-
+    CBB enc_pms;
     uint8_t *ptr;
-    if (!CBB_reserve(enc_pms, &ptr, RSA_size(rsa)) ||
+    size_t enc_pms_len;
+    if (!CBB_add_u16_length_prefixed(&body, &enc_pms) ||
+        !CBB_reserve(&enc_pms, &ptr, RSA_size(rsa)) ||
         !RSA_encrypt(rsa, &enc_pms_len, ptr, RSA_size(rsa), pms.data(),
                      pms.size(), RSA_PKCS1_PADDING) ||
-        !CBB_did_write(enc_pms, enc_pms_len) ||
+        !CBB_did_write(&enc_pms, enc_pms_len) ||
         !CBB_flush(&body)) {
       return ssl_hs_error;
     }
@@ -1407,40 +1375,16 @@
   }
 
   size_t sig_len = max_sig_len;
-  // The SSL3 construction for CertificateVerify does not decompose into a
-  // single final digest and signature, and must be special-cased.
-  if (ssl_protocol_version(ssl) == SSL3_VERSION) {
-    if (hs->config->cert->key_method != NULL) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY);
+  switch (ssl_private_key_sign(hs, ptr, &sig_len, max_sig_len,
+                               signature_algorithm,
+                               hs->transcript.buffer())) {
+    case ssl_private_key_success:
+      break;
+    case ssl_private_key_failure:
       return ssl_hs_error;
-    }
-
-    uint8_t digest[EVP_MAX_MD_SIZE];
-    size_t digest_len;
-    if (!hs->transcript.GetSSL3CertVerifyHash(
-            digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
-      return ssl_hs_error;
-    }
-
-    UniquePtr<EVP_PKEY_CTX> pctx(
-        EVP_PKEY_CTX_new(hs->config->cert->privatekey.get(), nullptr));
-    if (!pctx ||
-        !EVP_PKEY_sign_init(pctx.get()) ||
-        !EVP_PKEY_sign(pctx.get(), ptr, &sig_len, digest, digest_len)) {
-      return ssl_hs_error;
-    }
-  } else {
-    switch (ssl_private_key_sign(hs, ptr, &sig_len, max_sig_len,
-                                 signature_algorithm,
-                                 hs->transcript.buffer())) {
-      case ssl_private_key_success:
-        break;
-      case ssl_private_key_failure:
-        return ssl_hs_error;
-      case ssl_private_key_retry:
-        hs->state = state_send_client_certificate_verify;
-        return ssl_hs_private_key_operation;
-    }
+    case ssl_private_key_retry:
+      hs->state = state_send_client_certificate_verify;
+      return ssl_hs_private_key_operation;
   }
 
   if (!CBB_did_write(&child, sig_len) ||
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 02657f3..2456d12 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -213,7 +213,6 @@
         0x03, 0x03,  // TLS 1.2
         0x03, 0x02,  // TLS 1.1
         0x03, 0x01,  // TLS 1
-        0x03, 0x00,  // SSL 3
     };
 
     static const uint8_t kDTLSVersions[] = {
@@ -232,12 +231,10 @@
                versions_len);
     } else {
       if (client_hello->version >= TLS1_2_VERSION) {
-        versions_len = 8;
-      } else if (client_hello->version >= TLS1_1_VERSION) {
         versions_len = 6;
-      } else if (client_hello->version >= TLS1_VERSION) {
+      } else if (client_hello->version >= TLS1_1_VERSION) {
         versions_len = 4;
-      } else if (client_hello->version >= SSL3_VERSION) {
+      } else if (client_hello->version >= TLS1_VERSION) {
         versions_len = 2;
       }
       CBS_init(&versions, kTLSVersions + sizeof(kTLSVersions) - versions_len,
@@ -917,8 +914,7 @@
                                    SSL3_MT_CERTIFICATE_REQUEST) ||
         !CBB_add_u8_length_prefixed(&body, &cert_types) ||
         !CBB_add_u8(&cert_types, SSL3_CT_RSA_SIGN) ||
-        (ssl_protocol_version(ssl) >= TLS1_VERSION &&
-         !CBB_add_u8(&cert_types, TLS_CT_ECDSA_SIGN)) ||
+        !CBB_add_u8(&cert_types, TLS_CT_ECDSA_SIGN) ||
         // TLS 1.2 has no way to specify different signature algorithms for
         // certificates and the online signature, so emit the more restrictive
         // certificate list.
@@ -959,26 +955,7 @@
     return ssl_hs_read_message;
   }
 
-  if (msg.type != SSL3_MT_CERTIFICATE) {
-    if (ssl->version == SSL3_VERSION &&
-        msg.type == SSL3_MT_CLIENT_KEY_EXCHANGE) {
-      // In SSL 3.0, the Certificate message is omitted to signal no
-      // certificate.
-      if (hs->config->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-        return ssl_hs_error;
-      }
-
-      // OpenSSL returns X509_V_OK when no certificates are received. This is
-      // classed by them as a bug, but it's assumed by at least NGINX.
-      hs->new_session->verify_result = X509_V_OK;
-      hs->state = state12_verify_client_certificate;
-      return ssl_hs_ok;
-    }
-
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+  if (!ssl_check_message_type(ssl, msg, SSL3_MT_CERTIFICATE)) {
     return ssl_hs_error;
   }
 
@@ -1011,14 +988,6 @@
     // No client certificate so the handshake buffer may be discarded.
     hs->transcript.FreeBuffer();
 
-    // In SSL 3.0, sending no certificate is signaled by omitting the
-    // Certificate message.
-    if (ssl->version == SSL3_VERSION) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATES_RETURNED);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-      return ssl_hs_error;
-    }
-
     if (hs->config->verify_mode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
       // Fail for TLS only if we required a certificate
       OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
@@ -1101,16 +1070,12 @@
   Array<uint8_t> premaster_secret;
   if (alg_k & SSL_kRSA) {
     CBS encrypted_premaster_secret;
-    if (ssl->version > SSL3_VERSION) {
-      if (!CBS_get_u16_length_prefixed(&client_key_exchange,
-                                       &encrypted_premaster_secret) ||
-          CBS_len(&client_key_exchange) != 0) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-        return ssl_hs_error;
-      }
-    } else {
-      encrypted_premaster_secret = client_key_exchange;
+    if (!CBS_get_u16_length_prefixed(&client_key_exchange,
+                                     &encrypted_premaster_secret) ||
+        CBS_len(&client_key_exchange) != 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      return ssl_hs_error;
     }
 
     // Allocate a buffer large enough for an RSA decryption.
@@ -1317,29 +1282,9 @@
     return ssl_hs_error;
   }
 
-  bool sig_ok;
-  // The SSL3 construction for CertificateVerify does not decompose into a
-  // single final digest and signature, and must be special-cased.
-  if (ssl_protocol_version(ssl) == SSL3_VERSION) {
-    uint8_t digest[EVP_MAX_MD_SIZE];
-    size_t digest_len;
-    if (!hs->transcript.GetSSL3CertVerifyHash(
-            digest, &digest_len, hs->new_session.get(), signature_algorithm)) {
-      return ssl_hs_error;
-    }
-
-    UniquePtr<EVP_PKEY_CTX> pctx(
-        EVP_PKEY_CTX_new(hs->peer_pubkey.get(), nullptr));
-    sig_ok = pctx &&
-             EVP_PKEY_verify_init(pctx.get()) &&
-             EVP_PKEY_verify(pctx.get(), CBS_data(&signature),
-                             CBS_len(&signature), digest, digest_len);
-  } else {
-    sig_ok =
-        ssl_public_key_verify(ssl, signature, signature_algorithm,
-                              hs->peer_pubkey.get(), hs->transcript.buffer());
-  }
-
+  bool sig_ok =
+      ssl_public_key_verify(ssl, signature, signature_algorithm,
+                            hs->peer_pubkey.get(), hs->transcript.buffer());
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
   sig_ok = true;
   ERR_clear_error();
diff --git a/ssl/internal.h b/ssl/internal.h
index 412c1fe..494b039 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -609,14 +609,6 @@
   // the number of bytes written. Otherwise, it returns false.
   bool GetHash(uint8_t *out, size_t *out_len);
 
-  // GetSSL3CertVerifyHash writes the SSL 3.0 CertificateVerify hash into the
-  // bytes pointed to by |out| and writes the number of bytes to
-  // |*out_len|. |out| must have room for |EVP_MAX_MD_SIZE| bytes. It returns
-  // one on success and zero on failure.
-  bool GetSSL3CertVerifyHash(uint8_t *out, size_t *out_len,
-                             const SSL_SESSION *session,
-                             uint16_t signature_algorithm);
-
   // GetFinishedMAC computes the MAC for the Finished message into the bytes
   // pointed by |out| and writes the number of bytes to |*out_len|. |out| must
   // have room for |EVP_MAX_MD_SIZE| bytes. It returns true on success and false
@@ -778,9 +770,6 @@
   // omit_length_in_ad_ is true if the length should be omitted in the
   // AEAD's ad parameter.
   bool omit_length_in_ad_ : 1;
-  // omit_version_in_ad_ is true if the version should be omitted
-  // in the AEAD's ad parameter.
-  bool omit_version_in_ad_ : 1;
   // omit_ad_ is true if the AEAD's ad parameter should be omitted.
   bool omit_ad_ : 1;
   // ad_is_header_ is true if the AEAD's ad parameter is the record header.
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index 788a897..2466811 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -397,7 +397,7 @@
   OPENSSL_memcpy(random + (SSL3_RANDOM_SIZE - rand_len), CBS_data(&challenge),
                  rand_len);
 
-  // Write out an equivalent SSLv3 ClientHello.
+  // Write out an equivalent TLS ClientHello.
   size_t max_v3_client_hello = SSL3_HM_HEADER_LENGTH + 2 /* version */ +
                                SSL3_RANDOM_SIZE + 1 /* session ID length */ +
                                2 /* cipher list length */ +
diff --git a/ssl/ssl_aead_ctx.cc b/ssl/ssl_aead_ctx.cc
index 363c959..322b1b5 100644
--- a/ssl/ssl_aead_ctx.cc
+++ b/ssl/ssl_aead_ctx.cc
@@ -42,7 +42,6 @@
       random_variable_nonce_(false),
       xor_fixed_nonce_(false),
       omit_length_in_ad_(false),
-      omit_version_in_ad_(false),
       omit_ad_(false),
       ad_is_header_(false) {
   OPENSSL_memset(fixed_nonce_, 0, sizeof(fixed_nonce_));
@@ -147,7 +146,6 @@
     aead_ctx->variable_nonce_included_in_record_ = true;
     aead_ctx->random_variable_nonce_ = true;
     aead_ctx->omit_length_in_ad_ = true;
-    aead_ctx->omit_version_in_ad_ = (protocol_version == SSL3_VERSION);
   }
 
   return aead_ctx;
@@ -235,10 +233,8 @@
   OPENSSL_memcpy(storage, seqnum, 8);
   size_t len = 8;
   storage[len++] = type;
-  if (!omit_version_in_ad_) {
-    storage[len++] = static_cast<uint8_t>((record_version >> 8));
-    storage[len++] = static_cast<uint8_t>(record_version);
-  }
+  storage[len++] = static_cast<uint8_t>((record_version >> 8));
+  storage[len++] = static_cast<uint8_t>(record_version);
   if (!omit_length_in_ad_) {
     storage[len++] = static_cast<uint8_t>((plaintext_len >> 8));
     storage[len++] = static_cast<uint8_t>(plaintext_len);
diff --git a/ssl/ssl_cipher.cc b/ssl/ssl_cipher.cc
index fdc1d2c..42be591 100644
--- a/ssl/ssl_cipher.cc
+++ b/ssl/ssl_cipher.cc
@@ -613,36 +613,23 @@
     }
   } else if (cipher->algorithm_mac == SSL_SHA1) {
     if (cipher->algorithm_enc == SSL_eNULL) {
-      if (version == SSL3_VERSION) {
-        *out_aead = EVP_aead_null_sha1_ssl3();
-      } else {
-        *out_aead = EVP_aead_null_sha1_tls();
-      }
+      *out_aead = EVP_aead_null_sha1_tls();
     } else if (cipher->algorithm_enc == SSL_3DES) {
-      if (version == SSL3_VERSION) {
-        *out_aead = EVP_aead_des_ede3_cbc_sha1_ssl3();
-        *out_fixed_iv_len = 8;
-      } else if (version == TLS1_VERSION) {
+      if (version == TLS1_VERSION) {
         *out_aead = EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv();
         *out_fixed_iv_len = 8;
       } else {
         *out_aead = EVP_aead_des_ede3_cbc_sha1_tls();
       }
     } else if (cipher->algorithm_enc == SSL_AES128) {
-      if (version == SSL3_VERSION) {
-        *out_aead = EVP_aead_aes_128_cbc_sha1_ssl3();
-        *out_fixed_iv_len = 16;
-      } else if (version == TLS1_VERSION) {
+      if (version == TLS1_VERSION) {
         *out_aead = EVP_aead_aes_128_cbc_sha1_tls_implicit_iv();
         *out_fixed_iv_len = 16;
       } else {
         *out_aead = EVP_aead_aes_128_cbc_sha1_tls();
       }
     } else if (cipher->algorithm_enc == SSL_AES256) {
-      if (version == SSL3_VERSION) {
-        *out_aead = EVP_aead_aes_256_cbc_sha1_ssl3();
-        *out_fixed_iv_len = 16;
-      } else if (version == TLS1_VERSION) {
+      if (version == TLS1_VERSION) {
         *out_aead = EVP_aead_aes_256_cbc_sha1_tls_implicit_iv();
         *out_fixed_iv_len = 16;
       } else {
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 8f969c5..9796f0c 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -464,10 +464,7 @@
     return false;
   }
 
-  // We do not accept at SSL 3.0. SSL 3.0 will be removed entirely in the future
-  // and requires retaining more data for renegotiation_info.
-  uint16_t version = ssl_protocol_version(ssl);
-  if (version == SSL3_VERSION || version >= TLS1_3_VERSION) {
+  if (ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return false;
   }
 
@@ -1490,9 +1487,8 @@
   *out_len = 0;
   OPENSSL_memset(out, 0, max_out);
 
-  // tls-unique is not defined for SSL 3.0 or TLS 1.3.
+  // tls-unique is not defined for TLS 1.3.
   if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) < TLS1_VERSION ||
       ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return 0;
   }
@@ -1644,7 +1640,6 @@
 
 size_t SSL_get_finished(const SSL *ssl, void *buf, size_t count) {
   if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) < TLS1_VERSION ||
       ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return 0;
   }
@@ -1660,7 +1655,6 @@
 
 size_t SSL_get_peer_finished(const SSL *ssl, void *buf, size_t count) {
   if (!ssl->s3->initial_handshake_complete ||
-      ssl_protocol_version(ssl) < TLS1_VERSION ||
       ssl_protocol_version(ssl) >= TLS1_3_VERSION) {
     return 0;
   }
diff --git a/ssl/ssl_session.cc b/ssl/ssl_session.cc
index 5e8ffe0..0498aaf 100644
--- a/ssl/ssl_session.cc
+++ b/ssl/ssl_session.cc
@@ -739,7 +739,6 @@
   size_t ticket_len = 0;
   const bool tickets_supported =
       !(SSL_get_options(hs->ssl) & SSL_OP_NO_TICKET) &&
-      hs->ssl->version > SSL3_VERSION &&
       SSL_early_callback_ctx_extension_get(
           client_hello, TLSEXT_TYPE_session_ticket, &ticket, &ticket_len);
   if (tickets_supported && ticket_len > 0) {
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index d6e0713..30fcbab 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -71,7 +71,6 @@
 static const size_t kTicketKeyLen = 48;
 
 static const VersionParam kAllVersions[] = {
-    {SSL3_VERSION, VersionParam::is_tls, "SSL3"},
     {TLS1_VERSION, VersionParam::is_tls, "TLS1"},
     {TLS1_1_VERSION, VersionParam::is_tls, "TLS1_1"},
     {TLS1_2_VERSION, VersionParam::is_tls, "TLS1_2"},
@@ -1965,13 +1964,6 @@
     uint16_t max_version;
     std::vector<uint8_t> expected;
   } kTests[] = {
-    {SSL3_VERSION,
-     {0x16, 0x03, 0x00, 0x00, 0x3b, 0x01, 0x00, 0x00, 0x37, 0x03, 0x00,
-      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-      0x00, 0x10, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0x00,
-      0x2f, 0x00, 0x35, 0x00, 0x0a, 0x00, 0xff, 0x01, 0x00}},
     {TLS1_VERSION,
      {0x16, 0x03, 0x01, 0x00, 0x5a, 0x01, 0x00, 0x00, 0x56, 0x03, 0x01, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -2015,8 +2007,6 @@
     // Our default cipher list varies by CPU capabilities, so manually place the
     // ChaCha20 ciphers in front.
     const char *cipher_list = "CHACHA20:ALL";
-    // SSLv3 is off by default.
-    ASSERT_TRUE(SSL_CTX_set_min_proto_version(ctx.get(), SSL3_VERSION));
     ASSERT_TRUE(SSL_CTX_set_max_proto_version(ctx.get(), t.max_version));
     ASSERT_TRUE(SSL_CTX_set_strict_cipher_list(ctx.get(), cipher_list));
 
@@ -2349,11 +2339,6 @@
                                     session.get(),
                                     false /* expect session not reused */));
 
-    // SSL 3.0 cannot renew sessions.
-    if (version() == SSL3_VERSION) {
-      continue;
-    }
-
     // Renew the session 10 seconds before expiration.
     time_t new_start_time = kStartTime + timeout - 10;
     g_current_time.tv_sec = new_start_time;
@@ -2436,10 +2421,6 @@
 }
 
 TEST_P(SSLVersionTest, DefaultTicketKeyRotation) {
-  if (GetParam().version == SSL3_VERSION) {
-    return;
-  }
-
   static const time_t kStartTime = 1001;
   g_current_time.tv_sec = kStartTime;
   uint8_t ticket_key[kTicketKeyLen];
@@ -2509,11 +2490,6 @@
 }
 
 TEST_P(SSLVersionTest, SNICallback) {
-  // SSL 3.0 lacks extensions.
-  if (version() == SSL3_VERSION) {
-    return;
-  }
-
   bssl::UniquePtr<X509> cert2 = GetECDSATestCertificate();
   ASSERT_TRUE(cert2);
   bssl::UniquePtr<EVP_PKEY> key2 = GetECDSATestKey();
@@ -2619,12 +2595,13 @@
   EXPECT_TRUE(SSL_CTX_set_min_proto_version(ctx.get(), 0));
   EXPECT_EQ(TLS1_VERSION, ctx->conf_min_version);
 
-  // SSL 3.0 and TLS 1.3 are available, but not by default.
-  EXPECT_TRUE(SSL_CTX_set_min_proto_version(ctx.get(), SSL3_VERSION));
-  EXPECT_EQ(SSL3_VERSION, ctx->conf_min_version);
+  // TLS 1.3 is available, but not by default.
   EXPECT_TRUE(SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_VERSION));
   EXPECT_EQ(TLS1_3_VERSION, ctx->conf_max_version);
 
+  // SSL 3.0 is not available.
+  EXPECT_FALSE(SSL_CTX_set_min_proto_version(ctx.get(), SSL3_VERSION));
+
   // TLS1_3_DRAFT_VERSION is not an API-level version.
   EXPECT_FALSE(
       SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_DRAFT23_VERSION));
@@ -2655,8 +2632,6 @@
 
 static const char *GetVersionName(uint16_t version) {
   switch (version) {
-    case SSL3_VERSION:
-      return "SSLv3";
     case TLS1_VERSION:
       return "TLSv1";
     case TLS1_1_VERSION:
@@ -2697,11 +2672,6 @@
 // Tests that that |SSL_get_pending_cipher| is available during the ALPN
 // selection callback.
 TEST_P(SSLVersionTest, ALPNCipherAvailable) {
-  // SSL 3.0 lacks extensions.
-  if (version() == SSL3_VERSION) {
-    return;
-  }
-
   ASSERT_TRUE(UseCertAndKey(client_ctx_.get()));
 
   static const uint8_t kALPNProtos[] = {0x03, 'f', 'o', 'o'};
@@ -3018,11 +2988,6 @@
 }
 
 TEST_P(SSLVersionTest, GetServerName) {
-  // No extensions in SSL 3.0.
-  if (version() == SSL3_VERSION) {
-    return;
-  }
-
   ClientConfig config;
   config.servername = "host1";
 
diff --git a/ssl/ssl_transcript.cc b/ssl/ssl_transcript.cc
index 345f9d3..a5c3309 100644
--- a/ssl/ssl_transcript.cc
+++ b/ssl/ssl_transcript.cc
@@ -271,103 +271,9 @@
   return true;
 }
 
-static bool SSL3HandshakeMAC(const SSL_SESSION *session,
-                             const EVP_MD_CTX *ctx_template, const char *sender,
-                             size_t sender_len, uint8_t *p, size_t *out_len) {
-  ScopedEVP_MD_CTX ctx;
-  if (!EVP_MD_CTX_copy_ex(ctx.get(), ctx_template)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-    return false;
-  }
-
-  static const uint8_t kPad1[48] = {
-      0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-      0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-      0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-      0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-  };
-
-  static const uint8_t kPad2[48] = {
-      0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
-      0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
-      0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
-      0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
-  };
-
-  size_t n = EVP_MD_CTX_size(ctx.get());
-
-  size_t npad = (48 / n) * n;
-  EVP_DigestUpdate(ctx.get(), sender, sender_len);
-  EVP_DigestUpdate(ctx.get(), session->master_key, session->master_key_length);
-  EVP_DigestUpdate(ctx.get(), kPad1, npad);
-  unsigned md_buf_len;
-  uint8_t md_buf[EVP_MAX_MD_SIZE];
-  EVP_DigestFinal_ex(ctx.get(), md_buf, &md_buf_len);
-
-  if (!EVP_DigestInit_ex(ctx.get(), EVP_MD_CTX_md(ctx.get()), NULL)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-    return false;
-  }
-  EVP_DigestUpdate(ctx.get(), session->master_key, session->master_key_length);
-  EVP_DigestUpdate(ctx.get(), kPad2, npad);
-  EVP_DigestUpdate(ctx.get(), md_buf, md_buf_len);
-  unsigned len;
-  EVP_DigestFinal_ex(ctx.get(), p, &len);
-
-  *out_len = len;
-  return true;
-}
-
-bool SSLTranscript::GetSSL3CertVerifyHash(uint8_t *out, size_t *out_len,
-                                          const SSL_SESSION *session,
-                                          uint16_t signature_algorithm) {
-  if (Digest() != EVP_md5_sha1()) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return false;
-  }
-
-  if (signature_algorithm == SSL_SIGN_RSA_PKCS1_MD5_SHA1) {
-    size_t md5_len, len;
-    if (!SSL3HandshakeMAC(session, md5_.get(), NULL, 0, out, &md5_len) ||
-        !SSL3HandshakeMAC(session, hash_.get(), NULL, 0, out + md5_len, &len)) {
-      return false;
-    }
-    *out_len = md5_len + len;
-    return true;
-  }
-
-  if (signature_algorithm == SSL_SIGN_ECDSA_SHA1) {
-    return SSL3HandshakeMAC(session, hash_.get(), NULL, 0, out, out_len);
-  }
-
-  OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-  return false;
-}
-
 bool SSLTranscript::GetFinishedMAC(uint8_t *out, size_t *out_len,
                                    const SSL_SESSION *session,
                                    bool from_server) {
-  if (session->ssl_version == SSL3_VERSION) {
-    if (Digest() != EVP_md5_sha1()) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return false;
-    }
-
-    const char *sender = from_server ? SSL3_MD_SERVER_FINISHED_CONST
-                                     : SSL3_MD_CLIENT_FINISHED_CONST;
-    const size_t sender_len = 4;
-    size_t md5_len, len;
-    if (!SSL3HandshakeMAC(session, md5_.get(), sender, sender_len, out,
-                          &md5_len) ||
-        !SSL3HandshakeMAC(session, hash_.get(), sender, sender_len,
-                          out + md5_len, &len)) {
-      return false;
-    }
-
-    *out_len = md5_len + len;
-    return true;
-  }
-
   static const char kClientLabel[] = "client finished";
   static const char kServerLabel[] = "server finished";
   auto label = from_server
diff --git a/ssl/ssl_versions.cc b/ssl/ssl_versions.cc
index 0f55066..a5a7cd9 100644
--- a/ssl/ssl_versions.cc
+++ b/ssl/ssl_versions.cc
@@ -27,7 +27,6 @@
 
 bool ssl_protocol_version_from_wire(uint16_t *out, uint16_t version) {
   switch (version) {
-    case SSL3_VERSION:
     case TLS1_VERSION:
     case TLS1_1_VERSION:
     case TLS1_2_VERSION:
@@ -62,7 +61,6 @@
     TLS1_2_VERSION,
     TLS1_1_VERSION,
     TLS1_VERSION,
-    SSL3_VERSION,
 };
 
 static const uint16_t kDTLSVersions[] = {
@@ -114,9 +112,6 @@
     case TLS1_VERSION:
       return "TLSv1";
 
-    case SSL3_VERSION:
-      return "SSLv3";
-
     case DTLS1_VERSION:
       return "DTLSv1";
 
@@ -200,7 +195,6 @@
   uint16_t version;
   uint32_t flag;
 } kProtocolVersions[] = {
-    {SSL3_VERSION, SSL_OP_NO_SSLv3},
     {TLS1_VERSION, SSL_OP_NO_TLSv1},
     {TLS1_1_VERSION, SSL_OP_NO_TLSv1_1},
     {TLS1_2_VERSION, SSL_OP_NO_TLSv1_2},
diff --git a/ssl/t1_enc.cc b/ssl/t1_enc.cc
index 5947627..93170b9 100644
--- a/ssl/t1_enc.cc
+++ b/ssl/t1_enc.cc
@@ -164,56 +164,6 @@
                               seed2.size());
 }
 
-static bool ssl3_prf(Span<uint8_t> out, Span<const uint8_t> secret,
-                     Span<const char> label, Span<const uint8_t> seed1,
-                     Span<const uint8_t> seed2) {
-  ScopedEVP_MD_CTX md5;
-  ScopedEVP_MD_CTX sha1;
-  uint8_t buf[16], smd[SHA_DIGEST_LENGTH];
-  uint8_t c = 'A';
-  size_t k = 0;
-  while (!out.empty()) {
-    k++;
-    if (k > sizeof(buf)) {
-      // bug: 'buf' is too small for this ciphersuite
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return false;
-    }
-
-    for (size_t j = 0; j < k; j++) {
-      buf[j] = c;
-    }
-    c++;
-    if (!EVP_DigestInit_ex(sha1.get(), EVP_sha1(), NULL)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-      return false;
-    }
-    EVP_DigestUpdate(sha1.get(), buf, k);
-    EVP_DigestUpdate(sha1.get(), secret.data(), secret.size());
-    // |label| is ignored for SSLv3.
-    EVP_DigestUpdate(sha1.get(), seed1.data(), seed1.size());
-    EVP_DigestUpdate(sha1.get(), seed2.data(), seed2.size());
-    EVP_DigestFinal_ex(sha1.get(), smd, NULL);
-
-    if (!EVP_DigestInit_ex(md5.get(), EVP_md5(), NULL)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
-      return false;
-    }
-    EVP_DigestUpdate(md5.get(), secret.data(), secret.size());
-    EVP_DigestUpdate(md5.get(), smd, SHA_DIGEST_LENGTH);
-    if (out.size() < MD5_DIGEST_LENGTH) {
-      EVP_DigestFinal_ex(md5.get(), smd, NULL);
-      OPENSSL_memcpy(out.data(), smd, out.size());
-      break;
-    }
-    EVP_DigestFinal_ex(md5.get(), out.data(), NULL);
-    out = out.subspan(MD5_DIGEST_LENGTH);
-  }
-
-  OPENSSL_cleanse(smd, SHA_DIGEST_LENGTH);
-  return true;
-}
-
 static bool get_key_block_lengths(const SSL *ssl, size_t *out_mac_secret_len,
                                   size_t *out_key_len, size_t *out_iv_len,
                                   const SSL_CIPHER *cipher) {
@@ -318,16 +268,9 @@
   } else {
     auto label =
         MakeConstSpan(kMasterSecretLabel, sizeof(kMasterSecretLabel) - 1);
-    if (ssl_protocol_version(ssl) == SSL3_VERSION) {
-      if (!ssl3_prf(out_span, premaster, label, ssl->s3->client_random,
-                    ssl->s3->server_random)) {
-        return 0;
-      }
-    } else {
-      if (!tls1_prf(hs->transcript.Digest(), out_span, premaster, label,
-                    ssl->s3->client_random, ssl->s3->server_random)) {
-        return 0;
-      }
+    if (!tls1_prf(hs->transcript.Digest(), out_span, premaster, label,
+                  ssl->s3->client_random, ssl->s3->server_random)) {
+      return 0;
     }
   }
 
@@ -357,11 +300,6 @@
   static const char kLabel[] = "key expansion";
   auto label = MakeConstSpan(kLabel, sizeof(kLabel) - 1);
 
-  if (ssl_protocol_version(ssl) == SSL3_VERSION) {
-    return ssl3_prf(out_span, master_key, label, ssl->s3->server_random,
-                    ssl->s3->client_random);
-  }
-
   const EVP_MD *digest = ssl_session_get_digest(session);
   return tls1_prf(digest, out_span, master_key, label, ssl->s3->server_random,
                   ssl->s3->client_random);
@@ -371,11 +309,6 @@
                                const char *label, size_t label_len,
                                const uint8_t *context, size_t context_len,
                                int use_context) {
-  if (!ssl->s3->have_version || ssl->version == SSL3_VERSION) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_HANDSHAKE_NOT_COMPLETE);
-    return 0;
-  }
-
   // Exporters may be used in False Start and server 0-RTT, where the handshake
   // has progressed enough. Otherwise, they may not be used during a handshake.
   if (SSL_in_init(ssl) &&
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index 20b96f4..6db7de7 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -243,7 +243,7 @@
   out->compression_methods_len = CBS_len(&compression_methods);
 
   // If the ClientHello ends here then it's valid, but doesn't have any
-  // extensions. (E.g. SSLv3.)
+  // extensions.
   if (CBS_len(&client_hello) == 0) {
     out->extensions = NULL;
     out->extensions_len = 0;
@@ -862,7 +862,7 @@
 
 static bool ext_ems_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   // Extended master secret is not necessary in TLS 1.3.
-  if (hs->min_version >= TLS1_3_VERSION || hs->max_version <= SSL3_VERSION) {
+  if (hs->min_version >= TLS1_3_VERSION) {
     return true;
   }
 
@@ -880,7 +880,6 @@
 
   if (contents != NULL) {
     if (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
-        ssl->version == SSL3_VERSION ||
         CBS_len(contents) != 0) {
       return false;
     }
@@ -902,9 +901,7 @@
 
 static bool ext_ems_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  uint16_t version = ssl_protocol_version(hs->ssl);
-  if (version >= TLS1_3_VERSION ||
-      version == SSL3_VERSION) {
+  if (ssl_protocol_version(hs->ssl) >= TLS1_3_VERSION) {
     return true;
   }
 
@@ -3085,12 +3082,6 @@
 
 int ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, size_t header_len) {
   SSL *const ssl = hs->ssl;
-  // Don't add extensions for SSLv3 unless doing secure renegotiation.
-  if (hs->client_version == SSL3_VERSION &&
-      !ssl->s3->send_connection_binding) {
-    return 1;
-  }
-
   CBB extensions;
   if (!CBB_add_u16_length_prefixed(out, &extensions)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
@@ -3239,7 +3230,6 @@
 static int ssl_scan_clienthello_tlsext(SSL_HANDSHAKE *hs,
                                        const SSL_CLIENT_HELLO *client_hello,
                                        int *out_alert) {
-  SSL *const ssl = hs->ssl;
   for (size_t i = 0; i < kNumExtensions; i++) {
     if (kExtensions[i].init != NULL) {
       kExtensions[i].init(hs);
@@ -3261,16 +3251,9 @@
       return 0;
     }
 
-    // RFC 5746 made the existence of extensions in SSL 3.0 somewhat
-    // ambiguous. Ignore all but the renegotiation_info extension.
-    if (ssl->version == SSL3_VERSION && type != TLSEXT_TYPE_renegotiate) {
-      continue;
-    }
-
     unsigned ext_index;
     const struct tls_extension *const ext =
         tls_extension_find(&ext_index, type);
-
     if (ext == NULL) {
       if (!custom_ext_parse_clienthello(hs, out_alert, type, &extension)) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_PARSING_EXTENSION);
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 32d224c..4010f62 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1190,10 +1190,9 @@
 
   SSL_CTX_set0_buffer_pool(ssl_ctx.get(), g_pool);
 
-  // Enable SSL 3.0 and TLS 1.3 for tests.
+  // Enable TLS 1.3 for tests.
   if (!config->is_dtls &&
-      (!SSL_CTX_set_min_proto_version(ssl_ctx.get(), SSL3_VERSION) ||
-       !SSL_CTX_set_max_proto_version(ssl_ctx.get(), TLS1_3_VERSION))) {
+      !SSL_CTX_set_max_proto_version(ssl_ctx.get(), TLS1_3_VERSION)) {
     return nullptr;
   }
 
@@ -2125,9 +2124,6 @@
   if (config->no_tls1) {
     SSL_set_options(ssl.get(), SSL_OP_NO_TLSv1);
   }
-  if (config->no_ssl3) {
-    SSL_set_options(ssl.get(), SSL_OP_NO_SSLv3);
-  }
   if (!config->expected_channel_id.empty() ||
       config->enable_channel_id) {
     SSL_set_tls_channel_id_enabled(ssl.get(), 1);
diff --git a/ssl/test/fuzzer.h b/ssl/test/fuzzer.h
index b240630..1ca970d 100644
--- a/ssl/test/fuzzer.h
+++ b/ssl/test/fuzzer.h
@@ -409,11 +409,9 @@
     if (!SSL_CTX_set_strict_cipher_list(ctx_.get(), "ALL:NULL-SHA")) {
       return false;
     }
-    if (protocol_ == kTLS) {
-      if (!SSL_CTX_set_max_proto_version(ctx_.get(), TLS1_3_VERSION) ||
-          !SSL_CTX_set_min_proto_version(ctx_.get(), SSL3_VERSION)) {
-        return false;
-      }
+    if (protocol_ == kTLS &&
+        !SSL_CTX_set_max_proto_version(ctx_.get(), TLS1_3_VERSION)) {
+      return false;
     }
 
     SSL_CTX_set_early_data_enabled(ctx_.get(), 1);
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 29ac50a..83a28d4 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -1354,11 +1354,6 @@
 
 var tlsVersions = []tlsVersion{
 	{
-		name:        "SSL3",
-		version:     VersionSSL30,
-		excludeFlag: "-no-ssl3",
-	},
-	{
 		name:        "TLS1",
 		version:     VersionTLS10,
 		excludeFlag: "-no-tls1",
@@ -1838,7 +1833,7 @@
 		},
 		{
 			name:          "DisableEverything",
-			flags:         []string{"-no-tls13", "-no-tls12", "-no-tls11", "-no-tls1", "-no-ssl3"},
+			flags:         []string{"-no-tls13", "-no-tls12", "-no-tls11", "-no-tls1"},
 			shouldFail:    true,
 			expectedError: ":NO_SUPPORTED_VERSIONS_ENABLED:",
 		},
@@ -3174,33 +3169,22 @@
 		flags = append(flags, "-cipher", "DEFAULT:NULL-SHA")
 	}
 
-	var shouldServerFail, shouldClientFail bool
-	if hasComponent(suite.name, "ECDHE") && ver.version == VersionSSL30 {
-		// BoringSSL clients accept ECDHE on SSLv3, but
-		// a BoringSSL server will never select it
-		// because the extension is missing.
-		shouldServerFail = true
-	}
+	var shouldFail bool
 	if isTLS12Only(suite.name) && ver.version < VersionTLS12 {
-		shouldClientFail = true
-		shouldServerFail = true
+		shouldFail = true
 	}
 	if !isTLS13Suite(suite.name) && ver.version >= VersionTLS13 {
-		shouldClientFail = true
-		shouldServerFail = true
+		shouldFail = true
 	}
 	if isTLS13Suite(suite.name) && ver.version < VersionTLS13 {
-		shouldClientFail = true
-		shouldServerFail = true
+		shouldFail = true
 	}
 
 	var sendCipherSuite uint16
 	var expectedServerError, expectedClientError string
 	serverCipherSuites := []uint16{suite.id}
-	if shouldServerFail {
+	if shouldFail {
 		expectedServerError = ":NO_SHARED_CIPHER:"
-	}
-	if shouldClientFail {
 		expectedClientError = ":WRONG_CIPHER_RETURNED:"
 		// Configure the server to select ciphers as normal but
 		// select an incompatible cipher in ServerHello.
@@ -3208,12 +3192,8 @@
 		sendCipherSuite = suite.id
 	}
 
-	// For cipher suites and versions where exporters are defined, verify
-	// that they interoperate.
-	var exportKeyingMaterial int
-	if ver.version > VersionSSL30 {
-		exportKeyingMaterial = 1024
-	}
+	// Verify exporters interoperate.
+	exportKeyingMaterial := 1024
 
 	testCases = append(testCases, testCase{
 		testType: serverTest,
@@ -3235,7 +3215,7 @@
 		keyFile:              keyFile,
 		flags:                flags,
 		resumeSession:        true,
-		shouldFail:           shouldServerFail,
+		shouldFail:           shouldFail,
 		expectedError:        expectedServerError,
 		exportKeyingMaterial: exportKeyingMaterial,
 	})
@@ -3252,19 +3232,19 @@
 			PreSharedKey:         []byte(psk),
 			PreSharedKeyIdentity: pskIdentity,
 			Bugs: ProtocolBugs{
-				IgnorePeerCipherPreferences: shouldClientFail,
+				IgnorePeerCipherPreferences: shouldFail,
 				SendCipherSuite:             sendCipherSuite,
 			},
 		},
 		tls13Variant:         ver.tls13Variant,
 		flags:                flags,
 		resumeSession:        true,
-		shouldFail:           shouldClientFail,
+		shouldFail:           shouldFail,
 		expectedError:        expectedClientError,
 		exportKeyingMaterial: exportKeyingMaterial,
 	})
 
-	if shouldClientFail {
+	if shouldFail {
 		return
 	}
 
@@ -3287,10 +3267,9 @@
 
 	// Test bad records for all ciphers. Bad records are fatal in TLS
 	// and ignored in DTLS.
-	var shouldFail bool
+	shouldFail = protocol == tls
 	var expectedError string
-	if protocol == tls {
-		shouldFail = true
+	if shouldFail {
 		expectedError = ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"
 	}
 
@@ -3796,34 +3775,32 @@
 			tls13Variant: ver.tls13Variant,
 			flags:        []string{"-require-any-client-certificate"},
 		})
-		if ver.version != VersionSSL30 {
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     ver.name + "-Server-ClientAuth-ECDSA",
-				config: Config{
-					MinVersion:   ver.version,
-					MaxVersion:   ver.version,
-					Certificates: []Certificate{ecdsaP256Certificate},
-				},
-				tls13Variant: ver.tls13Variant,
-				flags:        []string{"-require-any-client-certificate"},
-			})
-			testCases = append(testCases, testCase{
-				testType: clientTest,
-				name:     ver.name + "-Client-ClientAuth-ECDSA",
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-					ClientAuth: RequireAnyClientCert,
-					ClientCAs:  certPool,
-				},
-				tls13Variant: ver.tls13Variant,
-				flags: []string{
-					"-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile),
-					"-key-file", path.Join(*resourceDir, ecdsaP256KeyFile),
-				},
-			})
-		}
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     ver.name + "-Server-ClientAuth-ECDSA",
+			config: Config{
+				MinVersion:   ver.version,
+				MaxVersion:   ver.version,
+				Certificates: []Certificate{ecdsaP256Certificate},
+			},
+			tls13Variant: ver.tls13Variant,
+			flags:        []string{"-require-any-client-certificate"},
+		})
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     ver.name + "-Client-ClientAuth-ECDSA",
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				ClientAuth: RequireAnyClientCert,
+				ClientCAs:  certPool,
+			},
+			tls13Variant: ver.tls13Variant,
+			flags: []string{
+				"-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile),
+				"-key-file", path.Join(*resourceDir, ecdsaP256KeyFile),
+			},
+		})
 
 		testCases = append(testCases, testCase{
 			name: "NoClientCertificate-" + ver.name,
@@ -3890,57 +3867,55 @@
 			expectedLocalError: certificateRequired,
 		})
 
-		if ver.version != VersionSSL30 {
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "SkipClientCertificate-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						SkipClientCertificate: true,
-					},
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipClientCertificate-" + ver.name,
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					SkipClientCertificate: true,
 				},
-				// Setting SSL_VERIFY_PEER allows anonymous clients.
-				flags:         []string{"-verify-peer"},
-				tls13Variant:  ver.tls13Variant,
-				shouldFail:    true,
-				expectedError: ":UNEXPECTED_MESSAGE:",
-			})
+			},
+			// Setting SSL_VERIFY_PEER allows anonymous clients.
+			flags:         []string{"-verify-peer"},
+			tls13Variant:  ver.tls13Variant,
+			shouldFail:    true,
+			expectedError: ":UNEXPECTED_MESSAGE:",
+		})
 
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "VerifyPeerIfNoOBC-NoChannelID-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-				},
-				flags: []string{
-					"-enable-channel-id",
-					"-verify-peer-if-no-obc",
-				},
-				tls13Variant:       ver.tls13Variant,
-				shouldFail:         true,
-				expectedError:      ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
-				expectedLocalError: certificateRequired,
-			})
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "VerifyPeerIfNoOBC-NoChannelID-" + ver.name,
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+			},
+			flags: []string{
+				"-enable-channel-id",
+				"-verify-peer-if-no-obc",
+			},
+			tls13Variant:       ver.tls13Variant,
+			shouldFail:         true,
+			expectedError:      ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
+			expectedLocalError: certificateRequired,
+		})
 
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "VerifyPeerIfNoOBC-ChannelID-" + ver.name,
-				config: Config{
-					MinVersion: ver.version,
-					MaxVersion: ver.version,
-					ChannelID:  channelIDKey,
-				},
-				expectChannelID: true,
-				tls13Variant:    ver.tls13Variant,
-				flags: []string{
-					"-enable-channel-id",
-					"-verify-peer-if-no-obc",
-				},
-			})
-		}
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "VerifyPeerIfNoOBC-ChannelID-" + ver.name,
+			config: Config{
+				MinVersion: ver.version,
+				MaxVersion: ver.version,
+				ChannelID:  channelIDKey,
+			},
+			expectChannelID: true,
+			tls13Variant:    ver.tls13Variant,
+			flags: []string{
+				"-enable-channel-id",
+				"-verify-peer-if-no-obc",
+			},
+		})
 
 		testCases = append(testCases, testCase{
 			testType: serverTest,
@@ -4078,7 +4053,7 @@
 					flags = []string{expectEMSFlag}
 				}
 
-				test := testCase{
+				testCases = append(testCases, testCase{
 					testType: testType,
 					name:     prefix + "ExtendedMasterSecret-" + ver.name + suffix,
 					config: Config{
@@ -4091,12 +4066,7 @@
 					},
 					tls13Variant: ver.tls13Variant,
 					flags:        flags,
-					shouldFail:   ver.version == VersionSSL30 && with,
-				}
-				if test.shouldFail {
-					test.expectedLocalError = "extended master secret required but not supported by peer"
-				}
-				testCases = append(testCases, test)
+				})
 			}
 		}
 	}
@@ -4561,23 +4531,6 @@
 	if config.protocol == tls {
 		tests = append(tests, testCase{
 			testType: clientTest,
-			name:     "ClientAuth-NoCertificate-Client-SSL3",
-			config: Config{
-				MaxVersion: VersionSSL30,
-				ClientAuth: RequestClientCert,
-			},
-		})
-		tests = append(tests, testCase{
-			testType: serverTest,
-			name:     "ClientAuth-NoCertificate-Server-SSL3",
-			config: Config{
-				MaxVersion: VersionSSL30,
-			},
-			// Setting SSL_VERIFY_PEER allows anonymous clients.
-			flags: []string{"-verify-peer"},
-		})
-		tests = append(tests, testCase{
-			testType: clientTest,
 			name:     "ClientAuth-NoCertificate-Client-TLS13",
 			config: Config{
 				MaxVersion: VersionTLS13,
@@ -4800,9 +4753,6 @@
 		if config.protocol == dtls && !vers.hasDTLS {
 			continue
 		}
-		if vers.version == VersionSSL30 {
-			continue
-		}
 		tests = append(tests, testCase{
 			testType: clientTest,
 			name:     "OCSPStapling-Client-" + vers.name,
@@ -5923,6 +5873,46 @@
 			},
 		},
 	})
+
+	// SSL 3.0 support has been removed. Test that the shim does not
+	// support it.
+	testCases = append(testCases, testCase{
+		name: "NoSSL3-Client",
+		config: Config{
+			MinVersion: VersionSSL30,
+			MaxVersion: VersionSSL30,
+		},
+		shouldFail:         true,
+		expectedLocalError: "tls: client did not offer any supported protocol versions",
+	})
+	testCases = append(testCases, testCase{
+		name: "NoSSL3-Client-Unsolicited",
+		config: Config{
+			MinVersion: VersionSSL30,
+			MaxVersion: VersionSSL30,
+			Bugs: ProtocolBugs{
+				// The above test asserts the client does not
+				// offer SSL 3.0 in the supported_versions
+				// list. Additionally assert that it rejects an
+				// unsolicited SSL 3.0 ServerHello.
+				NegotiateVersion: VersionSSL30,
+			},
+		},
+		shouldFail:         true,
+		expectedError:      ":UNSUPPORTED_PROTOCOL:",
+		expectedLocalError: "remote error: protocol version not supported",
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "NoSSL3-Server",
+		config: Config{
+			MinVersion: VersionSSL30,
+			MaxVersion: VersionSSL30,
+		},
+		shouldFail:         true,
+		expectedError:      ":UNSUPPORTED_PROTOCOL:",
+		expectedLocalError: "remote error: protocol version not supported",
+	})
 }
 
 func addMinimumVersionTests() {
@@ -6051,12 +6041,8 @@
 	// halves to EncryptedExtensions in TLS 1.3. Duplicate each of these
 	// tests for both. Also test interaction with 0-RTT when implemented.
 
-	// Repeat extensions tests all versions except SSL 3.0.
+	// Repeat extensions tests at all versions.
 	for _, ver := range tlsVersions {
-		if ver.version == VersionSSL30 {
-			continue
-		}
-
 		// Test that duplicate extensions are rejected.
 		testCases = append(testCases, testCase{
 			testType: clientTest,
@@ -7227,70 +7213,6 @@
 		flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"},
 	})
 
-	// Extensions should not function in SSL 3.0.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-NoALPN",
-		config: Config{
-			MaxVersion: VersionSSL30,
-			NextProtos: []string{"foo", "bar", "baz"},
-		},
-		flags: []string{
-			"-select-alpn", "foo",
-		},
-		expectNoNextProto: true,
-	})
-
-	// Test session tickets separately as they follow a different codepath.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-NoTickets",
-		config: Config{
-			MaxVersion: VersionSSL30,
-			Bugs: ProtocolBugs{
-				// Historically, session tickets in SSL 3.0
-				// failed in different ways depending on whether
-				// the client supported renegotiation_info.
-				NoRenegotiationInfo: true,
-			},
-		},
-		resumeSession: true,
-	})
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-NoTickets2",
-		config: Config{
-			MaxVersion: VersionSSL30,
-		},
-		resumeSession: true,
-	})
-
-	// But SSL 3.0 does send and process renegotiation_info.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-RenegotiationInfo",
-		config: Config{
-			MaxVersion: VersionSSL30,
-			Bugs: ProtocolBugs{
-				RequireRenegotiationInfo: true,
-			},
-		},
-		flags: []string{"-expect-secure-renegotiation"},
-	})
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SSLv3Extensions-RenegotiationInfo-SCSV",
-		config: Config{
-			MaxVersion: VersionSSL30,
-			Bugs: ProtocolBugs{
-				NoRenegotiationInfo:      true,
-				SendRenegotiationSCSV:    true,
-				RequireRenegotiationInfo: true,
-			},
-		},
-		flags: []string{"-expect-secure-renegotiation"},
-	})
-
 	// Test that illegal extensions in TLS 1.3 are rejected by the client if
 	// in ServerHello.
 	testCases = append(testCases, testCase{
@@ -7569,14 +7491,6 @@
 func addResumptionVersionTests() {
 	for _, sessionVers := range tlsVersions {
 		for _, resumeVers := range tlsVersions {
-			// SSL 3.0 does not have tickets and TLS 1.3 does not
-			// have session IDs, so skip their cross-resumption
-			// tests.
-			if (sessionVers.version >= VersionTLS13 && resumeVers.version == VersionSSL30) ||
-				(resumeVers.version >= VersionTLS13 && sessionVers.version == VersionSSL30) {
-				continue
-			}
-
 			protocols := []protocol{tls}
 			if sessionVers.hasDTLS && resumeVers.hasDTLS {
 				protocols = append(protocols, dtls)
@@ -8400,22 +8314,6 @@
 		expectedLocalError: "remote error: no renegotiation",
 	})
 
-	// Renegotiation is not allowed at SSL 3.0.
-	testCases = append(testCases, testCase{
-		name: "Renegotiate-Client-SSL3",
-		config: Config{
-			MaxVersion: VersionSSL30,
-		},
-		renegotiate: 1,
-		flags: []string{
-			"-renegotiate-freely",
-			"-expect-total-renegotiations", "1",
-		},
-		shouldFail:         true,
-		expectedError:      ":NO_RENEGOTIATION:",
-		expectedLocalError: "remote error: no renegotiation",
-	})
-
 	// Renegotiation is not allowed when there is an unfinished write.
 	testCases = append(testCases, testCase{
 		name: "Renegotiate-Client-UnfinishedWrite",
@@ -8702,12 +8600,6 @@
 				continue
 			}
 
-			// TODO(davidben): Support ECDSA in SSL 3.0 in Go for testing
-			// or remove it in C.
-			if ver.version == VersionSSL30 && alg.cert != testCertRSA {
-				continue
-			}
-
 			var shouldSignFail, shouldVerifyFail bool
 			// ecdsa_sha1 does not exist in TLS 1.3.
 			if ver.version >= VersionTLS13 && alg.id == signatureECDSAWithSHA1 {
@@ -8794,32 +8686,29 @@
 				expectedError: verifyError,
 			})
 
-			// No signing cipher for SSL 3.0.
-			if ver.version > VersionSSL30 {
-				testCases = append(testCases, testCase{
-					testType: serverTest,
-					name:     "ServerAuth-Sign" + suffix,
-					config: Config{
-						MaxVersion:   ver.version,
-						CipherSuites: signingCiphers,
-						VerifySignatureAlgorithms: []signatureAlgorithm{
-							fakeSigAlg1,
-							alg.id,
-							fakeSigAlg2,
-						},
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "ServerAuth-Sign" + suffix,
+				config: Config{
+					MaxVersion:   ver.version,
+					CipherSuites: signingCiphers,
+					VerifySignatureAlgorithms: []signatureAlgorithm{
+						fakeSigAlg1,
+						alg.id,
+						fakeSigAlg2,
 					},
-					tls13Variant: ver.tls13Variant,
-					flags: []string{
-						"-cert-file", path.Join(*resourceDir, getShimCertificate(alg.cert)),
-						"-key-file", path.Join(*resourceDir, getShimKey(alg.cert)),
-						"-enable-all-curves",
-						"-enable-ed25519",
-					},
-					shouldFail:                     shouldSignFail,
-					expectedError:                  signError,
-					expectedPeerSignatureAlgorithm: alg.id,
-				})
-			}
+				},
+				tls13Variant: ver.tls13Variant,
+				flags: []string{
+					"-cert-file", path.Join(*resourceDir, getShimCertificate(alg.cert)),
+					"-key-file", path.Join(*resourceDir, getShimKey(alg.cert)),
+					"-enable-all-curves",
+					"-enable-ed25519",
+				},
+				shouldFail:                     shouldSignFail,
+				expectedError:                  signError,
+				expectedPeerSignatureAlgorithm: alg.id,
+			})
 
 			testCases = append(testCases, testCase{
 				name: "ServerAuth-Verify" + suffix,
@@ -9977,9 +9866,6 @@
 
 func addExportKeyingMaterialTests() {
 	for _, vers := range tlsVersions {
-		if vers.version == VersionSSL30 {
-			continue
-		}
 		testCases = append(testCases, testCase{
 			name: "ExportKeyingMaterial-" + vers.name,
 			config: Config{
@@ -10247,19 +10133,6 @@
 		}
 	}
 
-	testCases = append(testCases, testCase{
-		name: "ExportKeyingMaterial-SSL3",
-		config: Config{
-			MaxVersion: VersionSSL30,
-		},
-		exportKeyingMaterial: 1024,
-		exportLabel:          "label",
-		exportContext:        "context",
-		useExportContext:     true,
-		shouldFail:           true,
-		expectedError:        "failed to export keying material",
-	})
-
 	// Exporters work during a False Start.
 	testCases = append(testCases, testCase{
 		name: "ExportKeyingMaterial-FalseStart",
@@ -10772,11 +10645,6 @@
 func addCurveTests() {
 	for _, curve := range testCurves {
 		for _, ver := range tlsVersions {
-			// SSL 3.0 cannot reliably negotiate curves.
-			if ver.version == VersionSSL30 {
-				continue
-			}
-
 			suffix := curve.name + "-" + ver.name
 
 			testCases = append(testCases, testCase{
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index fa38688..e1405e5 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -59,7 +59,6 @@
   { "-no-tls12", &TestConfig::no_tls12 },
   { "-no-tls11", &TestConfig::no_tls11 },
   { "-no-tls1", &TestConfig::no_tls1 },
-  { "-no-ssl3", &TestConfig::no_ssl3 },
   { "-enable-channel-id", &TestConfig::enable_channel_id },
   { "-shim-writes-first", &TestConfig::shim_writes_first },
   { "-expect-session-miss", &TestConfig::expect_session_miss },
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index c98bf93..a0c5c4d 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -45,7 +45,6 @@
   bool no_tls12 = false;
   bool no_tls11 = false;
   bool no_tls1 = false;
-  bool no_ssl3 = false;
   std::string expected_channel_id;
   bool enable_channel_id = false;
   std::string send_channel_id;