1

I need to make an API call (of sorts) in Django as a part of the custom authentication system we require. A username and password is sent to a specific URL over SSL (using GET for those parameters) and the response should be an HTTP 200 "OK" response with the body containing XML with the user's info.

On an unsuccessful auth, it will return an HTTP 401 "Unauthorized" response.

For security reasons, I need to check:

  1. The request was sent over an HTTPS connection
  2. The server certificate's public key matches an expected value (I use 'certificate pinning' to defend against broken CAs)

Is this possible in python/django using pycurl/urllib2 or any other method?

jfs
  • 399,953
  • 195
  • 994
  • 1,670
TC Fox
  • 980
  • 4
  • 13
  • 25

2 Answers2

3

Using M2Crypto:

from M2Crypto import SSL
ctx = SSL.Context('sslv3')
ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9)
if ctx.load_verify_locations('ca.pem') != 1:
   raise Exception('No CA certs')

c = SSL.Connection(ctx)
c.connect(('www.google.com', 443)) # automatically checks cert matches host
c.send('GET / \n')
c.close()

Using urllib2_ssl (it goes without saying but to be explicit: use it at your own risk):

import urllib2, urllib2_ssl

opener = urllib2.build_opener(urllib2_ssl.HTTPSHandler(ca_certs='ca.pem'))
xml = opener.open('https://example.com/').read()

Related: Making HTTPS Requests secure in Python.

Using pycurl:

c = pycurl.Curl()
c.setopt(pycurl.URL, "https://example.com?param1=val1&param2=val2")
c.setopt(pycurl.HTTPGET, 1)
c.setopt(pycurl.CAINFO, 'ca.pem')
c.setopt(pycurl.SSL_VERIFYPEER, 1)
c.setopt(pycurl.SSL_VERIFYHOST, 2)
c.setopt(pycurl.SSLVERSION,     3)    
c.setopt(pycurl.NOBODY, 1)
c.setopt(pycurl.NOSIGNAL, 1)
c.perform()
c.close()

To implement 'certificate pinning' provide different 'ca.pem' for different domains.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • Going through with pycurl, it seems! It's a hideous wrapper, but it does the job, and certianly more powerful than the other alternatives. Tested it now and it seems to work (was going to add CURLOPT_CERTINFO support but it doesn't seem to behave with pycurl). – TC Fox Dec 14 '11 at 03:14
  • @TrojanCentaur: Why do you need `CERTINFO` i.e., how do you like to use it once you get it? `CERTINFO` requires curl to be build with openssl. What does `pycurl.version` return? You could use M2Crypto variant to get cert. info instead; it is the most complete Python wrapper for openssl. I don't understand "more powerful" part. "more powerful" to do what? – jfs Dec 14 '11 at 05:37
0

httplib2 can do https requests with certificate validation:

import httplib2
http = httplib2.Http(ca_certs='/path/to/cert.pem')
try:
    http.request('https://...')
except httplib2.SSLHandshakeError, e:
    # do something

Just make sure that your httplib2 is up to date. The one which is shipped with my distribution (ubuntu 10.04) does not have ca_certs parameter.

Also in similar question to yours there is an example of certificate validation with pycurl.

Community
  • 1
  • 1
Ski
  • 14,197
  • 3
  • 54
  • 64
  • 1
    As I understand it `httplib2` use default `ssl` module that doesn't perform any hostname validation in Python 2.x – jfs Dec 14 '11 at 00:37