acmecert: Fix cryptography bugs.
[utils.git] / acmecert
index 56b1a58..14d0f00 100755 (executable)
--- a/acmecert
+++ b/acmecert
@@ -4,7 +4,6 @@
 
 import sys, os, getopt, binascii, json, pprint, signal, time, calendar, threading
 import urllib.request
-import Crypto.PublicKey.RSA, Crypto.Random, Crypto.Hash.SHA256, Crypto.Signature.PKCS1_v1_5
 
 ### General utilities
 
@@ -44,6 +43,14 @@ class maybeopen(object):
 
 ### Crypto utilities
 
+_cryptobke = None
+def cryptobke():
+    global _cryptobke
+    if _cryptobke is None:
+        from cryptography.hazmat import backends
+        _cryptobke = backends.default_backend()
+    return _cryptobke
+
 class dererror(Exception):
     pass
 
@@ -354,7 +361,7 @@ class problem(msgerror):
 
     @classmethod
     def read(cls, err, **kw):
-        self = cls(err.code, json.load(err), **kw)
+        self = cls(err.code, json.loads(err.read().decode("utf-8")), **kw)
         return self
 
 def jreq(url, data, auth):
@@ -369,7 +376,7 @@ def jreq(url, data, auth):
     enc = {"protected": authdata, "payload": data, "signature": seal}
     try:
         with req(url, data=enc) as resp:
-            return json.load(resp), resp.headers
+            return json.loads(resp.read().decode("utf-8")), resp.headers
     except urllib.error.HTTPError as exc:
         if exc.headers["Content-Type"] == "application/problem+json":
             raise problem.read(exc, url=url)
@@ -382,12 +389,13 @@ class jwkauth(object):
         self.key = key
 
     def authdata(self):
-        return {"jwk": {"kty": "RSA", "e": ebignum(self.key.e), "n": ebignum(self.key.n)}}
+        pub = self.key.public_key().public_numbers()
+        return {"jwk": {"kty": "RSA", "e": ebignum(pub.e), "n": ebignum(pub.n)}}
 
     def sign(self, data):
-        dig = Crypto.Hash.SHA256.new()
-        dig.update(data)
-        return Crypto.Signature.PKCS1_v1_5.new(self.key).sign(dig)
+        from cryptography.hazmat.primitives import hashes
+        from cryptography.hazmat.primitives.asymmetric import padding
+        return self.key.sign(data, padding.PKCS1v15(), hashes.SHA256())
 
 class account(object):
     def __init__(self, uri, key):
@@ -398,9 +406,9 @@ class account(object):
         return {"kid": self.uri}
 
     def sign(self, data):
-        dig = Crypto.Hash.SHA256.new()
-        dig.update(data)
-        return Crypto.Signature.PKCS1_v1_5.new(self.key).sign(dig)
+        from cryptography.hazmat.primitives import hashes
+        from cryptography.hazmat.primitives.asymmetric import padding
+        return self.key.sign(data, padding.PKCS1v15(), hashes.SHA256())
 
     def getinfo(self):
         data, headers = jreq(self.uri, None, self)
@@ -412,16 +420,22 @@ class account(object):
             raise Exception("account is not valid: %s" % (data.get("status", "\"\"")))
 
     def write(self, out):
+        from cryptography.hazmat.primitives import serialization
         out.write("%s\n" % (self.uri,))
-        out.write("%s\n" % (self.key.exportKey().decode("us-ascii"),))
+        out.write("%s\n" % (self.key.private_bytes(
+            encoding=serialization.Encoding.PEM,
+            format=serialization.PrivateFormat.TraditionalOpenSSL,
+            encryption_algorithm=serialization.NoEncryption()
+        ).decode("us-ascii"),))
 
     @classmethod
     def read(cls, fp):
+        from cryptography.hazmat.primitives import serialization
         uri = fp.readline()
         if uri == "":
             raise Exception("missing account URI")
         uri = uri.strip()
-        key = Crypto.PublicKey.RSA.importKey(fp.read())
+        key = serialization.load_pem_private_key(fp.read().encode("us-ascii"), password=None, backend=cryptobke())
         return cls(uri, key)
 
 ### ACME protocol
@@ -432,11 +446,12 @@ def directory():
     global _directory
     if _directory is None:
         with req(service) as resp:
-            _directory = json.load(resp)
+            _directory = json.loads(resp.read().decode("utf-8"))
     return _directory
 
 def register(keysize=4096):
-    key = Crypto.PublicKey.RSA.generate(keysize, Crypto.Random.new().read)
+    from cryptography.hazmat.primitives.asymmetric import rsa
+    key = rsa.generate_private_key(public_exponent=65537, key_size=keysize, backend=cryptobke())
     data, headers = jreq(directory()["newAccount"], {"termsOfServiceAgreed": True}, jwkauth(key))
     return account(headers["Location"], key)
     
@@ -446,10 +461,12 @@ def mkorder(acct, csr):
     return data
 
 def httptoken(acct, ch):
-    jwk = {"kty": "RSA", "e": ebignum(acct.key.e), "n": ebignum(acct.key.n)}
-    dig = Crypto.Hash.SHA256.new()
+    from cryptography.hazmat.primitives import hashes
+    pub = acct.key.public_key().public_numbers()
+    jwk = {"kty": "RSA", "e": ebignum(pub.e), "n": ebignum(pub.n)}
+    dig = hashes.Hash(hashes.SHA256(), backend=cryptobke())
     dig.update(json.dumps(jwk, separators=(',', ':'), sort_keys=True).encode("us-ascii"))
-    khash = base64url(dig.digest())
+    khash = base64url(dig.finalize())
     return ch["token"], ("%s.%s" % (ch["token"], khash))
 
 def finalize(acct, csr, orderid):