Skip to content

Commit

Permalink
Add priv key uri support to SignerStore
Browse files Browse the repository at this point in the history
implements option 2 from repository-service-tuf/repository-service-tuf#580
supersedes #427 (does not include a custom "relative file path signer",
can be added in a follow-up PR)

--

Change `SignerStore.get` to load non-cached signers from private key uri
configured on the passed public key in a  "x-rstuf-online-key-uri" field.

If the public key does not include a uri, RSTUF_KEYVAULT_BACKEND is used
as fallback.

securesystemslib.signer.CryptoSigner is "registered" to load signers
from private key files. No key specific secrets handling is added. This
means the keys must be stored unencryped, preferrably using the secrets
handling of the deployment platform (e.g. docker secrets).

Default schemes in `securesystemslib.signer.SIGNER_FOR_URI_SCHEME` can
be used but are untested.

**Tests**
Add test to load actual signer from private key file.

Uses new unencrypted ed25519 private key copied from:
secure-systems-lab/securesystemslib@7952c3f

Public key stubs in other tests are updated, because signer store now
reads the `unrecognized_fields` attribute, which is mandatory in Key
objects.

--

implements option 2 described in
repository-service-tuf/repository-service-tuf#580

mostly supersedes #427 (does not include a custom "relative file path
signer")

Signed-off-by: Lukas Puehringer <[email protected]>
  • Loading branch information
lukpueh committed Feb 5, 2024
1 parent 331e7e7 commit d5d915c
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 11 deletions.
37 changes: 29 additions & 8 deletions repository_service_tuf_worker/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@
# SPDX-License-Identifier: MIT

from dynaconf import Dynaconf
from securesystemslib.signer import Key, Signer
from securesystemslib.signer import (
SIGNER_FOR_URI_SCHEME,
CryptoSigner,
Key,
Signer,
)

from repository_service_tuf_worker.interfaces import IKeyVault

RSTUF_ONLINE_KEY_URI_FIELD = "x-rstuf-online-key-uri"

# Register non-default securesystemslib file signer
# secure-systems-lab/securesystemslib#617
SIGNER_FOR_URI_SCHEME[CryptoSigner.FILE_URI_SCHEME] = CryptoSigner


class SignerStore:
"""Generic signer store."""
Expand All @@ -16,15 +27,25 @@ def __init__(self, settings: Dynaconf):
self._signers: dict[str, Signer] = {}

def get(self, key: Key) -> Signer:
"""Return signer for passed key."""
"""Return signer for passed key.
- signer is loaded from the uri included in the passed public key
(see SIGNER_FOR_URI_SCHEME for available uri schemes)
- RSTUF_KEYVAULT_BACKEND is used as fallback, if no URI is included
"""

if key.keyid not in self._signers:
vault = self._settings.get("KEYVAULT")
if not isinstance(vault, IKeyVault):
raise ValueError(
"RSTUF_KEYVAULT_BACKEND is required for online signing"
)
if uri := key.unrecognized_fields.get(RSTUF_ONLINE_KEY_URI_FIELD):
self._signers[key.keyid] = Signer.from_priv_key_uri(uri, key)

else:
vault = self._settings.get("KEYVAULT")
if not isinstance(vault, IKeyVault):
raise ValueError(
"RSTUF_KEYVAULT_BACKEND is required for online signing"
)

self._signers[key.keyid] = vault.get(key)
self._signers[key.keyid] = vault.get(key)

return self._signers[key.keyid]
3 changes: 3 additions & 0 deletions tests/files/pem/ed25519_private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIGiI3w9x2HZ9UKGi51USN5JN2wtppaYVCRIBTp8ESaj3
-----END PRIVATE KEY-----
39 changes: 36 additions & 3 deletions tests/unit/tuf_repository_service_worker/test_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@
#
# SPDX-License-Identifier: MIT

from pathlib import Path

import pytest
from pretend import stub
from securesystemslib.signer import CryptoSigner, Key

from repository_service_tuf_worker.interfaces import IKeyVault
from repository_service_tuf_worker.signer import SignerStore
from repository_service_tuf_worker.signer import (
RSTUF_ONLINE_KEY_URI_FIELD,
SignerStore,
)

_FILES = Path(__file__).parent.parent.parent / "files"


class TestSigner:
Expand Down Expand Up @@ -36,7 +44,7 @@ def get(self, public_key):

fake_id = "fake_id"
fake_signer = stub()
fake_key = stub(keyid=fake_id)
fake_key = stub(keyid=fake_id, unrecognized_fields={})
fake_settings = stub(get=lambda x: FakeKeyVault())

store = SignerStore(fake_settings)
Expand All @@ -47,10 +55,35 @@ def get(self, public_key):

def test_get_no_vault(self):
fake_id = "fake_id"
fake_key = stub(keyid=fake_id)
fake_key = stub(keyid=fake_id, unrecognized_fields={})
fake_settings = stub(get=lambda x: None)

store = SignerStore(fake_settings)

with pytest.raises(ValueError):
store.get(fake_key)

def test_get_from_file_uri(self):
path = _FILES / "pem" / "ed25519_private.pem"
uri = f"file:{path}?encrypted=false"

key_metadata = {
"keytype": "ed25519",
"scheme": "ed25519",
"keyval": {
"public": (
"4f66dabebcf30628963786001984c0b7"
"5c175cdcf3bc4855933a2628f0cd0a0f"
)
},
RSTUF_ONLINE_KEY_URI_FIELD: uri,
}

fake_id = "fake_id"
key = Key.from_dict(fake_id, key_metadata)

fake_settings = stub()
store = SignerStore(fake_settings)
signer = store.get(key)

assert isinstance(signer, CryptoSigner)

0 comments on commit d5d915c

Please sign in to comment.