summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/data/ssl/certificates/README3
-rw-r--r--net/data/ssl/certificates/ocsp-test-root.pem51
-rw-r--r--net/ocsp/nss_ocsp.cc12
-rw-r--r--net/test/base_test_server.cc44
-rw-r--r--net/test/base_test_server.h21
-rw-r--r--net/tools/testserver/asn1.py165
-rw-r--r--net/tools/testserver/minica.py328
-rwxr-xr-xnet/tools/testserver/testserver.py113
-rw-r--r--net/url_request/url_request_unittest.cc166
9 files changed, 866 insertions, 37 deletions
diff --git a/net/data/ssl/certificates/README b/net/data/ssl/certificates/README
index be9ded9..2006d58 100644
--- a/net/data/ssl/certificates/README
+++ b/net/data/ssl/certificates/README
@@ -104,3 +104,6 @@ unit tests.
net/socket/ssl_client_socket_unittest.cc. These chains are valid until
26 Feb 2022 and are generated by
net/data/ssl/scripts/generate-redundant-test-chains.sh.
+
+- ocsp-test-root.pem : A root certificate for the code in
+ net/tools/testserver/minica.py
diff --git a/net/data/ssl/certificates/ocsp-test-root.pem b/net/data/ssl/certificates/ocsp-test-root.pem
new file mode 100644
index 0000000..493fe54
--- /dev/null
+++ b/net/data/ssl/certificates/ocsp-test-root.pem
@@ -0,0 +1,51 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Testing CA
+ Validity
+ Not Before: Jan 1 06:00:00 2010 GMT
+ Not After : Dec 1 06:00:00 2032 GMT
+ Subject: CN=Testing CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:a7:19:98:f2:93:0b:fe:73:d0:31:a8:7f:13:3d:
+ 2f:37:8e:ee:ee:d5:2a:77:e4:4d:0f:c9:ff:6f:07:
+ ff:32:cb:f3:da:99:9d:e4:ed:65:83:2a:fc:b0:80:
+ 7f:98:78:75:06:53:9d:25:8a:0c:e3:c2:c7:79:67:
+ 65:30:99:a9:03:4a:9b:11:5a:87:6c:39:a8:c4:e4:
+ ed:4a:cd:0c:64:09:59:46:fb:39:ee:eb:47:a0:70:
+ 4d:bb:01:8a:cf:48:c3:a1:c4:b8:95:fc:40:9f:b4:
+ a3:40:a9:86:b1:af:c4:55:19:ab:9e:ca:47:c3:01:
+ 85:c7:71:c6:4a:a5:ec:f0:7d
+ Exponent: 3 (0x3)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.11129.2.4.1
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 48:0c:c9:ab:8f:f2:cc:80:f1:1f:b3:3a:45:18:de:ab:c5:e0:
+ d7:d4:64:a0:c4:86:2e:fc:58:3a:d7:86:ba:02:4e:29:95:72:
+ 9f:20:5d:43:b2:41:4e:7c:a4:86:a1:df:b3:ab:7e:46:cb:af:
+ 41:7d:c2:2b:b4:d3:22:d3:67:3e:13:ef:b6:9f:5c:8a:0d:3c:
+ a7:58:eb:a9:21:d2:9b:6b:e5:b6:4f:d6:7c:22:a7:b3:18:82:
+ b2:16:7d:d6:5c:7d:c9:46:be:91:49:e8:d2:42:95:cd:f8:8a:
+ 91:50:e7:5b:2a:26:68:ef:e7:e7:c6:24:d1:3c:01:9d:6c:48:
+ a4:f5
+-----BEGIN CERTIFICATE-----
+MIIB0DCCATmgAwIBAgIBATANBgkqhkiG9w0BAQUFADAVMRMwEQYDVQQDEwpUZXN0
+aW5nIENBMB4XDTEwMDEwMTA2MDAwMFoXDTMyMTIwMTA2MDAwMFowFTETMBEGA1UE
+AxMKVGVzdGluZyBDQTCBnTANBgkqhkiG9w0BAQEFAAOBiwAwgYcCgYEApxmY8pML
+/nPQMah/Ez0vN47u7tUqd+RND8n/bwf/Msvz2pmd5O1lgyr8sIB/mHh1BlOdJYoM
+48LHeWdlMJmpA0qbEVqHbDmoxOTtSs0MZAlZRvs57utHoHBNuwGKz0jDocS4lfxA
+n7SjQKmGsa/EVRmrnspHwwGFx3HGSqXs8H0CAQOjMjAwMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwGgYDVR0gAQEABBAwDjAMBgorBgEEAdZ5AgQBMA0GCSqGSIb3DQEBBQUA
+A4GBAEgMyauP8syA8R+zOkUY3qvF4NfUZKDEhi78WDrXhroCTimVcp8gXUOyQU58
+pIah37OrfkbLr0F9wiu00yLTZz4T77afXIoNPKdY66kh0ptr5bZP1nwip7MYgrIW
+fdZcfclGvpFJ6NJClc34ipFQ51sqJmjv5+fGJNE8AZ1sSKT1
+-----END CERTIFICATE-----
diff --git a/net/ocsp/nss_ocsp.cc b/net/ocsp/nss_ocsp.cc
index 07cd019..80f9db0 100644
--- a/net/ocsp/nss_ocsp.cc
+++ b/net/ocsp/nss_ocsp.cc
@@ -49,6 +49,8 @@ class OCSPIOLoop {
void StartUsing() {
base::AutoLock autolock(lock_);
used_ = true;
+ io_loop_ = MessageLoopForIO::current();
+ DCHECK(io_loop_);
}
// Called on IO loop.
@@ -456,8 +458,7 @@ class OCSPServerSession {
OCSPIOLoop::OCSPIOLoop()
: shutdown_(false),
used_(false),
- io_loop_(MessageLoopForIO::current()) {
- DCHECK(io_loop_);
+ io_loop_(NULL) {
}
OCSPIOLoop::~OCSPIOLoop() {
@@ -512,13 +513,6 @@ void OCSPIOLoop::AddRequest(OCSPRequestSession* request) {
}
void OCSPIOLoop::RemoveRequest(OCSPRequestSession* request) {
- {
- // Ignore if we've already shutdown.
- base::AutoLock auto_lock(lock_);
- if (shutdown_)
- return;
- }
-
DCHECK(ContainsKey(requests_, request));
requests_.erase(request);
}
diff --git a/net/test/base_test_server.cc b/net/test/base_test_server.cc
index c4f0fc6..eefd953 100644
--- a/net/test/base_test_server.cc
+++ b/net/test/base_test_server.cc
@@ -55,6 +55,7 @@ void GetCiphersList(int cipher, base::ListValue* values) {
BaseTestServer::HTTPSOptions::HTTPSOptions()
: server_certificate(CERT_OK),
+ ocsp_status(OCSP_OK),
request_client_certificate(false),
bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY),
record_resume(false) {}
@@ -79,12 +80,31 @@ FilePath BaseTestServer::HTTPSOptions::GetCertificateFile() const {
// This chain uses its own dedicated test root certificate to avoid
// side-effects that may affect testing.
return FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem"));
+ case CERT_AUTO:
+ return FilePath();
default:
NOTREACHED();
}
return FilePath();
}
+std::string BaseTestServer::HTTPSOptions::GetOCSPArgument() const {
+ if (server_certificate != CERT_AUTO)
+ return "";
+
+ switch (ocsp_status) {
+ case OCSP_OK:
+ return "ok";
+ case OCSP_REVOKED:
+ return "revoked";
+ case OCSP_INVALID:
+ return "invalid";
+ default:
+ NOTREACHED();
+ return "";
+ }
+}
+
const char BaseTestServer::kLocalhost[] = "127.0.0.1";
const char BaseTestServer::kGDataAuthToken[] = "testtoken";
@@ -309,17 +329,25 @@ bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const {
arguments->Set("log-to-console", base::Value::CreateNullValue());
if (type_ == TYPE_HTTPS) {
+ arguments->Set("https", base::Value::CreateNullValue());
+
// Check the certificate arguments of the HTTPS server.
FilePath certificate_path(certificates_dir_);
- certificate_path = certificate_path.Append(
- https_options_.GetCertificateFile());
- if (certificate_path.IsAbsolute() &&
- !file_util::PathExists(certificate_path)) {
- LOG(ERROR) << "Certificate path " << certificate_path.value()
- << " doesn't exist. Can't launch https server.";
- return false;
+ FilePath certificate_file(https_options_.GetCertificateFile());
+ if (!certificate_file.value().empty()) {
+ certificate_path = certificate_path.Append(certificate_file);
+ if (certificate_path.IsAbsolute() &&
+ !file_util::PathExists(certificate_path)) {
+ LOG(ERROR) << "Certificate path " << certificate_path.value()
+ << " doesn't exist. Can't launch https server.";
+ return false;
+ }
+ arguments->SetString("cert-and-key-file", certificate_path.value());
}
- arguments->SetString("https", certificate_path.value());
+
+ std::string ocsp_arg = https_options_.GetOCSPArgument();
+ if (!ocsp_arg.empty())
+ arguments->SetString("ocsp", ocsp_arg);
// Check the client certificate related arguments.
if (https_options_.request_client_certificate)
diff --git a/net/test/base_test_server.h b/net/test/base_test_server.h
index 7264a65..fd04e55 100644
--- a/net/test/base_test_server.h
+++ b/net/test/base_test_server.h
@@ -46,6 +46,11 @@ class BaseTestServer {
struct HTTPSOptions {
enum ServerCertificate {
CERT_OK,
+
+ // CERT_AUTO causes the testserver to generate a test certificate issued
+ // by "Testing CA" (see net/data/ssl/certificates/ocsp-test-root.pem).
+ CERT_AUTO,
+
CERT_MISMATCHED_NAME,
CERT_EXPIRED,
// Cross-signed certificate to test PKIX path building. Contains an
@@ -55,6 +60,14 @@ class BaseTestServer {
CERT_CHAIN_WRONG_ROOT,
};
+ // OCSPStatus enumerates the types of OCSP response that the testserver
+ // can produce.
+ enum OCSPStatus {
+ OCSP_OK,
+ OCSP_REVOKED,
+ OCSP_INVALID,
+ };
+
// Bitmask of bulk encryption algorithms that the test server supports
// and that can be selectively enabled or disabled.
enum BulkCipher {
@@ -83,9 +96,17 @@ class BaseTestServer {
// |server_certificate|.
FilePath GetCertificateFile() const;
+ // GetOCSPArgument returns the value of any OCSP argument to testserver or
+ // the empty string if there is none.
+ std::string GetOCSPArgument() const;
+
// The certificate to use when serving requests.
ServerCertificate server_certificate;
+ // If |server_certificate==CERT_AUTO| then this determines the type of OCSP
+ // response returned.
+ OCSPStatus ocsp_status;
+
// True if a CertificateRequest should be sent to the client during
// handshaking.
bool request_client_certificate;
diff --git a/net/tools/testserver/asn1.py b/net/tools/testserver/asn1.py
new file mode 100644
index 0000000..c0e0398
--- /dev/null
+++ b/net/tools/testserver/asn1.py
@@ -0,0 +1,165 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This file implements very minimal ASN.1, DER serialization.
+
+import types
+
+
+def ToDER(obj):
+ '''ToDER converts the given object into DER encoding'''
+ if type(obj) == types.NoneType:
+ # None turns into NULL
+ return TagAndLength(5, 0)
+ if type(obj) == types.StringType:
+ # Strings are PRINTABLESTRING
+ return TagAndLength(19, len(obj)) + obj
+ if type(obj) == types.BooleanType:
+ val = "\x00"
+ if obj:
+ val = "\xff"
+ return TagAndLength(1, 1) + val
+ if type(obj) == types.IntType or type(obj) == types.LongType:
+ big_endian = []
+ val = obj
+ while val != 0:
+ big_endian.append(val & 0xff)
+ val >>= 8
+
+ if len(big_endian) == 0 or big_endian[-1] >= 128:
+ big_endian.append(0)
+
+ big_endian.reverse()
+ return TagAndLength(2, len(big_endian)) + ToBytes(big_endian)
+
+ return obj.ToDER()
+
+
+def ToBytes(array_of_bytes):
+ '''ToBytes converts the array of byte values into a binary string'''
+ return ''.join([chr(x) for x in array_of_bytes])
+
+
+def TagAndLength(tag, length):
+ der = [tag]
+ if length < 128:
+ der.append(length)
+ elif length < 256:
+ der.append(0x81)
+ der.append(length)
+ elif length < 65535:
+ der.append(0x82)
+ der.append(length >> 8)
+ der.append(length & 0xff)
+ else:
+ assert False
+
+ return ToBytes(der)
+
+
+class Raw(object):
+ '''Raw contains raw DER encoded bytes that are used verbatim'''
+ def __init__(self, der):
+ self.der = der
+
+ def ToDER(self):
+ return self.der
+
+
+class Explicit(object):
+ '''Explicit prepends an explicit tag'''
+ def __init__(self, tag, child):
+ self.tag = tag
+ self.child = child
+
+ def ToDER(self):
+ der = ToDER(self.child)
+ tag = self.tag
+ tag |= 0x80 # content specific
+ tag |= 0x20 # complex
+ return TagAndLength(tag, len(der)) + der
+
+
+class ENUMERATED(object):
+ def __init__(self, value):
+ self.value = value
+
+ def ToDER(self):
+ return TagAndLength(10, 1) + chr(self.value)
+
+
+class SEQUENCE(object):
+ def __init__(self, children):
+ self.children = children
+
+ def ToDER(self):
+ der = ''.join([ToDER(x) for x in self.children])
+ return TagAndLength(0x30, len(der)) + der
+
+
+class SET(object):
+ def __init__(self, children):
+ self.children = children
+
+ def ToDER(self):
+ der = ''.join([ToDER(x) for x in self.children])
+ return TagAndLength(0x31, len(der)) + der
+
+
+class OCTETSTRING(object):
+ def __init__(self, val):
+ self.val = val
+
+ def ToDER(self):
+ return TagAndLength(4, len(self.val)) + self.val
+
+
+class OID(object):
+ def __init__(self, parts):
+ self.parts = parts
+
+ def ToDER(self):
+ if len(self.parts) < 2 or self.parts[0] > 6 or self.parts[1] >= 40:
+ assert False
+
+ der = [self.parts[0]*40 + self.parts[1]]
+ for x in self.parts[2:]:
+ if x == 0:
+ der.append(0)
+ else:
+ octets = []
+ while x != 0:
+ v = x & 0x7f
+ if len(octets) > 0:
+ v |= 0x80
+ octets.append(v)
+ x >>= 7
+ octets.reverse()
+ der = der + octets
+
+ return TagAndLength(6, len(der)) + ToBytes(der)
+
+
+class UTCTime(object):
+ def __init__(self, time_str):
+ self.time_str = time_str
+
+ def ToDER(self):
+ return TagAndLength(23, len(self.time_str)) + self.time_str
+
+
+class GeneralizedTime(object):
+ def __init__(self, time_str):
+ self.time_str = time_str
+
+ def ToDER(self):
+ return TagAndLength(24, len(self.time_str)) + self.time_str
+
+
+class BitString(object):
+ def __init__(self, bits):
+ self.bits = bits
+
+ def ToDER(self):
+ return TagAndLength(3, 1 + len(self.bits)) + "\x00" + self.bits
diff --git a/net/tools/testserver/minica.py b/net/tools/testserver/minica.py
new file mode 100644
index 0000000..9824fd0
--- /dev/null
+++ b/net/tools/testserver/minica.py
@@ -0,0 +1,328 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import asn1
+import hashlib
+import os
+
+
+# This file implements very minimal certificate and OCSP generation. It's
+# designed to test revocation checking.
+
+def RandomNumber(length_in_bytes):
+ '''RandomNumber returns a random number of length 8*|length_in_bytes| bits'''
+ rand = os.urandom(length_in_bytes)
+ n = 0
+ for x in rand:
+ n <<= 8
+ n |= ord(x)
+ return n
+
+
+def ModExp(n, e, p):
+ '''ModExp returns n^e mod p'''
+ r = 1
+ while e != 0:
+ if e & 1:
+ r = (r*n) % p
+ e >>= 1
+ n = (n*n) % p
+ return r
+
+# PKCS1v15_SHA1_PREFIX is the ASN.1 prefix for a SHA1 signature.
+PKCS1v15_SHA1_PREFIX = '3021300906052b0e03021a05000414'.decode('hex')
+
+class RSA(object):
+ def __init__(self, modulus, e, d):
+ self.m = modulus
+ self.e = e
+ self.d = d
+
+ self.modlen = 0
+ m = modulus
+ while m != 0:
+ self.modlen += 1
+ m >>= 8
+
+ def Sign(self, message):
+ digest = hashlib.sha1(message).digest()
+ prefix = PKCS1v15_SHA1_PREFIX
+
+ em = ['\xff'] * (self.modlen - 1 - len(prefix) - len(digest))
+ em[0] = '\x00'
+ em[1] = '\x01'
+ em += "\x00" + prefix + digest
+
+ n = 0
+ for x in em:
+ n <<= 8
+ n |= ord(x)
+
+ s = ModExp(n, self.d, self.m)
+ out = []
+ while s != 0:
+ out.append(s & 0xff)
+ s >>= 8
+ out.reverse()
+ return '\x00' * (self.modlen - len(out)) + asn1.ToBytes(out)
+
+ def ToDER(self):
+ return asn1.ToDER(asn1.SEQUENCE([self.m, self.e]))
+
+
+def Name(cn = None, c = None, o = None):
+ names = asn1.SEQUENCE([])
+
+ if cn is not None:
+ names.children.append(
+ asn1.SET([
+ asn1.SEQUENCE([
+ COMMON_NAME, cn,
+ ])
+ ])
+ )
+
+ if c is not None:
+ names.children.append(
+ asn1.SET([
+ asn1.SEQUENCE([
+ COUNTRY, c,
+ ])
+ ])
+ )
+
+ if o is not None:
+ names.children.append(
+ asn1.SET([
+ asn1.SEQUENCE([
+ ORGANIZATION, o,
+ ])
+ ])
+ )
+
+ return names
+
+
+# The private key and root certificate name are hard coded here:
+
+# This is the private key
+KEY = RSA(0x00a71998f2930bfe73d031a87f133d2f378eeeeed52a77e44d0fc9ff6f07ff32cbf3da999de4ed65832afcb0807f98787506539d258a0ce3c2c77967653099a9034a9b115a876c39a8c4e4ed4acd0c64095946fb39eeeb47a0704dbb018acf48c3a1c4b895fc409fb4a340a986b1afc45519ab9eca47c30185c771c64aa5ecf07d,
+ 3,
+ 0x6f6665f70cb2a9a28acbc5aa0cd374cfb49f49e371a542de0a86aa4a0554cc87f7e71113edf399021ca875aaffbafaf8aee268c3b15ded2c84fb9a4375bbc6011d841e57833bc6f998d25daf6fa7f166b233e3e54a4bae7a5aaaba21431324967d5ff3e1d4f413827994262115ca54396e7068d0afa7af787a5782bc7040e6d3)
+
+# And the same thing in PEM format
+KEY_PEM = '''-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCnGZjykwv+c9AxqH8TPS83ju7u1Sp35E0Pyf9vB/8yy/PamZ3k
+7WWDKvywgH+YeHUGU50ligzjwsd5Z2UwmakDSpsRWodsOajE5O1KzQxkCVlG+znu
+60egcE27AYrPSMOhxLiV/ECftKNAqYaxr8RVGaueykfDAYXHccZKpezwfQIBAwKB
+gG9mZfcMsqmiisvFqgzTdM+0n0njcaVC3gqGqkoFVMyH9+cRE+3zmQIcqHWq/7r6
++K7iaMOxXe0shPuaQ3W7xgEdhB5XgzvG+ZjSXa9vp/FmsjPj5UpLrnpaqrohQxMk
+ln1f8+HU9BOCeZQmIRXKVDlucGjQr6eveHpXgrxwQObTAkEA2wBAfuduw5G0/VfN
+Wx66D5fbPccfYFqLM5LuTimLmNqzK2gIKXckB2sm44gJZ6wVlumaB1CSNug2LNYx
+3cAjUwJBAMNUo1hbI8ugqqwI9kpxv9+2Heea4BlnXbS6tYF8pvkHMoliuxNbXmmB
+u4zNB5iZ6V0ZZ4nvtUNo2cGr/h/Lcu8CQQCSACr/RPSCYSNTj948vya1D+d+hL+V
+kbIiYfQ0G7Jl5yIc8AVw+hgE8hntBVuacrkPRmaviwwkms7IjsvpKsI3AkEAgjhs
+5ZIX3RXHHVtO3EvVP86+mmdAEO+TzdHOVlMZ+1ohsOx8t5I+8QEnszNaZbvw6Lua
+W/UjgkXmgR1UFTJMnwJBAKErmAw21/g3SST0a4wlyaGT/MbXL8Ouwnb5IOKQVe55
+CZdeVeSh6cJ4hAcQKfr2s1JaZTJFIBPGKAif5HqpydA=
+-----END RSA PRIVATE KEY-----
+'''
+
+# Root certificate CN
+ISSUER_CN = "Testing CA"
+
+# All certificates are issued under this policy OID, in the Google arc:
+CERT_POLICY_OID = asn1.OID([1, 3, 6, 1, 4, 1, 11129, 2, 4, 1])
+
+# These result in the following root certificate:
+# -----BEGIN CERTIFICATE-----
+# MIIB0TCCATqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAVMRMwEQYDVQQDEwpUZXN0aW5nIENBMB4X
+# DTEwMDEwMTA2MDAwMFoXDTMyMTIwMTA2MDAwMFowFTETMBEGA1UEAxMKVGVzdGluZyBDQTCBnTAN
+# BgkqhkiG9w0BAQEFAAOBiwAwgYcCgYEApxmY8pML/nPQMah/Ez0vN47u7tUqd+RND8n/bwf/Msvz
+# 2pmd5O1lgyr8sIB/mHh1BlOdJYoM48LHeWdlMJmpA0qbEVqHbDmoxOTtSs0MZAlZRvs57utHoHBN
+# uwGKz0jDocS4lfxAn7SjQKmGsa/EVRmrnspHwwGFx3HGSqXs8H0CAQOjMzAxMBIGA1UdEwEB/wQI
+# MAYBAf8CAQAwGwYDVR0gAQEABBEwDzANBgsrBgEEAdZ5AgHODzANBgkqhkiG9w0BAQUFAAOBgQA/
+# STb40A6D+93jMfLGQzXc997IsaJZdoPt7tYa8PqGJBL62EiTj+erd/H5pDZx/2/bcpOG4m9J56yg
+# wOohbllw2TM+oeEd8syzV6X+1SIPnGI56JRrm3UXcHYx1Rq5loM9WKAiz/WmIWmskljsEQ7+542p
+# q0pkHjs8nuXovSkUYA==
+# -----END CERTIFICATE-----
+
+# If you update any of the above, you can generate a new root with the
+# following line:
+# print DERToPEM(MakeCertificate(ISSUER_CN, ISSUER_CN, 1, KEY, KEY, None))
+
+
+# Various OIDs
+
+AIA_OCSP = asn1.OID([1, 3, 6, 1, 5, 5, 7, 48, 1])
+AUTHORITY_INFORMATION_ACCESS = asn1.OID([1, 3, 6, 1, 5, 5, 7, 1, 1])
+BASIC_CONSTRAINTS = asn1.OID([2, 5, 29, 19])
+CERT_POLICIES = asn1.OID([2, 5, 29, 32])
+COMMON_NAME = asn1.OID([2, 5, 4, 3])
+COUNTRY = asn1.OID([2, 5, 4, 6])
+HASH_SHA1 = asn1.OID([1, 3, 14, 3, 2, 26])
+OCSP_TYPE_BASIC = asn1.OID([1, 3, 6, 1, 5, 5, 7, 48, 1, 1])
+ORGANIZATION = asn1.OID([2, 5, 4, 10])
+PUBLIC_KEY_RSA = asn1.OID([1, 2, 840, 113549, 1, 1, 1])
+SHA1_WITH_RSA_ENCRYPTION = asn1.OID([1, 2, 840, 113549, 1, 1, 5])
+
+
+def MakeCertificate(
+ issuer_cn, subject_cn, serial, pubkey, privkey, ocsp_url = None):
+ '''MakeCertificate returns a DER encoded certificate, signed by privkey.'''
+ extensions = asn1.SEQUENCE([])
+
+ # Default subject name fields
+ c = "XX"
+ o = "Testing Org"
+
+ if issuer_cn == subject_cn:
+ # Root certificate.
+ c = None
+ o = None
+ extensions.children.append(
+ asn1.SEQUENCE([
+ basic_constraints,
+ True,
+ asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([
+ True, # IsCA
+ 0, # Path len
+ ]))),
+ ]))
+
+ if ocsp_url is not None:
+ extensions.children.append(
+ asn1.SEQUENCE([
+ AUTHORITY_INFORMATION_ACCESS,
+ False,
+ asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([
+ asn1.SEQUENCE([
+ AIA_OCSP,
+ asn1.Raw(asn1.TagAndLength(0x86, len(ocsp_url)) + ocsp_url),
+ ]),
+ ]))),
+ ]))
+
+ extensions.children.append(
+ asn1.SEQUENCE([
+ CERT_POLICIES,
+ False,
+ asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([
+ asn1.SEQUENCE([ # PolicyInformation
+ CERT_POLICY_OID,
+ ]),
+ ]))),
+ ])
+ )
+
+ tbsCert = asn1.ToDER(asn1.SEQUENCE([
+ asn1.Explicit(0, 2), # Version
+ serial,
+ asn1.SEQUENCE([SHA1_WITH_RSA_ENCRYPTION, None]), # SignatureAlgorithm
+ Name(cn = issuer_cn), # Issuer
+ asn1.SEQUENCE([ # Validity
+ asn1.UTCTime("100101060000Z"), # NotBefore
+ asn1.UTCTime("321201060000Z"), # NotAfter
+ ]),
+ Name(cn = subject_cn, c = c, o = o), # Subject
+ asn1.SEQUENCE([ # SubjectPublicKeyInfo
+ asn1.SEQUENCE([ # Algorithm
+ PUBLIC_KEY_RSA,
+ None,
+ ]),
+ asn1.BitString(asn1.ToDER(pubkey)),
+ ]),
+ asn1.Explicit(3, extensions),
+ ]))
+
+ return asn1.ToDER(asn1.SEQUENCE([
+ asn1.Raw(tbsCert),
+ asn1.SEQUENCE([
+ SHA1_WITH_RSA_ENCRYPTION,
+ None,
+ ]),
+ asn1.BitString(privkey.Sign(tbsCert)),
+ ]))
+
+
+def MakeOCSPResponse(issuer_cn, issuer_key, serial, revoked):
+ # https://tools.ietf.org/html/rfc2560
+ issuer_name_hash = asn1.OCTETSTRING(
+ hashlib.sha1(asn1.ToDER(Name(cn = issuer_cn))).digest())
+
+ issuer_key_hash = asn1.OCTETSTRING(
+ hashlib.sha1(asn1.ToDER(issuer_key)).digest())
+
+ cert_status = None
+ if revoked:
+ cert_status = asn1.Explicit(1, asn1.GeneralizedTime("20100101060000Z"))
+ else:
+ cert_status = asn1.Raw(asn1.TagAndLength(0x80 | 0, 0))
+
+ basic_resp_data_der = asn1.ToDER(asn1.SEQUENCE([
+ asn1.Explicit(2, issuer_key_hash),
+ asn1.GeneralizedTime("20100101060000Z"), # producedAt
+ asn1.SEQUENCE([
+ asn1.SEQUENCE([ # SingleResponse
+ asn1.SEQUENCE([ # CertID
+ asn1.SEQUENCE([ # hashAlgorithm
+ HASH_SHA1,
+ None,
+ ]),
+ issuer_name_hash,
+ issuer_key_hash,
+ serial,
+ ]),
+ cert_status,
+ asn1.GeneralizedTime("20100101060000Z"), # thisUpdate
+ asn1.Explicit(0, asn1.GeneralizedTime("20300101060000Z")), # nextUpdate
+ ]),
+ ]),
+ ]))
+
+ basic_resp = asn1.SEQUENCE([
+ asn1.Raw(basic_resp_data_der),
+ asn1.SEQUENCE([
+ SHA1_WITH_RSA_ENCRYPTION,
+ None,
+ ]),
+ asn1.BitString(issuer_key.Sign(basic_resp_data_der)),
+ ])
+
+ resp = asn1.SEQUENCE([
+ asn1.ENUMERATED(0),
+ asn1.Explicit(0, asn1.SEQUENCE([
+ OCSP_TYPE_BASIC,
+ asn1.OCTETSTRING(asn1.ToDER(basic_resp)),
+ ]))
+ ])
+
+ return asn1.ToDER(resp)
+
+
+def DERToPEM(der):
+ pem = '-----BEGIN CERTIFICATE-----\n'
+ pem += der.encode('base64')
+ pem += '-----END CERTIFICATE-----\n'
+ return pem
+
+
+def GenerateCertKeyAndOCSP(subject = "127.0.0.1",
+ ocsp_url = "http://127.0.0.1",
+ ocsp_revoked = False):
+ '''GenerateCertKeyAndOCSP returns a (cert_and_key_pem, ocsp_der) where:
+ * cert_and_key_pem contains a certificate and private key in PEM format
+ with the given subject common name and OCSP URL.
+ * ocsp_der contains a DER encoded OCSP response or None if ocsp_url is
+ None'''
+
+ serial = RandomNumber(16)
+ cert_der = MakeCertificate(ISSUER_CN, subject, serial, KEY, KEY, ocsp_url)
+ cert_pem = DERToPEM(cert_der)
+
+ ocsp_der = None
+ if ocsp_url is not None:
+ ocsp_der = MakeOCSPResponse(ISSUER_CN, KEY, serial, ocsp_revoked)
+
+ return (cert_pem + KEY_PEM, ocsp_der)
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index ff20c0f..6461997 100755
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -19,15 +19,17 @@ import BaseHTTPServer
import cgi
import errno
import httplib
+import minica
import optparse
import os
import random
import re
import select
-import SocketServer
import socket
-import sys
+import SocketServer
import struct
+import sys
+import threading
import time
import urllib
import urlparse
@@ -105,25 +107,35 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
class HTTPServer(ClientRestrictingServerMixIn, StoppableHTTPServer):
- """This is a specialization of StoppableHTTPerver that adds client
+ """This is a specialization of StoppableHTTPServer that adds client
verification."""
pass
+class OCSPServer(ClientRestrictingServerMixIn, BaseHTTPServer.HTTPServer):
+ """This is a specialization of HTTPServer that serves an
+ OCSP response"""
+
+ def serve_forever_on_thread(self):
+ self.thread = threading.Thread(target = self.serve_forever,
+ name = "OCSPServerThread")
+ self.thread.start()
+
+ def stop_serving(self):
+ self.shutdown()
+ self.thread.join()
class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
ClientRestrictingServerMixIn,
StoppableHTTPServer):
- """This is a specialization of StoppableHTTPerver that add https support and
+ """This is a specialization of StoppableHTTPServer that add https support and
client verification."""
- def __init__(self, server_address, request_hander_class, cert_path,
+ def __init__(self, server_address, request_hander_class, pem_cert_and_key,
ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
record_resume_info):
- s = open(cert_path).read()
- self.cert_chain = tlslite.api.X509CertChain().parseChain(s)
- s = open(cert_path).read()
- self.private_key = tlslite.api.parsePEMKey(s, private=True)
+ self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
+ self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
self.ssl_client_auth = ssl_client_auth
self.ssl_client_cas = []
for ca_file in ssl_client_cas:
@@ -1889,6 +1901,20 @@ def MakeDataDir():
return my_data_dir
+class OCSPHandler(BasePageHandler):
+ def __init__(self, request, client_address, socket_server):
+ handlers = [self.OCSPResponse]
+ self.ocsp_response = socket_server.ocsp_response
+ BasePageHandler.__init__(self, request, client_address, socket_server,
+ [], handlers, [], handlers, [])
+
+ def OCSPResponse(self):
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/ocsp-response')
+ self.send_header('Content-Length', str(len(self.ocsp_response)))
+ self.end_headers()
+
+ self.wfile.write(self.ocsp_response)
class TCPEchoHandler(SocketServer.BaseRequestHandler):
"""The RequestHandler class for TCP echo server.
@@ -1969,19 +1995,55 @@ def main(options, args):
server_data = {}
server_data['host'] = host
+ ocsp_server = None
+
if options.server_type == SERVER_HTTP:
- if options.cert:
- # let's make sure the cert file exists.
- if not os.path.isfile(options.cert):
- print 'specified server cert file not found: ' + options.cert + \
- ' exiting...'
- return
+ if options.https:
+ pem_cert_and_key = None
+ if options.cert_and_key_file:
+ if not os.path.isfile(options.cert_and_key_file):
+ print ('specified server cert file not found: ' +
+ options.cert_and_key_file + ' exiting...')
+ return
+ pem_cert_and_key = file(options.cert_and_key_file, 'r').read()
+ else:
+ # generate a new certificate and run an OCSP server for it.
+ ocsp_server = OCSPServer((host, 0), OCSPHandler)
+ print ('OCSP server started on %s:%d...' %
+ (host, ocsp_server.server_port))
+
+ ocsp_der = None
+ ocsp_revoked = False
+ ocsp_invalid = False
+
+ if options.ocsp == 'ok':
+ pass
+ elif options.ocsp == 'revoked':
+ ocsp_revoked = True
+ elif options.ocsp == 'invalid':
+ ocsp_invalid = True
+ else:
+ print 'unknown OCSP status: ' + options.ocsp_status
+ return
+
+ (pem_cert_and_key, ocsp_der) = \
+ minica.GenerateCertKeyAndOCSP(
+ subject = "127.0.0.1",
+ ocsp_url = ("http://%s:%d/ocsp" %
+ (host, ocsp_server.server_port)),
+ ocsp_revoked = ocsp_revoked)
+
+ if ocsp_invalid:
+ ocsp_der = '3'
+
+ ocsp_server.ocsp_response = ocsp_der
+
for ca_cert in options.ssl_client_ca:
if not os.path.isfile(ca_cert):
print 'specified trusted client CA file not found: ' + ca_cert + \
' exiting...'
return
- server = HTTPSServer((host, port), TestPageHandler, options.cert,
+ server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
options.ssl_client_auth, options.ssl_client_ca,
options.ssl_bulk_cipher, options.record_resume)
print 'HTTPS server started on %s:%d...' % (host, server.server_port)
@@ -2061,10 +2123,15 @@ def main(options, args):
startup_pipe.write(server_data_json)
startup_pipe.close()
+ if ocsp_server is not None:
+ ocsp_server.serve_forever_on_thread()
+
try:
server.serve_forever()
except KeyboardInterrupt:
print 'shutting down server'
+ if ocsp_server is not None:
+ ocsp_server.stop_serving()
server.stop = True
if __name__ == '__main__':
@@ -2095,10 +2162,16 @@ if __name__ == '__main__':
'server will listen on an ephemeral port.')
option_parser.add_option('', '--data-dir', dest='data_dir',
help='Directory from which to read the files.')
- option_parser.add_option('', '--https', dest='cert',
- help='Specify that https should be used, specify '
- 'the path to the cert containing the private key '
- 'the server should use.')
+ option_parser.add_option('', '--https', action='store_true', dest='https',
+ help='Specify that https should be used.')
+ option_parser.add_option('', '--cert-and-key-file', dest='cert_and_key_file',
+ help='specify the path to the file containing the '
+ 'certificate and private key for the server in PEM '
+ 'format')
+ option_parser.add_option('', '--ocsp', dest='ocsp', default='ok',
+ help='The type of OCSP response generated for the '
+ 'automatically generated certificate. One of '
+ '[ok,revoked,invalid]')
option_parser.add_option('', '--https-record-resume', dest='record_resume',
const=True, default=False, action='store_const',
help='Record resumption cache events rather than'
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 92e32ae..784b14a 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -27,6 +27,8 @@
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
+#include "net/base/cert_test_util.h"
+#include "net/base/ev_root_ca_metadata.h"
#include "net/base/load_flags.h"
#include "net/base/mock_host_resolver.h"
#include "net/base/net_errors.h"
@@ -35,6 +37,7 @@
#include "net/base/net_module.h"
#include "net/base/net_util.h"
#include "net/base/ssl_connection_status_flags.h"
+#include "net/base/test_root_certs.h"
#include "net/base/upload_data.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_store_test_helpers.h"
@@ -45,6 +48,7 @@
#include "net/http/http_network_session.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
+#include "net/ocsp/nss_ocsp.h"
#include "net/proxy/proxy_service.h"
#include "net/socket/ssl_client_socket.h"
#include "net/test/test_server.h"
@@ -58,6 +62,10 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
using base::Time;
namespace net {
@@ -1367,6 +1375,164 @@ TEST_F(HTTPSRequestTest, HTTPSExpiredTest) {
}
}
+class RevCheckedEnabledSSLConfigService : public SSLConfigService {
+ public:
+ virtual void GetSSLConfig(SSLConfig* config) {
+ *config = SSLConfig();
+ config->rev_checking_enabled = true;
+ config->verify_ev_cert = true;
+ }
+};
+
+// This the fingerprint of the "Testing CA" certificate used by the testserver.
+// See net/data/ssl/certificates/ocsp-test-root.pem.
+static const SHA1Fingerprint kOCSPTestCertFingerprint =
+ { { 0xf1, 0xad, 0xf6, 0xce, 0x42, 0xac, 0xe7, 0xb4, 0xf4, 0x24,
+ 0xdb, 0x1a, 0xf7, 0xa0, 0x9f, 0x09, 0xa1, 0xea, 0xf1, 0x5c } };
+
+// This is the policy OID contained in the certificates that testserver
+// generates.
+static const char kOCSPTestCertPolicy[] = "1.3.6.1.4.1.11129.2.4.1";
+
+class HTTPSOCSPTest : public HTTPSRequestTest {
+ public:
+ HTTPSOCSPTest()
+ : context_(new TestURLRequestContext(true)),
+ ev_test_policy_(EVRootCAMetadata::GetInstance(),
+ kOCSPTestCertFingerprint,
+ kOCSPTestCertPolicy) {
+ context_->set_ssl_config_service(new RevCheckedEnabledSSLConfigService);
+ context_->Init();
+
+ scoped_refptr<net::X509Certificate> root_cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ocsp-test-root.pem");
+ CHECK_NE(static_cast<X509Certificate*>(NULL), root_cert);
+ test_root_.reset(new ScopedTestRoot(root_cert));
+
+#if defined(USE_NSS)
+ SetURLRequestContextForNSSHttpIO(context_.get());
+ EnsureNSSHttpIOInit();
+#endif
+ }
+
+ void DoConnection(const TestServer::HTTPSOptions& https_options,
+ CertStatus* out_cert_status) {
+ TestServer test_server(https_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ d.set_allow_certificate_errors(true);
+ URLRequest r(test_server.GetURL(""), &d);
+ r.set_context(context_.get());
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ *out_cert_status = r.ssl_info().cert_status;
+ }
+
+ ~HTTPSOCSPTest() {
+#if defined(USE_NSS)
+ ShutdownNSSHttpIO();
+#endif
+ }
+
+ private:
+ scoped_ptr<ScopedTestRoot> test_root_;
+ scoped_refptr<TestURLRequestContext> context_;
+ ScopedTestEVPolicy ev_test_policy_;
+};
+
+#if !defined(OS_ANDROID) && !defined(USE_OPENSSL)
+
+static bool
+SystemSupportsOCSP() {
+#if defined(OS_WIN)
+ return base::win::GetVersion() >= base::win::VERSION_VISTA;
+#elif defined(OS_ANDROID)
+ return false;
+#else
+ return true;
+#endif
+}
+
+// TODO(jnd): http://crbug.com/117478 - EV verification is not yet supported.
+TEST_F(HTTPSOCSPTest, Valid) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::HTTPSOptions https_options(TestServer::HTTPSOptions::CERT_AUTO);
+ https_options.ocsp_status = TestServer::HTTPSOptions::OCSP_OK;
+
+ CertStatus cert_status;
+ DoConnection(https_options, &cert_status);
+
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+
+#if defined(OS_MACOSX)
+ // On OS X, we use the system to tell us whether a certificate is EV or not
+ // and the system won't recognise our testing root.
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+#else
+ EXPECT_TRUE(cert_status & CERT_STATUS_IS_EV);
+#endif
+
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+TEST_F(HTTPSOCSPTest, Revoked) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::HTTPSOptions https_options(
+ TestServer::HTTPSOptions::CERT_AUTO);
+ https_options.ocsp_status = TestServer::HTTPSOptions::OCSP_REVOKED;
+
+ CertStatus cert_status;
+ DoConnection(https_options, &cert_status);
+
+#if !defined(OS_MACOSX)
+ // Doesn't pass on OS X yet for reasons that need to be investigated.
+ EXPECT_EQ(CERT_STATUS_REVOKED, cert_status & CERT_STATUS_ALL_ERRORS);
+#endif
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+TEST_F(HTTPSOCSPTest, Invalid) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::HTTPSOptions https_options(
+ TestServer::HTTPSOptions::CERT_AUTO);
+ https_options.ocsp_status = TestServer::HTTPSOptions::OCSP_INVALID;
+
+ CertStatus cert_status;
+ DoConnection(https_options, &cert_status);
+
+#if defined(OS_WIN)
+ // Windows can return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION but we don't
+ // have that ability on other platforms.
+ EXPECT_EQ(CERT_STATUS_UNABLE_TO_CHECK_REVOCATION,
+ cert_status & CERT_STATUS_ALL_ERRORS);
+#else
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+#endif
+
+ // Without a positive OCSP response, we shouldn't show the EV status.
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+#endif // !OS_ANDROID && !USE_OPENSSL
+
// This tests that a load of www.google.com with a certificate error sets
// the |certificate_errors_are_fatal| flag correctly. This flag will cause
// the interstitial to be fatal.