Example #1
0
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)
Example #2
0
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)
Example #3
0
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 
Example #4
0
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
Example #5
0
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 }
Example #6
0
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
Example #7
0
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 }
Example #8
0
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
Example #9
0
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
Example #10
0
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}