Skip to content

Commit 5cca292

Browse files
fix: Load PEM private key from base64-encoded parameter
Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
1 parent 70d78be commit 5cca292

File tree

4 files changed

+37
-10
lines changed

4 files changed

+37
-10
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ classifiers = [
2424
"Programming Language :: Python :: 3.14",
2525
]
2626
dependencies = [
27+
"cryptography>=42",
2728
"meltano>=3.7",
2829
"snowflake-connector-python>=4,<5",
2930
]

src/meltano_state_backend_snowflake/backend.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,21 @@ def __init__(
178178
self._ensure_tables()
179179

180180
def _load_private_key(self, pkey_base64: str) -> bytes:
181-
# Restore '+' chars that parse_qs decodes as spaces
182-
pkey_base64 = pkey_base64.replace(" ", "+")
183-
# Add padding if stripped (e.g. from URL query params)
184-
pkey_base64 += "=" * (-len(pkey_base64) % 4)
185-
return base64.b64decode(pkey_base64)
181+
from cryptography.hazmat.primitives.serialization import (
182+
Encoding,
183+
NoEncryption,
184+
PrivateFormat,
185+
load_pem_private_key,
186+
)
187+
188+
# We assume the value is a PEM private key
189+
private_key = load_pem_private_key(base64.b64decode(pkey_base64), password=None)
190+
191+
return private_key.private_bytes(
192+
encoding=Encoding.DER,
193+
format=PrivateFormat.PKCS8,
194+
encryption_algorithm=NoEncryption(),
195+
)
186196

187197
@cached_property
188198
def connection(self) -> snowflake.connector.SnowflakeConnection:

tests/test_backend.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ def project(tmp_path: Path) -> Project:
3737
@pytest.fixture
3838
def pkey_base64() -> str:
3939
dummy_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
40-
dummy_private_key = dummy_key.private_bytes(
41-
encoding=serialization.Encoding.DER,
40+
dummy_pem = dummy_key.private_bytes(
41+
encoding=serialization.Encoding.PEM,
4242
format=serialization.PrivateFormat.PKCS8,
4343
encryption_algorithm=serialization.NoEncryption(),
4444
)
45-
return base64.b64encode(dummy_private_key).decode("utf-8")
45+
return base64.b64encode(dummy_pem).decode("utf-8")
4646

4747

4848
def test_get_manager(project: Project) -> None:
@@ -464,7 +464,14 @@ def test_private_key_from_query_param(base_uri: str, pkey_base64: str) -> None:
464464
"""Test private key extracted from query parameter."""
465465
manager = SnowflakeStateStoreManager(uri=f"{base_uri}&private_key_base64={pkey_base64}")
466466
assert manager.private_key is not None
467-
assert manager.private_key == base64.b64decode(pkey_base64)
467+
# _load_private_key re-serializes to DER PKCS8
468+
pem_key = serialization.load_pem_private_key(base64.b64decode(pkey_base64), password=None)
469+
expected_der = pem_key.private_bytes(
470+
encoding=serialization.Encoding.DER,
471+
format=serialization.PrivateFormat.PKCS8,
472+
encryption_algorithm=serialization.NoEncryption(),
473+
)
474+
assert manager.private_key == expected_der
468475

469476

470477
def test_missing_account_validation() -> None:
@@ -519,7 +526,14 @@ def test_private_key_without_password(pkey_base64: str) -> None:
519526
uri="snowflake://myuser@myaccount/mydb?warehouse=mywh",
520527
private_key_base64=pkey_base64,
521528
)
522-
assert manager.private_key == base64.b64decode(pkey_base64)
529+
# _load_private_key re-serializes to DER PKCS8
530+
pem_key = serialization.load_pem_private_key(base64.b64decode(pkey_base64), password=None)
531+
expected_der = pem_key.private_bytes(
532+
encoding=serialization.Encoding.DER,
533+
format=serialization.PrivateFormat.PKCS8,
534+
encryption_algorithm=serialization.NoEncryption(),
535+
)
536+
assert manager.private_key == expected_der
523537
assert manager.password is None
524538

525539

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)