Skip to content

Commit

Permalink
Add --subjectPublicKeyInfo arg to in-toto-verify
Browse files Browse the repository at this point in the history
blocks on:
- in-toto#649, and
- secure-systems-lab/securesystemslib#678 + release

---

This is meant as replacement for `--layout-keys`, supporting a
consistent standard key file format (subjectPublicKeyInfo/pem).

It is part of a series of patches to prepare for deprecation of legacy
securesystemslib interfaces and key file formats.

**Change details**

Adds helper to load public key file as SSlibKey and convert it to its
dictionary representation with the keyid included, to make it compatible
with verifylib.in_toto_verify.

in-toto-verify uses this for keys passed with --subjectPublicKeyInfo.

NOTE: requires unreleased securesystemslib API, which **blocks** this PR.

In the future we might want to support Key (SSlibKey's base class)
natively in in_toto_verify.

This PR also adds a deprecation warning for --layout-keys and tests
using the demo supply chain.

Test public key files come from secure-systems-lab/securesystemslib#604.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
  • Loading branch information
lukpueh committed Nov 24, 2023
1 parent c540f8c commit 1e087f8
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 8 deletions.
34 changes: 29 additions & 5 deletions in_toto/in_toto_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
sort_action_groups,
title_case_action_groups,
)
from in_toto.models._signer import load_public_key_from_file
from in_toto.models.metadata import Metadata

# Command line interfaces should use in_toto base logger (c.f. in_toto.log)
Expand Down Expand Up @@ -175,6 +176,19 @@ def create_parser():
),
)

named_args.add_argument(
"--subjectPublicKeyInfo",
type=str,
dest="subject_public_key_info",
metavar="<path>",
nargs="+",
help=(
"replacement for '--layout-keys' using a standard "
"subjectPublicKeyInfo/PEM format. Key type is detected "
"automatically and need not be specified with '--key-type'."
),
)

named_args.add_argument(
"-g",
"--gpg",
Expand Down Expand Up @@ -227,13 +241,13 @@ def main():

LOG.setLevelVerboseOrQuiet(args.verbose, args.quiet)

# For verifying at least one of --layout-keys or --gpg must be specified
# Note: Passing both at the same time is possible.
if (args.layout_keys is None) and (args.gpg is None):
# For verifying at least one public key must be specified
if not (args.layout_keys or args.gpg or args.subject_public_key_info):
parser.print_help()
parser.error(
"wrong arguments: specify at least one of"
" '--layout-keys path [path ...]' or '--gpg id [id ...]'"
"wrong arguments: specify at least one layout verification key:"
" '--layout-keys path [path ...]' or '--gpg id [id ...]' or "
" '--subjectPublicKeyInfo path [path ...]'."
)

try:
Expand All @@ -243,6 +257,11 @@ def main():
layout_key_dict = {}
if args.layout_keys is not None:
LOG.info("Loading layout key(s)...")
LOG.warning(
"'-k', '--layout-keys' is deprecated, use "
"'--subjectPublicKeyInfo' instead."
)

layout_key_dict.update(
interface.import_publickeys_from_file(
args.layout_keys, args.key_types
Expand All @@ -255,6 +274,11 @@ def main():
gpg_interface.export_pubkeys(args.gpg, homedir=args.gpg_home)
)

if args.subject_public_key_info:
for path in args.subject_public_key_info:
key = load_public_key_from_file(path)
layout_key_dict[key["keyid"]] = key

verifylib.in_toto_verify(layout, layout_key_dict, args.link_dir)

except Exception as e: # pylint: disable=broad-exception-caught
Expand Down
25 changes: 23 additions & 2 deletions in_toto/models/_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@
"""

from dataclasses import dataclass
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple

import securesystemslib.gpg.exceptions as gpg_exceptions
import securesystemslib.gpg.functions as gpg
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.serialization import (
load_pem_private_key,
load_pem_public_key,
)
from securesystemslib import exceptions
from securesystemslib.signer import (
CryptoSigner,
Key,
SecretsHandler,
Signature,
Signer,
SSlibKey,
)


Expand All @@ -48,6 +52,23 @@ def load_crypto_signer_from_pkcs8_file(
return signer


def load_public_key_from_file(path: str) -> dict[str, Any]:
"""Internal helper to load key from SubjectPublicKeyInfo/PEM file."""
with open(path, "rb") as f:
data = f.read()

crypto_public_key = load_pem_public_key(data)
key = SSlibKey.from_crypto(crypto_public_key)

# NOTE: Would be nice to support `Key` instances natively in in-toto.
# Until then we keep using the in-toto -tailored dict representation of
# `Key`, which is expected in metadata model and verifylib API.
key_dict = key.to_dict()
key_dict["keyid"] = key.keyid

return key_dict


class GPGSignature(Signature):
"""A container class containing information about a gpg signature.
Besides the signature, it also contains other meta information
Expand Down
2 changes: 1 addition & 1 deletion requirements-pinned.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pynacl==1.5.0
# via securesystemslib
python-dateutil==2.8.2
# via -r requirements.txt
securesystemslib[crypto,pynacl] @ git+https://github.com/lukpueh/securesystemslib@refactor-crytposigner
securesystemslib[crypto,pynacl] @ git+https://github.com/lukpueh/securesystemslib@make-sslibkey-from-crypto-public
# via -r requirements.txt
six==1.16.0
# via python-dateutil
4 changes: 4 additions & 0 deletions tests/pems/ecdsa_public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcLYSZyFGeKdWNt5dWFbnv6N9NyHC
oUNLcG6GZIxLwN8Q8MUdHdOOxGkDnyBRSJpIZ/r/oDECSTwfCYhdogweLA==
-----END PUBLIC KEY-----
3 changes: 3 additions & 0 deletions tests/pems/ed25519_public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAT2bavrzzBiiWN4YAGYTAt1wXXNzzvEhVkzomKPDNCg8=
-----END PUBLIC KEY-----
9 changes: 9 additions & 0 deletions tests/pems/rsa_public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhX6rioiL/cX5Ys32InF
U52H8tL14QeX0tacZdb+AwcH6nIh97h3RSHvGD7Xy6uaMRmGldAnSVYwJHqoJ5j2
ynVzU/RFpr+6n8Ps0QFg5GmlEqZboFjLbS0bsRQcXXnqJNsVLEPT3ULvu1rFRbWz
AMFjNtNNk5W/u0GEzXn3D03jIdhD8IKAdrTRf0VMD9TRCXLdMmEU2vkf1NVUnOTb
/dRX5QA8TtBylVnouZknbavQ0J/pPlHLfxUgsKzodwDlJmbPG9BWwXqQCmP0DgOG
NIZ1X281MOBaGbkNVEuntNjCSaQxQjfALVVU5NAfal2cwMINtqaoc7Wa+TWvpFEI
WwIDAQAB
-----END PUBLIC KEY-----
69 changes: 69 additions & 0 deletions tests/test_in_toto_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import os
import shutil
import unittest
from pathlib import Path

from securesystemslib.gpg.constants import have_gpg
from securesystemslib.interface import (
Expand All @@ -33,9 +34,14 @@
from securesystemslib.signer import SSlibSigner

from in_toto.in_toto_verify import main as in_toto_verify_main
from in_toto.models._signer import load_crypto_signer_from_pkcs8_file
from in_toto.models.metadata import Metadata
from tests.common import CliTestCase, GPGKeysMixin, TmpDirMixin

DEMO_FILES = Path(__file__).parent / "demo_files"
PEMS = Path(__file__).parent / "pems"
SCRIPTS = Path(__file__).parent / "scripts"


class TestInTotoVerifyTool(CliTestCase, TmpDirMixin):
"""
Expand Down Expand Up @@ -503,5 +509,68 @@ def test_gpg_signed_layout_with_gpg_functionary_keys(self):
self.assert_cli_sys_exit(args, 0)


class TestInTotoVerifySubjectPublicKeyInfoKeys(CliTestCase, TmpDirMixin):
"""Tests in-toto-verify like TestInTotoVerifyTool but with
standard PEM/SubjectPublicKeyInfo keys."""

cli_main_func = staticmethod(in_toto_verify_main)

@classmethod
def setUpClass(cls):
"""Creates and changes into temporary directory.
* Copy files needed for verification:
- demo *.link files
- final product
- inspection scripts
* Sign layout with keys in "pems" dir
* Dump layout
"""
cls.set_up_test_dir()

# Copy demo files and inspection scripts
for demo_file in [
"foo.tar.gz",
"package.2f89b927.link",
"write-code.776a00e2.link",
]:
shutil.copy(DEMO_FILES / demo_file, demo_file)

shutil.copytree(SCRIPTS, "scripts")

# Load layout template
layout_template = Metadata.load(
str(DEMO_FILES / "demo.layout.template")
)

# Load keys and sign
cls.public_key_paths = []
for keytype in ["rsa", "ed25519", "ecdsa"]:
cls.public_key_paths.append(str(PEMS / f"{keytype}_public.pem"))
signer = load_crypto_signer_from_pkcs8_file(
PEMS / f"{keytype}_private_unencrypted.pem"
)

layout_template.create_signature(signer)

layout_template.dump("demo.layout")

@classmethod
def tearDownClass(cls):
cls.tear_down_test_dir()

def test_main_multiple_keys(self):
"""Test in-toto-verify CLI tool with multiple keys."""

args = [
"--layout",
"demo.layout",
"--subjectPublicKeyInfo",
] + self.public_key_paths
self.assert_cli_sys_exit(args, 0)


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

0 comments on commit 1e087f8

Please sign in to comment.