Opened 4 years ago

Closed 4 years ago

#9167 closed defect (fixed)

Find a more stable key pinning scheme for www.google.com

Reported by: dcf Owned by: dcf
Priority: Medium Milestone:
Component: Archived/Flashproxy Version:
Severity: Keywords:
Cc: Actual Points:
Parent ID: Points:
Reviewer: Sponsor:

Description

I've had to update the public key pin for www.google.com in flashproxy-reg-appspot often: March 31, June 13, and again today on June 28.

transport_security_state_static.h in the Chromium source code is where the pins are stored, I believe. That file hasn't had any google.com changes since March 28. That makes me think we're doing something wrong and hashing more than we're supposed to.

transport_security_state_static_generate.go is the program used to generate the Chromium file. It does:

        h := sha1.New()
        h.Write(cert.RawSubjectPublicKeyInfo)

Whereas flashproxy-reg-appspot does:

        pubkey_der = cert.get_pubkey().as_der()
        pubkey_digest = sha1(pubkey_der).digest()

The Chromium source code says:

// This file is automatically generated by transport_security_state_static_generate.go

// These are SubjectPublicKeyInfo hashes for public key pinning. The
// hashes are SHA1 digests.

...

static const char kSPKIHash_Google1024[] =
	"\x40\xc5\x40\x1d\x6f\x8c\xba\xf0\x8b\x00"
	"\xed\xef\xb1\xee\x87\xd0\x05\xb3\xb9\xcd";

static const char kSPKIHash_Google2048[] =
	"\x01\xb9\x21\xc5\x8d\x0b\xdf\x8d\xe0\x29"
	"\xff\x9c\x92\x2e\xcd\x55\x6a\x7e\xa3\x39";

static const char kSPKIHash_GoogleBackup1024[] =
	"\x7d\x5b\xa3\xca\x8e\x37\x65\x1d\x7c\x71"
	"\xc3\xe3\xb7\x74\xcd\xe9\x7b\x1b\x59\x43";

static const char kSPKIHash_GoogleBackup2048[] =
	"\xbe\xae\xce\xca\x34\xa7\xa8\xe7\x28\xf6"
	"\x7c\x8c\x08\x31\x9d\xcb\xbe\xde\x8a\x33";

static const char kSPKIHash_GoogleG2[] =
	"\x43\xda\xd6\x30\xee\x53\xf8\xa9\x80\xca"
	"\x6e\xfd\x85\xf4\x6a\xa3\x79\x90\xe0\xea";

...

static const char* const kGoogleAcceptableCerts[] = {
	kSPKIHash_VeriSignClass3,
	kSPKIHash_VeriSignClass3_G3,
	kSPKIHash_Google1024,
	kSPKIHash_Google2048,
	kSPKIHash_GoogleBackup1024,
	kSPKIHash_GoogleBackup2048,
	kSPKIHash_GoogleG2,
	kSPKIHash_EquifaxSecureCA,
	kSPKIHash_GeoTrustGlobal,
	NULL,
};

None of these digests match what we have in flashproxy-reg-appspot:

# SHA-1 digest of expected public keys. Any of these is valid. See
# http://www.imperialviolet.org/2011/05/04/pinning.html for the reason behind
# hashing the public key, not the entire certificate.
PUBKEY_SHA1 = tuple(x.decode("hex") for x in (
    "c70ccd442ff4528c603aefef85206fd693990e09",
    "1697e17a8a3317f031721b7b6293cd50643bbbd3",
    "291e750bafedac444486327e50f26f64d840991a",
    "1e3f66cfa0eb03136297fdb238ad6619c30ff375",
))

Our other programs using pins (flashproxy-reg-email and facilitator-email-poller) have been more stable historically but could benefit from whatever works for flashproxy-reg-appspot.

Child Tickets

Attachments (1)

intermediate-pin.patch (6.1 KB) - added by dcf 4 years ago.
Two patches implementing the pin checking of intermediate certs.

Download all attachments as: .zip

Change History (4)

comment:1 Changed 4 years ago by dcf

It looks like we are hashing the right thing (pubkey_der). What it looks like we're doing wrong is we need to also check intermediate certificates' keys.

Do

openssl s_client -connect www.google.com:443 -showcerts

Running the leaf certificate through these OpenSSL commands gives

1e3f66cfa0eb03136297fdb238ad6619c30ff375  p1.key

which is what we have pinned in flashproxy-reg-appspot. But doing the same with the intermediate certificate gives

40c5401d6f8cbaf08b00edefb1ee87d005b3b9cd  p2.key

which is the same as a Chromium digest:

static const char kSPKIHash_Google1024[] =
	"\x40\xc5\x40\x1d\x6f\x8c\xba\xf0\x8b\x00"
	"\xed\xef\xb1\xee\x87\xd0\x05\xb3\xb9\xcd";

Changed 4 years ago by dcf

Attachment: intermediate-pin.patch added

Two patches implementing the pin checking of intermediate certs.

comment:2 Changed 4 years ago by dcf

Status: newneeds_review

Please take a look at these two patches.

The first refactors the connection to directly use OpenSSL functions through M2Crypto. Python's ssl.wrap_socket doesn't provide enough access: we can getpeercert but as far as I can tell there's no way to get the certificate chain.

The second patch iterates over the certificate chain and looks for a match. It also uses a sha1sum copied directly from the Chromium source code.

comment:3 Changed 4 years ago by dcf

Resolution: fixed
Status: needs_reviewclosed

I effected the same change in flashproxy-reg-appspot, flashproxy-reg-email, and facilitator-email-poller.

Note: See TracTickets for help on using tickets.