@@ -45,17 +45,68 @@ def sign(payload, key, headers=None, algorithm=ALGORITHMS.HS256):
4545 return signed_output
4646
4747
48- def verify (token , key , algorithms , verify = True ):
48+ def sign_detached (payload , key , headers = None , algorithm = ALGORITHMS .HS256 ):
49+ """Signs a claims set and returns a JWS as a detached payload string, as per RFC7797
50+
51+ Args:
52+ payload (str or dict): A string to sign
53+ key (str or dict): The key to use for signing the claim set. Can be
54+ individual JWK or JWK set.
55+ headers (dict, optional): A set of headers that will be added to
56+ the default headers. Any headers that are added as additional
57+ headers will override the default headers.
58+ if the signature needs to be generated on encoded payload, then
59+ header has to contain {"b64":True}
60+ algorithm (str, optional): The algorithm to use for signing the
61+ the claims. Defaults to HS256.
62+
63+ Returns:
64+ str: The string representation of the header, and signature in detached jws format
65+ payload: the payload as received in the request or encoed if {"b4":True} header is passed in the call
66+
67+ Raises:
68+ JWSError: If there is an error signing the token.
69+
70+ Examples:
71+
72+ >>> jws.sign_detached({'a': 'b'}, 'secret', algorithm='HS256')
73+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8', {'a': 'b'}
74+
75+
76+ >>> jws.sign_detached({'a': 'b'}, 'secret', {"b64": True}, algorithm='HS256')
77+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8', eyJhIjoiYiJ9
78+
79+ """
80+
81+ if algorithm not in ALGORITHMS .SUPPORTED :
82+ raise JWSError ("Algorithm %s not supported." % algorithm )
83+
84+ if headers :
85+ if "b64" in headers and headers ["b64" ] is True :
86+ payload = _encode_payload (payload )
87+ headers .update ({"crit" : ["b64" ]})
88+ else :
89+ headers = {"b64" : "false" }
90+
91+ encoded_header = _encode_header (algorithm , additional_headers = headers )
92+ signed_output = _sign_header_and_claims (encoded_header , payload , algorithm , key , True )
93+
94+ return signed_output , payload
95+
96+
97+ def verify (token , key , algorithms = None , verify = True , payload = None ):
4998 """Verifies a JWS string's signature.
5099
51100 Args:
52101 token (str): A signed JWS to be verified.
53102 key (str or dict): A key to attempt to verify the payload with. Can be
54103 individual JWK or JWK set.
55104 algorithms (str or list): Valid algorithms that should be used to verify the JWS.
105+ payload (str or dict): Unencoded payload if the token is a detached jws
56106
57107 Returns:
58108 str: The str representation of the payload, assuming the signature is valid.
109+ If the token is a detached jws with "b64" true in the header, the return value will be encoded payload
59110
60111 Raises:
61112 JWSError: If there is an exception verifying a token.
@@ -65,9 +116,12 @@ def verify(token, key, algorithms, verify=True):
65116 >>> token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8'
66117 >>> jws.verify(token, 'secret', algorithms='HS256')
67118
119+ >>> token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8'
120+ >>> jws.verify(token, 'secret', algorithms='HS256', payload={"a":"b"})
121+
68122 """
69123
70- header , payload , signing_input , signature = _load (token )
124+ header , payload , signing_input , signature = _load (token , payload )
71125
72126 if verify :
73127 _verify_signature (signing_input , header , signature , key , algorithms )
@@ -126,7 +180,7 @@ def get_unverified_claims(token):
126180
127181
128182def _encode_header (algorithm , additional_headers = None ):
129- header = {"typ" : "JWT " , "alg" : algorithm }
183+ header = {"typ" : "JOSE " , "alg" : algorithm }
130184
131185 if additional_headers :
132186 header .update (additional_headers )
@@ -153,7 +207,7 @@ def _encode_payload(payload):
153207 return base64url_encode (payload )
154208
155209
156- def _sign_header_and_claims (encoded_header , encoded_claims , algorithm , key ):
210+ def _sign_header_and_claims (encoded_header , encoded_claims , algorithm , key , is_detached = False ):
157211 signing_input = b"." .join ([encoded_header , encoded_claims ])
158212 try :
159213 if not isinstance (key , Key ):
@@ -164,12 +218,15 @@ def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key):
164218
165219 encoded_signature = base64url_encode (signature )
166220
167- encoded_string = b"." .join ([encoded_header , encoded_claims , encoded_signature ])
221+ if is_detached :
222+ encoded_string = b".." .join ([encoded_header , encoded_signature ])
223+ else :
224+ encoded_string = b"." .join ([encoded_header , encoded_claims , encoded_signature ])
168225
169226 return encoded_string .decode ("utf-8" )
170227
171228
172- def _load (jwt ):
229+ def _load (jwt , payload = None ):
173230 if isinstance (jwt , str ):
174231 jwt = jwt .encode ("utf-8" )
175232 try :
@@ -189,10 +246,15 @@ def _load(jwt):
189246 if not isinstance (header , Mapping ):
190247 raise JWSError ("Invalid header string: must be a json object" )
191248
192- try :
193- payload = base64url_decode (claims_segment )
194- except (TypeError , binascii .Error ):
195- raise JWSError ("Invalid payload padding" )
249+ if not payload :
250+ try :
251+ payload = base64url_decode (claims_segment )
252+ except (TypeError , binascii .Error ):
253+ raise JWSError ("Invalid payload padding" )
254+ else :
255+ if "b64" in header and header ["b64" ] is True :
256+ payload = _encode_payload (payload )
257+ signing_input = b"" .join ([signing_input , payload ])
196258
197259 try :
198260 signature = base64url_decode (crypto_segment )
0 commit comments