Browse Source

added badNonce retries

Daniel Roesler 7 years ago
parent
commit
68c565ec32
1 changed files with 11 additions and 6 deletions
  1. 11 6
      acme_tiny.py

+ 11 - 6
acme_tiny.py

@@ -1,9 +1,9 @@
 #!/usr/bin/env python
 import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging
 try:
-    from urllib.request import urlopen # Python 3
+    from urllib.request import urlopen, Request # Python 3
 except ImportError:
-    from urllib2 import urlopen # Python 2
+    from urllib2 import urlopen, Request # Python 2
 
 DEFAULT_CA = "https://acme-v02.api.letsencrypt.org" # DEPRECATED! USE DEFAULT_DIRECTORY_URL INSTEAD
 DEFAULT_DIRECTORY_URL = "https://acme-v02.api.letsencrypt.org/directory"
@@ -27,9 +27,9 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, disable_check
         return proc, out, err
 
     # helper function - make request and automatically parse json response
-    def _do_request(url, data=None, err_msg="Error"):
+    def _do_request(url, data=None, err_msg="Error", depth=0):
         try:
-            resp = urlopen(url, data)
+            resp = urlopen(Request(url, data=data, headers={"Content-Type": "application/jose+json"}))
             resp_data, code, headers = resp.read().decode("utf8"), resp.getcode(), resp.headers
             resp_data = json.loads(resp_data) # try to parse json results
         except ValueError:
@@ -37,12 +37,14 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, disable_check
         except IOError as e:
             resp_data = e.read().decode("utf8") if hasattr(e, "read") else str(e)
             code, headers = getattr(e, "code", None), {}
+        if depth < 100 and code == 400 and json.loads(resp_data)['type'] == "urn:ietf:params:acme:error:badNonce":
+            raise IndexError(resp_data) # allow 100 retrys for bad nonces
         if code not in [200, 201, 204]:
             raise ValueError("{0}:\nUrl: {1}\nData: {2}\nResponse Code: {3}\nResponse: {4}".format(err_msg, url, data, code, resp_data))
         return resp_data, code, headers
 
     # helper function - make signed requests
-    def _send_signed_request(url, payload, err_msg):
+    def _send_signed_request(url, payload, err_msg, depth=0):
         payload64 = _b64(json.dumps(payload).encode('utf8'))
         new_nonce = _do_request(directory['newNonce'])[2]['Replay-Nonce']
         protected = {"url": url, "alg": alg, "nonce": new_nonce}
@@ -53,7 +55,10 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, disable_check
         if proc.returncode != 0:
             raise IOError("OpenSSL Error: {0}".format(err))
         data = json.dumps({"protected": protected64, "payload": payload64, "signature": _b64(out)})
-        return _do_request(url, data=data.encode('utf8'), err_msg=err_msg)
+        try:
+            return _do_request(url, data=data.encode('utf8'), err_msg=err_msg, depth=depth)
+        except IndexError: # retry bad nonces (they raise IndexError)
+            return _send_signed_request(url, payload, err_msg, depth=(depth + 1))
 
     # helper function - poll until complete
     def _poll_until_not(url, pending_statuses, err_msg):