Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/google-auth/google/auth/_service_account_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Helper functions for loading data from a Google service account file."""

from collections.abc import Mapping
import io
import json

Expand Down Expand Up @@ -53,6 +54,13 @@ def from_dict(data, require=None, use_rsa_signer=True):
)

# Create a signer.
if (
isinstance(data, Mapping)
and isinstance(data.get("private_key"), (str, bytes))
and crypt.is_mldsa_key(data["private_key"])
):
return crypt.PqcSigner.from_service_account_info(data)

if use_rsa_signer:
signer = crypt.RSASigner.from_service_account_info(data)
else:
Expand Down
83 changes: 83 additions & 0 deletions packages/google-auth/tests/test__service_account_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import base64
import json
import os
from unittest import mock

import pytest # type: ignore

Expand Down Expand Up @@ -107,3 +109,84 @@ def test_from_filename_es384_signer():
assert isinstance(signer, crypt.EsSigner)
assert signer.key_id == GDCH_SERVICE_ACCOUNT_ES384_INFO["private_key_id"]
assert signer.algorithm == "ES384"


def test_from_dict_mldsa_signer_auto_detect_upgrade_required(monkeypatch):
if crypt.pqc is not None:
monkeypatch.setattr(crypt.pqc, "mldsa", None)
else:
mock_pqc = mock.Mock()
mock_pqc.mldsa = None
mock_pqc.is_mldsa_key = lambda key: True
mock_pqc.PqcSigner.from_service_account_info = mock.Mock(
side_effect=RuntimeError(
"Post-Quantum ML-DSA Service Account keys require cryptography>=47.0.0. "
"Please upgrade your cryptography library (pip install 'cryptography>=47.0.0')."
)
)
monkeypatch.setattr(crypt, "pqc", mock_pqc)

der_bytes = (
b"\x30\x20\x02\x01\x00\x30\x0b"
b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x03\x12"
b"\x04\x0a\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00"
)
b64_key = base64.b64encode(der_bytes).decode("ascii")
mldsa_pem = f"-----BEGIN PRIVATE KEY-----\n{b64_key}\n-----END PRIVATE KEY-----"
info = {
"private_key": mldsa_pem,
"private_key_id": "test_mldsa_key_id",
"client_email": "test@example.com",
}
with pytest.raises(RuntimeError) as excinfo:
_service_account_info.from_dict(info)

assert (
"Post-Quantum ML-DSA Service Account keys require cryptography>=47.0.0"
in str(excinfo.value)
)
assert (
"Please upgrade your cryptography library (pip install 'cryptography>=47.0.0')"
in str(excinfo.value)
)


def test_from_dict_mldsa_signer_auto_detect_success(monkeypatch):
class MockMLDSA65PrivateKey:
pass

mock_mldsa = mock.Mock()
mock_mldsa.MLDSA65PrivateKey = MockMLDSA65PrivateKey

der_bytes = (
b"\x30\x20\x02\x01\x00\x30\x0b"
b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x03\x12"
b"\x04\x0a\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00"
)
b64_key = base64.b64encode(der_bytes).decode("ascii")
mldsa_pem = f"-----BEGIN PRIVATE KEY-----\n{b64_key}\n-----END PRIVATE KEY-----"
info = {
"private_key": mldsa_pem,
"private_key_id": "test_mldsa_key_id",
"client_email": "test@example.com",
}

if crypt.pqc is not None:
monkeypatch.setattr(crypt.pqc, "mldsa", mock_mldsa)
monkeypatch.setattr(
crypt.pqc.serialization,
"load_pem_private_key",
lambda key, password, backend: MockMLDSA65PrivateKey(),
)
else:
mock_pqc = mock.Mock()
mock_pqc.mldsa = mock_mldsa
mock_pqc.is_mldsa_key = lambda key: True
mock_pqc.PqcSigner.from_service_account_info = mock.Mock(
return_value=mock.Mock(key_id="test_mldsa_key_id", algorithm="ML-DSA-65")
)
monkeypatch.setattr(crypt, "pqc", mock_pqc)

signer = _service_account_info.from_dict(info)
assert signer.key_id == "test_mldsa_key_id"
assert signer.algorithm == "ML-DSA-65"
Loading