0

I am currently writing a python language plugin for a compiler I have written that automates http calls for a RESTful API. I have managed to get the login/authentication working using the socket and ssl modules, but this low-level approach seems to create potential problems with parsing the response in order to obtain the authentication token and secret. The requests module seems popular/efficient, however, I cannot seem to get it to function properly for my particular authentication needs. I am using a trust store in the form of a .pem file (containing just a public key) that I converted from my .jks file used to authenticate for the Java plugin. The server expects the username and password to be submitted in the request body in json format. Here is the code I have been trying to use:

#Server and login data
...
host = 'localhost'
port = 8443
pem_file = "C:\\Users\\aharasta\\pycert.pem"

#Digest password with MD5 algorithm
m = hashlib.md5()
m.update(password)
encrypted_password = m.hexdigest()

url = <url>
data = {'userid': user_name, 'password': encrypted_password}
json_data = json.dumps(data)
headers = {'Content-type': 'application/json', 'Accept': 'text/plain', 'Content \                     
          Length': len(json_data)} 

r = requests.post(url, headers = headers, data = json_data, cert = pem_file)
print(r)

Upon execution, this code will raise an SSL error stating "certificate verify failed". If I add the parameter verify = False or verify = pem_file, I will receive a 404 response from the server. I should also note that when I launch the server in debug mode and execute the request (with one of the verify parameters), it never makes it to the server's authentication methods, or any methods for that matter. Any insight or help on this matter would be greatly appreciated!

Ian Stapleton Cordasco
  • 26,944
  • 4
  • 67
  • 72
Andrew Harasta
  • 149
  • 2
  • 4
  • 10

1 Answers1

1

First there are a couple problems with what you posted:

  • You specify host and port but don't give an example url, so we can guess that you're using a local deployment for testing and can actually view the requests to the server. I'm not sure what methods you're talking about, but if you're using something akin to Flask for server development, you might not want to send the Authentication as JSON encoded data. There are Authentication headers for a reason, and requests has Authentication handlers for a reason. ;-)

  • You shouldn't specify the Content-Length header yourself. requests will do this for you. Beyond that, you're specifying it incorrectly (according to what you posted), so the 404 may be from receiving a header your server doesn't recognize.

Now, there should be no reason to specify verify=False, and you can specify either cert=pem_file or verify=pem_file. Either or both should be fine but you should never use verify=False.

Finally, the SSLError that is raised is telling you that the pem file you're providing is not matching what the server is specifying it. With that in mind, you might want to check your local server's settings. Requests doesn't handle certificate verification itself, but urllib3 provides that. We just set it up based on the parameters you provide. And I doubt this is the fault of urllib3 since it raises an SSLError which arises from the standard library's ssl module.

Edit

The explanation is in the documentation specifying a *.pem file with cert is invalid. You have to use verify='/path/to/file.pem' to do this correctly.

Edit #2

To inspect a request that was already sent you can do this:

r = requests.post(...)
r.request
# PreparedRequest('POST', url, ...)
r.request.body
r.request.headers
# etc.

To modify a request before sending you can do the following:

from requests import Request, Session

s = Session()
r = Request('POST', url, datajson_data, headers=headers)
p = r.prepare()
p.body = 'New body'
p.headers = #etc.
s.send(p, verify=pem_file)
Ian Stapleton Cordasco
  • 26,944
  • 4
  • 67
  • 72
  • I am using JBoss for the server deployment, and I am not at liberty to change the code on the server side. I don't understand why that same `pem` file works fine when I wrap it into an `ssl` socket, but not with the `requests` module. I believe the only headers I need to specify are Content-Type and Accept (perhaps I don't need to specify any headers?), so I removed Content-Length. Unfortunately, this did not improve matters. – Andrew Harasta Feb 05 '13 at 16:13
  • You should be supplying `Content-Type` without a doubt, `Accept` is optional, but that isn't the problem. When you say you use the ssl socket, do you mean calling `import ssl; ss.wrap_socket(socket, certfile=pem_file)`? – Ian Stapleton Cordasco Feb 05 '13 at 16:23
  • Yes, but I am using the ca_certs parameter, so it looks like: `import ssl; conn = ssl.wrap_socket(sock, ca_certs = pem_file)` the full socket configuration can be found [here](http://stackoverflow.com/questions/14699932/how-to-extract-json-data-from-a-response-containing-a-header-and-body) – Andrew Harasta Feb 05 '13 at 17:00
  • So I know of a server that has it's own self-signed certificate. Oddly enough, using `cert='/path/to/pem_file'` didn't work, but `verify='/path/to/pem_file'` did (without specifying `cert`). Have you tried that? – Ian Stapleton Cordasco Feb 05 '13 at 18:31
  • @AndrewHarasta I edited my answer with the place in the documentation that this is explained. – Ian Stapleton Cordasco Feb 05 '13 at 18:34
  • Yes, this results in a `<404>` status response. Is there a way to view the post request prior to sending it to the server? It seems like the REST API I am trying to communicate with doesn't recognize something in the request. I feel like the verify=pem_file should work for a trust store such as this. Also, just wanted to thank you for taking the time in helping me troubleshoot this. I am very impressed with this Stack Overflow community so far! – Andrew Harasta Feb 05 '13 at 18:48
  • @AndrewHarasta what you can do with requests is not easily described in a comment. I'm going to update my answer with what you should do to inspect the request. Also, I'm a collaborator on requests so that's another reason I'm personally interested in helping you debug this. :-) – Ian Stapleton Cordasco Feb 05 '13 at 22:53
  • `s.send(p, verify=pem_file)` results in a "[Errno 10053] An established connection was aborted by the software in your host machine". We are using the Restlet framework on the server-side. Not sure if you guys have experienced issues with requests and this framework in the past. The request body is just fine, and I just don't see what would be causing issues specifically in the header. `str: POST https://localhost:8443//login HTTP/1.1 Host: localhost Content-Type: text/json Content-Length: 82` This header works with ssl/socket modules, the same .pem file and the same request body. – Andrew Harasta Feb 06 '13 at 00:57
  • I take it that is a `ConnectionError` that is raised? I don't think we've ever had issues with that framework in the past. Also, their documentation seems to be quite an epic troll. If you click on anything other than the Java SE/Java EE/GAE/etc. documentation, you get sent to the way-back machine which just redirects without end. Either they hate their users and fellow developers, or they're too lazy to bother providing good documentation. – Ian Stapleton Cordasco Feb 06 '13 at 04:03
  • Yes, `ConnectionError` followed by the above Errno in quotes. Yeah the Restlet framework is probably pretty heavily Java-favored at this point, and I know what you mean about their documentation. It's definitely not very good. I was hoping I would be able to see the entire request header that requests puts together, instead of just a dictionary of the header values. I suspect that it's just a `POST HTTP/1.1` line at the top, but if it's something more than that, I'm wondering if that could be the cause of `<404>`. – Andrew Harasta Feb 06 '13 at 18:29
  • That is constructed by urllib3, I don't how to retrieve that information from it. – Ian Stapleton Cordasco Feb 08 '13 at 03:35