Repository: certbot/certbot

Issue:
jwk thumbprint is mismade
The JWK.thumbprint method isn't quite right. 

I think the problem is that the `json.dumps` the thumbprint method uses can mis-order the keys in the JSON hash. There is an order that [RFC7638](https://tools.ietf.org/html/rfc7638#section-3.3) says the keys must be in. The go-jose code does that with this line of code: https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk.go#L155

Going off the go-jose test vectors, this RSA public key JWK: 

```
{
    "kty": "RSA",
    "kid": "bilbo.baggins@hobbiton.example",
    "use": "sig",
    "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
 "e": "AQAB"
}
```

must be thumbprinted into `f63838e96077ad1fc01c3f8405774dedc0641f558ebb4b40dccf5f9b6d66a932`.

In case I fat-fingered the key or its hash, here they are in the go-jose tests: https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk_test.go#L331-L344 and https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk_test.go#L384



Discussion:
I believe this should be a launch ticket.

Also, here's the test I used. The _strip_whitespace method is just the trivial regex substitution for `\s+`.

```
def test_like_go_jose(self):
    # RSA public key
    jsonKey = _strip_whitespace("""{
    "kty": "RSA",
    "kid": "bilbo.baggins@hobbiton.example",
    "use": "sig",
    "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT
    -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV
    wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-
oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde
    3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC
    LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g
    HdrNP5zw",
 "e": "AQAB"
}""")

    key = JWKRSA.load(jsonKey)
    import binascii
    hex_tp = binascii.hexlify(key.thumbprint())
    self.assertEqual("f63838e96077ad1fc01c3f8405774dedc0641f558ebb4b40dccf5f9b6d66a932", hex_tp)
```

In case I screwed up _strip_whitespace, here it is:

```
whitespace_regex = re.compile(r'\s+')
def _strip_whitespace(str):
    return re.sub(whitespace_regex, '', str)
```

Okay, I got the go ahead to put this in the launch milestone. It's being added to the launch milestone on the thought that we are only going to use the new challenges for the GA launch. If we aren't, the ticket will come back out.

+1 to having this in launch.

The way I've seen every other implementation implement thumbprints is to do template filling, since JSON libraries don't give you the control you need.  See, e.g., the [go-jose implementation](https://github.com/square/go-jose/blob/master/jwk.go#L140).

Indeed, we hash the following:

```
{
"e":"AQAB",
"kty":"RSA",
"n":"m2Fylv-Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEk"
}
```

Obviously, we need to get rid of the newlines.


Resolving patch:
diff --git a/acme/acme/jose/jwk.py b/acme/acme/jose/jwk.py
--- a/acme/acme/jose/jwk.py
+++ b/acme/acme/jose/jwk.py
@@ -35,7 +35,7 @@ class JWK(json_util.TypedJSONObjectWithFields):
     _thumbprint_json_dumps_params = {
         # "no whitespace or line breaks before or after any syntactic
         # elements"
-        'indent': 0,
+        'indent': None,
         'separators': (',', ':'),
         # "members ordered lexicographically by the Unicode [UNICODE]
         # code points of the member names"


Test patch:
diff --git a/acme/acme/jose/jwk_test.py b/acme/acme/jose/jwk_test.py
--- a/acme/acme/jose/jwk_test.py
+++ b/acme/acme/jose/jwk_test.py
@@ -1,4 +1,5 @@
 """Tests for acme.jose.jwk."""
+import binascii
 import unittest
 
 from acme import test_util
@@ -40,8 +41,9 @@ def test_thumbprint_public(self):
 class JWKOctTest(unittest.TestCase, JWKTestBaseMixin):
     """Tests for acme.jose.jwk.JWKOct."""
 
-    thumbprint = (b"=,\xdd;I\x1a+i\x02x\x8a\x12?06IM\xc2\x80"
-                  b"\xe4\xc3\x1a\xfc\x89\xf3)'\xce\xccm\xfd5")
+    thumbprint = (b"\xf3\xe7\xbe\xa8`\xd2\xdap\xe9}\x9c\xce>"
+                  b"\xd0\xfcI\xbe\xcd\x92'\xd4o\x0e\xf41\xea"
+                  b"\x8e(\x8a\xb2i\x1c")
 
     def setUp(self):
         from acme.jose.jwk import JWKOct
@@ -71,8 +73,8 @@ class JWKRSATest(unittest.TestCase, JWKTestBaseMixin):
     """Tests for acme.jose.jwk.JWKRSA."""
     # pylint: disable=too-many-instance-attributes
 
-    thumbprint = (b'\x08\xfa1\x87\x1d\x9b6H/*\x1eW\xc2\xe3\xf6P'
-                  b'\xefs\x0cKB\x87\xcf\x85yO\x045\x0e\x91\x80\x0b')
+    thumbprint = (b'\x83K\xdc#3\x98\xca\x98\xed\xcb\x80\x80<\x0c'
+                  b'\xf0\x95\xb9H\xb2*l\xbd$\xe5&|O\x91\xd4 \xb0Y')
 
     def setUp(self):
         from acme.jose.jwk import JWKRSA
@@ -168,6 +170,22 @@ def test_from_json_non_schema_errors(self):
         self.assertRaises(errors.DeserializationError, JWK.from_json,
                           {'kty': 'RSA', 'e': 'AQAB', 'n': '1'})
 
+    def test_thumbprint_go_jose(self):
+        # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk.go#L155
+        # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk_test.go#L331-L344
+        # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk_test.go#L384
+        from acme.jose.jwk import JWKRSA
+        key = JWKRSA.json_loads("""{
+    "kty": "RSA",
+    "kid": "bilbo.baggins@hobbiton.example",
+    "use": "sig",
+    "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
+    "e": "AQAB"
+}""")
+        self.assertEqual(
+            binascii.hexlify(key.thumbprint()),
+            b"f63838e96077ad1fc01c3f8405774dedc0641f558ebb4b40dccf5f9b6d66a932")
+
 
 if __name__ == '__main__':
     unittest.main()  # pragma: no cover