Skip to content

Commit

Permalink
Made unserialized format mostly not base64; added msgpack serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
Richard Barnes committed Aug 23, 2013
1 parent d49eada commit c30c9c2
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 95 deletions.
4 changes: 2 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ In no particular order:
* Support "jku" / "x5u"
* Support "JWP" (or something for unsigned content)
* Present key used for verification in result
* Change unserialized format from base64 to binary
* Add Msgpack serialization


# DONE
Expand All @@ -18,3 +16,5 @@ In no particular order:
* Support "jwk"
* Support "x5c"
* Add tests for "jwk" and "x5c"
* Change unserialized format from base64 to binary
* Add Msgpack serialization
24 changes: 15 additions & 9 deletions jose/josecrypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def extractRSAfromCert(pem):
rsa = RSA.importKey(subjectPublicKeyInfo)
return exportKey(rsa, "RSA")

def findKey(header, keys):
def findKey(header, keys, allowDefault=True):
"""
Locates a usable key in a set of keys based on identifiers
in the header.
Expand All @@ -176,28 +176,34 @@ def findKey(header, keys):
certificate in the chain, and search as for "jwk"
- If none found, return the extracted public key
- If there is only one key in the set, that key is used.
- If allowDefualt == False, this will not be done
If no key is found, throws an exception.
@type header: dict
@param header: Header with key identifier(s)
@type keys : list or set
@param keys : Set of JWKs from which key is to be chosen
@type allowDefault: boolean
@param allowDefault: If there is a single key and no other match,
return the single key
@rtype: dict
@return: JWK selected from the set
"""
# Try "kid" search
if "kid" in header:
for k in keys:
if "kid" in k and k["kid"] == header["kid"]:
return k

# Try "jwk" search
def allFieldsMatch(a, b):
for k in a:
if k not in b or a[k] != b[k]:
return False
return True

# Try "kid" search
if "kid" in header:
for k in keys:
if "kid" in k and k["kid"] == header["kid"]:
return k

# Try "jwk" search
if "jwk" in header:
for k in keys:
if allFieldsMatch(header["jwk"], k):
Expand All @@ -212,9 +218,9 @@ def allFieldsMatch(a, b):
if allFieldsMatch(jwk, k):
return k
return jwk

# Only one key in the set
if len(keys) == 1:
if len(keys) == 1 and allowDefault:
return keys[0]

# Tried everything, give up
Expand Down
48 changes: 20 additions & 28 deletions jose/jwe.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,43 +103,39 @@ def encrypt(header, keys, plaintext, protect=[], aad=b''):
(CEK, JWEEncryptedKey, JWEInitializationVector, params) \
= josecrypto.generateSenderParams( \
header["alg"], header["enc"], key, header=header)
EncodedJWEInitializationVector = b64enc(JWEInitializationVector)
EncodedJWEEncryptedKey = b64enc(JWEEncryptedKey)
for name in params:
header[name] = params[name]

# Split the header
(JWEUnprotectedHeader, JWEProtectedHeader) = splitHeader(header,protect)
if len(JWEProtectedHeader) > 0:
EncodedJWEProtectedHeader = b64enc(json.dumps(JWEProtectedHeader))
SerializedJWEProtectedHeader = json.dumps(JWEProtectedHeader)
else:
EncodedJWEProtectedHeader = ""
SerializedJWEProtectedHeader = ""

# Construct the AAD
JWEAuthenticatedData = createSigningInput( \
EncodedJWEProtectedHeader, JWEAAD, JWE=True)
SerializedJWEProtectedHeader, JWEAAD, JWE=True)

# Perform the encryption
(JWECiphertext, JWEAuthenticationTag) = josecrypto.encrypt( \
header["enc"], CEK, JWEInitializationVector, \
JWEAuthenticatedData, JWEPlaintext )
EncodedJWECiphertext = b64enc(JWECiphertext)
EncodedJWEAuthenticationTag = b64enc(JWEAuthenticationTag)

# Assemble the JWE and return
JWE = {
"ciphertext": EncodedJWECiphertext
"ciphertext": JWECiphertext
}
if len(JWEUnprotectedHeader) > 0:
JWE["unprotected"] = JWEUnprotectedHeader
if len(JWEProtectedHeader) > 0:
JWE["protected"] = EncodedJWEProtectedHeader
JWE["protected"] = SerializedJWEProtectedHeader
if len(JWEEncryptedKey) > 0:
JWE["encrypted_key"] = EncodedJWEEncryptedKey
JWE["encrypted_key"] = JWEEncryptedKey
if len(JWEInitializationVector) > 0:
JWE["iv"] = EncodedJWEInitializationVector
JWE["iv"] = JWEInitializationVector
if len(JWEAuthenticationTag) > 0:
JWE["tag"] = EncodedJWEAuthenticationTag
JWE["tag"] = JWEAuthenticationTag
return JWE

def decrypt(JWE, keys):
Expand Down Expand Up @@ -185,22 +181,18 @@ def decrypt(JWE, keys):
return decrypt_multi(JWE, keys)

# Capture the crypto inputs
EncodedJWECiphertext = JWE["ciphertext"]
EncodedJWEInitializationVector = JWE["iv"] if "iv" in JWE else ""
EncodedJWEAuthenticationTag = JWE["tag"] if "tag" in JWE else ""
EncodedJWEAAD = JWE["aad"] if "aad" in JWE else ""
JWECiphertext = b64dec(EncodedJWECiphertext)
JWEInitializationVector = b64dec(EncodedJWEInitializationVector)
JWEAuthenticationTag = b64dec(EncodedJWEAuthenticationTag)
JWEAAD = b64dec(EncodedJWEAAD)
JWECiphertext = JWE["ciphertext"]
JWEInitializationVector = JWE["iv"] if "iv" in JWE else ""
JWEAuthenticationTag = JWE["tag"] if "tag" in JWE else ""
JWEAAD = JWE["aad"] if "aad" in JWE else ""

# Reassemble the header
JWEUnprotectedHeader = JWE["unprotected"] if ("unprotected" in JWE) else {}
if "protected" in JWE:
EncodedJWEProtectedHeader = JWE["protected"]
JWEProtectedHeader = json.loads(b64dec(EncodedJWEProtectedHeader))
SerializedJWEProtectedHeader = JWE["protected"]
JWEProtectedHeader = json.loads(SerializedJWEProtectedHeader)
else:
EncodedJWEProtectedHeader = ""
SerializedJWEProtectedHeader = ""
JWEProtectedHeader = {}
header = joinHeader(JWEUnprotectedHeader, JWEProtectedHeader)

Expand All @@ -210,14 +202,13 @@ def decrypt(JWE, keys):

# Construct the AAD
JWEAuthenticatedData = createSigningInput( \
EncodedJWEProtectedHeader, JWEAAD, JWE=True)
SerializedJWEProtectedHeader, JWEAAD, JWE=True)

# Locate the key
key = josecrypto.findKey(header, keys)

# Unwrap or derive the key according to 'alg'
EncodedJWEEncryptedKey = JWE["encrypted_key"] if "encrypted_key" in JWE else ""
JWEEncryptedKey = b64dec(EncodedJWEEncryptedKey)
JWEEncryptedKey = JWE["encrypted_key"] if "encrypted_key" in JWE else ""
CEK = josecrypto.decryptKey(header["alg"], header["enc"], key, JWEEncryptedKey, header)

# Perform the decryption
Expand Down Expand Up @@ -313,7 +304,7 @@ def encrypt_multi(header, recipients, keys, plaintext, protect=[], aad=b''):
r = joinHeader(r, params)
wrappedKeys.append({
"header": r,
"encrypted_key": b64enc(encryptedKey)
"encrypted_key": encryptedKey
})

# Assemble and return the overall JWE
Expand Down Expand Up @@ -365,8 +356,9 @@ def decrypt_multi(JWE, keys):
selectedRecipient = None
for r in JWE["recipients"]:
try:
key = josecrypto.findKey(r["header"], keys)
key = josecrypto.findKey(r["header"], keys, allowDefault=False)
selectedRecipient = r
break
except:
pass
if selectedRecipient == None:
Expand Down
35 changes: 15 additions & 20 deletions jose/jws.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,36 +75,34 @@ def sign(header, keys, payload, protect=[]):

# Capture the payload
JWSPayload = copy(payload)
EncodedJWSPayload = b64enc(JWSPayload)

# Split the header
(JWSUnprotectedHeader, JWSProtectedHeader) = splitHeader(header, protect)
if len(JWSProtectedHeader) > 0:
EncodedJWSProtectedHeader = b64enc(json.dumps(JWSProtectedHeader))
SerializedJWSProtectedHeader = json.dumps(JWSProtectedHeader)
else:
EncodedJWSProtectedHeader = ""
SerializedJWSProtectedHeader = ""

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

# Construct the JWS Signing Input
JWSSigningInput = createSigningInput(EncodedJWSProtectedHeader, EncodedJWSPayload)
JWSSigningInput = createSigningInput(SerializedJWSProtectedHeader, JWSPayload)

# Look up key
key = josecrypto.findKey(header, keys)

# Compute the signature
JWSSignature = josecrypto.sign(header["alg"], key, JWSSigningInput)
EncodedJWSSignature = b64enc(JWSSignature)

# Assemble and return the object
JWS = {
"payload": EncodedJWSPayload,
"signature": EncodedJWSSignature
"payload": JWSPayload,
"signature": JWSSignature
}
if len(JWSProtectedHeader) > 0:
JWS["protected"] = EncodedJWSProtectedHeader
JWS["protected"] = SerializedJWSProtectedHeader
if len(JWSUnprotectedHeader) > 0:
JWS["unprotected"] = JWSUnprotectedHeader
return JWS
Expand Down Expand Up @@ -157,17 +155,15 @@ def verify(JWS, keys):


# Capture the payload
EncodedJWSPayload = JWS["payload"]
JWSPayload = b64dec(EncodedJWSPayload)
JWSPayload = JWS["payload"]

# Reassemble the header
JWSUnprotectedHeader = JWS["unprotected"] if ("unprotected" in JWS) else {}
EncodedJWSProtectedHeader = JWS["protected"] if ("protected" in JWS) else ""
if "protected" in JWS:
EncodedJWSProtectedHeader = JWS["protected"]
JWSProtectedHeader = json.loads(b64dec(EncodedJWSProtectedHeader))
SerializedJWSProtectedHeader = JWS["protected"]
JWSProtectedHeader = json.loads(SerializedJWSProtectedHeader)
else:
EncodedJWSProtectedHeader = ""
SerializedJWSProtectedHeader = ""
JWSProtectedHeader = {}
header = joinHeader(JWSUnprotectedHeader, JWSProtectedHeader)

Expand All @@ -176,14 +172,13 @@ def verify(JWS, keys):
raise Exception("Unsupported critical fields")

# Construct the JWS Signing Input
JWSSigningInput = createSigningInput(EncodedJWSProtectedHeader, EncodedJWSPayload)
JWSSigningInput = createSigningInput(SerializedJWSProtectedHeader, JWSPayload)

# Look up the key
key = josecrypto.findKey(header, keys)

# Verify the signature
EncodedJWSSignature = JWS["signature"]
JWSSignature = b64dec(EncodedJWSSignature)
JWSSignature = JWS["signature"]
JWSVerificationResult = josecrypto.verify( \
header["alg"], key, JWSSigningInput, JWSSignature )

Expand Down Expand Up @@ -237,7 +232,7 @@ def sign_multi(signers, keys, payload):
JWSs.append(sign(s["header"], keys, payload, protect))
# Combine the JWSs by deleting their payloads
JWS = {
"payload": b64enc(payload),
"payload": payload,
"signatures": []
}
for j in JWSs:
Expand Down Expand Up @@ -298,7 +293,7 @@ def verify_multi(JWS, keys):
# Reconstruct and validte individual JWSs for each signer
payload = JWS["payload"]
results = {
"payload": b64dec(payload),
"payload": payload,
"results": []
}
for s in JWS["signatures"]:
Expand All @@ -314,7 +309,7 @@ def verify_multi(JWS, keys):
if ("unprotected" in s):
r["unprotected"] = s["unprotected"]
if ("protected" in s):
r["protected"] = json.loads(b64dec(s["protected"]))
r["protected"] = json.loads(s["protected"])
results["results"].append(r)

# Return the results array
Expand Down
Loading

0 comments on commit c30c9c2

Please sign in to comment.