I maintain a fork of urllib3. I added assertion of PubKey digests, so every request to https will have its SSL certificate pinned.

Because git isn’t my tool of choice, I keep forgetting how I keep the fork in sync with the original repo.

I have not done a sync for quite some time, so I decided to write it down for posterity.

How I keep the fork in sync with the official urllib3 repo by shazow:

$ git clone https://github.com/encodeltd/urllib3.git
$ cd urllib3
$ git remote add upstream https://github.com/shazow/urllib3.git          # add shazow's repo as remote upstream
$ git pull upstream master                                               # pull from shazow's repo
remote: Counting objects: 2184, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 2184 (delta 567), reused 556 (delta 556), pack-reused 1594
Receiving objects: 100% (2184/2184), 671.38 KiB | 682.00 KiB/s, done.
Resolving deltas: 100% (1570/1570), completed with 95 local objects.
From https://github.com/shazow/urllib3
 * branch            master     -> FETCH_HEAD
 * [new branch]      master     -> upstream/master
Auto-merging urllib3/util/ssl_.py
Auto-merging urllib3/util/__init__.py
Auto-merging urllib3/contrib/pyopenssl.py
Auto-merging urllib3/connectionpool.py
CONFLICT (content): Merge conflict in urllib3/connectionpool.py
Auto-merging urllib3/connection.py
CONFLICT (content): Merge conflict in urllib3/connection.py
Removing docs/security.rst
Removing docs/pools.rst
Removing docs/managers.rst
Removing docs/helpers.rst
Removing docs/exceptions.rst
Removing docs/doc-requirements.txt
Removing docs/contrib.rst
Removing docs/collections.rst
Removing docs/README
Automatic merge failed; fix conflicts and then commit the result.
$ vim urllib3/connectionpool.py                                          # resolve conflicts manually
$ vim urllib3/connection.py                                              # resolve conflicts manually
$ git add urllib3/connectionpool.py urllib3/connection.py
$ git commit -m "Merged in changes from 'master' of https://github.com/shazow/urllib3"
$ git push

Installation

To install the fork, run:

pip install https://github.com/encodeltd/urllib3/archive/master.zip

Usage

Here is a testing script (pin_ssl_cert.py) for sha1, sha256 PubKey digests SSL certificate pinning using my fork of urllib3:

from __future__ import print_function

import sys
import urllib3
import logging

from urllib3.contrib import pyopenssl

pyopenssl.inject_into_urllib3()

log = logging.getLogger('urllib3.connection')
log.setLevel(logging.INFO)
log.addHandler(logging.StreamHandler(sys.stdout))


def test_pin_ssl_cert(test_request):
    try:
        test_response = test_request.request('GET', 'https://google.com/')
        print(test_response.status)
    except ValueError, e:
        print(e)


# test ssl cert pinning with correct set of digests
test_request = urllib3.PoolManager(assert_pubkey_digests=(
    'ncSxX0vhBA8b60xgmhytIUPfYkNVRUPNLnzIApgO16I=',
    'nDg/4zpPacE7OGtJSc2/jRnSiW4=',
    '7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=',
    'Q9rWMO5T+KmAym79hfRqo3mQ4Oo=',
    'h6801m+z8v3zbgkRHpq6L29Esgfzhj89C1SyUCOQmqU=',
    'wHqYaI2J+6sFZAwRfap9ZbjKzE4='))
test_pin_ssl_cert(test_request)

# test ssl cert pinning with incorrect set of digests
test_request = urllib3.PoolManager(assert_pubkey_digests=('-'))
test_pin_ssl_cert(test_request)

The following is its output:

$ python pin_ssl_cert.py
Verifying connection using provided PubKey digests: ('ncSxX0vhBA8b60xgmhytIUPfYkNVRUPNLnzIApgO16I=', 'nDg/4zpPacE7OGtJSc2/jRnSiW4=', '7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=', 'Q9rWMO5T+KmAym79hfRqo3mQ4Oo=', 'h6801m+z8v3zbgkRHpq6L29Esgfzhj89C1SyUCOQmqU=', 'wHqYaI2J+6sFZAwRfap9ZbjKzE4=')
Verifying connection using provided PubKey digests: ('ncSxX0vhBA8b60xgmhytIUPfYkNVRUPNLnzIApgO16I=', 'nDg/4zpPacE7OGtJSc2/jRnSiW4=', '7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=', 'Q9rWMO5T+KmAym79hfRqo3mQ4Oo=', 'h6801m+z8v3zbgkRHpq6L29Esgfzhj89C1SyUCOQmqU=', 'wHqYaI2J+6sFZAwRfap9ZbjKzE4=')
200
Verifying connection using provided PubKey digests: -
Public key digests (sha1, or sha256) do not match any of the expected pins!

Received:
(ncSxX0vhBA8b60xgmhytIUPfYkNVRUPNLnzIApgO16I=,
nDg/4zpPacE7OGtJSc2/jRnSiW4=,
7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=,
Q9rWMO5T+KmAym79hfRqo3mQ4Oo=,
h6801m+z8v3zbgkRHpq6L29Esgfzhj89C1SyUCOQmqU=,
wHqYaI2J+6sFZAwRfap9ZbjKzE4=)

Expected:
(LQ==)