def app_verify_session( app_session_token, data_pubkey_hex, config_path=CONFIG_PATH ): """ Verify and decode a JWT app session token. The session is valid if the signature matches and the token is not expired. Return the decoded session token payload on success Return None on error """ pubkey = str(data_pubkey_hex) verifier = jsontokens.TokenVerifier() valid = verifier.verify( app_session_token, pubkey ) if not valid: log.debug("Failed to verify with {}".format(pubkey)) return None session_jwt = jsontokens.decode_token(app_session_token) session = session_jwt['payload'] # must match session structure try: jsonschema.validate(session, APP_SESSION_SCHEMA) except ValidationError as ve: if BLOCKSTACK_TEST: log.exception(ve) return None # session must not be expired if session['expires'] < time.time(): log.debug("Token is expired") return None return session
def wrap_token(token): token_record = { "token": token, "decodedToken": decode_token(token), "encrypted": False } return token_record
def app_make_session(blockchain_id, app_public_key, app_domain, methods, app_public_keys, requester_device_id, master_data_privkey, session_lifetime=None, config_path=CONFIG_PATH): """ Make a session JWT for this application. Verify with user private key Sign with master private key Return {'session': session jwt, 'session_token': session token} on success Return {'error': ...} on error """ conf = get_config(path=config_path) assert conf if session_lifetime is None: session_lifetime = conf.get('default_session_lifetime', 1e80) # ysi-storage.js assumes it needs to use an # uncompressed address. let's do that if we need to app_datastore_public_key = keylib.key_formatting.decompress(app_public_key) app_user_id = data.datastore_get_id(app_datastore_public_key) api_endpoint_host = conf.get('api_endpoint_host', DEFAULT_API_HOST) api_endpoint_port = conf.get('api_endpoint_port', DEFAULT_API_PORT) api_endpoint = '{}:{}'.format(api_endpoint_host, api_endpoint_port) ses = { 'version': 1, 'blockchain_id': blockchain_id, 'app_domain': app_domain, 'methods': methods, 'app_public_keys': app_public_keys, 'app_user_id': app_user_id, 'api_endpoint': api_endpoint, 'device_id': requester_device_id, 'storage': { 'classes': classify_storage_drivers(), 'preferences': {} }, 'timestamp': int(time.time()), 'expires': int(time.time() + session_lifetime), } jsonschema.validate(ses, APP_SESSION_SCHEMA) signer = jsontokens.TokenSigner() session_token = signer.sign(ses, master_data_privkey) session = jsontokens.decode_token(session_token) return {'session': session, 'session_token': session_token}
def verify_token(token, public_key_or_address, signing_algorithm="ES256K"): """ A function for validating an individual token. """ decoded_token = decode_token(token) decoded_token_payload = decoded_token["payload"] if "subject" not in decoded_token_payload: raise ValueError("Token doesn't have a subject") if "publicKey" not in decoded_token_payload["subject"]: raise ValueError("Token doesn't have a subject public key") if "issuer" not in decoded_token_payload: raise ValueError("Token doesn't have an issuer") if "publicKey" not in decoded_token_payload["issuer"]: raise ValueError("Token doesn't have an issuer public key") if "claim" not in decoded_token_payload: raise ValueError("Token doesn't have a claim") issuer_public_key = str(decoded_token_payload["issuer"]["publicKey"]) public_key_object = ECPublicKey(issuer_public_key) compressed_public_key = compress(issuer_public_key) decompressed_public_key = decompress(issuer_public_key) if public_key_object._type == PubkeyType.compressed: compressed_address = public_key_object.address() uncompressed_address = bin_hash160_to_address( bin_hash160( decompress(public_key_object.to_bin()) ) ) elif public_key_object._type == PubkeyType.uncompressed: compressed_address = bin_hash160_to_address( bin_hash160( compress(public_key_object.to_bin()) ) ) uncompressed_address = public_key_object.address() else: raise ValueError("Invalid issuer public key format") if public_key_or_address == compressed_public_key: pass elif public_key_or_address == decompressed_public_key: pass elif public_key_or_address == compressed_address: pass elif public_key_or_address == uncompressed_address: pass else: raise ValueError("Token public key doesn't match the verifying value") token_verifier = TokenVerifier() if not token_verifier.verify(token, public_key_object.to_pem()): raise ValueError("Token was not signed by the issuer public key") return decoded_token
def verify_token(token, public_key_or_address, signing_algorithm="ES256K"): """ A function for validating an individual token. """ decoded_token = decode_token(token) decoded_token_payload = decoded_token["payload"] if "subject" not in decoded_token_payload: raise ValueError("Token doesn't have a subject") if "publicKey" not in decoded_token_payload["subject"]: raise ValueError("Token doesn't have a subject public key") if "issuer" not in decoded_token_payload: raise ValueError("Token doesn't have an issuer") if "publicKey" not in decoded_token_payload["issuer"]: raise ValueError("Token doesn't have an issuer public key") if "claim" not in decoded_token_payload: raise ValueError("Token doesn't have a claim") issuer_public_key = str(decoded_token_payload["issuer"]["publicKey"]) public_key_object = ECPublicKey(issuer_public_key) compressed_public_key = compress(issuer_public_key) decompressed_public_key = decompress(issuer_public_key) if public_key_object._type == PubkeyType.compressed: compressed_address = public_key_object.address() uncompressed_address = bin_hash160_to_address( bin_hash160(decompress(public_key_object.to_bin()))) elif public_key_object._type == PubkeyType.uncompressed: compressed_address = bin_hash160_to_address( bin_hash160(compress(public_key_object.to_bin()))) uncompressed_address = public_key_object.address() else: raise ValueError("Invalid issuer public key format") if public_key_or_address == compressed_public_key: pass elif public_key_or_address == decompressed_public_key: pass elif public_key_or_address == compressed_address: pass elif public_key_or_address == uncompressed_address: pass else: raise ValueError("Token public key doesn't match the verifying value") token_verifier = TokenVerifier() if not token_verifier.verify(token, public_key_object.to_pem()): raise ValueError("Token was not signed by the issuer public key") return decoded_token
def app_make_session( blockchain_id, app_private_key, app_domain, methods, app_public_keys, requester_device_id, master_data_privkey, session_lifetime=None, config_path=CONFIG_PATH ): """ Make a session JWT for this application. Verify with user private key Sign with master private key Return {'session': session jwt, 'session_token': session token} on success Return {'error': ...} on error """ conf = get_config(path=config_path) assert conf if session_lifetime is None: session_lifetime = conf.get('default_session_lifetime', 1e80) app_public_key = get_pubkey_hex(app_private_key) app_user_id = data.datastore_get_id(app_public_key) api_endpoint_host = conf.get('api_endpoint_host', DEFAULT_API_HOST) api_endpoint_port = conf.get('api_endpoint_port', DEFAULT_API_PORT) api_endpoint = '{}:{}'.format(api_endpoint_host, api_endpoint_port) ses = { 'version': 1, 'blockchain_id': blockchain_id, 'app_domain': app_domain, 'methods': methods, 'app_public_keys': app_public_keys, 'app_user_id': app_user_id, 'api_endpoint': api_endpoint, 'device_id': requester_device_id, 'storage': { 'classes': classify_storage_drivers(), 'preferences': {} }, 'timestamp': int(time.time()), 'expires': int(time.time() + session_lifetime), } jsonschema.validate(ses, APP_SESSION_SCHEMA) signer = jsontokens.TokenSigner() session_token = signer.sign( ses, master_data_privkey ) session = jsontokens.decode_token(session_token) return {'session': session, 'session_token': session_token}
def test_signing_decoding_and_verifying(self): token_signer = TokenSigner(crypto_backend=openssl_backend) token = token_signer.sign(self.sample_payload, self.private_key_hex) #print token self.assertTrue(isinstance(token, (unicode, str))) decoded_token = TokenVerifier.decode(token) self.assertTrue(isinstance(decoded_token, dict)) decoded_token_2 = decode_token(token) #print json.dumps(decoded_token_2, indent=2) self.assertTrue(isinstance(decoded_token_2, dict)) token_verifier = TokenVerifier() token_is_valid = token_verifier.verify(token, self.public_key_hex) #print token_is_valid self.assertTrue(token_is_valid)
def app_verify_session( app_session_token, data_pubkey_hex, config_path=CONFIG_PATH ): """ Verify and decode a JWT app session token. The session is valid if the signature matches and the token is not expired. Return the decoded session token payload on success Return None on error """ pubkey = str(data_pubkey_hex) verifier = jsontokens.TokenVerifier() valid = False try: valid = verifier.verify( app_session_token, pubkey ) if not valid: log.debug("Failed to verify with {}".format(pubkey)) return None except: log.debug("Not a valid token") return None session = None session_jwt = None try: session_jwt = jsontokens.decode_token(app_session_token) session = session_jwt['payload'] except: log.debug("Failed to decode token") return None # must match session structure try: jsonschema.validate(session, APP_SESSION_SCHEMA) except ValidationError as ve: if BLOCKSTACK_TEST: log.exception(ve) return None # session must not be expired if session['expires'] < time.time(): log.debug("Token is expired") return None return session
def func(username): prof_json = subprocess.Popen(['blockstack', 'lookup', username], stdout=subprocess.PIPE).stdout.read() data = json.loads(prof_json) if data.get('error'): return zonefile = data['zonefile'] last_index = zonefile.rfind('"') url = zonefile[zonefile.rfind('"', 0, last_index) + 1:last_index] if (not "https://" in url): url = "https://" + url opened_url = urllib.urlopen(url) data = json.loads(opened_url.read()) token = jsontokens.decode_token(data[0]['token']) encoded_PK = token['payload']['subject']['publicKey'] key = keylib.public_key.ECPublicKey(str(encoded_PK)) return key.address()
def get_data_pubkeys(blockchain_id): """ Get device IDs and public keys for a blockchain ID TODO: load these from the token file """ ses = sessions[blockchain_id] session = jsontokens.decode_token(ses)['payload'] device_ids = [dk['device_id'] for dk in session['app_public_keys']] device_pubkeys = [dk['public_key'] for dk in session['app_public_keys']] data_pubkeys = [{ 'device_id': dev_id, 'public_key': pubkey, } for (dev_id, pubkey) in zip(device_ids, device_pubkeys)] print "\ndata public keys for {} are\n{}\n".format(blockchain_id, json.dumps(data_pubkeys, indent=4, sort_keys=True)) return data_pubkeys
def app_make_session( app_domain, methods, master_data_privkey_hex, app_user_id=None, app_user_privkey=None, session_lifetime=None, blockchain_ids=None, config_path=CONFIG_PATH ): """ Make a session JWT for this application. Verify with user private key Sign with master private key Return {'session': session jwt, 'session_token': session token} on success Return {'error': ...} on error """ if session_lifetime is None: conf = get_config(path=config_path) assert conf session_lifetime = conf.get('default_session_lifetime', 1e80) if app_user_id is None: if app_user_privkey is None: if master_data_privkey_hex is not None: assert app_domain is not None, "need app domain to derive app key" app_user_privkey = data.datastore_get_privkey(master_data_privkey_hex, app_domain, config_path=config_path) else: # TODO: load from disk raise NotImplemented("Local app user private keys are not supported at this time") app_user_pubkey = get_pubkey_hex(app_user_privkey) app_user_id = data.datastore_get_id(app_user_pubkey) ses = { 'app_domain': app_domain, 'methods': methods, 'app_user_id': app_user_id, 'timestamp': int(time.time()), 'expires': int(time.time() + session_lifetime), } if blockchain_ids is not None: ses['blockchain_ids'] = blockchain_ids jsonschema.validate(ses, APP_SESSION_SCHEMA) signer = jsontokens.TokenSigner() session_token = signer.sign( ses, master_data_privkey_hex ) session = jsontokens.decode_token(session_token) return {'session': session, 'session_token': session_token}
def parse_mutable_data(mutable_data_json_txt, public_key, public_key_hash=None, data_hash=None, bsk_version=None, return_public_key=False): """ Given the serialized JSON for a piece of mutable data, parse it into a JSON document. Verify that it was signed by public_key's or public_key_hash's private key. Try to verify with both keys, if given. Returns: * the parsed JSON dict on success (if a profile) * the raw data (otherwise) * the dict {'data': ..., 'public_key': ...} (if return_public_key is True) Return None on error """ # newer version? if mutable_data_json_txt.startswith("bsk2.") or bsk_version == 2: raw = False if not mutable_data_json_txt.startswith("bsk2."): # raw data; will authenticate with data hash raw = True if data_hash is None: log.error( "Corrupt data: data text does not start with 'bsk2.', and no data hash given" ) return None return parse_mutable_data_v2(mutable_data_json_txt, public_key, public_key_hash=public_key_hash, data_hash=data_hash, raw=raw, return_public_key=return_public_key) # legacy parser assert public_key is not None or public_key_hash is not None, 'Need a public key or public key hash' mutable_data_jwt = None try: mutable_data_jwt = json.loads(mutable_data_json_txt) assert isinstance(mutable_data_jwt, (dict, list)) except: # TODO: Check use of catchall exception handler log.error('Invalid JSON') return None mutable_data_json = None # try pubkey, if given if public_key is not None: mutable_data_json = blockstack_profiles.get_profile_from_tokens( mutable_data_jwt, str(public_key)) if len(mutable_data_json) > 0: if return_public_key: return { 'data': mutable_data_json, 'public_key': str(public_key) } else: return mutable_data_json msg = 'Failed to verify with public key "{}"' log.warn(msg.format(public_key)) # try pubkey address if public_key_hash is not None: # NOTE: these should always have version byte 0 # TODO: use jsontokens directly public_key_hash_0 = keylib.address_formatting.bin_hash160_to_address( keylib.address_formatting.address_to_bin_hash160( str(public_key_hash)), version_byte=0) mutable_data_json = blockstack_profiles.get_profile_from_tokens( mutable_data_jwt, public_key_hash_0) if len(mutable_data_json) > 0: log.debug('Verified with {}'.format(public_key_hash)) if return_public_key: profile_token = jsontokens.decode_token( mutable_data_jwt[0]['token']) issuer_public_key = profile_token['payload']['issuer'][ 'publicKey'] # use the one that corresponds to the address ret_pubkey = None if virtualchain.address_reencode( keylib.public_key_to_address( keylib.key_formatting.compress( str(issuer_public_key))) ) == virtualchain.address_reencode(str(public_key_hash)): ret_pubkey = keylib.key_formatting.compress( issuer_public_key) elif virtualchain.address_reencode( keylib.public_key_to_address( keylib.key_formatting.decompress( str(issuer_public_key))) ) == virtualchain.address_reencode(str(public_key_hash)): ret_pubkey = keylib.key_formatting.decompress( issuer_public_key) else: raise Exception( "BUG: public key {} does not match {}".format( issuer_public_key, public_key_hash)) return {'data': mutable_data_json, 'public_key': ret_pubkey} else: return mutable_data_json msg = 'Failed to verify with public key hash "{}" ("{}")' log.warn(msg.format(public_key_hash, public_key_hash_0)) # try sha256 hash if data_hash is not None: log.error("Verifying profiles by hash it not supported") return None