Skip to content

Commit a554be1

Browse files
committed
fix(spanner): avoid bytearray memory copies and handle premature stream closure
1 parent 1340081 commit a554be1

3 files changed

Lines changed: 30 additions & 11 deletions

File tree

packages/google-cloud-spanner/google/cloud/spanner_v1/omni/login_client.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,19 @@ def login(
9393
try:
9494
call = self._stub.Login(req_iterator, timeout=timeout)
9595

96+
def _safe_next(stage: str) -> login_pb2.LoginResponse:
97+
try:
98+
return next(call)
99+
except StopIteration:
100+
raise ValueError(f"Server closed stream prematurely during {stage}")
101+
96102
# Step 1: Handshake Request
97103
handshake_req = login_pb2.LoginRequest(
98104
username=username,
99105
handshake_request=authentication_pb2.PasswordAuthenticationHandshakeRequest(),
100106
)
101107
req_iterator.send(handshake_req)
102-
handshake_resp = next(call)
108+
handshake_resp = _safe_next("handshake")
103109

104110
if not handshake_resp.HasField("handshake_response"):
105111
raise ValueError("Failed to receive handshake response from server")
@@ -119,15 +125,15 @@ def login(
119125
# Step 2: Initial OPAQUE Request
120126
initial_req = authenticator.initial_request()
121127
req_iterator.send(initial_req)
122-
initial_resp = next(call)
128+
initial_resp = _safe_next("initial OPAQUE exchange")
123129

124130
# Step 3: Final OPAQUE Request
125131
final_req = authenticator.final_request(initial_resp)
126132
req_iterator.send(final_req)
127133
req_iterator.close()
128134

129135
# Final Response with AccessToken
130-
final_resp = next(call)
136+
final_resp = _safe_next("final OPAQUE exchange")
131137
if not final_resp.HasField("access_token"):
132138
raise ValueError(
133139
"Server failed to return an access token in final response"

packages/google-cloud-spanner/google/cloud/spanner_v1/omni/opaque.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ def initial_request(self) -> login_pb2.LoginRequest:
437437
raise ValueError("Authenticator already used or password not available")
438438

439439
try:
440-
blinded_message, blind_scalar = blind(bytes(self._password))
440+
blinded_message, blind_scalar = blind(self._password)
441441
self._blind = bytearray(blind_scalar)
442442

443443
self._client_nonce = nonce()
@@ -497,7 +497,7 @@ def final_request(
497497
)
498498

499499
try:
500-
oprf = finalize(bytes(self._blind), evaluated_message)
500+
oprf = finalize(self._blind, evaluated_message)
501501
stretched_oprf = stretch(oprf, self.hash_parameters)
502502
randomized_password = extract(concat(oprf, stretched_oprf))
503503

@@ -529,12 +529,8 @@ def final_request(
529529
server_public_key,
530530
)
531531

532-
dh1 = diffie_hellman(
533-
bytes(self._client_private_keyshare), server_public_keyshare
534-
)
535-
dh2 = diffie_hellman(
536-
bytes(self._client_private_keyshare), server_public_key
537-
)
532+
dh1 = diffie_hellman(self._client_private_keyshare, server_public_keyshare)
533+
dh2 = diffie_hellman(self._client_private_keyshare, server_public_key)
538534
dh3 = diffie_hellman(client_private_key, server_public_keyshare)
539535

540536
input_key_material = concat(dh1, dh2, dh3)

packages/google-cloud-spanner/tests/unit/omni/test_login_client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,23 @@ def test_login_exception_cancels_call(self):
277277

278278
mock_call.cancel.assert_called_once()
279279

280+
def test_login_premature_stream_closure(self):
281+
mock_call = mock.MagicMock()
282+
mock_call.__next__.side_effect = StopIteration
283+
284+
with mock.patch(
285+
"google.cloud.spanner_v1.omni.proto.login_pb2_grpc.LoginServiceStub"
286+
) as mock_stub_cls:
287+
mock_stub = mock_stub_cls.return_value
288+
mock_stub.Login.return_value = mock_call
289+
290+
client = LoginClient(self.mock_channel)
291+
with self.assertRaises(ValueError) as cm:
292+
client.login("user", "pass")
293+
self.assertIn(
294+
"Server closed stream prematurely during handshake", str(cm.exception)
295+
)
296+
280297

281298
if __name__ == "__main__":
282299
unittest.main()

0 commit comments

Comments
 (0)