def test_key_signing(self): """Test that you can sign with a key""" signer_keypair = Key() message = generate_random_string(50) factory = CryptoFactory(sawtooth_signing.create_context("secp256k1")) signer = factory.new_signer( Secp256k1PrivateKey.from_hex(signer_keypair.private_key)) signature = signer.sign(bytes(message, "utf8")) self.assertTrue(SIGNATURE_PATTERN.match(signature)) return signature, message, signer_keypair.public_key
def get_test_inputs(self, message_type=RBACPayload.CREATE_USER): if message_type == RBACPayload.CREATE_USER: signer = Key() message = user_transaction_pb2.CreateUser(name="foobar") message.user_id = signer.public_key inputs = [make_user_address(signer.public_key)] outputs = inputs return message, message_type, inputs, outputs, signer else: raise Exception( "get_test_payload doesn't yet support {}".format(message_type))
def test_key_class_random_keys(self): value1 = Key() value2 = Key() self.assertTrue(isinstance(value1, Key)) self.assertTrue(isinstance(value2, Key)) self.assertNotEqual(value1, value2) self.assertIsInstance(value1.public_key, str) self.assertIsInstance(value2.public_key, str) self.assertIsInstance(value1.private_key, str) self.assertIsInstance(value2.private_key, str) self.assertEqual(len(value1.public_key), PUBLIC_KEY_LENGTH * 2) self.assertEqual(len(value2.public_key), PUBLIC_KEY_LENGTH * 2) self.assertEqual(len(value1.private_key), PRIVATE_KEY_LENGTH * 2) self.assertEqual(len(value2.private_key), PRIVATE_KEY_LENGTH * 2) self.assertTrue(PUBLIC_KEY_PATTERN.match(value1.public_key)) self.assertTrue(PUBLIC_KEY_PATTERN.match(value2.public_key)) self.assertTrue(PRIVATE_KEY_PATTERN.match(value1.private_key)) self.assertTrue(PRIVATE_KEY_PATTERN.match(value2.private_key)) self.assertNotEqual(value1.public_key, value2.public_key) self.assertNotEqual(value1.private_key, value2.private_key)
def unmake(batch_object, signer_public_key=None, batcher_public_key=BATCHER_KEY_PAIR.public_key): """ Will unmake a batch_request, batch_list, batch, transaction or payload, and return a list of the included messages. Validation of signatures will occur if public keys are provided. Only used for testing purposes. """ if isinstance( batch_object, (client_batch_submit_pb2.ClientBatchSubmitRequest, batch_pb2.BatchList), ): return list( itertools.chain(*[ unmake( batch_object=batch, signer_public_key=signer_public_key, batcher_public_key=batcher_public_key, ) for batch in batch_object.batches ])) if isinstance(batch_object, batch_pb2.Batch): batch_header = batch_pb2.BatchHeader() batch_header.ParseFromString(batch_object.header) if batcher_public_key: # pylint: disable=no-member assert batch_header.signer_public_key == batcher_public_key batcher_keypair = Key(public_key=batcher_public_key) assert batcher_keypair.verify( signature=batch_object.header_signature, message=batch_object.header) transactions = list(batch_object.transactions) return [ unmake_item(batch_object=transaction, signer_public_key=signer_public_key) for transaction in transactions ] return [ unmake_item(batch_object=batch_object, signer_public_key=signer_public_key) ]
def get_test_inputs(self, message_type=RBACPayload.CREATE_USER): """Returns test data inputs for testing batcher functions""" if message_type == RBACPayload.CREATE_USER: signer = Key() message = user_transaction_pb2.CreateUser(name="foobar") message.user_id = signer.public_key inputs = [addresser.user.address(message.user_id)] outputs = inputs return message, message_type, inputs, outputs, signer raise Exception("batcher test doesn't support message_type: {}".format( message_type))
def test_make_addresses(self): """Test making addresses without manager""" self.assertTrue(callable(self.rbac.user.make_addresses)) name = self.test.user.name() keypair = Key() message = self.rbac.user.make(user_id=keypair.public_key, name=name) inputs, outputs = self.rbac.user.make_addresses(message=message) user_address = self.rbac.user.address(object_id=message.user_id) self.assertIsInstance(inputs, list) self.assertIn(user_address, inputs) self.assertEqual(len(inputs), 1) self.assertEqual(inputs, outputs)
def test_make(self): """Test getting a test data user with keys""" self.assertTrue(callable(self.rbac.user.make)) name = self.test.user.name() keypair = Key() message = self.rbac.user.make(user_id=keypair.public_key, name=name) self.assertIsInstance(message, protobuf.user_transaction_pb2.CreateUser) self.assertIsInstance(message.user_id, str) self.assertIsInstance(message.name, str) self.assertEqual(message.user_id, keypair.public_key) self.assertEqual(message.name, name)
def test_key_signature_validation(self): signature, message, pubkey = self.test_key_signing() public_key = Secp256k1PublicKey.from_hex(pubkey) context = sawtooth_signing.create_context("secp256k1") self.assertTrue( context.verify(signature, bytes(message, "utf8"), public_key)) self.assertFalse( context.verify(signature, bytes(message + "foo", "utf8"), public_key)) other = Secp256k1PublicKey.from_hex(Key().public_key) self.assertFalse( context.verify(signature, bytes(message, "utf8"), other))
async def get_transactor_key(request): """Get transactor key out of request.""" id_dict = deserialize_api_key(request.app.config.SECRET_KEY, extract_request_token(request)) next_id = id_dict.get("id") auth_data = await get_auth_by_next_id(next_id) encrypted_private_key = auth_data.get("encrypted_private_key") private_key = decrypt_private_key(request.app.config.AES_KEY, next_id, encrypted_private_key) hex_private_key = binascii.hexlify(private_key) return Key(hex_private_key), next_id
def test_key_class_random_keys(self): """Test the key class generates different keys each time initialized""" value1 = Key() value2 = Key() self.assertTrue(isinstance(value1, Key)) self.assertTrue(isinstance(value2, Key)) self.assertNotEqual(value1, value2) self.assertIsInstance(value1.public_key, str) self.assertIsInstance(value2.public_key, str) self.assertIsInstance(value1.private_key, str) self.assertIsInstance(value2.private_key, str) self.assertEqual(len(value1.public_key), PUBLIC_KEY_LENGTH * 2) self.assertEqual(len(value2.public_key), PUBLIC_KEY_LENGTH * 2) self.assertEqual(len(value1.private_key), PRIVATE_KEY_LENGTH * 2) self.assertEqual(len(value2.private_key), PRIVATE_KEY_LENGTH * 2) self.assertTrue(PUBLIC_KEY_PATTERN.match(value1.public_key)) self.assertTrue(PUBLIC_KEY_PATTERN.match(value2.public_key)) self.assertTrue(PRIVATE_KEY_PATTERN.match(value1.private_key)) self.assertTrue(PRIVATE_KEY_PATTERN.match(value2.private_key)) self.assertNotEqual(value1.public_key, value2.public_key) self.assertNotEqual(value1.private_key, value2.private_key)
def test_unit_with_other_signer(self): """Test with signer is neither user nor manager""" user_key = Key() user_id = user_key.public_key manager_key = Key() manager_id = manager_key.public_key other_key = Key() name = self.test.user.name() message = protobuf.user_transaction_pb2.CreateUser( user_id=user_id, name=name, metadata=None, manager_id=manager_id) self.rbac.user.make_payload(message=message, signer_keypair=user_key) self.rbac.user.make_payload(message=message, signer_keypair=manager_key) with self.assertRaises(ValueError): self.rbac.user.make_payload(message=message, signer_keypair=other_key) with self.assertRaises(ValueError): self.rbac.user.create(signer_keypair=other_key, message=message)
def test_with_manager_not_in_state(self): """Test creating a user with manager not in state""" user_key = Key() user_id = user_key.public_key manager_key = Key() manager_id = manager_key.public_key name = self.test.user.name() message = protobuf.user_transaction_pb2.CreateUser( user_id=user_id, name=name, metadata=None, manager_id=manager_id) inputs, outputs = self.rbac.user.make_addresses( message=message, signer_keypair=user_key) payload = self.rbac.user.batch.make_payload( message=message, message_type=self.rbac.user.message_type, inputs=inputs, outputs=outputs, ) _, status = self.rbac.user.send(signer_keypair=user_key, payload=payload) self.assertStatusInvalid(status)
async def get_transactor_key(request): id_dict = deserialize_api_key(request.app.config.SECRET_KEY, extract_request_token(request)) user_id = id_dict.get("id") auth_data = await auth_query.fetch_info_by_user_id( request.app.config.DB_CONN, user_id) encrypted_private_key = auth_data.get("encrypted_private_key") private_key = decrypt_private_key(request.app.config.AES_KEY, user_id, encrypted_private_key) hex_private_key = binascii.hexlify(private_key) return Key(hex_private_key), user_id
def assertValidTransactionHeader(self, header, signature, payload, signer_public_key): """Check a transaction header is valid given a payload""" if isinstance(header, bytes): decoded = transaction_pb2.TransactionHeader() decoded.ParseFromString(header) header = decoded self.assertIsInstance(header, transaction_pb2.TransactionHeader) self.assertEqual(header.family_name, addresser.family.name) self.assertEqual(header.family_version, addresser.family.version) self.assertIsInstance(header.nonce, str) self.assertEqual(len(header.nonce), 32) self.assertEqual(header.signer_public_key, signer_public_key) self.assertEqual(header.payload_sha512, sha512(payload.SerializeToString()).hexdigest()) signer = Key(public_key=signer_public_key) self.assertTrue( signer.verify(signature=signature, message=header.SerializeToString())) other_key = Key() self.assertFalse( other_key.verify(signature=signature, message=header.SerializeToString())) self.assertEqual(header.inputs, payload.inputs) self.assertEqual(header.outputs, payload.outputs)
def assertValidBatch(self, batch, payload, signer_public_key, batcher_public_key): """Check a batch is valid given a payload""" self.assertIsInstance(batch, batch_pb2.Batch) batch_header = batch_pb2.BatchHeader() batch_header.ParseFromString(batch.header) self.assertIsInstance(batch_header, batch_pb2.BatchHeader) self.assertEqual(batch_header.signer_public_key, batcher_public_key) batcher_keypair = Key(public_key=batcher_public_key) self.assertTrue( batcher_keypair.verify( signature=batch.header_signature, message=batch_header.SerializeToString(), )) other_key = Key() self.assertFalse( other_key.verify( signature=batch.header_signature, message=batch_header.SerializeToString(), )) transactions = list(batch.transactions) self.assertEqual(len(transactions), 1) self.assertValidTransaction( transaction=transactions[0], payload=payload, signer_public_key=signer_public_key, )
async def create_new_user(request): required_fields = ["name", "username", "password", "email"] utils.validate_fields(required_fields, request.json) # Generate keys txn_key = Key() txn_user_id = rbac.user.unique_id() encrypted_private_key = encrypt_private_key(request.app.config.AES_KEY, txn_key.public_key, txn_key.private_key_bytes) # Build create user transaction batch_list = rbac.user.batch_list( signer_keypair=txn_key, signer_user_id=txn_user_id, user_id=txn_user_id, name=request.json.get("name"), username=request.json.get("username"), email=request.json.get("email"), metadata=request.json.get("metadata"), manager=request.json.get("manager"), key=txn_key.public_key, ) # Submit transaction and wait for complete await utils.send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) # Save new user in auth table hashed_password = hashlib.sha256( request.json.get("password").encode("utf-8")).hexdigest() auth_entry = { "user_id": txn_user_id, "hashed_password": hashed_password, "encrypted_private_key": encrypted_private_key, "username": request.json.get("username"), "email": request.json.get("email"), } conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) await auth_query.create_auth_entry(conn, auth_entry) conn.close() # Send back success response return create_user_response(request, txn_user_id)
def test_create(self): """Test creating a user on the blockchain""" self.assertTrue(callable(self.rbac.user.create)) name = self.test.user.name() user_key = Key() user_id = user_key.public_key message = self.rbac.user.make(user_id=user_id, name=name) user, status = self.rbac.user.create(signer_keypair=user_key, message=message, object_id=user_id) self.assertStatusSuccess(status) self.assertEqualMessage(user, message)
def get_testdata_inputs(self, message_type=RBACPayload.CREATE_USER): """Get test data inputs for a create user message""" if message_type == RBACPayload.CREATE_USER: signer = Key() message = user_transaction_pb2.CreateUser(name=self.get_testdata_name()) message.user_id = signer.public_key inputs = [self.address(signer.public_key)] outputs = inputs return message, message_type, inputs, outputs, signer else: raise Exception( "get_testdata_payload doesn't yet support {}".format(message_type) )
def load_config(app): # pylint: disable=too-many-branches # CLI Options will override config file options opts = parse_args(sys.argv[1:]) app.config.HOST = opts.host app.config.PORT = opts.port app.config.VALIDATOR_HOST = opts.validator_host app.config.VALIDATOR_PORT = opts.validator_port app.config.TIMEOUT = int(opts.timeout) app.config.DB_HOST = opts.db_host app.config.DB_PORT = opts.db_port app.config.DB_NAME = opts.db_name app.config.CHATBOT_HOST = opts.chatbot_host app.config.CHATBOT_PORT = opts.chatbot_port app.config.CLIENT_HOST = opts.client_host app.config.CLIENT_PORT = opts.client_port app.config.DEBUG = bool(opts.debug) app.config.SECRET_KEY = opts.secret_key app.config.AES_KEY = opts.aes_key app.config.AIOHTTP_CONN_LIMIT = opts.aiohttp_conn_limit app.config.AIOHTTP_DNS_TTL = opts.aiohttp_dns_ttl if SECRET_KEY is DEFAULT_CONFIG["SECRET_KEY"]: LOGGER.warning( """ --------------------------------------------- WARNING: The API secret key was not provided. Using an insecure default key. Consider adding the following to the environment (e.g. .env file): SECRET_KEY=%s --------------------------------------------- """, generate_secret_key(), ) if AES_KEY is DEFAULT_CONFIG["AES_KEY"]: LOGGER.warning( """ --------------------------------------------- WARNING: The AES secret key was not provided. Using an insecure default key. Consider adding the following to the environment (e.g. .env file): AES_KEY=%s --------------------------------------------- """, generate_aes_key(), ) app.config.BATCHER_KEY_PAIR = Key()
def unmake_item(batch_object, signer_public_key=None): """ Will unmake_item a transaction or payload, and return a message. Validation of signatures will occur if public keys are provided. Only used for testing purposes. """ if isinstance(batch_object, transaction_pb2.Transaction): header = transaction_pb2.TransactionHeader() header.ParseFromString(batch_object.header) # pylint: disable=no-member assert header.payload_sha512 == sha512( batch_object.payload).hexdigest() if signer_public_key: assert header.signer_public_key == signer_public_key signer = Key(public_key=signer_public_key) signer.verify(signature=batch_object.header_signature, message=batch_object.header) payload = RBACPayload() payload.ParseFromString(batch_object.payload) batch_object = payload if isinstance(batch_object, RBACPayload): message, _ = unmake_payload(payload=batch_object) return message raise Exception("unmake doesn't handle type {}\n{}".format( type(batch_object), batch_object))
async def fetch_info_by_username(request): username = request.json.get("id") conn = await db_utils.create_connection( request.app.config.DB_HOST, request.app.config.DB_PORT, request.app.config.DB_NAME, ) result = (await r.table("auth").get_all( username, index="username").limit(1).coerce_to("array").run(conn)) if result: return result[0] # Auth record not found, check if the username exists result = (await r.table("users").get_all( username, index="username").limit(1).coerce_to("array").run(conn)) if not result: raise ApiNotFound( "No user with username '{}' exists.".format(username)) result = result[0] # Generate and store key and auth record first time a user logs in user_id = result.get("user_id") user_key = Key() batch_list = rbac.key.batch_list( signer_keypair=user_key, signer_user_id=user_id, user_id=user_id, key_id=user_key.public_key, ) await utils.send(request.app.config.VAL_CONN, batch_list, request.app.config.TIMEOUT) encrypted_private_key = encrypt_private_key(request.app.config.AES_KEY, user_key.public_key, user_key.private_key_bytes) auth_entry = { "user_id": user_id, "username": result.get("username"), "email": result.get("email"), "encrypted_private_key": encrypted_private_key, } await r.table("auth").insert(auth_entry).run(conn) conn.close() return auth_entry
async def non_admin_creation(request): """Creating non-admin users. Args: request: obj: a request object """ try: txn_key, txn_user_id = await get_transactor_key(request) is_admin = await check_admin_status(txn_user_id) if not is_admin: raise ApiForbidden( "You do not have the authorization to create an account.") next_id = str(uuid4()) key_pair = Key() return txn_key, txn_user_id, next_id, key_pair except BadSignature: raise ApiForbidden( "You do not have the authorization to create an account.")
async def next_admin_creation(request): """Creating the admin user. Used exclusively for the creation of the NEXT admin Args: request: obj: a request object """ try: txn_key, txn_user_id = await get_transactor_key(request) is_admin = await check_admin_status(txn_user_id) if not is_admin: raise ApiUnauthorized( "You do not have the authorization to create an account.") except ApiUnauthorized: txn_key = Key() txn_user_id = str(uuid4()) key_pair = txn_key next_id = txn_user_id return txn_key, txn_user_id, next_id, key_pair
def load_config(app): """Load configuration (alphabetical)""" app.config.AES_KEY = get_config("AES_KEY") app.config.AIOHTTP_CONN_LIMIT = int(get_config("AIOHTTP_CONN_LIMIT")) app.config.AIOHTTP_DNS_TTL = int(get_config("AIOHTTP_DNS_TTL")) app.config.BATCHER_KEY_PAIR = Key() app.config.CHATBOT_HOST = get_config("CHATBOT_HOST") app.config.CHATBOT_PORT = get_config("CHATBOT_PORT") app.config.CLIENT_HOST = get_config("CLIENT_HOST") app.config.CLIENT_PORT = get_config("CLIENT_PORT") app.config.DB_HOST = get_config("DB_HOST") app.config.DB_NAME = get_config("DB_NAME") app.config.DB_PORT = get_config("DB_PORT") app.config.DEBUG = bool(get_config("DEBUG")) app.config.LOGGING_LEVEL = get_config("LOGGING_LEVEL") app.config.SECRET_KEY = get_config("SECRET_KEY") app.config.PORT = get_config("SERVER_PORT") app.config.TIMEOUT = int(get_config("TIMEOUT")) app.config.VALIDATOR = get_config("VALIDATOR")
def make_with_key( self, name, user_id=None, user_name=None, email=None, metadata=None, manager_id=None, ): keypair = Key() if user_id is None: user_id = keypair.public_key user = self.make( user_id=user_id, name=name, user_name=user_name, email=email, metadata=metadata, manager_id=manager_id, ) return user, keypair
def test_unit_with_self_manager(self): """Test creating a user with self as manager""" user_key = Key() user_id = user_key.public_key name = self.test.user.name() with self.assertRaises(ValueError): message = self.rbac.user.make(user_id=user_id, name=name, metadata=None, manager_id=user_id) message = protobuf.user_transaction_pb2.CreateUser(user_id=user_id, name=name, metadata=None, manager_id=user_id) with self.assertRaises(ValueError): payload = self.rbac.user.make_payload(message=message) with self.assertRaises(ValueError): payload = self.rbac.user.create(signer_keypair=user_key, message=message)
def load_config(app): """Load configuration (alphabetical)""" host = get_config("HOST") + ":" + get_config("SERVER_PORT") app.config.AES_KEY = get_config("AES_KEY") app.config.AIOHTTP_CONN_LIMIT = int(get_config("AIOHTTP_CONN_LIMIT")) app.config.AIOHTTP_DNS_TTL = int(get_config("AIOHTTP_DNS_TTL")) app.config.API_CONTACT_EMAIL = "*****@*****.**" app.config.API_DESCRIPTION = "Available API endpoints for Sawtooth Next Directory." app.config.API_HOST = host app.config.API_LICENSE_NAME = "Apache License 2.0" app.config.API_LICENSE_URL = ( "https://github.com/tmobile/sawtooth-next-directory/blob/develop/LICENSE" ) app.config.API_PRODUCES_CONTENT_TYPES = ["application/json"] app.config.API_SCHEMES = ["http", "https"] app.config.API_TITLE = "Sawtooth Next Directory API" app.config.API_SECURITY = [{"authToken": []}] app.config.API_SECURITY_DEFINITIONS = { "authToken": { "type": "apiKey", "in": "header", "name": "Authorization", "description": "Paste your auth token.", } } app.config.BATCHER_KEY_PAIR = Key() app.config.CHATBOT_HOST = get_config("CHATBOT_HOST") app.config.CHATBOT_PORT = get_config("CHATBOT_PORT") app.config.CLIENT_HOST = get_config("CLIENT_HOST") app.config.CLIENT_PORT = get_config("CLIENT_PORT") app.config.DB_HOST = get_config("DB_HOST") app.config.DB_NAME = get_config("DB_NAME") app.config.DB_PORT = get_config("DB_PORT") app.config.DEBUG = bool(get_config("DEBUG")) app.config.LOGGING_LEVEL = get_config("LOGGING_LEVEL") app.config.SECRET_KEY = get_config("SECRET_KEY") app.config.PORT = int(get_config("SERVER_PORT")) app.config.TIMEOUT = int(get_config("TIMEOUT")) app.config.VALIDATOR = get_config("VALIDATOR") app.config.WORKERS = int(get_config("WORKERS"))
def imports(self): """ Imports a test user Imported user has no key assignment """ signer_keypair = Key() # TODO: will need to change to a provider key message = self.imports_message() status = rbac.user.imports.new( signer_user_id=message.user_id, signer_keypair=signer_keypair, message=message, ) assert len(status) == 1 assert status[0]["status"] == "COMMITTED" user = rbac.user.get(object_id=message.user_id) assert user.user_id == message.user_id assert user.name == message.name return user
def make_with_key( self, name, user_id=None, user_name=None, email=None, metadata=None, manager_id=None, ): """Makes a CreateUser message with a new keypair""" keypair = Key() if user_id is None: user_id = keypair.public_key message = self.make( user_id=user_id, name=name, user_name=user_name, email=email, metadata=metadata, manager_id=manager_id, ) return message, keypair
def assertValidTransactionHeader( self, header, signature, message, message_type, inputs, outputs, signer_public_key, ): if isinstance(header, bytes): decoded = transaction_pb2.TransactionHeader() decoded.ParseFromString(header) header = decoded payload = self.make_payload(message=message, message_type=message_type) self.assertIsInstance(header, transaction_pb2.TransactionHeader) self.assertEqual(header.family_name, addresser.FAMILY_NAME) self.assertEqual(header.family_version, addresser.FAMILY_VERSION) self.assertIsInstance(header.nonce, str) self.assertEqual(len(header.nonce), 32) self.assertEqual(header.signer_public_key, signer_public_key) self.assertEqual( header.payload_sha512, sha512(payload.SerializeToString()).hexdigest() ) signer = Key(public_key=signer_public_key) self.assertTrue( signer.verify(signature=signature, message=header.SerializeToString()) ) other_key = Key() self.assertFalse( other_key.verify(signature=signature, message=header.SerializeToString()) ) self.assertEqual(header.inputs, inputs) self.assertEqual(header.outputs, outputs) self.assertValidInputs( inputs=header.inputs, outputs=header.outputs, message_type=message_type, message=message, )