Example #1
0
def validate_signatures(body):
    att_stmts = []
    if isinstance(body, list):
        for s in body:
            if 'attachments' in s:
                att_stmts.append(s)
    elif 'attachments' in body:
        att_stmts.append(body)
    if att_stmts:
        # find if any of those statements with attachments have a signed statement
        signed_stmts = [(s, a) for s in att_stmts
                        for a in s.get('attachments', None) if a['usageType']
                        == "http://adlnet.gov/expapi/attachments/signature"]
        for ss in signed_stmts:
            sha2_key = ss[1]['sha2']
            signature = att_cache.get(sha2_key)
            algorithm = jws.get_unverified_headers(signature).get('alg', None)
            x5c = jws.get_unverified_headers(signature).get('x5c', None)
            jws_payload = jws.get_unverified_claims(signature)
            body_payload = ss[0]
            # If x.509 was used to sign, the public key should be in the x5c header and you need to verify it
            # If using RS256, RS384, or RS512 some JWS libs require a real private key to create JWS - xAPI spec
            # only has SHOULD - need to look into. If x.509 is necessary then if no x5c header is found this should fail
            if x5c:
                verified = False
                try:
                    verified = jws.verify(signature, cert_to_key(x5c[0]),
                                          algorithm)
                except Exception, e:
                    att_cache.delete(sha2_key)
                    raise BadRequest("The JWS is not valid: %s" % e.message)
                else:
                    if not verified:
                        att_cache.delete(sha2_key)
                        raise BadRequest(
                            "The JWS is not valid - could not verify signature"
                        )
                    # Compare statements
                    if not compare_payloads(jws_payload, body_payload):
                        att_cache.delete(sha2_key)
                        raise BadRequest(
                            "The JWS is not valid - payload and body statements do not match"
                        )
            else:
                # Compare statements
                if not compare_payloads(jws_payload, body_payload):
                    att_cache.delete(sha2_key)
                    raise BadRequest(
                        "The JWS is not valid - payload and body statements do not match"
                    )
def get_and_verify(url, keys, output: str) -> bool:
    """Downloads and verifies metadata"""
    try:
        with urllib.request.urlopen(url) as webfile:
            sig = webfile.read()
    except urllib.error.URLError as e:
        raise RuntimeError("Failed to get URL " + url + " (" + str(e) + ")")

    jwsdict = json.loads(sig)
    
    with open(keys, 'r') as keysfile:
        keyset_str = keysfile.read()

    payload = jwsdict['payload']
    for s in jwsdict['signatures']:
        try:
            protected = s['protected']
            signature = s['signature']
            compact = protected + "." + payload + "." + signature

            exp_header = jws.get_unverified_headers(compact)['exp']
            exp = datetime.utcfromtimestamp(int(exp_header))
            if exp < datetime.utcnow():
                error_print("Signature expired at: " + str(exp))
                continue
            
            verified_payload = jws.verify(compact, keyset_str, None)
            with open(output, 'wb') as outfile:
                outfile.write(verified_payload)
                return True
        except jws.JWSError:
            continue

    return False
def _decode_vc(jws_raw, key_resolver):
    # before we can verify the vc, we first need to resolve the key
    # the key ID is stored in the header
    # Per the health cards IG,
    ## "Issuers SHALL publish keys as JSON Web Key Sets (see RFC7517), available at <<iss value from Signed JWT>> + .well-known/jwks.json"
    # therefore, we need decode the claims to get the iss value in order to resolve the key
    # The claims are compressed via Deflate, so decompress the data
    # then, extract the iss claim to get access to the base URL, use that to resolve key with id = kid
    # then, verify the jws
    unverified_headers = jws.get_unverified_headers(jws_raw)

    # we expect data to be zipped, so deflate the data
    if unverified_headers.get('zip') == 'DEF':
        unverfied_claims_zip = jws.get_unverified_claims(jws_raw)
        raw_data = inflate(unverfied_claims_zip)
        data = json.loads(raw_data)
    else:
        raise Exception('Expecting payload to be compressed')

    iss = data['iss']
    kid = unverified_headers['kid']

    key = key_resolver(iss, kid, 'ES256')

    verified_jws = jws.verify(jws_raw, key, algorithms='ES256')
    payload = json.loads(inflate(verified_jws))
    return payload
Example #4
0
    def process_request(self, req: Request, resp: Response):
        self.context.logger.debug(f'process_request: {req.method} {req.path}')
        if req.method == 'POST':
            if req.content_type != 'application/jose+json':
                raise UnsupportedMediaTypeMalformed(
                    detail=f'{req.content_type} is an unsupported media type')

            data = req.media
            token = f'{data["protected"]}.{data["payload"]}.{data["signature"]}'

            headers = jws.get_unverified_headers(token)
            protected = json.loads(b64_decode(data['protected']))

            self.context.logger.debug(f'(Unverified) headers: {headers}')

            if headers.get('kid') and protected.get('jwk'):
                raise Unauthorized(
                    detail='The "jwk" and "kid" fields are mutually exclusive')

            if headers.get('url') != req.uri:
                raise Unauthorized(
                    detail=f'JWS header URL ({headers.get("url")})'
                    f' does not match requested URL ({req.uri})')
            # Existing account
            kid = headers.get('kid', None)
            account = self.context.get_account_using_kid(kid)
            if account:
                if account.status != 'valid':
                    self.context.logger.info(f'Account {account} deactivated')
                    raise Unauthorized(detail='Account deactivated')
                self.context.logger.info(
                    f'Authenticating request for account {account}')
                req.context['account'] = account
                jwk = account.jwk
            # Account registration
            elif req.path.endswith('/new-account') or req.path.endswith(
                    '/new-account/'):
                jwk = protected['jwk']
                if protected['alg'] not in SUPPORTED_ALGORITHMS:
                    raise BadSignatureAlgorithm(
                        algorithms=SUPPORTED_ALGORITHMS)
                req.context['account_creation'] = True
            else:
                self.context.logger.warning(
                    f'Account not found using kid {kid}')
                raise Unauthorized(detail='Account not found')

            try:
                ret = jws.verify(token, jwk, algorithms=SUPPORTED_ALGORITHMS)
            except JOSEError as e:
                self.context.logger.error(
                    f'Exception while verifying token: {e}')
                raise ServerInternal(detail=f'{e}')

            self.context.logger.debug(f'Verified data: {ret}')
            req.context['jose_verified_data'] = ret
            req.context['jose_headers'] = headers
Example #5
0
def validate_signatures(body):
    att_stmts = []
    if isinstance(body, list):
        for s in body:
            if 'attachments' in s:
                att_stmts.append(s)
    elif 'attachments' in body:
        att_stmts.append(body)
    if att_stmts:
        # find if any of those statements with attachments have a signed statement
        signed_stmts = [(s,a) for s in att_stmts for a in s.get('attachments', None) if a['usageType'] == "http://adlnet.gov/expapi/attachments/signature"]
        for ss in signed_stmts:
            sha2_key = ss[1]['sha2']
            signature = att_cache.get(sha2_key)
            algorithm = jws.get_unverified_headers(signature).get('alg', None)
            x5c = jws.get_unverified_headers(signature).get('x5c', None)
            jws_payload = jws.get_unverified_claims(signature)
            body_payload = ss[0]
            # If x.509 was used to sign, the public key should be in the x5c header and you need to verify it
            # If using RS256, RS384, or RS512 some JWS libs require a real private key to create JWS - xAPI spec
            # only has SHOULD - need to look into. If x.509 is necessary then if no x5c header is found this should fail
            if x5c:
                verified = False
                try:
                    verified = jws.verify(signature, cert_to_key(x5c[0]), algorithm)
                except Exception, e:
                    att_cache.delete(sha2_key)
                    raise BadRequest("The JWS is not valid: %s" % e.message)
                else:
                    if not verified:
                        att_cache.delete(sha2_key)
                        raise BadRequest("The JWS is not valid - could not verify signature")                
                    # Compare statements
                    if not compare_payloads(jws_payload, body_payload):
                        att_cache.delete(sha2_key)
                        raise BadRequest("The JWS is not valid - payload and body statements do not match")                    
            else:
                # Compare statements
                if not compare_payloads(jws_payload, body_payload):
                    att_cache.delete(sha2_key)
                    raise BadRequest("The JWS is not valid - payload and body statements do not match")    
Example #6
0
def validate_signature(tup, part):
    sha2_key = tup[1][0]
    signature = get_part_payload(part)
    algorithm = jws.get_unverified_headers(signature).get('alg', None)
    if not algorithm:
        raise BadRequest(
            "No signing algorithm found for JWS signature")
    if algorithm != 'RS256' and algorithm != 'RS384' and algorithm != 'RS512':
        raise BadRequest(
            "JWS signature must be calculated with SHA-256, SHA-384 or" \
            "SHA-512 algorithms")
    x5c = jws.get_unverified_headers(signature).get('x5c', None)
    jws_payload = jws.get_unverified_claims(signature)
    body_payload = tup[0]
    # If x.509 was used to sign, the public key should be in the x5c header and you need to verify it
    # If using RS256, RS384, or RS512 some JWS libs require a real private key to create JWS - xAPI spec
    # only has SHOULD - need to look into. If x.509 is necessary then
    # if no x5c header is found this should fail
    if x5c:
        verified = False
        try:
            verified = jws.verify(
                signature, cert_to_key(x5c[0]), algorithm)
        except Exception as e:
            raise BadRequest("The JWS is not valid: %s" % e.message)
        else:
            if not verified:
                raise BadRequest(
                    "The JWS is not valid - could not verify signature")
            # Compare statements
            if not compare_payloads(jws_payload, body_payload, sha2_key):
                raise BadRequest(
                    "The JWS is not valid - payload and body statements do not match")
    else:
        # Compare statements
        if not compare_payloads(jws_payload, body_payload, sha2_key):
            raise BadRequest(
                "The JWS is not valid - payload and body statements do not match")
Example #7
0
def validate_signature(tup, part):
    sha2_key = tup[1][0]
    signature = get_part_payload(part)
    algorithm = jws.get_unverified_headers(signature).get('alg', None)
    if not algorithm:
        raise BadRequest("No signing algorithm found for JWS signature")
    if algorithm != 'RS256' and algorithm != 'RS384' and algorithm != 'RS512':
        raise BadRequest(
            "JWS signature must be calculated with SHA-256, SHA-384 or" \
            "SHA-512 algorithms")
    x5c = jws.get_unverified_headers(signature).get('x5c', None)
    jws_payload = jws.get_unverified_claims(signature)
    body_payload = tup[0]
    # If x.509 was used to sign, the public key should be in the x5c header and you need to verify it
    # If using RS256, RS384, or RS512 some JWS libs require a real private key to create JWS - xAPI spec
    # only has SHOULD - need to look into. If x.509 is necessary then
    # if no x5c header is found this should fail
    if x5c:
        verified = False
        try:
            verified = jws.verify(signature, cert_to_key(x5c[0]), algorithm)
        except Exception as e:
            raise BadRequest("The JWS is not valid: %s" % e.message)
        else:
            if not verified:
                raise BadRequest(
                    "The JWS is not valid - could not verify signature")
            # Compare statements
            if not compare_payloads(jws_payload, body_payload, sha2_key):
                raise BadRequest(
                    "The JWS is not valid - payload and body statements do not match"
                )
    else:
        # Compare statements
        if not compare_payloads(jws_payload, body_payload, sha2_key):
            raise BadRequest(
                "The JWS is not valid - payload and body statements do not match"
            )
Example #8
0
def validate_non_signature_attachment(unsigned_stmts, sha2s, part_dict):
    for tup in unsigned_stmts:
        atts = tup[0]['attachments']
        for att in atts:
            sha2 = att.get('sha2')
            # Doesn't have to be there if fileUrl
            if 'fileUrl' not in att:
                # Should be listed in sha2s - sha2s couldn't not match
                if sha2 not in sha2s:
                    raise BadRequest(
                        "Could not find attachment payload with sha: %s" % sha2)                    
                part = part_dict[sha2]
                signature = get_part_payload(part)
                try:
                    jws.get_unverified_headers(signature)
                except Exception, e:
                    # If there is an error that means the payload is not a JWS
                    # signature which is what we expected
                    pass
                else:
                    raise BadRequest(
                        "usageType must be 'http://adlnet.gov/expapi/attachments/signature' when "\
                        "signing statements")
Example #9
0
def validate_non_signature_attachment(unsigned_stmts, sha2s, part_dict):
    for tup in unsigned_stmts:
        atts = tup[0]['attachments']
        for att in atts:
            sha2 = att.get('sha2')
            # Doesn't have to be there if fileUrl
            if 'fileUrl' not in att:
                # Should be listed in sha2s - sha2s couldn't not match
                if sha2 not in sha2s:
                    raise BadRequest(
                        "Could not find attachment payload with sha: %s" %
                        sha2)
                part = part_dict[sha2]
                signature = get_part_payload(part)
                try:
                    jws.get_unverified_headers(signature)
                except Exception, e:
                    # If there is an error that means the payload is not a JWS
                    # signature which is what we expected
                    pass
                else:
                    raise BadRequest(
                        "usageType must be 'http://adlnet.gov/expapi/attachments/signature' when "\
                        "signing statements")
Example #10
0
def get_unverified_header(token):
    """Returns the decoded headers without verification of any kind.

    Args:
        token (str): A signed JWT to decode the headers from.

    Returns:
        dict: The dict representation of the token headers.

    Raises:
        JWTError: If there is an exception decoding the token.
    """
    try:
        headers = jws.get_unverified_headers(token)
    except Exception:
        raise JWTError('Error decoding token headers.')

    return headers
Example #11
0
def get_unverified_header(token):
    """Returns the decoded headers without verification of any kind.

    Args:
        token (str): A signed JWT to decode the headers from.

    Returns:
        dict: The dict representation of the token headers.

    Raises:
        JWTError: If there is an exception decoding the token.
    """
    try:
        headers = jws.get_unverified_headers(token)
    except:
        raise JWTError('Error decoding token headers.')

    return headers
Example #12
0
def pkcs11_jws_verify(token: str) -> Optional[str]:
    """
    Perform JWS Sign with a key stored in an PKCS#11 token.

    This requires temporarily switching the key class for the algorithm in the token.
    See the documentation for pkcs11_jws_sign for more details.

    :return: The signed payload from the token, or None if verify is not supported
    """
    headers = jws.get_unverified_headers(token)
    _alg = headers['alg']

    # First, check if verify for this algorithm is supported by the token (current YubiKey can
    # sign with ECDSA, but not verify).
    # We have to do it in this hackish manner since the JOSE library will ignore any exceptions
    # from the verify method and just turn everything into an
    # JWSError('Signature verification failed.') exception.
    _p11key = P11Key('', _alg, P11Key_params)
    with _p11key.token.open(user_pin=_p11key.p11.pin) as session:
        _p11key._load_key(session)
        info = _p11key.token.slot.get_mechanism_info(_p11key._mechanism)
    logger.debug('Token mechanism info:\n{}'.format(info))
    if pkcs11.MechanismFlag.VERIFY not in info.flags:
        logger.info(
            'Verify is not a supported operation for algorithm {} using the PKCS#11 token'
            .format(_p11key._algorithm))
        return None

    orig_key = jwk.get_key(_alg)
    try:
        # First check if verify is a supported operation using this al
        jwk.register_key(_alg, P11Key)
        res = jws.verify(
            token, '', algorithms=[jwk.ALGORITHMS.ES256, jwk.ALGORITHMS.RS256])
    except:
        raise
    finally:
        jwk.register_key(_alg, orig_key)
    return res