def decrypt_multi(JWE, keys): """ Decrypt and verify a multi-recipient JWE object B{You should not have to use this method directly, since the L{decrypt} method will transparently handle multiple-recipient objects.} To decrypt a multi-recipient JWE object, we require two inputs: 1. The JWE object to be decrypted 2. A set of keys from which to draw the public key or shared key This method identifies whether it can use one of the per-recipient structures in the JWE to decrypt the object. If so, it performs the decryption and returns the results as a dictionary of the same form as the L{decrypt} method. If the verification succeeds, the "result" field will be set to True, and the "protected" and "payload" fields will be populated. If verification fails, then the "plaintext" field will be set to False, and the other two fields will not be present. @type JWE : dict @param JWE : Unserialized JWS object @type keys: list or set of JWK @param keys: Set of keys from which the decryption key will be selected @rtype: dict @return: Decryption results, including the boolean result and, if succesful, the signed header parameters and payload """ if not isJOSE(JWE): raise Exception("Cannot process something that's not a JOSE object") elif isJOSE_serialized(JWE): JWE = serialize.deserialize(JWE) if not isJWE_unserialized_multi(JWE): raise Exception("decrypt_multi called on a non-multi-recipient JWE") # Copy most of the JWE into a new object single = copy(JWE) del single["recipients"] # Find something we can use to decrypt selectedRecipient = None for r in JWE["recipients"]: try: key = josecrypto.findKey(r["header"], keys, allowDefault=False) selectedRecipient = r break except: pass if selectedRecipient == None: raise Exception("Unable to locate a usable key in multi-recipient JWE") # Construct a standard JWE and decrypt if "unprotected" not in single: single["unprotected"] = {} single["unprotected"] = joinHeader(JWE["unprotected"], selectedRecipient["header"]) single["encrypted_key"] = selectedRecipient["encrypted_key"] return decrypt(single, keys)
def encrypt(header, keys, plaintext, protect=[], aad=b''): """ Encrypt a payload and construct a JWE to encode the encrypted content. To perform a JWE encryption, we take five inputs, two of which are optional: 1. A header describing how the encryption should be done 2. A set of keys from which the encryption key will be selected 3. A plaintext to be encrypted 4. (optional) A list of header fields to be integrity-protected 5. (optional) An explicit Additional Authenticated Data value The implementation then splits the header into unprotected and protected parts, computes the ciphertext and tag according to the header, and assembles the final object. The JWE object returned is unserialized. It has the same structure as a JSON-formatted JWE object, but it is still a python dictionary. It can be serialized using the methods in the L{jose.serialize} module. @type header : dict @param header : Dictionary of JWE header parameters @type keys : list or set of JWK @param keys : Set of keys from which the signing key will be selected (based on the header) @type plaintext: byte string @param plaintext: The payload to be encrypted @type protect : list of string / string @param protect : List of header fields to protect, or the string "*" to @type aad : byte string @param aad : Payload to be authenticated, but not encrypted indicate that all header fields should be protected @rtype: dict @return: Unserialized JWS object """ # TODO validate input # Capture the plaintext and AAD inputs JWEPlaintext = copy(plaintext) JWEAAD = copy(aad) # Compress the plaintext if required if "zip" in header and header["zip"] == "DEF": JWEPlaintext = zlib.compress(JWEPlaintext) # Locate the key key = josecrypto.findKey(header, keys) # Generate cryptographic parameters according to "alg" and "enc" # Copy additional parameters into the header (CEK, JWEEncryptedKey, JWEInitializationVector, params) \ = josecrypto.generateSenderParams( \ header["alg"], header["enc"], key, header=header) for name in params: header[name] = params[name] # Split the header (JWEUnprotectedHeader, JWEProtectedHeader) = splitHeader(header,protect) if len(JWEProtectedHeader) > 0: SerializedJWEProtectedHeader = json.dumps(JWEProtectedHeader) else: SerializedJWEProtectedHeader = "" # Construct the AAD JWEAuthenticatedData = createSigningInput( \ SerializedJWEProtectedHeader, JWEAAD, JWE=True) # Perform the encryption (JWECiphertext, JWEAuthenticationTag) = josecrypto.encrypt( \ header["enc"], CEK, JWEInitializationVector, \ JWEAuthenticatedData, JWEPlaintext ) # Assemble the JWE and return JWE = { "ciphertext": JWECiphertext } if len(JWEUnprotectedHeader) > 0: JWE["unprotected"] = JWEUnprotectedHeader if len(JWEProtectedHeader) > 0: JWE["protected"] = SerializedJWEProtectedHeader if len(JWEEncryptedKey) > 0: JWE["encrypted_key"] = JWEEncryptedKey if len(JWEInitializationVector) > 0: JWE["iv"] = JWEInitializationVector if len(JWEAuthenticationTag) > 0: JWE["tag"] = JWEAuthenticationTag return JWE
def encrypt_multi(header, recipients, keys, plaintext, protect=[], aad=b''): """ recipients = ["header"] Generate a multi-recipient JWE object A multi-recipient JWS object is equivalent to several individual JWE objects with the same payload and the same CEK. Thus, the inputs to this method are largely similar to the L{encrypt} method. 1. A header describing how the encryption should be done 2. A list of per-recipient headers 3. A set of keys from which the encryption key will be selected 4. A plaintext to be encrypted 5. (optional) A list of global header fields to be integrity-protected 6. (optional) An explicit Additional Authenticated Data value Like the L{encrypt} function, this function returns an unserialized JWS object. The only difference is that the output of this function will have multiple recipients. Note that while some or all of the global header parameters may be protected (according to the 'protect' argument), all per-recipient header parameters are unprotected. @type header : dict @param header : Dictionary of JWE header parameters @type recipients: list of dict @param recipients: List of per-recipient header dictionaries @type keys : list or set of JWK @param keys : Set of keys from which the signing key will be selected (based on the header) @type plaintext: byte string @param plaintext: The payload to be encrypted @type protect : list of string / string @param protect : List of header fields to protect, or the string "*" to @type aad : byte string @param aad : Payload to be authenticated, but not encrypted indicate that all header fields should be protected @rtype: dict @return: Unserialized JWS object """ # Generate random key if "enc" not in header: raise Exception("'enc' parameter must be specified in top header") enc = header["enc"] (CEK, IV) = josecrypto.generateKeyIV(enc) CEK_JWK = { "kty": "oct", "k": b64enc(CEK) } # Encrypt with "dir" (and then hide the "dir") if "alg" in protect: protect.remove(protect.index("alg")) header["alg"] = "dir" baseJWE = encrypt(header, [CEK_JWK], plaintext, protect, aad) del baseJWE["unprotected"]["alg"] # Wrap the keys wrappedKeys = [] for r in recipients: # Pull and verify parameters if "alg" not in r: raise Exception("'alg' parameter required per recipient") alg = r["alg"] if alg == "dir" or alg == "ECDH-ES": raise Exception("Direct encryption not allowed with multi-recipient ('dir'/'ECDH-ES')") # Locate wrapping key and wrap key jwk = josecrypto.findKey(r, keys) (CEK, encryptedKey, IV, params) = josecrypto.generateSenderParams(\ alg, enc, jwk, header=r, inCEK=CEK) r = joinHeader(r, params) wrappedKeys.append({ "header": r, "encrypted_key": encryptedKey }) # Assemble and return the overall JWE baseJWE["recipients"] = wrappedKeys return baseJWE
def decrypt(JWE, keys): """ Decrypt and verify a JWE object To decrypt a JWE object, we require two inputs: 1. The JWE object to be decrypted 2. A set of keys from which to draw the public key or shared key This function returns decryption/verification results as a dictionary with the following fields: - "result": The boolean result of the verification - "protected": (optional) The protected headers, as a dictionary - "plaintext": (optional) The signed payload If the verification succeeds, the "result" field will be set to True, and the "protected" and "payload" fields will be populated. If verification fails, then the "plaintext" field will be set to False, and the other two fields will not be present. @type JWE : dict @param JWE : Unserialized JWS object @type keys: list or set of JWK @param keys: Set of keys from which the decryption key will be selected @rtype: dict @return: Decryption results, including the boolean result and, if succesful, the signed header parameters and payload """ # Deserialize if necessary if not isJOSE(JWE): raise Exception("Cannot process something that's not a JOSE object") elif isJOSE_serialized(JWE): JWE = serialize.deserialize(JWE) # Make sure we have a JWE if not isJWE_unserialized(JWE): raise Exception("decrypt() called with something other than a JWE") # Handle multi-recipient JWEs separately if isJWE_unserialized_multi(JWE): return decrypt_multi(JWE, keys) # Capture the crypto inputs 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: SerializedJWEProtectedHeader = JWE["protected"] JWEProtectedHeader = json.loads(SerializedJWEProtectedHeader) else: SerializedJWEProtectedHeader = "" JWEProtectedHeader = {} header = joinHeader(JWEUnprotectedHeader, JWEProtectedHeader) # Check that we support everything critical if not criticalParamsSupported(header, supported_hdr_ext): raise Exception("Unsupported critical fields") # Construct the AAD JWEAuthenticatedData = createSigningInput( \ SerializedJWEProtectedHeader, JWEAAD, JWE=True) # Locate the key key = josecrypto.findKey(header, keys) # Unwrap or derive the key according to 'alg' JWEEncryptedKey = JWE["encrypted_key"] if "encrypted_key" in JWE else "" CEK = josecrypto.decryptKey(header["alg"], header["enc"], key, JWEEncryptedKey, header) # Perform the decryption (JWEPlaintext, JWEVerificationResult) = josecrypto.decrypt( \ header["enc"], CEK, JWEInitializationVector, JWEAuthenticatedData, \ JWECiphertext, JWEAuthenticationTag ) # Decompress the plaintext if necessary if "zip" in header and header["zip"] == "DEF": JWEPlaintext = zlib.decompress(JWEPlaintext) # Return the results of decryption if JWEVerificationResult: return { "result": JWEVerificationResult, "plaintext": JWEPlaintext, "protected": JWEProtectedHeader } else: return { "result": JWEVerificationResult }
def sign(header, keys, payload, protect=[]): """ Sign a payload and construct a JWS to encode the signature. To perform a JWS signature, we take four inputs, one of which is optional: 1. A header describing how the signature should be done 2. A set of keys from which the signing key will be selected 3. A payload to be signed 4. (optional) A list of header fields to be integrity-protected The implementation then splits the header into unprotected and protected parts, computes the signature according to the header, and assembles the final object. The JWS object returned is unserialized. It has the same structure as a JSON-formatted JWS object, but it is still a python dictionary. It can be serialized using the methods in the L{jose.serialize} module. @type header : dict @param header : Dictionary of JWS header parameters @type keys : list or set of JWK @param keys : Set of keys from which the signing key will be selected ( based on the header) @type payload: byte string @param payload: The payload to be signed @type protect: list of string / string @param protect: List of header fields to protect, or the string "*" to indicate that all header fields should be protected @rtype: dict @return: Unserialized JWS object """ # TODO validate inputs # Capture the payload JWSPayload = copy(payload) # Split the header (JWSUnprotectedHeader, JWSProtectedHeader) = splitHeader(header, protect) if len(JWSProtectedHeader) > 0: SerializedJWSProtectedHeader = json.dumps(JWSProtectedHeader) else: 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(SerializedJWSProtectedHeader, JWSPayload) # Look up key key = josecrypto.findKey(header, keys) # Compute the signature JWSSignature = josecrypto.sign(header["alg"], key, JWSSigningInput) # Assemble and return the object JWS = { "payload": JWSPayload, "signature": JWSSignature } if len(JWSProtectedHeader) > 0: JWS["protected"] = SerializedJWSProtectedHeader if len(JWSUnprotectedHeader) > 0: JWS["unprotected"] = JWSUnprotectedHeader return JWS
def verify(JWS, keys): """ Verify a JWS object To verify a JWS object, we require two inputs: 1. The JWS object to be verified 2. A set of keys from which to draw the public key or shared key (This implementation does not currently support using the "jwk" attribute for a public key, so the key must be provided.) This method returns verification results as a dictionary with the following fields: - "result": The boolean result of the verification - "protected": (optional) The protected headers, as a dictionary - "payload": (optional) The signed payload If the verification succeeds, the "result" field will be set to True, and the "protected" and "payload" fields will be populated. If verification fails, then the "result" field will be set to False, and the other two fields will not be present. @type JWS : dict @param JWS : Unserialized JWS object @type keys: list or set of JWK @param keys: Set of keys from which the public/shared key will be selected ( based on the header) @rtype: dict @return: Verification results, including the boolean result and, if succesful, the signed header parameters and payload """ # Deserialize if necessary if not isJOSE(JWS): raise Exception("Cannot process something that's not a JOSE object") elif isJOSE_serialized(JWS): JWS = serialize.deserialize(JWS) # Make sure we have a JWS if not isJWS_unserialized(JWS): raise Exception("decrypt() called with something other than a JWS") # Handle multi-signature JWSs separately if isJWS_unserialized_multi(JWS): return verify_multi(JWS, keys) # Capture the payload JWSPayload = JWS["payload"] # Reassemble the header JWSUnprotectedHeader = JWS["unprotected"] if ("unprotected" in JWS) else {} if "protected" in JWS: SerializedJWSProtectedHeader = JWS["protected"] JWSProtectedHeader = json.loads(SerializedJWSProtectedHeader) else: SerializedJWSProtectedHeader = "" JWSProtectedHeader = {} header = joinHeader(JWSUnprotectedHeader, JWSProtectedHeader) # Check that we support everything critical if not criticalParamsSupported(header, supported_hdr_ext): raise Exception("Unsupported critical fields") # Construct the JWS Signing Input JWSSigningInput = createSigningInput(SerializedJWSProtectedHeader, JWSPayload) # Look up the key key = josecrypto.findKey(header, keys) # Verify the signature JWSSignature = JWS["signature"] JWSVerificationResult = josecrypto.verify( \ header["alg"], key, JWSSigningInput, JWSSignature ) # Return the verified payload and headers if JWSVerificationResult: return { "result": True, "payload": JWSPayload, "protected": JWSProtectedHeader } else: return { "result": False }
def encrypt(header, keys, plaintext, protect=[], aad=b''): """ Encrypt a payload and construct a JWE to encode the encrypted content. To perform a JWE encryption, we take five inputs, two of which are optional: 1. A header describing how the encryption should be done 2. A set of keys from which the encryption key will be selected 3. A plaintext to be encrypted 4. (optional) A list of header fields to be integrity-protected 5. (optional) An explicit Additional Authenticated Data value The implementation then splits the header into unprotected and protected parts, computes the ciphertext and tag according to the header, and assembles the final object. The JWE object returned is unserialized. It has the same structure as a JSON-formatted JWE object, but it is still a python dictionary. It can be serialized using the methods in the L{jose.serialize} module. @type header : dict @param header : Dictionary of JWE header parameters @type keys : list or set of JWK @param keys : Set of keys from which the signing key will be selected (based on the header) @type plaintext: byte string @param plaintext: The payload to be encrypted @type protect : list of string / string @param protect : List of header fields to protect, or the string "*" to @type aad : byte string @param aad : Payload to be authenticated, but not encrypted indicate that all header fields should be protected @rtype: dict @return: Unserialized JWS object """ # TODO validate input # Capture the plaintext and AAD inputs JWEPlaintext = copy(plaintext) JWEAAD = copy(aad) # Compress the plaintext if required if "zip" in header and header["zip"] == "DEF": JWEPlaintext = zlib.compress(JWEPlaintext) # Locate the key key = josecrypto.findKey(header, keys) # Generate cryptographic parameters according to "alg" and "enc" # Copy additional parameters into the header (CEK, JWEEncryptedKey, JWEInitializationVector, params) \ = josecrypto.generateSenderParams( \ header["alg"], header["enc"], key, header=header) for name in params: header[name] = params[name] # Split the header (JWEUnprotectedHeader, JWEProtectedHeader) = splitHeader(header, protect) if len(JWEProtectedHeader) > 0: SerializedJWEProtectedHeader = json.dumps(JWEProtectedHeader) else: SerializedJWEProtectedHeader = "" # Construct the AAD JWEAuthenticatedData = createSigningInput( \ SerializedJWEProtectedHeader, JWEAAD, JWE=True) # Perform the encryption (JWECiphertext, JWEAuthenticationTag) = josecrypto.encrypt( \ header["enc"], CEK, JWEInitializationVector, \ JWEAuthenticatedData, JWEPlaintext ) # Assemble the JWE and return JWE = {"ciphertext": JWECiphertext} if len(JWEUnprotectedHeader) > 0: JWE["unprotected"] = JWEUnprotectedHeader if len(JWEProtectedHeader) > 0: JWE["protected"] = SerializedJWEProtectedHeader if len(JWEEncryptedKey) > 0: JWE["encrypted_key"] = JWEEncryptedKey if len(JWEInitializationVector) > 0: JWE["iv"] = JWEInitializationVector if len(JWEAuthenticationTag) > 0: JWE["tag"] = JWEAuthenticationTag return JWE
def encrypt_multi(header, recipients, keys, plaintext, protect=[], aad=b''): """ recipients = ["header"] Generate a multi-recipient JWE object A multi-recipient JWS object is equivalent to several individual JWE objects with the same payload and the same CEK. Thus, the inputs to this method are largely similar to the L{encrypt} method. 1. A header describing how the encryption should be done 2. A list of per-recipient headers 3. A set of keys from which the encryption key will be selected 4. A plaintext to be encrypted 5. (optional) A list of global header fields to be integrity-protected 6. (optional) An explicit Additional Authenticated Data value Like the L{encrypt} function, this function returns an unserialized JWS object. The only difference is that the output of this function will have multiple recipients. Note that while some or all of the global header parameters may be protected (according to the 'protect' argument), all per-recipient header parameters are unprotected. @type header : dict @param header : Dictionary of JWE header parameters @type recipients: list of dict @param recipients: List of per-recipient header dictionaries @type keys : list or set of JWK @param keys : Set of keys from which the signing key will be selected (based on the header) @type plaintext: byte string @param plaintext: The payload to be encrypted @type protect : list of string / string @param protect : List of header fields to protect, or the string "*" to @type aad : byte string @param aad : Payload to be authenticated, but not encrypted indicate that all header fields should be protected @rtype: dict @return: Unserialized JWS object """ # Generate random key if "enc" not in header: raise Exception("'enc' parameter must be specified in top header") enc = header["enc"] (CEK, IV) = josecrypto.generateKeyIV(enc) CEK_JWK = {"kty": "oct", "k": b64enc(CEK)} # Encrypt with "dir" (and then hide the "dir") if "alg" in protect: protect.remove(protect.index("alg")) header["alg"] = "dir" baseJWE = encrypt(header, [CEK_JWK], plaintext, protect, aad) del baseJWE["unprotected"]["alg"] # Wrap the keys wrappedKeys = [] for r in recipients: # Pull and verify parameters if "alg" not in r: raise Exception("'alg' parameter required per recipient") alg = r["alg"] if alg == "dir" or alg == "ECDH-ES": raise Exception( "Direct encryption not allowed with multi-recipient ('dir'/'ECDH-ES')" ) # Locate wrapping key and wrap key jwk = josecrypto.findKey(r, keys) (CEK, encryptedKey, IV, params) = josecrypto.generateSenderParams(\ alg, enc, jwk, header=r, inCEK=CEK) r = joinHeader(r, params) wrappedKeys.append({"header": r, "encrypted_key": encryptedKey}) # Assemble and return the overall JWE baseJWE["recipients"] = wrappedKeys return baseJWE
def decrypt(JWE, keys): """ Decrypt and verify a JWE object To decrypt a JWE object, we require two inputs: 1. The JWE object to be decrypted 2. A set of keys from which to draw the public key or shared key This function returns decryption/verification results as a dictionary with the following fields: - "result": The boolean result of the verification - "protected": (optional) The protected headers, as a dictionary - "plaintext": (optional) The signed payload If the verification succeeds, the "result" field will be set to True, and the "protected" and "payload" fields will be populated. If verification fails, then the "plaintext" field will be set to False, and the other two fields will not be present. @type JWE : dict @param JWE : Unserialized JWS object @type keys: list or set of JWK @param keys: Set of keys from which the decryption key will be selected @rtype: dict @return: Decryption results, including the boolean result and, if succesful, the signed header parameters and payload """ # Deserialize if necessary if not isJOSE(JWE): raise Exception("Cannot process something that's not a JOSE object") elif isJOSE_serialized(JWE): JWE = serialize.deserialize(JWE) # Make sure we have a JWE if not isJWE_unserialized(JWE): raise Exception("decrypt() called with something other than a JWE") # Handle multi-recipient JWEs separately if isJWE_unserialized_multi(JWE): return decrypt_multi(JWE, keys) # Capture the crypto inputs 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: SerializedJWEProtectedHeader = JWE["protected"] JWEProtectedHeader = json.loads(SerializedJWEProtectedHeader) else: SerializedJWEProtectedHeader = "" JWEProtectedHeader = {} header = joinHeader(JWEUnprotectedHeader, JWEProtectedHeader) # Check that we support everything critical if not criticalParamsSupported(header, supported_hdr_ext): raise Exception("Unsupported critical fields") # Construct the AAD JWEAuthenticatedData = createSigningInput( \ SerializedJWEProtectedHeader, JWEAAD, JWE=True) # Locate the key key = josecrypto.findKey(header, keys) # Unwrap or derive the key according to 'alg' JWEEncryptedKey = JWE["encrypted_key"] if "encrypted_key" in JWE else "" CEK = josecrypto.decryptKey(header["alg"], header["enc"], key, JWEEncryptedKey, header) # Perform the decryption (JWEPlaintext, JWEVerificationResult) = josecrypto.decrypt( \ header["enc"], CEK, JWEInitializationVector, JWEAuthenticatedData, \ JWECiphertext, JWEAuthenticationTag ) # Decompress the plaintext if necessary if "zip" in header and header["zip"] == "DEF": JWEPlaintext = zlib.decompress(JWEPlaintext) # Return the results of decryption if JWEVerificationResult: return { "result": JWEVerificationResult, "plaintext": JWEPlaintext, "protected": JWEProtectedHeader } else: return {"result": JWEVerificationResult}