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
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
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 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")
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" )
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")
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
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
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