Parse error reading JWS

I'm running a client with the following request and I was wondering if someone had an idea why this fails with error 400 please ?
code *************
header = {
"alg": "ES256",
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": base64.urlsafe_b64encode(public_key.public_numbers().x.to_bytes(length=32, byteorder="big")).decode().rstrip("="),
"y": base64.urlsafe_b64encode(public_key.public_numbers().y.to_bytes(length=32, byteorder="big")).decode().rstrip("="),
},
"nonce": res_head.headers.get('Replay-Nonce'),
"url": res_get.json()["newAccount"]
}

payload = {
"termsOfServiceAgreed": "true",
"contact": [
"mailto:test@gmail.com"
]
}

protected_header = base64_helper(json.dumps(header,indent=4))

encoded_payload = base64_helper(json.dumps(payload,indent=4))

msg = f"{protected_header}.{encoded_payload}".encode('ascii')

signature = base64.urlsafe_b64encode(private_key.sign(msg,ec.ECDSA(hashes.SHA256()))).decode("ascii").rstrip("=").replace("-","+").replace("_","/")

final_payload = {
"protected": protected_header,
"payload": encoded_payload,
"signature": signature
}

url = res_get.json()["newAccount"]
headers = {'Content-type': 'application/jose+json'} #'Accept': 'text/plain'
res_post = requests.post(url,verify=False, data=final_payload, headers=headers)

def base64_helper(data):
if(isinstance(data,dict)):
data = json.dumps(data,indent=4)
return base64.urlsafe_b64encode(data.encode('UTF-8')).decode('ascii').rstrip("=")

result**************
{
"alg": "ES256",
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": "5rFwxh1FppncQC1sHBK2jR1DZ2-ZXxFA1x5778zvhfU",
"y": "fGN0R0uCb6k9c3oCyqrkwVe7biYlWlT9ELPvHFyVSCM"
},
"nonce": "2ekqnNv4lxhO_EiwbZEVLA",
"url": "https://127.0.0.1:14000/sign-me-up"
}
{
"termsOfServiceAgreed": "true",
"contact": [
"mailto:test@gmail.com"
]
}
{
"protected": "ewogICAgImFsZyI6ICJFUzI1NiIsCiAgICAiandrIjogewogICAgICAgICJjcnYiOiAiUC0yNTYiLAogICAgICAgICJrdHkiOiAiRUMiLAogICAgICAgICJ4IjogIjVyRnd4aDFGcHBuY1FDMXNIQksyalIxRFoyLVpYeEZBMXg1Nzc4enZoZlUiLAogICAgICAgICJ5IjogImZHTjBSMHVDYjZrOWMzb0N5cXJrd1ZlN2JpWWxXbFQ5RUxQdkhGeVZTQ00iCiAgICB9LAogICAgIm5vbmNlIjogIjJla3FuTnY0bHhoT19FaXdiWkVWTEEiLAogICAgInVybCI6ICJodHRwczovLzEyNy4wLjAuMToxNDAwMC9zaWduLW1lLXVwIgp9",
"payload": "ewogICAgInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjogInRydWUiLAogICAgImNvbnRhY3QiOiBbCiAgICAgICAgIm1haWx0bzpyb3kuc2hvaGFtOTVAZ21haWwuY29tIgogICAgXQp9",
"signature": "MEUCIQDTW+67a+5Cw4GRhLamDzdTIWsEZfMcK97JBt/H+PgI7gIgYaarNYCZqpSn3NRF3edl1lpsLKZ+/bVg60voQOomg0c"
}

b'-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5rFwxh1FppncQC1sHBK2jR1DZ2+Z\nXxFA1x5778zvhfV8Y3RHS4JvqT1zegLKquTBV7tuJiVaVP0Qs+8cXJVIIw==\n-----END PUBLIC KEY-----\n'

1 Like

That signature is not a valid base64url encoding.

Why here and not for protected and payload?

3 Likes

Aren't those replacements backwards?

Here's my own base-64 encoder from PHP:

strtr(rtrim(base64_encode($binarystring), '='), '+/', '-_');

+ -> -
/ -> _

1 Like

So should I delete it from the signature? or should I add it to protected and payload?
Why isn't it a valid encoding?

1 Like

I think you should probably delete those replace calls entirely, because base64.urlsafe_b64* do it for you?

>>> import base64
>>> base64.urlsafe_b64decode('MEUCIQDTW+67a+5Cw4GRhLamDzdTIWsEZfMcK97JBt/H+PgI7gIgYaarNYCZqpSn3NRF3edl1lpsLKZ+/bVg60voQOomg0c')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/base64.py", line 133, in urlsafe_b64decode
    return b64decode(s)
  File "/usr/lib/python3.8/base64.py", line 87, in b64decode
    return binascii.a2b_base64(s)
binascii.Error: Incorrect padding
1 Like

I think you are right, usually it should

but now I deleted it and the json looks like that :
"protected": "ewogICAgImFsZyI6ICJFUzI1NiIsCiAgICAibm9uY2UiOiAidWwwYVY4YUhuYkpBSTRFcFNJZzJKQSIsCiAgICAidXJsIjogImh0dHBzOi8vMTI3LjAuMC4xOjE0MDAwL3NpZ24tbWUtdXAiLAogICAgImp3ayI6IHsKICAgICAgICAiY3J2IjogIlAtMjU2IiwKICAgICAgICAia3R5IjogIkVDIiwKICAgICAgICAieCI6ICJ1cXNwRVpmTHZQQUxpNmc2dll6ZTQ1dXJSdk45d3RzN240b0thNkd3emNnIiwKICAgICAgICAieSI6ICJubHE2eXVaSElhRWpPbGlZb3MxLU12a0puOWhhalFEMFI1UHNYajFVendNIgogICAgfQp9",
"payload": "ewogICAgInRlcm1zT2ZTZXJ2aWNlQWdyZWVkIjogdHJ1ZSwKICAgICJjb250YWN0IjogWwogICAgICAgICJtYWlsdG86cm95LnNob2hhbTk1QGdtYWlsLmNvbSIKICAgIF0KfQ",
"signature": "MEUCIQDDFLA-VXXMemrCrEiiXYYAtqSUQwA8PqiUmcS41X0S_QIgTQ2AZCWCB-PRFkUp_XnqSsIJbPJKvlBSFfdihujV6OY"'

So it has the unwanted symbols

1 Like

You want - and _.

2 Likes

That JWS parses correctly.

3 Likes

Alright, that's good to know :slight_smile: Thanks !
However I still get the 400 error even though it seems fine, any ideas? I really don't know what to do with it, the x and y values are fine, the Json is fine and still I can't submit a newAccount request

1 Like

Oh wait now I get
"detail": "JWS verification error: square/go-jose: error in cryptographic primitive",

1 Like

As _az mentioned, your protected and payload need to be base64 encoded.

1 Like

with header = {
"alg": "ES256",
"nonce": res_head.headers.get('Replay-Nonce'),
"url": res_get.json()["newAccount"],
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": base64.urlsafe_b64encode(public_key.public_numbers().x.to_bytes(length=32, byteorder="big")).decode('utf-8'),
"y": base64.urlsafe_b64encode(public_key.public_numbers().y.to_bytes(length=32, byteorder="big")).decode('utf-8'),
},

} 

I'm still at
"detail": "Parse error reading JWS",
"status": 400

But they are

protected_header = base64.urlsafe_b64encode(json.dumps(header,indent=4).encode('utf-8')).decode('utf-8')

encoded_payload = base64.urlsafe_b64encode(json.dumps(payload,indent=4).encode('utf-8')).decode('utf-8')

msg = f"{protected_header}.{encoded_payload}".rstrip("=").encode('utf-8')

signature = base64.urlsafe_b64encode(private_key.sign(msg,ec.ECDSA(hashes.SHA256()))).decode("utf-8")

final_payload = {
"protected": protected_header,
"payload": encoded_payload,
"signature": signature
}

print(json.dumps(final_payload,indent=4,cls=MyEncoder))
url = res_get.json()["newAccount"]
headers = {'Content-type': 'application/jose+json'} #'Accept': 'text/plain'
res_post = requests.post(url,verify=False, data=json.dumps(final_payload,indent=4), headers=headers)
print(res_post.content.decode("utf-8"))

1 Like

EllipticCurvePrivateKey.sign produces a DER-encoded signature: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign

You need to get the two (r,s) integers out of it and pack them together, then base64url encode that.

https://tools.ietf.org/html/rfc7515#appendix-A.3.1 describes the process, but the cryptography link above mentions a function that can do this for you.

4 Likes

Thank you !! It works now :slight_smile:

4 Likes