"Invalid status value " error msg POST-as-GET action

I want to GET info about auth object.

POST to https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/172480296

but get response:

{u'status': 400, u'type': u'urn:ietf:params:acme:error:malformed', u'detail': u'Invalid status value'}

here is req:

protected: {"alg": "ES256", "kid": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/17092284", "nonce": "0003QE4ai_1cEssEm4VZ6z6MSrdbAPl4lDSkQgHv72jEK0k", "url": "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/172480296"}
payload: {}

what does status mean in this situation?

AND

How to use POST-as-GET properly ?

1 Like

payload should be an empty (zero-length) string: https://tools.ietf.org/html/rfc8555#section-6.3

1 Like

IF payload is empty.

how to make signature and JWT ?

as far as I know the format is "protected_header.payload.signature"

1 Like

only use "protected_header."

2 Likes

Even without signature ?

1 Like

You sign "protected_header."

The payload part of the json object will be an empty string.

3 Likes
# -*- coding: utf-8 -*-

auth_url = "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/172480296"

account_url = "https://acme-staging-v02.api.letsencrypt.org/acme/acct/17092284"


key_dict = {
    "y": "...",
    "x": "...",
    "crv": "P-256",
    "kty": "EC",
    "d": "..."
}


import requests
import json
from jwcrypto import jwk, jws
from pprint import pprint as pp


# Get a nonce
resp = requests.head('https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce')
pp(resp.headers)
replay_nonce = resp.headers['Replay-Nonce']
print('Nonce is: {}'.format(replay_nonce))


key = jwk.JWK.from_json(json.dumps(key_dict))




# Create the protected and payload structures
protected_header = {
    "alg": "ES256",
    "nonce": replay_nonce,
    "url": auth_url,
    # "jwk": key.export(private_key=False, as_dict=True)
    "kid": account_url
}

# payload = ""

payload = None

protected_enc = json.dumps(protected_header, sort_keys=True)
payload_enc = json.dumps(payload)


print("protected: {}".format(protected_enc))
print("payload: {}".format(payload_enc))


# Sign the JWS
jwstoken = jws.JWS(payload=payload_enc)
# jwstoken.detach_payload()
jwstoken.add_signature(
    key,
    'ES256',
    protected=protected_enc,
    # header=header,
    # json_encode({"kid": key.thumbprint()})
)



jws_enc = jwstoken.serialize()

print('=======req=======')
pp(jws_enc)


# Send the JWS to the ACME server
session = requests.Session()
session.headers.update({'Content-Type': 'application/jose+json'})
resp = session.post(
    url=auth_url,
    data=jws_enc,
)

print('========response========')
print(resp.headers)
print(resp.json())

response:

{'Content-Length': '105', 'Cache-Control': 'public, max-age=0, no-cache', 'Server': 'nginx', 'Connection': 'keep-alive', 'Link': 'https://acme-staging-v02.api.letsencrypt.org/directory;rel="index"', 'Boulder-Requester': '17092284', 'Date': 'Tue, 15 Dec 2020 05:16:04 GMT', 'Content-Type': 'application/problem+json', 'Replay-Nonce': '0003PiFeJx5C-dhiU90uXsb5460HCjMWADFKuO5wjdKBgTg'}
{u'status': 400, u'type': u'urn:ietf:params:acme:error:malformed', u'detail': u'Invalid status value'}

@_az

It seems that jwcrytpo has bug in this scenario.

Just do:

payload_enc = ""

JSON-encoding None or "" is going to result in something other than a zero-length string, which is no longer POST-as-GET.

1 Like

then jwcrypto raise exception like this:

    raise InvalidJWSObject('Missing Payload')
payload: 
jwcrypto.jws.InvalidJWSObject: Invalid JWS Object [Missing Payload]

Ah, yeah.

The problem is that an empty string is not a "truthy" value in Python, so the library raises an error.

I agree with you that that is probably a bug in jwcrypto. There's no requirement in JWS standard that it cannot be an empty value, all it says is "arbitrary sequence of octets".

Other libraries I've checked all appear to support blank payloads, so I'd suggest filing an issue against jwcrypto.

2 Likes

Could you give some example here ? which package do well with this ?

I was looking at libraries in other languages (like go-jose), so they might not be a great help to you.

I know of two Python libraries which can sign empty payloads:

from jose import jws
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend

account_key = ec.generate_private_key(curve=ec.SECP256R1, backend=default_backend())

payload = ""
protected = {"url": "http://xyz"}

signed_parts = jws.sign(
    payload.encode("utf-8"),
    account_key,
    headers=protected,
    algorithm=jws.ALGORITHMS.ES256,
).split(".")

signed = {
    "protected": signed_parts[0],
    "payload": signed_parts[1],
    "signature": signed_parts[2],
}

print(signed)
2 Likes

OK, thank you. I will try it now.

I think you must a PRO.

Does cryptography have any method to load a existed KEY ?

for example:

{
    "y": "...",
    "x": "...",
    "crv": "P-256",
    "kty": "EC",
    "d": "..."
}

cryptography doesn't know about JWKs. This functionality would need to come from the jose library.

If you have an account key in dictionary format like:

account_key = json.loads(some_file.read())

then you can pass that directly to jws.sign.


# -*- coding: utf-8 -*-

auth_url = "https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/172480296"

account_url = "https://acme-staging-v02.api.letsencrypt.org/acme/acct/17092284"


key_dict = {
    "y": "...",
    "x": "...",
    "crv": "P-256",
    "kty": "EC",
    "d": "..."
}

import requests
import json

from jose import jws

from pprint import pprint as pp


# Get a nonce
resp = requests.head('https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce')
pp(resp.headers)
replay_nonce = resp.headers['Replay-Nonce']
print('Nonce is: {}'.format(replay_nonce))


# Generate a key
# Create the protected and payload structures
protected_header = {
    "alg": "ES256",
    "nonce": replay_nonce,
    "url": auth_url,
    # "jwk": key.export(private_key=False, as_dict=True)
    "kid": account_url
}

payload = ""

protected_enc = json.dumps(protected_header, sort_keys=True)
payload_enc = ""

print("protected: {}".format(protected_enc))
print("payload: {}".format(payload_enc))

# Sign the JWS
signed_parts = jws.sign(
    payload.encode("utf-8"),
    key_dict,
    headers=protected_header,
    algorithm=jws.ALGORITHMS.ES256,
).split(".")

signed = {
    "protected": signed_parts[0],
    "payload": signed_parts[1],
    "signature": signed_parts[2],
}

print('=======req=======')
pp(signed)


# Send the JWS to the ACME server
session = requests.Session()
session.headers.update({'Content-Type': 'application/jose+json'})
resp = session.post(
    url=auth_url,
    data=signed,
)

print('========response========')
print(resp.headers)
pp(resp.json())

OUTPUT:

=======req=======
{'payload': u'',
'protected': u'eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvYWNjdC8xNzA5MjI4NCIsIm5vbmNlIjoiMDAwMzRqZXptdEtSN1N5akF0Q1psNXd0RDlKd1pkZ1V0dXZ0czl2WWNMdXpXejAiLCJ0eXAiOiJKV1QiLCJ1cmwiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2F1dGh6LXYzLzE3MjQ4MDI5NiJ9',
'signature': u'9N-E9aMsH2gyNcjUkyYT52CWphyjHDO2_BJr-Twduo3UVjhPko9F_RSTixN74mrnKWoCdByhxQvY4NUnMWtaBw'}
========response========
{'Content-Length': '108', 'Server': 'nginx', 'Connection': 'keep-alive', 'Link': 'https://acme-staging-v02.api.letsencrypt.org/directory;rel="index"', 'Cache-Control': 'public, max-age=0, no-cache', 'Date': 'Tue, 15 Dec 2020 06:51:41 GMT', 'Content-Type': 'application/problem+json', 'Replay-Nonce': '0003JyPk5Js9L2c1tH4Hwq0BAOUb_BFStbocQ1agEil2kgI'}
{u'detail': u'Parse error reading JWS',
u'status': 400,
u'type': u'urn:ietf:params:acme:error:malformed'}

My internet is having a meltdown so I can't really test your code, but you will probably need to change data=signed to json=signed, since the JWS library you are using no longer returns a JSON-encoded string.f

2 Likes

Oh, yes. It works.

My mistake of Data Format.

Or you can just write your own library, like I did.

:grin: