Skip to content

Commit

Permalink
Added support for JWP
Browse files Browse the repository at this point in the history
  • Loading branch information
Richard Barnes committed Aug 24, 2013
1 parent c30c9c2 commit 5ad79d8
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 8 deletions.
6 changes: 4 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

In no particular order:

* Support "jku" / "x5u"
* Support "JWP" (or something for unsigned content)
* Support "jku" / "x5u" (?)
* Support "zip" in JWS (after signature)
* Present key used for verification in result
* Refine crypto API to complete analogy to WebCrypto (with intelligent defaults)


# DONE
Expand All @@ -18,3 +19,4 @@ In no particular order:
* Add tests for "jwk" and "x5c"
* Change unserialized format from base64 to binary
* Add Msgpack serialization
* Support "JWP" (or something for unsigned content)
122 changes: 122 additions & 0 deletions jose/jwp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env python

"""
JSON Web Payload
JWP is a representation of a payload and header, but with no cryptographic
protection. Thus, the main value added by JWP is in:
- Common high-level structure with JWP and JWE
- Compression with the "zip" header
- Criticality with the "crit" header
Since there's no cryptography going on, it's not as clear for JWP what the
relevant verbs are for creation and parsing of objects. So we use the
verbs "bundle" and "unbundle", respectively.
"""

import json
import zlib
from copy import copy
from util import *
from validate import *
import serialize

supported_alg = [
"none"
]
"""
A list of "alg" values for JWP supported by this implementation
"""

supported_hdr_ext = []
"""
A list of supported header extensions. Currently empty because
we don't support any.
"""

def bundle(header, payload):
"""
Construct a JWP to encode the header and payload.
Also performs compression if required by "zip", and verifies that
the "crit" header is well-formed.
@type header : dict
@param header : Dictionary of JWP header parameters
@type payload: byte string
@param payload: The payload to be signed
@rtype: dict
@return: Unserialized JWP object
"""
# TODO validate inputs

# Capture the payload and header
JWPHeader = copy(header)
JWPPayload = copy(payload)

# Verify that if "alg" is present, it is set to "none"
if "alg" not in JWPHeader:
JWPHeader["alg"] == "none"
if JWPHeader["alg"] != "none":
raise Exception("'alg' value in JWP header must be 'none'")

# Check that critical header is sensible, if present
if not compliantCrit(JWPHeader):
raise Exception("'crit' parameter contains unsuitable fields")

# Perform compression if required
if "zip" in JWPHeader and JWPHeader["zip"] == "DEF":
JWPPayload = zlib.compress(JWPPayload)

# Assemble and return the object
JWP = {
"unprotected": JWPHeader,
"payload": JWPPayload
}
return JWP

def unbundle(JWP):
"""
Unbundle a JWP object
Also performs decompression if required by "zip", and verification of
support for fields in the "crit" header.
@type JWP : dict
@param JWP : Unserialized JWP object
@rtype: dict
@return: Verification results, including the boolean result and, if succesful,
the signed header parameters and payload
"""

# Deserialize if necessary
if not isJOSE(JWP):
raise Exception("Cannot process something that's not a JOSE object")
elif isJOSE_serialized(JWP):
JWP = serialize.deserialize(JWP)

# Make sure we have a JWP
if not isJWP_unserialized(JWP):
raise Exception("decrypt() called with something other than a JWP")

# Capture the payload and header
JWPHeader = JWP["unprotected"]
JWPPayload = JWP["payload"]

# Verify that if "alg" is present, it is set to "none"
if "alg" not in JWPHeader or JWPHeader["alg"] != "none":
raise Exception("'alg' value in JWP header must be 'none'")

# Check that we support everything critical
if not criticalParamsSupported(JWPHeader, supported_hdr_ext):
raise Exception("Unsupported critical fields")

# Decompress if required
if "zip" in JWPHeader and JWPHeader["zip"] == "DEF":
JWPPayload = zlib.decompress(JWPPayload)

# Return the verified payload and headers
return {
"unprotected": JWPHeader,
"payload": JWPPayload
}
13 changes: 11 additions & 2 deletions jose/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import msgpack
from copy import copy
from validate.unserialized import \
isJOSE_unserialized, isJWS_unserialized, isJWE_unserialized
isJOSE_unserialized, isJWS_unserialized, isJWE_unserialized, isJWP_unserialized
from util import b64enc, b64dec


Expand Down Expand Up @@ -123,6 +123,10 @@ def serialize_compact(jose):
"""
if not isJOSE_unserialized(jose):
raise Exception("Can't serialize something that's not unserialized JOSE")
elif isJWP_unserialized(jose):
unprotected = b64enc(json.dumps(jose["unprotected"]))
payload = b64enc(jose["payload"])
return ".".join([unprotected,payload])
elif isJWS_unserialized(jose) and "unprotected" not in jose:
return ".".join([ \
b64enc(jose["protected"]), \
Expand All @@ -149,7 +153,12 @@ def deserialize_compact(x):
@return: The unserialized form of the input
"""
components = x.split(".")
if len(components) == 3:
if len(components) == 2:
jose = {
"unprotected": json.loads(b64dec(components[0])),
"payload": b64dec(components[1])
}
elif len(components) == 3:
jose = {
"protected": b64dec(components[0]),
"payload": b64dec(components[1]),
Expand Down
48 changes: 48 additions & 0 deletions jose/test/jwp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python

import jose.jwp
from jose.serialize import serialize_compact

# This should pass, with compression

header = {"alg":"none", "zip": "DEF"}
payload = "a" * 512

p1 = jose.jwp.bundle(header, payload)
c1 = serialize_compact(p1)
u1 = jose.jwp.unbundle(c1)
print c1
print u1
print


# This should fail (bad "alg")

header = {"alg": "RS256"}
payload = "payload"

try:
p2 = jose.jwp.bundle(header, payload)
c2 = serialize_compact(p2)
u2 = jose.jwp.unbundle(c2)
print c2
print u2
except Exception as e:
print e
print


# This should fail (bad "crit")
header = {"alg":"none", "crit": ["ebert"]}
payload = "payload"

try:
p3 = jose.jwp.bundle(header, payload)
c3 = serialize_compact(p3)
u3 = jose.jwp.unbundle(c3)
print c3
print u3
except Exception as e:
print e
print

31 changes: 28 additions & 3 deletions jose/validate/serialized.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@

### JSON-serialized

def isJWP_JSON(x):
"""
Test whether input is a JWP-JSON object
@rtype: boolean
"""
try: return isJWP_unserialized(deserialize_JSON(x))
except: return False


def isJWS_JSON_single(x):
"""
Test whether input is a single-signature JWS-JSON object
Expand Down Expand Up @@ -64,10 +73,18 @@ def isJOSE_JSON(x):
Test whether input is a JOSE-JSON object
@rtype: boolean
"""
return isJWS_JSON(x) or isJWE_JSON(x)
return isJWS_JSON(x) or isJWE_JSON(x) or isJWP_JSON(x)

### Compact-serialized

def isJWP_compact(x):
"""
Test whether input is a JWP-compact object
@rtype: boolean
"""
try: return isJWP_unserialized(deserialize_compact(x))
except: return False

def isJWS_compact(x):
"""
Test whether input is a JWS-compact object
Expand All @@ -89,11 +106,19 @@ def isJOSE_compact(x):
Test whether input is a JOSE-compact object
@rtype: boolean
"""
return isJWS_compact(x) or isJWE_compact(x)
return isJWS_compact(x) or isJWE_compact(x) or isJWP_compact(x)


### msgpack-serialized

def isJWP_msgpack(x):
"""
Test whether input is a JWP-msgpack object
@rtype: boolean
"""
try: return isJWP_unserialized(deserialize_msgpack(x))
except: return False

def isJWS_msgpack_single(x):
"""
Test whether input is a single-signature JWS-msgpack object
Expand Down Expand Up @@ -145,4 +170,4 @@ def isJOSE_msgpack(x):
Test whether input is a JOSE-msgpack object
@rtype: boolean
"""
return isJWS_msgpack(x) or isJWE_msgpack(x)
return isJWS_msgpack(x) or isJWE_msgpack(x) or isJWP_msgpack(x)
15 changes: 14 additions & 1 deletion jose/validate/unserialized.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@

### Unserialized

def isJWP_unserialized(x):
"""
Test whether input is an unserialized JWP object
@rtype boolean
"""
if isinstance(x, dict) \
and "payload" in x and isinstance(x["unprotected"], dict)\
and "unprotected" in x and "signature" not in x:
return True
else:
return False

def isJWS_unserialized_single(x):
"""
Test whether input is a single-signature unserialized JWS object
Expand Down Expand Up @@ -79,6 +91,7 @@ def isJOSE_unserialized(x):
Test whether input is an unserialized JOSE object
@rtype: boolean
"""
return isJWS_unserialized(x) or isJWE_unserialized(x)
return isJWS_unserialized(x) or isJWE_unserialized(x) \
or isJWP_unserialized(x)


0 comments on commit 5ad79d8

Please sign in to comment.