From ec8456ce3711ba7ed3dd8b3362542b7668e8b9d8 Mon Sep 17 00:00:00 2001 From: Brandon Luong Date: Fri, 7 Feb 2025 18:52:33 +0000 Subject: [PATCH 01/19] brandonluong: add sign_proposal.py --- kms/singletenanthsm/sign_proposal.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 kms/singletenanthsm/sign_proposal.py diff --git a/kms/singletenanthsm/sign_proposal.py b/kms/singletenanthsm/sign_proposal.py new file mode 100644 index 00000000000..bd365e0cda0 --- /dev/null +++ b/kms/singletenanthsm/sign_proposal.py @@ -0,0 +1,17 @@ +"""TODO: brandonluong - DO NOT SUBMIT without one-line documentation for sign_proposal. + +TODO: brandonluong - DO NOT SUBMIT without a detailed description of sign_proposal. +""" + +from collections.abc import Sequence + +from absl import app + + +def main(argv: Sequence[str]) -> None: + if len(argv) > 1: + raise app.UsageError("Too many command-line arguments.") + + +if __name__ == "__main__": + app.run(main) From 82f7950ac36095a701ecac0a5bebfa2bb562a422 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Thu, 20 Feb 2025 21:58:48 +0000 Subject: [PATCH 02/19] Add ykman and gcloud utils --- kms/singletenanthsm/approve_sthi_proposal.py | 38 ++++ kms/singletenanthsm/gcloud_commands.py | 134 ++++++++++++++ kms/singletenanthsm/gcloud_commands_test.py | 33 ++++ ...public_key_25167010_slot_82 (RETIRED1).pem | 9 + ...public_key_28787105_slot_82 (RETIRED1).pem | 9 + kms/singletenanthsm/requirements.txt | 17 ++ kms/singletenanthsm/setup_yubikey.py | 33 ++++ kms/singletenanthsm/sign_proposal.py | 17 -- kms/singletenanthsm/sign_proposals_test.py | 113 ++++++++++++ kms/singletenanthsm/ykman_utils.py | 164 ++++++++++++++++++ kms/singletenanthsm/ykman_utils_test.py | 93 ++++++++++ 11 files changed, 643 insertions(+), 17 deletions(-) create mode 100644 kms/singletenanthsm/approve_sthi_proposal.py create mode 100644 kms/singletenanthsm/gcloud_commands.py create mode 100644 kms/singletenanthsm/gcloud_commands_test.py create mode 100644 kms/singletenanthsm/public_key_25167010_slot_82 (RETIRED1).pem create mode 100644 kms/singletenanthsm/public_key_28787105_slot_82 (RETIRED1).pem create mode 100644 kms/singletenanthsm/requirements.txt create mode 100644 kms/singletenanthsm/setup_yubikey.py delete mode 100644 kms/singletenanthsm/sign_proposal.py create mode 100644 kms/singletenanthsm/sign_proposals_test.py create mode 100644 kms/singletenanthsm/ykman_utils.py create mode 100644 kms/singletenanthsm/ykman_utils_test.py diff --git a/kms/singletenanthsm/approve_sthi_proposal.py b/kms/singletenanthsm/approve_sthi_proposal.py new file mode 100644 index 00000000000..626abec6046 --- /dev/null +++ b/kms/singletenanthsm/approve_sthi_proposal.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import gcloud_commands +import ykman_utils + + +def approve_proposal(sthi_proposal_resource): + # Fetch challenges + process = gcloud_commands.fetch_challenges(sthi_proposal_resource) + challenges = process.stdout + + # Sign challenges + signed_challenges = ykman_utils.sign_proposal(challenges) + + # Return signed challenges to gcloud + process = gcloud_commands.send_signed_challenges( + signed_challenges, + sthi_proposal_resource + ) + + +if __name__ == "__main__": + approve_proposal() + diff --git a/kms/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/gcloud_commands.py new file mode 100644 index 00000000000..d37903268cd --- /dev/null +++ b/kms/singletenanthsm/gcloud_commands.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess + +command_build_custom_gcloud = """ + pushd /tmp + curl -o installer.sh https://sdk.cloud.google.com + chmod +x installer.sh + ./installer.sh --disable-prompts --install-dir ~/sthi + rm installer.sh + popd + alias sthigcloud=~/sthi/google-cloud-sdk/bin/gcloud + sthigcloud auth login + """ + + +command_add_components = """ + ~/sthi/google-cloud-sdk/bin/gcloud components repositories add https://storage.googleapis.com/sthi-test-bucket/components-2.json + ~/sthi/google-cloud-sdk/bin/gcloud components update + """ + +def build_custom_gcloud(): + """Builds a custom gcloud binary.""" + try: + print("\nBuilding custom gcloud build") + process = subprocess.run( + command_build_custom_gcloud, + check=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud build executed successfully.") + print(process.stdout) + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(f"Error executing gcloud build: {e}") + try: + print("\nAdding gcloud components") + process = subprocess.run( + command_add_components, + check=False, + capture_output=False, + text=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud components add executed successfully.") + print(process.stdout) + return process + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(f"Error executing gcloud components update: {e}") + print(f"Error executing gcloud components update: {e}") + + +command_gcloud_list_proposal = ( + "~/sthi/google-cloud-sdk/bin/gcloud kms single-tenant-hsm list " + "--location=projects/hawksbill-playground/locations/global" +) + +command_gcloud_describe_proposal = """ + ~/sthi/google-cloud-sdk/bin/gcloud \ + kms single-tenant-hsm proposal describe """ + +def fetch_challenges(sthi_proposal_resource:str): + """Fetches challenges from the server.""" + + challenges = [] + + try: + print("\nfetching challenges") + process = subprocess.run( + command_gcloud_describe_proposal + sthi_proposal_resource, + capture_output=True, + check=False, + text=True, + shell=True, + # stderr=subprocess.STDOUT + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud command executed successfully.") + print(process.stdout) + return process + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(f"Error executing gcloud command: {e}") + + return challenges + +command_gcloud_approve_proposal = [ + "~/sthi/google-cloud-sdk/bin/gcloud", + "kms", + "single-tenant-hsm", + "proposal", + "approve", + "projects/hawksbill_playground/locations/global/singleTenantHsmInstances/my_instance/proposals/proposal1", +] + +def send_signed_challenges(): + print("Sending signed challenges") + try: + + process = subprocess.run( + " ".join(command_gcloud_approve_proposal), + capture_output=True, + # check=True, + text=True, + shell=True, + ) + print("gcloud command executed successfully.") + return process + + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(f"Error executing gcloud command: {e}") \ No newline at end of file diff --git a/kms/singletenanthsm/gcloud_commands_test.py b/kms/singletenanthsm/gcloud_commands_test.py new file mode 100644 index 00000000000..1a104a4fbe9 --- /dev/null +++ b/kms/singletenanthsm/gcloud_commands_test.py @@ -0,0 +1,33 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import gcloud_commands +import pytest + +test_proposal_resource = """projects/hawksbill-playground/locations/\ +us-east1/singleTenantHsmInstances/my_sth + """ + +# def test_build_custom(): +# process = gcloud_commands.build_custom_gcloud() +# assert not process.stderr + +# def test_fetch_challenges(): +# process = gcloud_commands.fetch_challenges(test_proposal_resource) +# # assert not process.stderr + +# def test_send_signed_challenges(): +# process = gcloud_commands.send_signed_challenges() +# # assert not process.stderr + diff --git a/kms/singletenanthsm/public_key_25167010_slot_82 (RETIRED1).pem b/kms/singletenanthsm/public_key_25167010_slot_82 (RETIRED1).pem new file mode 100644 index 00000000000..819e4ba5d28 --- /dev/null +++ b/kms/singletenanthsm/public_key_25167010_slot_82 (RETIRED1).pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqIWn0lbTgD2giIeORvrE +MjyOrvQLvngOSTivB3hfsSq7Z3if1xaYywg9+/FlChZ8yRys/xrfY1Dy2XJFyO2O +1Vu9uo9MvhHXatJz9UOORDzsMMm671MSi0j6cA8WRrGR5ZakkSikrd4ScLcjTdx1 +DQszrLzCkmRhpUb6CPDlCm39PlMBoGR13GKX3wAmjON5v56IJFttlM/+MLiwmXmM +bI50J6j3C2llcfkaeT36cuqXzpEQESKeZLmv2SZwuW1FMaDcLbqV9eiWx7LMexol +TMxyTUaqf+UYDXCaYacZxEdVMQYp0VRm16utV1/c+nwrj2UC2S4IsYTqKB7GKjmV +0wIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/public_key_28787105_slot_82 (RETIRED1).pem b/kms/singletenanthsm/public_key_28787105_slot_82 (RETIRED1).pem new file mode 100644 index 00000000000..d88dcd0414e --- /dev/null +++ b/kms/singletenanthsm/public_key_28787105_slot_82 (RETIRED1).pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4VKY5Fv5XlOMfoh31qzt +tOEnJ8dzRFoQQZFPcPkrhq43pAVAX5kRuFdJvZ6BhQPehdh+kjucE3eRBdjj0wfH +u9Auxah9fx4A1vwRSKIJMooV1GlTqfMf0PjGSPh7RORUV2YMH93KB0rXIqJGKR+a +3n0HqUUgiuZCKlG2CHPlwNulo4SPZtZL1T/XwpT9jVnC8yws9HRaJ/q2wGfrA2A0 +zS6AA8kjU68wsAenRTl1ZFhZdkfWhZY4yZbY75oEs2LncBFJ6FzrFGY/KX5GNqvM +4N02iB/1KjS3kOzYiv79t62fuOV8ufeLA0p1KvI9bZh+MnYJsufp3dYIFAr6uAUu +uQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/requirements.txt b/kms/singletenanthsm/requirements.txt new file mode 100644 index 00000000000..96a0cdfca9e --- /dev/null +++ b/kms/singletenanthsm/requirements.txt @@ -0,0 +1,17 @@ +backports.tarfile==1.2.0 +cffi==1.17.1 +click==8.1.8 +cryptography==44.0.0 +fido2==1.2.0 +importlib_metadata==8.6.1 +jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.1.0 +jeepney==0.8.0 +keyring==25.6.0 +more-itertools==10.6.0 +pycparser==2.22 +pyscard==2.2.1 +SecretStorage==3.3.3 +yubikey-manager==5.5.1 +zipp==3.21.0 diff --git a/kms/singletenanthsm/setup_yubikey.py b/kms/singletenanthsm/setup_yubikey.py new file mode 100644 index 00000000000..38dd41ef435 --- /dev/null +++ b/kms/singletenanthsm/setup_yubikey.py @@ -0,0 +1,33 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ykman_utils +import gcloud_commands + +def generate_private_keys_build_gcloud(): + """Generates an RSA key on slot 82 of every yubikey + connected to the local machine and builds the custom gcloud cli. + """ + try: + ykman_utils.generate_private_key() + except Exception as e: + raise Exception(f"Generating private keys failed {e}") + try: + gcloud_commands.build_custom_gcloud() + except Exception as e: + raise Exception(f"Generating custom gcloud build failed {e}") + +if __name__ == "__main__": + + generate_private_keys_build_gcloud() diff --git a/kms/singletenanthsm/sign_proposal.py b/kms/singletenanthsm/sign_proposal.py deleted file mode 100644 index bd365e0cda0..00000000000 --- a/kms/singletenanthsm/sign_proposal.py +++ /dev/null @@ -1,17 +0,0 @@ -"""TODO: brandonluong - DO NOT SUBMIT without one-line documentation for sign_proposal. - -TODO: brandonluong - DO NOT SUBMIT without a detailed description of sign_proposal. -""" - -from collections.abc import Sequence - -from absl import app - - -def main(argv: Sequence[str]) -> None: - if len(argv) > 1: - raise app.UsageError("Too many command-line arguments.") - - -if __name__ == "__main__": - app.run(main) diff --git a/kms/singletenanthsm/sign_proposals_test.py b/kms/singletenanthsm/sign_proposals_test.py new file mode 100644 index 00000000000..28499a0eb12 --- /dev/null +++ b/kms/singletenanthsm/sign_proposals_test.py @@ -0,0 +1,113 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# import pytest + +import cryptography.exceptions +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import _serialization +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.hazmat.primitives.serialization import load_pem_public_key +import approve_sthi_proposal + + +public_key = """-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyzXMN4kWOHPwCGiWHOZP +hpbiElngaVVll2UybTZq9ntJmyaVyYtzq2ypP5PkrOC0wfFsleU4T31ZAvgRFrcV +WLCCdFfr9TMBKkXz0VOXXFamDvyuKl7nKxjQnIqMWPd7Fo0JlDjCnrNew/KYpVZ7 +VIZ4vLpyPfBHmJnaFyW1CY7cebQM0lBNH5hBibFl92ZRgm5Gg8tEMBKiySQKlP7E +I6rnA1KTwuCirXG2U/uaYj3rhsZahsJvUdyrpQQGNtkjpMX3f+djBf6Zs3p2l8hE +DNMmpuRb3EB88Su8P6vuxoJgxc5M0rV9RfutnsE4mMWKD3WO1Q0IiU8HwErURl/G +RwIDAQAB +-----END PUBLIC KEY----- +""" + + +class Challenge: + + def __init__(self, challenge, public_key_pem): + self.challenge = challenge + self.public_key_pem = public_key_pem + + +example_challenge_1 = Challenge(b"123", public_key) +example_challenge_2 = Challenge(b"456", "hello") + + +class ChallengeReply: + + def __init__(self, signed_challenge, public_key_pem): + self.signed_challenge = signed_challenge + self.public_key_pem = public_key_pem + + +def verify_challenge_signatures(signed_challenges): + if not signed_challenges: + raise Exception("No signed challenges to verify") + for signed_challenge in signed_challenges: + public_key = load_pem_public_key( + example_challenge_1.public_key_pem.encode() + ) + try: + public_key.verify( + signed_challenge, + example_challenge_1.challenge, + padding.PKCS1v15(), + hashes.SHA256(), + ) + print(f"Signature verification success") + except cryptography.exceptions.InvalidSignature as e: + print(f"Signature verification failed: {e}") + + +def test_sign_proposal_success(): + + # Populate challenges + challenges = [] + challenges.append(example_challenge_1) + challenges.append(example_challenge_2) + signed_challenges = approve_sthi_proposal.sign_proposal(challenges) + if approve_sthi_proposal: + print("proposals signed") + else: + print("proposals not signed") + + verify_challenge_signatures(signed_challenges) + + +def test_sign_proposal_missing_challenges(): + challenges = [] + + +def test_sign_proposal_no_matching_public_keys(): + + challenges = [] + + +def test_fetch_challenges(): + print("fetching challenges") + approve_sthi_proposal.fetch_challenges + print("challenges fetched successfully") + + +if __name__ == "__main__": + test_fetch_challenges() + + test_sign_proposal_success() diff --git a/kms/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/ykman_utils.py new file mode 100644 index 00000000000..2db257b9b27 --- /dev/null +++ b/kms/singletenanthsm/ykman_utils.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ykman +import cryptography.exceptions + + +from cryptography.hazmat.primitives import _serialization +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.hazmat.primitives.serialization import load_pem_public_key + + +from ykman import piv +from ykman.device import list_all_devices +from yubikit.piv import hashes +from yubikit.piv import PIN_POLICY, TOUCH_POLICY, hashes +from yubikit.piv import SmartCardConnection + +DEFAULT_MANAGEMENT_KEY = "010203040506070801020304050607080102030405060708" +DEFAULT_PIN = "123456" + + +def generate_private_key( + key_type=piv.KEY_TYPE.RSA2048, + management_key=DEFAULT_MANAGEMENT_KEY, + pin=DEFAULT_PIN, +): + """Generates a private key on the yubikey""" + + devices = list_all_devices() + if not devices: + raise Exception("no yubikeys found") + print(f"{len(devices)} yubikeys detected") + for yubikey, device_info in devices: + with yubikey.open_connection(SmartCardConnection) as connection: + piv_session = piv.PivSession(connection) + piv_session.authenticate( + piv.MANAGEMENT_KEY_TYPE.TDES, + bytes.fromhex(management_key), + ) + piv_session.verify_pin(pin) + + public_key = piv_session.generate_key( + piv.SLOT.RETIRED1, + key_type=key_type, + pin_policy=PIN_POLICY.DEFAULT, + touch_policy=TOUCH_POLICY.ALWAYS, + ) + if not public_key: + raise Exception("failed to generate public key") + with open( + f"public_key_{device_info.serial}_slot_{piv.SLOT.RETIRED1}.pem", "wb" + ) as binary_file: + + # Write bytes to file + binary_file.write( + public_key.public_bytes( + encoding=_serialization.Encoding.PEM, + format=_serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ) + print( + f"Private key pair generated on device {device_info.serial} on key" + f" slot: {piv.SLOT.RETIRED1}" + ) + +class ChallengeReply: + + def __init__(self, signed_challenge, public_key_pem): + self.signed_challenge = signed_challenge + self.public_key_pem = public_key_pem + + +def sign_proposal(challenges): + """Signs a proposal's challenges using a Yubikey.""" + if not challenges: + raise Exception("Challenge list empty: No challenges to sign.") + signed_challenges = [] + devices = list_all_devices() + if not devices: + raise Exception("no yubikeys found") + for yubikey, _ in devices: + with yubikey.open_connection(SmartCardConnection) as connection: + # Make PivSession and fetch public key from Signature slot. + piv_session = piv.PivSession(connection) + # authenticate + piv_session.authenticate( + piv.MANAGEMENT_KEY_TYPE.TDES, + bytes.fromhex("010203040506070801020304050607080102030405060708"), + ) + piv_session.verify_pin("123456") + + # Get the public key from slot 82. + slot_metadata = piv_session.get_slot_metadata(slot=piv.SLOT.RETIRED1) + print(slot_metadata.public_key.public_bytes) + + # Check to see if any of the challenge public keys matches with the + # public key from slot 82. + for challenge in challenges: + key_public_bytes = slot_metadata.public_key.public_bytes( + encoding=_serialization.Encoding.PEM, + format=_serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + if key_public_bytes == challenge.public_key_pem.encode(): + + # sign the challenge + print("Press Yubikey to sign challenge") + signed_challenges.append( + ChallengeReply( + piv_session.sign( + slot=piv.SLOT.RETIRED1, + key_type=slot_metadata.key_type, + message=challenge.challenge, + hash_algorithm=hashes.SHA256(), + padding=padding.PKCS1v15(), + ), + challenge.public_key_pem + ) + ) + print("Challenge signed successfully") + if not signed_challenges: + raise Exception( + "No matching public keys between Yubikey and challenges. Make sure" + " key is generated in correct slot" + ) + return signed_challenges + + +def verify_challenge_signatures(challenge_replies, data): + if not challenge_replies: + raise Exception("No signed challenges to verify") + for challenge_reply in challenge_replies: + public_key = load_pem_public_key( + challenge_reply.public_key_pem.encode() + ) + try: + public_key.verify( + challenge_reply.signed_challenge, + data, + padding.PKCS1v15(), + hashes.SHA256(), + ) + print(f"Signature verification success") + except cryptography.exceptions.InvalidSignature as e: + raise cryptography.exceptions.InvalidSignature((f"Signature verification failed: {e}")) diff --git a/kms/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/ykman_utils_test.py new file mode 100644 index 00000000000..6c9ee0e463c --- /dev/null +++ b/kms/singletenanthsm/ykman_utils_test.py @@ -0,0 +1,93 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import cryptography.exceptions + + +import glob +import os +import pathlib +import pytest + +import ykman_utils + +class Challenge: + + def __init__(self, challenge, public_key_pem): + self.challenge = challenge + self.public_key_pem = public_key_pem + +class ChallengeReply: + + def __init__(self, signed_challenge, public_key_pem): + self.signed_challenge = signed_challenge + self.public_key_pem = public_key_pem + +challenge_test_data = b"test_data" + +# A yubikey connected to your local machine will be needed to run these tests. +# The generate_private_key() method will rewrite the key saved on slot 82(Retired1). +@pytest.fixture() +def key_setup(): + ykman_utils.generate_private_key() + +def challenges(): + public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("public_key*.pem")] + challenges = [] + + + for public_key_file in public_key_files: + file = open(public_key_file, "r") + public_key_pem = file.read() + challenges.append(Challenge(challenge_test_data, public_key_pem )) + + return challenges + + + +def test_sign_and_verify_challenges(): + signed_challenges = ykman_utils.sign_proposal(challenges()) + ykman_utils.verify_challenge_signatures(signed_challenges, challenge_test_data) + +def test_sign_empty_challenge_list_fail(): + with pytest.raises(Exception) as exec_info: + # empty_challenges = [] + signed_challenges = ykman_utils.sign_proposal([]) + assert "Challenge list empty" in str(exec_info.value) + +def test_sign_no_matching_public_keys_fail(): + modified_challenges = challenges() + for challenge in modified_challenges: + challenge.public_key_pem = "modified_public_key" + with pytest.raises(Exception) as exec_info: + signed_challenges = ykman_utils.sign_proposal(modified_challenges) + assert "No matching public keys" in str(exec_info.value) + +def test_verify_empty_challenge_replies_fail(): + with pytest.raises(Exception) as exec_info: + ykman_utils.verify_challenge_signatures([], challenge_test_data) + assert "No signed challenges to verify" in str(exec_info) + + +def test_verify_mismatching_data_fail(): + with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: + signed_challenges = ykman_utils.sign_proposal(challenges()) + ykman_utils.verify_challenge_signatures(signed_challenges, b"mismatched_data") + assert "Signature verification failed" in str(exec_info.value) + + + +# if __name__ == "__main__": +# ykman_utils.generate_private_key() +# test_sign_and_verify_challenges() \ No newline at end of file From 8e358e308825862348eee6c0cb1b3e965a4e49b6 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Mon, 17 Mar 2025 16:29:51 -0400 Subject: [PATCH 03/19] add setup command options and modify approve script --- kms/singletenanthsm/approve_proposal.py | 112 ++++++++++++++ kms/singletenanthsm/approve_proposal_test.py | 144 ++++++++++++++++++ kms/singletenanthsm/approve_sthi_proposal.py | 38 ----- kms/singletenanthsm/challenges/challenge1.txt | 1 + kms/singletenanthsm/challenges/challenge2.txt | 1 + kms/singletenanthsm/challenges/challenge3.txt | 1 + .../challenges/public_key1.pem | 9 ++ .../challenges/public_key2.pem | 9 ++ .../challenges/public_key3.pem | 9 ++ kms/singletenanthsm/fetch_challenges.py | 13 ++ kms/singletenanthsm/gcloud_commands.py | 15 +- ...public_key_25167010_slot_82 (RETIRED1).pem | 9 ++ ...public_key_28787103_slot_82 (RETIRED1).pem | 9 ++ ...public_key_28787105_slot_82 (RETIRED1).pem | 9 ++ ...public_key_25167010_slot_82 (RETIRED1).pem | 9 -- ...public_key_28787105_slot_82 (RETIRED1).pem | 9 -- kms/singletenanthsm/requirements.txt | 4 + .../{setup_yubikey.py => setup.py} | 26 +++- kms/singletenanthsm/sign_proposals_test.py | 10 +- .../signed_challenges/challenge1.txt | 2 + .../signed_challenges/public_key_1.pem | 9 ++ .../signed_challenges/public_key_2.pem | 9 ++ .../signed_challenges/public_key_3.pem | 9 ++ .../signed_challenges/signed_challenge1.txt | 3 + .../signed_challenges/signed_challenge2.txt | Bin 0 -> 256 bytes .../signed_challenges/signed_challenge3.txt | Bin 0 -> 256 bytes kms/singletenanthsm/ykman_utils.py | 84 ++++++++-- kms/singletenanthsm/ykman_utils_test.py | 58 +++++-- 28 files changed, 530 insertions(+), 81 deletions(-) create mode 100644 kms/singletenanthsm/approve_proposal.py create mode 100644 kms/singletenanthsm/approve_proposal_test.py delete mode 100644 kms/singletenanthsm/approve_sthi_proposal.py create mode 100644 kms/singletenanthsm/challenges/challenge1.txt create mode 100644 kms/singletenanthsm/challenges/challenge2.txt create mode 100644 kms/singletenanthsm/challenges/challenge3.txt create mode 100644 kms/singletenanthsm/challenges/public_key1.pem create mode 100644 kms/singletenanthsm/challenges/public_key2.pem create mode 100644 kms/singletenanthsm/challenges/public_key3.pem create mode 100644 kms/singletenanthsm/fetch_challenges.py create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_25167010_slot_82 (RETIRED1).pem create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787103_slot_82 (RETIRED1).pem create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787105_slot_82 (RETIRED1).pem delete mode 100644 kms/singletenanthsm/public_key_25167010_slot_82 (RETIRED1).pem delete mode 100644 kms/singletenanthsm/public_key_28787105_slot_82 (RETIRED1).pem rename kms/singletenanthsm/{setup_yubikey.py => setup.py} (56%) create mode 100644 kms/singletenanthsm/signed_challenges/challenge1.txt create mode 100644 kms/singletenanthsm/signed_challenges/public_key_1.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_2.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_3.pem create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge1.txt create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge2.txt create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge3.txt diff --git a/kms/singletenanthsm/approve_proposal.py b/kms/singletenanthsm/approve_proposal.py new file mode 100644 index 00000000000..6435770b78d --- /dev/null +++ b/kms/singletenanthsm/approve_proposal.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import sys +import gcloud_commands +import json +import os +import ykman_utils + + +def parse_only_challenges(sthi_output): + proposal_json = json.loads(sthi_output, strict= False) + challenges = proposal_json["quorumParameters"]["challenges"] + + directory_path = "challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + + challenge_count = 0 + for challenge in challenges: + challenge_count += 1 + print(challenge["challenge"]+ "\n") + print(challenge["publicKeyPem"].encode('utf-8').decode('unicode_escape')) + f = open("challenges/challenge{0}.txt".format(challenge_count), "w") + f.write(challenge["challenge"]) + f.close + + f = open("challenges/public_key{0}.pem".format(challenge_count), "w") + f.write(challenge["publicKeyPem"].encode('utf-8').decode('unicode_escape')) + f.close() + + + + + +def parse_challenges(fetch_string): + text_list = fetch_string.split("requiredApproverCount") + challenge_list = text_list[0].split("challenge:") + + directory_path = "challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + + + print("PRINTING CHALLENGES") + challenge_count = 0 + for challenge in challenge_list[1:]: + + challenge_count += 1 + elem_list = challenge.split(" ", 12) + print("challenge: " + elem_list[1]) + print("public key: " + elem_list[-1]) + + f = open("challenges/challenge{0}.txt".format(challenge_count), "w") + f.write(elem_list[1]) + f.close + + f = open("challenges/public_key{0}.pem".format(challenge_count), "w") + f.write(elem_list[-1]) + f.close() + + +def parse_args(args): + parser = argparse.ArgumentParser() + parser.add_argument("--proposal_resource", type=str, required=True) + return parser.parse_args(args) + + +def approve_proposal(): + parser = parse_args(sys.argv[1:]) + + # Fetch challenges + process = gcloud_commands.fetch_challenges(parser.proposal_resource) + + # Parse challenges into files + parse_only_challenges(process.stdout) + + # Sign challenges + challenges = ykman_utils.populate_challenges_from_files() + signed_challenged_files = [] + signed_challenges = ykman_utils.sign_proposal(challenges, signed_challenged_files) + + # Return signed challenges to gcloud + process = gcloud_commands.send_signed_challenges( + signed_challenges, + args.proposal_resource + ) + + +if __name__ == "__main__": + approve_proposal() + diff --git a/kms/singletenanthsm/approve_proposal_test.py b/kms/singletenanthsm/approve_proposal_test.py new file mode 100644 index 00000000000..36d2f6b452c --- /dev/null +++ b/kms/singletenanthsm/approve_proposal_test.py @@ -0,0 +1,144 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ykman_utils +import kms.singletenanthsm.approve_proposal as approve_proposal +import gcloud_commands +import pytest +import subprocess + +import sys +import argparse +from unittest.mock import patch, Mock +from unittest import mock + + +sample_sthi_output = """ +{ + "quorumParameters": { + "challenges": [ + { + "challenge": "-jgoZSrRHP_1WQt6f4cx911AkWfa9WzqVuuOLd7iw6iAwdkeVcBA_Q3sBqaP_5B6", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKbCvQEFWx65Ma57Ul24\n044s74u7H1nLQJ8WfBe33MKmOrsR31pO5dIfnJIs8d0vDFj3cm4MRRpIdI8PkOPE\noDDa9z0dz/t4qDpOKlRcxaFNZGoBs+j+TuC3rsrBxIxc+qSDn/WhDV2oAXaj3Jnw\n5DPCGljDObbzaDwTjovP3enfFCncCl76z0yGuKoyW4aqoK2ccHLRxNF69bYSzICE\nCiUxUeHJTvoLuJ8fwGUIKYsBb9tBuzGsrM+5Mj+bskvJLM3cNOkW1p1TOjLTtMk2\npDlY+9EdWMJ/6wbX88b7dl5T3aQOTzNrxjojpYPyNWdHIGXL1YwKNE3EkdN7kXkK\nyQIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "C-A4-b-ZKjpgY0EJZmDIbUsTaZs3iPq-iW9XWQ18zd_k-biw8iKiWX7eLBjwUoHk", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrAMwX6JXw9RIH8f2+lG\nVpaqAT2nwyzbPtpoRgemX/6ig3R8W9yNWOMvK6tf0MAVeHiyQIPucNXehr1VzQ1/\nVIlA9KA7LXznXwXDZkLl1/XHeJGbd3eyEA8K7tT3bFrSTZx/mVhEuJN9eLb5tL9t\ngqes7pgYYe2iyhUCqQM3oh4K5SJGooQDX41/VXP8PxCem+71oG/LWogIVMLnU9Yu\n33oQY6xvF1YKNgfGqnyNh5rPduQplJy04o/b6Xs3mcX/NUOBMUcLXDxe6tRGyy/2\nUX+6thK9gU2dXheK03rwhMv5jmpIGP7FShSs/+9N3nUIA+g5sTgnm2xqch5jOYXV\nlwIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "Q8nT85xO8wuYguKfPSVU60rmpO67ue-CvSFXxIVzjeh2AaYPQ3Vq7Jsu2Bk4ynAC", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoidJnb4gpd8Ua/QRpwjb\nCg+0pHhQ22bv0bX66fj8niLPV4o/TaQzyYD94zA7t6ulUmxj93w2U0EEmsufrBrt\nOgorVidL6MLuEOo0lWy0DHBsFDellFA75MTTIs76odhJshVfe9OSwFAakoMHiDAY\nLTmTEcaJsanUiHhIaxF5O/cTAFlkhOLFD0becK674OAnqqmabzJx2qlTMraR3yPi\nW1uLPdPBNeokBirk4WIO8XKtQJuOrGWhAxjIvGW20paRWgs0QI34ZgYQdnIjKwMO\n0dsr/vlHjfRs0OX2oVmBqSi+TNY240BC4HshQOSPBdR2HsC9vfQ0uax8+nEcZiNG\nCQIDAQAB\n-----END PUBLIC KEY-----\n" + } + ] + } +} + +""" + +sample_approved_challenges_output = """ +quorumParameters: + approvedTwoFactorPublicKeyPems: + - | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoidJnb4gpd8Ua/QRpwjb + Cg+0pHhQ22bv0bX66fj8niLPV4o/TaQzyYD94zA7t6ulUmxj93w2U0EEmsufrBrt + OgorVidL6MLuEOo0lWy0DHBsFDellFA75MTTIs76odhJshVfe9OSwFAakoMHiDAY + LTmTEcaJsanUiHhIaxF5O/cTAFlkhOLFD0becK674OAnqqmabzJx2qlTMraR3yPi + W1uLPdPBNeokBirk4WIO8XKtQJuOrGWhAxjIvGW20paRWgs0QI34ZgYQdnIjKwMO + 0dsr/vlHjfRs0OX2oVmBqSi+TNY240BC4HshQOSPBdR2HsC9vfQ0uax8+nEcZiNG + CQIDAQAB + -----END PUBLIC KEY----- + - | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKbCvQEFWx65Ma57Ul24 + 044s74u7H1nLQJ8WfBe33MKmOrsR31pO5dIfnJIs8d0vDFj3cm4MRRpIdI8PkOPE + oDDa9z0dz/t4qDpOKlRcxaFNZGoBs+j+TuC3rsrBxIxc+qSDn/WhDV2oAXaj3Jnw + 5DPCGljDObbzaDwTjovP3enfFCncCl76z0yGuKoyW4aqoK2ccHLRxNF69bYSzICE + CiUxUeHJTvoLuJ8fwGUIKYsBb9tBuzGsrM+5Mj+bskvJLM3cNOkW1p1TOjLTtMk2 + pDlY+9EdWMJ/6wbX88b7dl5T3aQOTzNrxjojpYPyNWdHIGXL1YwKNE3EkdN7kXkK + yQIDAQAB + -----END PUBLIC KEY----- + - | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrAMwX6JXw9RIH8f2+lG + VpaqAT2nwyzbPtpoRgemX/6ig3R8W9yNWOMvK6tf0MAVeHiyQIPucNXehr1VzQ1/ + VIlA9KA7LXznXwXDZkLl1/XHeJGbd3eyEA8K7tT3bFrSTZx/mVhEuJN9eLb5tL9t + gqes7pgYYe2iyhUCqQM3oh4K5SJGooQDX41/VXP8PxCem+71oG/LWogIVMLnU9Yu + 33oQY6xvF1YKNgfGqnyNh5rPduQplJy04o/b6Xs3mcX/NUOBMUcLXDxe6tRGyy/2 + UX+6thK9gU2dXheK03rwhMv5jmpIGP7FShSs/+9N3nUIA+g5sTgnm2xqch5jOYXV + lwIDAQAB + -----END PUBLIC KEY----- +""" + +# def create_parser(): +# parser = argparse.ArgumentParser(...) +# parser.add_argument... +# return parser + +@pytest.fixture() +def setup(): + parser = approve_proposal.parse_args(["proposal_resource", "my_proposal"]) + +def approve_mock_proposal(): + approve_proposal.approve_proposal() + +test_resource = "projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal" + +mock_completed_process = subprocess.CompletedProcess + +def test_get_challenges_mocked(mocker): + mock_response = mocker.MagicMock() + mock_response.stdout = sample_sthi_output + + # mock the challenge string returned by service + mocker.patch("subprocess.run", return_value=mock_response) + result = gcloud_commands.fetch_challenges(test_resource) + assert result.stdout == sample_sthi_output + assert type(result.stdout) is str + + # mock the creation of the challenges directory + + + # sign challenges + + + # mock sneding the signed challenges to gcloud + +# @patch("subprocess.run") +# def test_fetch_challenges_and_sign(): +# with patch.object(sys, "argv", ["approve_sthi_proposal.py", test_resource]): +# approve_sthi_proposal.approve_proposal() + +# mock_result = Mock() +# mock_result.returncode = 0 +# mock_result.stdout = sample_sthi_output +# mock_run.return_value = mock_result + +# self.assertEqual() + + # approve_sthi_proposal.approve_proposal() + +if __name__ == "__main__": + approve_proposal.parse_only_challenges(sample_sthi_output) + challenges = ykman_utils.populate_challenges_from_files() + for challenge in challenges: + print(challenge.challenge) + print(challenge.public_key_pem) + signed_challenged_files = [] + signed_challenges = ykman_utils.sign_proposal(challenges, signed_challenged_files) + for signed_files in signed_challenged_files: + print(signed_files) + # banana = 1 + 1 + + diff --git a/kms/singletenanthsm/approve_sthi_proposal.py b/kms/singletenanthsm/approve_sthi_proposal.py deleted file mode 100644 index 626abec6046..00000000000 --- a/kms/singletenanthsm/approve_sthi_proposal.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import gcloud_commands -import ykman_utils - - -def approve_proposal(sthi_proposal_resource): - # Fetch challenges - process = gcloud_commands.fetch_challenges(sthi_proposal_resource) - challenges = process.stdout - - # Sign challenges - signed_challenges = ykman_utils.sign_proposal(challenges) - - # Return signed challenges to gcloud - process = gcloud_commands.send_signed_challenges( - signed_challenges, - sthi_proposal_resource - ) - - -if __name__ == "__main__": - approve_proposal() - diff --git a/kms/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/challenges/challenge1.txt new file mode 100644 index 00000000000..96148c4d51d --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge1.txt @@ -0,0 +1 @@ +-jgoZSrRHP_1WQt6f4cx911AkWfa9WzqVuuOLd7iw6iAwdkeVcBA_Q3sBqaP_5B6 \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/challenges/challenge2.txt new file mode 100644 index 00000000000..d5b9f823ad0 --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge2.txt @@ -0,0 +1 @@ +C-A4-b-ZKjpgY0EJZmDIbUsTaZs3iPq-iW9XWQ18zd_k-biw8iKiWX7eLBjwUoHk \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/challenges/challenge3.txt new file mode 100644 index 00000000000..16f1a19270c --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge3.txt @@ -0,0 +1 @@ +Q8nT85xO8wuYguKfPSVU60rmpO67ue-CvSFXxIVzjeh2AaYPQ3Vq7Jsu2Bk4ynAC \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem new file mode 100644 index 00000000000..d6c87b690b9 --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKbCvQEFWx65Ma57Ul24 +044s74u7H1nLQJ8WfBe33MKmOrsR31pO5dIfnJIs8d0vDFj3cm4MRRpIdI8PkOPE +oDDa9z0dz/t4qDpOKlRcxaFNZGoBs+j+TuC3rsrBxIxc+qSDn/WhDV2oAXaj3Jnw +5DPCGljDObbzaDwTjovP3enfFCncCl76z0yGuKoyW4aqoK2ccHLRxNF69bYSzICE +CiUxUeHJTvoLuJ8fwGUIKYsBb9tBuzGsrM+5Mj+bskvJLM3cNOkW1p1TOjLTtMk2 +pDlY+9EdWMJ/6wbX88b7dl5T3aQOTzNrxjojpYPyNWdHIGXL1YwKNE3EkdN7kXkK +yQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem new file mode 100644 index 00000000000..b9b47aab8f4 --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrAMwX6JXw9RIH8f2+lG +VpaqAT2nwyzbPtpoRgemX/6ig3R8W9yNWOMvK6tf0MAVeHiyQIPucNXehr1VzQ1/ +VIlA9KA7LXznXwXDZkLl1/XHeJGbd3eyEA8K7tT3bFrSTZx/mVhEuJN9eLb5tL9t +gqes7pgYYe2iyhUCqQM3oh4K5SJGooQDX41/VXP8PxCem+71oG/LWogIVMLnU9Yu +33oQY6xvF1YKNgfGqnyNh5rPduQplJy04o/b6Xs3mcX/NUOBMUcLXDxe6tRGyy/2 +UX+6thK9gU2dXheK03rwhMv5jmpIGP7FShSs/+9N3nUIA+g5sTgnm2xqch5jOYXV +lwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem new file mode 100644 index 00000000000..126fffa463f --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoidJnb4gpd8Ua/QRpwjb +Cg+0pHhQ22bv0bX66fj8niLPV4o/TaQzyYD94zA7t6ulUmxj93w2U0EEmsufrBrt +OgorVidL6MLuEOo0lWy0DHBsFDellFA75MTTIs76odhJshVfe9OSwFAakoMHiDAY +LTmTEcaJsanUiHhIaxF5O/cTAFlkhOLFD0becK674OAnqqmabzJx2qlTMraR3yPi +W1uLPdPBNeokBirk4WIO8XKtQJuOrGWhAxjIvGW20paRWgs0QI34ZgYQdnIjKwMO +0dsr/vlHjfRs0OX2oVmBqSi+TNY240BC4HshQOSPBdR2HsC9vfQ0uax8+nEcZiNG +CQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/fetch_challenges.py b/kms/singletenanthsm/fetch_challenges.py new file mode 100644 index 00000000000..d09f749c41a --- /dev/null +++ b/kms/singletenanthsm/fetch_challenges.py @@ -0,0 +1,13 @@ +import gcloud_commands + + +test_proposal_resource = "projects/cloudkms-dev/locations/us-central1/singleTenantHsmInstances/brandonluong2/proposals/3389a96c-8a64-4bd5-9b97-6957f882416f" + +def test_fetch_challenges(): + process = gcloud_commands.fetch_challenges(test_proposal_resource) + return process + # assert not process.stderr + +if __name__ == "__main__": + challenges = test_fetch_challenges() + print(challenges) \ No newline at end of file diff --git a/kms/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/gcloud_commands.py index d37903268cd..dd8bd6f8bc4 100644 --- a/kms/singletenanthsm/gcloud_commands.py +++ b/kms/singletenanthsm/gcloud_commands.py @@ -50,6 +50,19 @@ def build_custom_gcloud(): print(process.stdout) except subprocess.CalledProcessError as e: raise subprocess.CalledProcessError(f"Error executing gcloud build: {e}") + # try: + # print("\nAdding sthigcloud alias") + # process = subprocess.run( + # "alias sthigcloud=~/sthi/google-cloud-sdk/bin/gcloud", + # check=False, + # capture_output=False, + # executable="/bin/bash", + # text=True, + # shell=True + # ) + # except subprocess.CalledProcessError as e: + # raise subprocess.CalledProcessError(f"Error executing gcloud alias update: {e}") + # print(f"Error executing gcloud alias update: {e}") try: print("\nAdding gcloud components") process = subprocess.run( @@ -88,7 +101,7 @@ def fetch_challenges(sthi_proposal_resource:str): try: print("\nfetching challenges") process = subprocess.run( - command_gcloud_describe_proposal + sthi_proposal_resource, + command_gcloud_describe_proposal + sthi_proposal_resource + " --format=json", capture_output=True, check=False, text=True, diff --git a/kms/singletenanthsm/generated_public_keys/public_key_25167010_slot_82 (RETIRED1).pem b/kms/singletenanthsm/generated_public_keys/public_key_25167010_slot_82 (RETIRED1).pem new file mode 100644 index 00000000000..0c00fb5c279 --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_25167010_slot_82 (RETIRED1).pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmED+fcYCwcVTzpmvwPc +m4RjHW+yZMWfLrlsPoAvA7uQDdeBSR8eDGrpG6yNm/Vpql6S6Ufog7G/m1eqd+XR +/33DP1qouHFqboB0wra+oq+Uk577YEJmNaKM/ej+N6v1NQLlQnXJWuKcTgXoseJb +i011JyLlstAF0DoNSipD7MqlDFS6QthFklunbUuTGvU/RS/+wTnLD/1tEHCAld/7 +NA9eSyANaysXjI7V40SXkXX1NJGkzv9kB3iGPVybpFI3lNlUf/5oQCNkawscPTOb +DYh3pa8i1uTEHB0tB2eBAKo0iuqJqAoTJZ+FQCc+33M4a8hFjmT0wRUA1H3b0YGP +LwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787103_slot_82 (RETIRED1).pem b/kms/singletenanthsm/generated_public_keys/public_key_28787103_slot_82 (RETIRED1).pem new file mode 100644 index 00000000000..faa921c46f4 --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_28787103_slot_82 (RETIRED1).pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu5l1cXUL3vj718gyG/wg +Hh2CXIaU5QOfkm15te/QQsid/8cRPFIR+yMT2Yjg/lvD96Pj4xctnIex9rTJj2TW +VXgRSP04oOO4qBNTYS1YdAruzEy6ObIRtNMXBIgVKDSlQ+pq9BKku9gqFLvPd/rA +94yIFEXbY9ma4ZGuHadu6KYWHTp3x8X8aNICqbEdYEKu31xf1a/arn3D97Mn5GKJ +/c7UtWQz1C8mTU/JPw8AM2uphqckepkcQ/mpAcfVhLbTQa6sg7j61Rj0VL5UiZj5 +NPVnv7QYxGI0LWrS41aM/LjVldgFgQv74pZylWd1K+rSbtmMZjHgdywFYcaK2Ks+ +GQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787105_slot_82 (RETIRED1).pem b/kms/singletenanthsm/generated_public_keys/public_key_28787105_slot_82 (RETIRED1).pem new file mode 100644 index 00000000000..e2e7cbb2935 --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_28787105_slot_82 (RETIRED1).pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/QYPaWqTcZd4ZQ/LAUu +KLCdfEp6osmxH+CN2OnSQidvV18IT4oqJv4iG7Xr2b9jWnpAIE6m/kiPtat6/m9d +z/knPKOiEb4kiai+TwtGpJ+RzW8vtyMZ3tBd6OEhiY9ErhDxsL4pOhYblQ8RRVu/ +GbDi4d38EdfXMQTfkP4QKf8Q/Ko4kWD2cYoNgeCNmvo0cejZlUG7IJ/66gd87nlm +welUDfPGAkt+RCkhZErTzIjLH0yhRiPNcVXnmPPhM36X4SXos7kDd9KxG4KuQP23 +1pT/kDjSDY2SkgZ9AeIifEULxgg+8j8Mf/FwUBONWvOpQObMKGfyD1BN8Z58mY3Z +0wIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/public_key_25167010_slot_82 (RETIRED1).pem b/kms/singletenanthsm/public_key_25167010_slot_82 (RETIRED1).pem deleted file mode 100644 index 819e4ba5d28..00000000000 --- a/kms/singletenanthsm/public_key_25167010_slot_82 (RETIRED1).pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqIWn0lbTgD2giIeORvrE -MjyOrvQLvngOSTivB3hfsSq7Z3if1xaYywg9+/FlChZ8yRys/xrfY1Dy2XJFyO2O -1Vu9uo9MvhHXatJz9UOORDzsMMm671MSi0j6cA8WRrGR5ZakkSikrd4ScLcjTdx1 -DQszrLzCkmRhpUb6CPDlCm39PlMBoGR13GKX3wAmjON5v56IJFttlM/+MLiwmXmM -bI50J6j3C2llcfkaeT36cuqXzpEQESKeZLmv2SZwuW1FMaDcLbqV9eiWx7LMexol -TMxyTUaqf+UYDXCaYacZxEdVMQYp0VRm16utV1/c+nwrj2UC2S4IsYTqKB7GKjmV -0wIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/public_key_28787105_slot_82 (RETIRED1).pem b/kms/singletenanthsm/public_key_28787105_slot_82 (RETIRED1).pem deleted file mode 100644 index d88dcd0414e..00000000000 --- a/kms/singletenanthsm/public_key_28787105_slot_82 (RETIRED1).pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4VKY5Fv5XlOMfoh31qzt -tOEnJ8dzRFoQQZFPcPkrhq43pAVAX5kRuFdJvZ6BhQPehdh+kjucE3eRBdjj0wfH -u9Auxah9fx4A1vwRSKIJMooV1GlTqfMf0PjGSPh7RORUV2YMH93KB0rXIqJGKR+a -3n0HqUUgiuZCKlG2CHPlwNulo4SPZtZL1T/XwpT9jVnC8yws9HRaJ/q2wGfrA2A0 -zS6AA8kjU68wsAenRTl1ZFhZdkfWhZY4yZbY75oEs2LncBFJ6FzrFGY/KX5GNqvM -4N02iB/1KjS3kOzYiv79t62fuOV8ufeLA0p1KvI9bZh+MnYJsufp3dYIFAr6uAUu -uQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/requirements.txt b/kms/singletenanthsm/requirements.txt index 96a0cdfca9e..e29d4ff4c8e 100644 --- a/kms/singletenanthsm/requirements.txt +++ b/kms/singletenanthsm/requirements.txt @@ -4,14 +4,18 @@ click==8.1.8 cryptography==44.0.0 fido2==1.2.0 importlib_metadata==8.6.1 +iniconfig==2.0.0 jaraco.classes==3.4.0 jaraco.context==6.0.1 jaraco.functools==4.1.0 jeepney==0.8.0 keyring==25.6.0 more-itertools==10.6.0 +packaging==24.2 +pluggy==1.5.0 pycparser==2.22 pyscard==2.2.1 +pytest==8.3.4 SecretStorage==3.3.3 yubikey-manager==5.5.1 zipp==3.21.0 diff --git a/kms/singletenanthsm/setup_yubikey.py b/kms/singletenanthsm/setup.py similarity index 56% rename from kms/singletenanthsm/setup_yubikey.py rename to kms/singletenanthsm/setup.py index 38dd41ef435..55602ec4d37 100644 --- a/kms/singletenanthsm/setup_yubikey.py +++ b/kms/singletenanthsm/setup.py @@ -12,9 +12,30 @@ # See the License for the specific language governing permissions and # limitations under the License. +import argparse import ykman_utils import gcloud_commands +def validate_operation(operation: str): + if operation == "build_custom_gcloud": + try: + gcloud_commands.build_custom_gcloud() + except Exception as e: + raise Exception(f"Generating custom gcloud build failed {e}") + elif operation == "generate_rsa_keys": + try: + ykman_utils.generate_private_key() + except Exception as e: + raise Exception(f"Generating private keys failed {e}") + elif operation == "generate_gcloud_and_keys": + generate_private_keys_build_gcloud() + else: + raise Exception("Operation type not valid. Operation flag value must be build_custom_gcloud," + " generate_rsa_keys, or generate_gcloud_and_keys") + + + + def generate_private_keys_build_gcloud(): """Generates an RSA key on slot 82 of every yubikey connected to the local machine and builds the custom gcloud cli. @@ -30,4 +51,7 @@ def generate_private_keys_build_gcloud(): if __name__ == "__main__": - generate_private_keys_build_gcloud() + parser = argparse.ArgumentParser() + parser.add_argument('--operation',type=str, required=True) + args = parser.parse_args() + validate_operation(args.operation) diff --git a/kms/singletenanthsm/sign_proposals_test.py b/kms/singletenanthsm/sign_proposals_test.py index 28499a0eb12..2fc4c333efd 100644 --- a/kms/singletenanthsm/sign_proposals_test.py +++ b/kms/singletenanthsm/sign_proposals_test.py @@ -25,7 +25,7 @@ from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives.serialization import load_pem_public_key -import approve_sthi_proposal +import kms.singletenanthsm.approve_proposal as approve_proposal public_key = """-----BEGIN PUBLIC KEY----- @@ -83,8 +83,8 @@ def test_sign_proposal_success(): challenges = [] challenges.append(example_challenge_1) challenges.append(example_challenge_2) - signed_challenges = approve_sthi_proposal.sign_proposal(challenges) - if approve_sthi_proposal: + signed_challenges = approve_proposal.sign_proposal(challenges) + if approve_proposal: print("proposals signed") else: print("proposals not signed") @@ -101,9 +101,11 @@ def test_sign_proposal_no_matching_public_keys(): challenges = [] + + def test_fetch_challenges(): print("fetching challenges") - approve_sthi_proposal.fetch_challenges + approve_proposal.fetch_challenges print("challenges fetched successfully") diff --git a/kms/singletenanthsm/signed_challenges/challenge1.txt b/kms/singletenanthsm/signed_challenges/challenge1.txt new file mode 100644 index 00000000000..cf515f2b3cd --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/challenge1.txt @@ -0,0 +1,2 @@ +$wEQݰ^sLum fgvwFJ p(T{LhՑck/.Vk`L 6ݤJ+scQJ7߮eCDH4,B7pФd%cRi'!78=CmS('G\wn2ular<ĖqŸ%OuUF[$P|e|cE +Kc!_ \ No newline at end of file diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem new file mode 100644 index 00000000000..b9b47aab8f4 --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrAMwX6JXw9RIH8f2+lG +VpaqAT2nwyzbPtpoRgemX/6ig3R8W9yNWOMvK6tf0MAVeHiyQIPucNXehr1VzQ1/ +VIlA9KA7LXznXwXDZkLl1/XHeJGbd3eyEA8K7tT3bFrSTZx/mVhEuJN9eLb5tL9t +gqes7pgYYe2iyhUCqQM3oh4K5SJGooQDX41/VXP8PxCem+71oG/LWogIVMLnU9Yu +33oQY6xvF1YKNgfGqnyNh5rPduQplJy04o/b6Xs3mcX/NUOBMUcLXDxe6tRGyy/2 +UX+6thK9gU2dXheK03rwhMv5jmpIGP7FShSs/+9N3nUIA+g5sTgnm2xqch5jOYXV +lwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem new file mode 100644 index 00000000000..d6c87b690b9 --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKbCvQEFWx65Ma57Ul24 +044s74u7H1nLQJ8WfBe33MKmOrsR31pO5dIfnJIs8d0vDFj3cm4MRRpIdI8PkOPE +oDDa9z0dz/t4qDpOKlRcxaFNZGoBs+j+TuC3rsrBxIxc+qSDn/WhDV2oAXaj3Jnw +5DPCGljDObbzaDwTjovP3enfFCncCl76z0yGuKoyW4aqoK2ccHLRxNF69bYSzICE +CiUxUeHJTvoLuJ8fwGUIKYsBb9tBuzGsrM+5Mj+bskvJLM3cNOkW1p1TOjLTtMk2 +pDlY+9EdWMJ/6wbX88b7dl5T3aQOTzNrxjojpYPyNWdHIGXL1YwKNE3EkdN7kXkK +yQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem new file mode 100644 index 00000000000..126fffa463f --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoidJnb4gpd8Ua/QRpwjb +Cg+0pHhQ22bv0bX66fj8niLPV4o/TaQzyYD94zA7t6ulUmxj93w2U0EEmsufrBrt +OgorVidL6MLuEOo0lWy0DHBsFDellFA75MTTIs76odhJshVfe9OSwFAakoMHiDAY +LTmTEcaJsanUiHhIaxF5O/cTAFlkhOLFD0becK674OAnqqmabzJx2qlTMraR3yPi +W1uLPdPBNeokBirk4WIO8XKtQJuOrGWhAxjIvGW20paRWgs0QI34ZgYQdnIjKwMO +0dsr/vlHjfRs0OX2oVmBqSi+TNY240BC4HshQOSPBdR2HsC9vfQ0uax8+nEcZiNG +CQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.txt b/kms/singletenanthsm/signed_challenges/signed_challenge1.txt new file mode 100644 index 00000000000..bdf0886dd3f --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/signed_challenge1.txt @@ -0,0 +1,3 @@ +N=ᣇ,\̸ +`hɓC.݋oD4C,@uisaR"mwKFu jT%[%FxU^a?rNӴN6`L~8~asCfzz}CBC0fbai zR?i^5(!-&w8NPBjOnCT31moY(#)xni(k##A3FvhPB>--LlcLw;2>Bk|mcf z^ff|v>$TyA@uS-`MZ}OB)~;;;O%BeAvNanqkGyA4wkoZbcy7=t8ukP$$U^S*1?bFWXU>P6@GKPnGsDbR73~LcR_V0Ez#Pc&330-X#kLt zhss(Xy`Yy9?PZO~8uF=DX@?f-n#=#MV*Bs=8M#(#^w_QOSoz6UXEAyh>a1Rugf>-0 zg`}uF5#5HUAp5(`y3+^Nj|CRL!r7xytz`^9YS7)Ig%|pK8P8tpj{4cY&W~lDD*dR4 z2x>$WR{GC$<^)Mg1WT)=qmrxvt7JU-_no#hY;%+0piqS96xGi+fS?OzYI|(~L|(Vu z8<`WLa-Zn_7bMsgROAkpP_TN9Fl+gU;y1F$Lyd823wGi!IbD&t_A!AY{_K@XVRc6s G^$v4uw1VIO literal 0 HcmV?d00001 diff --git a/kms/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/ykman_utils.py index 2db257b9b27..bfaef58c9b7 100644 --- a/kms/singletenanthsm/ykman_utils.py +++ b/kms/singletenanthsm/ykman_utils.py @@ -14,8 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import ykman +import pathlib import cryptography.exceptions +import glob +import re +import os +import ykman + from cryptography.hazmat.primitives import _serialization @@ -67,7 +72,7 @@ def generate_private_key( if not public_key: raise Exception("failed to generate public key") with open( - f"public_key_{device_info.serial}_slot_{piv.SLOT.RETIRED1}.pem", "wb" + f"generated_public_keys/public_key_{device_info.serial}_slot_{piv.SLOT.RETIRED1}.pem", "wb" ) as binary_file: # Write bytes to file @@ -82,14 +87,42 @@ def generate_private_key( f" slot: {piv.SLOT.RETIRED1}" ) +class Challenge: + + def __init__(self, challenge, public_key_pem): + self.challenge = challenge + self.public_key_pem = public_key_pem + class ChallengeReply: def __init__(self, signed_challenge, public_key_pem): self.signed_challenge = signed_challenge self.public_key_pem = public_key_pem - -def sign_proposal(challenges): +def populate_challenges_from_files(): + public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("challenges/public_key*.pem")] + print(public_key_files) + challenge_files = [challenge_file for challenge_file in pathlib.Path.cwd().glob("challenges/challenge*.txt")] + print(challenge_files) + + challenges = [] + + for public_key_file in public_key_files: + challenge_id = re.findall(r"\d+", str(public_key_file)) + for challenge_file in challenge_files: + if challenge_id == re.findall(r"\d+",str(challenge_file)): + print(public_key_file) + file = open(public_key_file, "r") + public_key_pem = file.read() + file.close() + file = open(challenge_file, "r") + challenge = file.read() + file.close() + challenges.append(Challenge(challenge, public_key_pem )) + return challenges + + +def sign_proposal(challenges, singed_challenge_files): """Signs a proposal's challenges using a Yubikey.""" if not challenges: raise Exception("Challenge list empty: No challenges to sign.") @@ -97,6 +130,7 @@ def sign_proposal(challenges): devices = list_all_devices() if not devices: raise Exception("no yubikeys found") + challenge_count = 0 for yubikey, _ in devices: with yubikey.open_connection(SmartCardConnection) as connection: # Make PivSession and fetch public key from Signature slot. @@ -119,23 +153,53 @@ def sign_proposal(challenges): encoding=_serialization.Encoding.PEM, format=_serialization.PublicFormat.SubjectPublicKeyInfo, ) - + print(key_public_bytes.decode()) + print(challenge.public_key_pem) if key_public_bytes == challenge.public_key_pem.encode(): # sign the challenge print("Press Yubikey to sign challenge") - signed_challenges.append( - ChallengeReply( - piv_session.sign( + signed_challenge = piv_session.sign( slot=piv.SLOT.RETIRED1, key_type=slot_metadata.key_type, - message=challenge.challenge, + message=challenge.challenge.encode('utf-8'), hash_algorithm=hashes.SHA256(), padding=padding.PKCS1v15(), - ), + ) + + signed_challenges.append( + ChallengeReply( + signed_challenge, challenge.public_key_pem ) ) + challenge_count += 1 + print("challenge_count", challenge_count) + directory_path = "signed_challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + with open( + f"signed_challenges/public_key_{challenge_count}.pem", "w" + ) as binary_file: + + # Write public key to file + binary_file.write( + challenge.public_key_pem + ) + with open( + f"signed_challenges/signed_challenge{challenge_count}.txt", "wb" + ) as binary_file: + + # Write public key to file + binary_file.write( + signed_challenge + ) + singed_challenge_files.append((f"signed_challenges/signed_challenge{challenge_count}.txt", + f"signed_challenges/public_key_{challenge_count}.pem") + ) print("Challenge signed successfully") if not signed_challenges: raise Exception( diff --git a/kms/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/ykman_utils_test.py index 6c9ee0e463c..9aa52766b3f 100644 --- a/kms/singletenanthsm/ykman_utils_test.py +++ b/kms/singletenanthsm/ykman_utils_test.py @@ -19,6 +19,7 @@ import os import pathlib import pytest +import re import ykman_utils @@ -36,14 +37,52 @@ def __init__(self, signed_challenge, public_key_pem): challenge_test_data = b"test_data" +# def populate_challenges_from_files(): +# public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("challenges/public_key*.pem")] +# print(public_key_files) +# challenge_files = [challenge_file for challenge_file in pathlib.Path.cwd().glob("challenges/challenge*.txt")] +# print(challenge_files) + +# challenges = [] + +# for public_key_file in public_key_files: +# challenge_id = re.findall(r"\d+", str(public_key_file)) +# for challenge_file in challenge_files: +# if challenge_id == re.findall(r"\d+",str(challenge_file)): +# print(public_key_file) +# file = open(public_key_file, "r") +# public_key_pem = file.read() +# file.close() +# file = open(challenge_file, "r") +# challenge = file.read() +# file.close() +# challenges.append(Challenge(challenge, public_key_pem )) +# return challenges + +def generate_test_challenge_files(): + # Create challenges list from challenges directory + challenges = ykman_utils.populate_challenges_from_files() + for challenge in challenges: + print(challenge.challenge) + print(challenge.public_key_pem) + # Signes challenges + signed_challenge_files = [] + signed_challenges = ykman_utils.sign_proposal(challenges, signed_challenge_files) + for signed_challenge in signed_challenge_files: + print(signed_challenge.signed_challenge.decode()) + print(signed_challenge.public_key_pem) + ykman_utils.verify_challenge_signatures(signed_challenges, b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN") + + + # A yubikey connected to your local machine will be needed to run these tests. # The generate_private_key() method will rewrite the key saved on slot 82(Retired1). -@pytest.fixture() +@pytest.fixture(autouse=True) def key_setup(): ykman_utils.generate_private_key() def challenges(): - public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("public_key*.pem")] + public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("generated_public_keys/public_key*.pem")] challenges = [] @@ -57,13 +96,13 @@ def challenges(): def test_sign_and_verify_challenges(): - signed_challenges = ykman_utils.sign_proposal(challenges()) + signed_challenges = ykman_utils.sign_proposal(challenges(),[]) ykman_utils.verify_challenge_signatures(signed_challenges, challenge_test_data) def test_sign_empty_challenge_list_fail(): with pytest.raises(Exception) as exec_info: # empty_challenges = [] - signed_challenges = ykman_utils.sign_proposal([]) + signed_challenges = ykman_utils.sign_proposal([],[]) assert "Challenge list empty" in str(exec_info.value) def test_sign_no_matching_public_keys_fail(): @@ -71,7 +110,7 @@ def test_sign_no_matching_public_keys_fail(): for challenge in modified_challenges: challenge.public_key_pem = "modified_public_key" with pytest.raises(Exception) as exec_info: - signed_challenges = ykman_utils.sign_proposal(modified_challenges) + signed_challenges = ykman_utils.sign_proposal(modified_challenges,[]) assert "No matching public keys" in str(exec_info.value) def test_verify_empty_challenge_replies_fail(): @@ -82,12 +121,13 @@ def test_verify_empty_challenge_replies_fail(): def test_verify_mismatching_data_fail(): with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: - signed_challenges = ykman_utils.sign_proposal(challenges()) + signed_challenges = ykman_utils.sign_proposal(challenges(),[]) ykman_utils.verify_challenge_signatures(signed_challenges, b"mismatched_data") assert "Signature verification failed" in str(exec_info.value) -# if __name__ == "__main__": -# ykman_utils.generate_private_key() -# test_sign_and_verify_challenges() \ No newline at end of file +if __name__ == "__main__": + # ykman_utils.generate_private_key() + # test_sign_and_verify_challenges() + generate_test_challenge_files() \ No newline at end of file From 554078a7148e6ab58b5b89a92c6411c807784245 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Wed, 30 Apr 2025 15:25:06 -0400 Subject: [PATCH 04/19] Added test to approve command as well as to gcloud module --- kms/singletenanthsm/README.rst | 105 +++ kms/singletenanthsm/approve_proposal.py | 183 ++--- kms/singletenanthsm/approve_proposal_test.py | 287 +++++--- kms/singletenanthsm/brandonluong/asr/BUILD | 57 ++ .../brandonluong/asr/hsm_functions.cc | 507 ++++++++++++++ .../brandonluong/asr/hsm_functions.h | 87 +++ .../brandonluong/asr/hsm_functions_test.cc | 122 ++++ .../brandonluong/cfmwrap/BUILD | 60 ++ .../cfmwrap/cavium_layers_test.cc | 635 ++++++++++++++++++ .../brandonluong/cfmwrap/hsm_functions.cc | 507 ++++++++++++++ .../brandonluong/cfmwrap/hsm_functions.h | 88 +++ .../cfmwrap/hsm_functions_perf_test.cc | 360 ++++++++++ .../cfmwrap/hsm_functions_test.cc | 485 +++++++++++++ .../brandonluong/conflicts/conflict.txt | 6 + .../brandonluong/quotes/index.html | 23 + .../brandonluong/singletenanthsm/README.rst | 105 +++ .../singletenanthsm/approve_proposal.py | 141 ++++ .../singletenanthsm/approve_proposal_test.py | 261 +++++++ .../singletenanthsm/challenges/challenge1.txt | 1 + .../singletenanthsm/challenges/challenge2.txt | 1 + .../singletenanthsm/challenges/challenge3.txt | 1 + .../challenges/public_key1.pem | 9 + .../challenges/public_key2.pem | 9 + .../challenges/public_key3.pem | 9 + .../singletenanthsm/gcloud_commands.py | 152 +++++ .../singletenanthsm/gcloud_commands_test.py | 321 +++++++++ .../public_key_25167010.pem | 9 + .../public_key_28787103.pem | 9 + .../public_key_28787105.pem | 9 + .../singletenanthsm/requirements.txt | 21 + .../brandonluong/singletenanthsm/setup.py | 69 ++ .../signed_challenges/public_key_1.pem | 9 + .../signed_challenges/public_key_2.pem | 9 + .../signed_challenges/public_key_3.pem | 9 + .../signed_challenges/signed_challenge1.txt | 1 + .../signed_challenges/signed_challenge2.txt | 3 + .../signed_challenges/signed_challenge3.txt | Bin 0 -> 256 bytes .../singletenanthsm/ykman_fake.py | 53 ++ .../singletenanthsm/ykman_utils.py | 260 +++++++ .../singletenanthsm/ykman_utils_test.py | 109 +++ kms/singletenanthsm/challenges/challenge1.txt | 1 - kms/singletenanthsm/challenges/challenge2.txt | 1 - kms/singletenanthsm/challenges/challenge3.txt | 1 - .../challenges/public_key1.pem | 9 - .../challenges/public_key2.pem | 9 - .../challenges/public_key3.pem | 9 - kms/singletenanthsm/fetch_challenges.py | 13 - kms/singletenanthsm/gcloud_commands.py | 64 +- kms/singletenanthsm/gcloud_commands_test.py | 310 ++++++++- ...public_key_25167010_slot_82 (RETIRED1).pem | 9 - ...public_key_28787103_slot_82 (RETIRED1).pem | 9 - ...public_key_28787105_slot_82 (RETIRED1).pem | 9 - kms/singletenanthsm/setup.py | 5 +- kms/singletenanthsm/sign_proposals_test.py | 115 ---- .../signed_challenges/challenge1.txt | 2 - .../signed_challenges/public_key_1.pem | 9 - .../signed_challenges/public_key_2.pem | 9 - .../signed_challenges/public_key_3.pem | 9 - .../signed_challenges/signed_challenge1.txt | 3 - .../signed_challenges/signed_challenge2.txt | Bin 256 -> 0 bytes .../signed_challenges/signed_challenge3.txt | Bin 256 -> 0 bytes .../singletenanthsm/README.rst | 105 +++ .../singletenanthsm/approve_proposal.py | 140 ++++ .../singletenanthsm/approve_proposal_test.py | 241 +++++++ .../singletenanthsm/challenges/challenge1.txt | 1 + .../singletenanthsm/challenges/challenge2.txt | 1 + .../singletenanthsm/challenges/challenge3.txt | 1 + .../challenges/public_key1.pem | 9 + .../challenges/public_key2.pem | 9 + .../challenges/public_key3.pem | 9 + .../singletenanthsm/gcloud_commands.py | 153 +++++ .../singletenanthsm/gcloud_commands_test.py | 321 +++++++++ .../public_key_25167010.pem | 9 + .../public_key_28787103.pem | 9 + .../public_key_28787105.pem | 9 + .../singletenanthsm/requirements.txt | 21 + kms/singletenanthsm/singletenanthsm/setup.py | 60 ++ .../signed_challenges/public_key_1.pem | 9 + .../signed_challenges/public_key_2.pem | 9 + .../signed_challenges/public_key_3.pem | 9 + .../signed_challenges/signed_challenge1.bin | 1 + .../signed_challenges/signed_challenge1.txt | 1 + .../signed_challenges/signed_challenge2.bin | Bin 0 -> 256 bytes .../signed_challenges/signed_challenge2.txt | 3 + .../signed_challenges/signed_challenge3.bin | 1 + .../signed_challenges/signed_challenge3.txt | Bin 0 -> 256 bytes .../singletenanthsm/ykman_fake.py | 59 ++ .../singletenanthsm/ykman_utils.py | 254 +++++++ .../singletenanthsm/ykman_utils_test.py | 98 +++ kms/singletenanthsm/ykman_fake.py | 59 ++ kms/singletenanthsm/ykman_utils.py | 110 +-- kms/singletenanthsm/ykman_utils_test.py | 63 +- 92 files changed, 6944 insertions(+), 518 deletions(-) create mode 100644 kms/singletenanthsm/README.rst create mode 100644 kms/singletenanthsm/brandonluong/asr/BUILD create mode 100644 kms/singletenanthsm/brandonluong/asr/hsm_functions.cc create mode 100644 kms/singletenanthsm/brandonluong/asr/hsm_functions.h create mode 100644 kms/singletenanthsm/brandonluong/asr/hsm_functions_test.cc create mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/BUILD create mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/cavium_layers_test.cc create mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.cc create mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.h create mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_perf_test.cc create mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_test.cc create mode 100644 kms/singletenanthsm/brandonluong/conflicts/conflict.txt create mode 100644 kms/singletenanthsm/brandonluong/quotes/index.html create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/README.rst create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal.py create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal_test.py create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge1.txt create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge2.txt create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge3.txt create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key1.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key2.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key3.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands.py create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands_test.py create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_25167010.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787103.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787105.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/requirements.txt create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/setup.py create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_1.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_2.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_3.pem create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge1.txt create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge2.txt create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge3.txt create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/ykman_fake.py create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils.py create mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils_test.py delete mode 100644 kms/singletenanthsm/challenges/challenge1.txt delete mode 100644 kms/singletenanthsm/challenges/challenge2.txt delete mode 100644 kms/singletenanthsm/challenges/challenge3.txt delete mode 100644 kms/singletenanthsm/challenges/public_key1.pem delete mode 100644 kms/singletenanthsm/challenges/public_key2.pem delete mode 100644 kms/singletenanthsm/challenges/public_key3.pem delete mode 100644 kms/singletenanthsm/fetch_challenges.py delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_25167010_slot_82 (RETIRED1).pem delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787103_slot_82 (RETIRED1).pem delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787105_slot_82 (RETIRED1).pem delete mode 100644 kms/singletenanthsm/sign_proposals_test.py delete mode 100644 kms/singletenanthsm/signed_challenges/challenge1.txt delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_1.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_2.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_3.pem delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge1.txt delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge2.txt delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge3.txt create mode 100644 kms/singletenanthsm/singletenanthsm/README.rst create mode 100644 kms/singletenanthsm/singletenanthsm/approve_proposal.py create mode 100644 kms/singletenanthsm/singletenanthsm/approve_proposal_test.py create mode 100644 kms/singletenanthsm/singletenanthsm/challenges/challenge1.txt create mode 100644 kms/singletenanthsm/singletenanthsm/challenges/challenge2.txt create mode 100644 kms/singletenanthsm/singletenanthsm/challenges/challenge3.txt create mode 100644 kms/singletenanthsm/singletenanthsm/challenges/public_key1.pem create mode 100644 kms/singletenanthsm/singletenanthsm/challenges/public_key2.pem create mode 100644 kms/singletenanthsm/singletenanthsm/challenges/public_key3.pem create mode 100644 kms/singletenanthsm/singletenanthsm/gcloud_commands.py create mode 100644 kms/singletenanthsm/singletenanthsm/gcloud_commands_test.py create mode 100644 kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_25167010.pem create mode 100644 kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787103.pem create mode 100644 kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787105.pem create mode 100644 kms/singletenanthsm/singletenanthsm/requirements.txt create mode 100644 kms/singletenanthsm/singletenanthsm/setup.py create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_1.pem create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_2.pem create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_3.pem create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.bin create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.txt create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.bin create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.txt create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.bin create mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.txt create mode 100644 kms/singletenanthsm/singletenanthsm/ykman_fake.py create mode 100644 kms/singletenanthsm/singletenanthsm/ykman_utils.py create mode 100644 kms/singletenanthsm/singletenanthsm/ykman_utils_test.py create mode 100644 kms/singletenanthsm/ykman_fake.py diff --git a/kms/singletenanthsm/README.rst b/kms/singletenanthsm/README.rst new file mode 100644 index 00000000000..a3fe9fee3d3 --- /dev/null +++ b/kms/singletenanthsm/README.rst @@ -0,0 +1,105 @@ +Google Cloud Key Management Service Python Samples +=============================================================================== + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=kms/singletenanthsm/README.rst + + +This directory contains samples for Google Cloud Key Management Service. The `Cloud Key Management Service`_ allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service. + + + + +.. _Cloud Key Management Service: https://cloud.google.com/kms/docs/ + + + + + +Setup +------------------------------------------------------------------------------- + + +Install Dependencies +++++++++++++++++++++ + +#. Clone python-kms and change directory to the sample directory you want to use. + + .. code-block:: bash + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. + + .. _Python Development Environment Setup Guide: + https://cloud.google.com/python/setup + +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ + +Samples +------------------------------------------------------------------------------- +Create a custom gcloud build to access the Single Tenant HSM service. + +Approve a Single Tenant HSM Instance Proposal. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Creates custom gcloud build to access single tenant HSM service+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +To run this sample: + +.. code-block:: bash + + $ python3 setup.py + + usage: setup.py [-h] [--operation] + + This application creates a custom gcloud build to access the single tenant HSM service. + + positional arguments: + operation The type of setup operation you want to perform. This includes build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'. + + optional arguments: + -h, --help show this help message and exit + + + +Approves a Single Tenant HSM Instance Proposal. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +To run this sample: + +.. code-block:: bash + + $ python3 approve_proposal.py + + usage: approve_proposal.py [-h] [--proposal_resource PROPOSAL_RESOURCE] + + This application fetches and approves the single tenant HSM instance proposal + specified in the "proposal_resource" field. + + For more information, visit https://cloud.google.com/kms/docs/attest-key. + + positional arguments: + --proposal_resource PROPOSAL_RESOURCE + The full name of the single tenant HSM instance proposal that needs to be approved. + + + + optional arguments: + -h, --help show this help message and exit + + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/kms/singletenanthsm/approve_proposal.py b/kms/singletenanthsm/approve_proposal.py index 6435770b78d..6684547b15d 100644 --- a/kms/singletenanthsm/approve_proposal.py +++ b/kms/singletenanthsm/approve_proposal.py @@ -15,98 +15,127 @@ # limitations under the License. import argparse -import sys -import gcloud_commands import json import os -import ykman_utils +import sys +from typing import List +import gcloud_commands +import ykman_utils -def parse_only_challenges(sthi_output): - proposal_json = json.loads(sthi_output, strict= False) - challenges = proposal_json["quorumParameters"]["challenges"] - directory_path = "challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - - challenge_count = 0 - for challenge in challenges: - challenge_count += 1 - print(challenge["challenge"]+ "\n") - print(challenge["publicKeyPem"].encode('utf-8').decode('unicode_escape')) - f = open("challenges/challenge{0}.txt".format(challenge_count), "w") - f.write(challenge["challenge"]) - f.close - - f = open("challenges/public_key{0}.pem".format(challenge_count), "w") - f.write(challenge["publicKeyPem"].encode('utf-8').decode('unicode_escape')) - f.close() - - - - - -def parse_challenges(fetch_string): - text_list = fetch_string.split("requiredApproverCount") - challenge_list = text_list[0].split("challenge:") - - directory_path = "challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - - - print("PRINTING CHALLENGES") - challenge_count = 0 - for challenge in challenge_list[1:]: - - challenge_count += 1 - elem_list = challenge.split(" ", 12) - print("challenge: " + elem_list[1]) - print("public key: " + elem_list[-1]) - - f = open("challenges/challenge{0}.txt".format(challenge_count), "w") - f.write(elem_list[1]) - f.close - - f = open("challenges/public_key{0}.pem".format(challenge_count), "w") - f.write(elem_list[-1]) - f.close() +def parse_challenges_into_files(sthi_output: str) -> List[bytes]: + """Parses the STHI output and writes the challenges and public keys to files. + + Args: + sthi_output: The output of the STHI command. + + Returns: + A list of the unsigned challenges. + """ + print("parsing challenges into files") + proposal_json = json.loads(sthi_output, strict=False) + challenges = proposal_json["quorumParameters"]["challenges"] + + directory_path = "challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + + challenge_count = 0 + unsigned_challenges = [] + for challenge in challenges: + challenge_count += 1 + print(challenge["challenge"] + "\n") + print(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f = open("challenges/challenge{0}.txt".format(challenge_count), "wb") + binary_challenge = ykman_utils.urlsafe_base64_to_binary( + challenge["challenge"] + ) + f.write(binary_challenge) + f.close() + + f = open("challenges/public_key{0}.pem".format(challenge_count), "w") + f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f.close() + unsigned_challenges.append( + ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"]) + ) + + return unsigned_challenges def parse_args(args): - parser = argparse.ArgumentParser() - parser.add_argument("--proposal_resource", type=str, required=True) - return parser.parse_args(args) + parser = argparse.ArgumentParser() + parser.add_argument("--proposal_resource", type=str, required=True) + return parser.parse_args(args) + + +def signed_challenges_to_files( + challenge_replies: list[ykman_utils.ChallengeReply], +) -> None: + """Writes the signed challenges and public keys to files. + + Args: + challenge_replies: A list of ChallengeReply objects. + + Returns: + A list of tuples containing the signed challenge file path and the public + key file path. + """ + signed_challenge_files = [] + challenge_count = 0 + for challenge_reply in challenge_replies: + challenge_count += 1 + print("challenge_count", challenge_count) + directory_path = "signed_challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + with open( + f"signed_challenges/public_key_{challenge_count}.pem", "w" + ) as public_key_file: + + # Write public key to file + public_key_file.write(challenge_reply.public_key_pem) + with open( + f"signed_challenges/signed_challenge{challenge_count}.bin", "wb" + ) as binary_file: + + # Write signed challenge to file + binary_file.write(challenge_reply.signed_challenge) + signed_challenge_files.append(( + f"signed_challenges/signed_challenge{challenge_count}.bin", + f"signed_challenges/public_key_{challenge_count}.pem", + )) + return signed_challenge_files def approve_proposal(): - parser = parse_args(sys.argv[1:]) + """Approves a proposal by fetching challenges, signing them, and sending them back to gcloud.""" + parser = parse_args(sys.argv[1:]) - # Fetch challenges - process = gcloud_commands.fetch_challenges(parser.proposal_resource) + # Fetch challenges + process = gcloud_commands.fetch_challenges(parser.proposal_resource) - # Parse challenges into files - parse_only_challenges(process.stdout) + # Parse challenges into files + unsigned_challenges = parse_challenges_into_files(process.stdout) - # Sign challenges - challenges = ykman_utils.populate_challenges_from_files() - signed_challenged_files = [] - signed_challenges = ykman_utils.sign_proposal(challenges, signed_challenged_files) + # Sign challenges + signed_challenges = ykman_utils.sign_challenges(unsigned_challenges) - # Return signed challenges to gcloud - process = gcloud_commands.send_signed_challenges( - signed_challenges, - args.proposal_resource - ) + # Parse signed challenges into files + signed_challenged_files = signed_challenges_to_files(signed_challenges) + # Return signed challenges to gcloud + gcloud_commands.send_signed_challenges( + signed_challenged_files, parser.proposal_resource + ) -if __name__ == "__main__": - approve_proposal() +if __name__ == "__main__": + approve_proposal() diff --git a/kms/singletenanthsm/approve_proposal_test.py b/kms/singletenanthsm/approve_proposal_test.py index 36d2f6b452c..b9e293fc9cb 100644 --- a/kms/singletenanthsm/approve_proposal_test.py +++ b/kms/singletenanthsm/approve_proposal_test.py @@ -11,134 +11,231 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from dataclasses import dataclass -import ykman_utils -import kms.singletenanthsm.approve_proposal as approve_proposal -import gcloud_commands -import pytest -import subprocess -import sys +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PublicFormat, +) + import argparse -from unittest.mock import patch, Mock +import json +import os +import subprocess from unittest import mock +from unittest.mock import Mock +from unittest.mock import patch +# from approve_proposal import parse_args + +import approve_proposal +import gcloud_commands +import pytest +import ykman_fake +import ykman_utils sample_sthi_output = """ { - "quorumParameters": { + "quorumParameters": { "challenges": [ { - "challenge": "-jgoZSrRHP_1WQt6f4cx911AkWfa9WzqVuuOLd7iw6iAwdkeVcBA_Q3sBqaP_5B6", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKbCvQEFWx65Ma57Ul24\n044s74u7H1nLQJ8WfBe33MKmOrsR31pO5dIfnJIs8d0vDFj3cm4MRRpIdI8PkOPE\noDDa9z0dz/t4qDpOKlRcxaFNZGoBs+j+TuC3rsrBxIxc+qSDn/WhDV2oAXaj3Jnw\n5DPCGljDObbzaDwTjovP3enfFCncCl76z0yGuKoyW4aqoK2ccHLRxNF69bYSzICE\nCiUxUeHJTvoLuJ8fwGUIKYsBb9tBuzGsrM+5Mj+bskvJLM3cNOkW1p1TOjLTtMk2\npDlY+9EdWMJ/6wbX88b7dl5T3aQOTzNrxjojpYPyNWdHIGXL1YwKNE3EkdN7kXkK\nyQIDAQAB\n-----END PUBLIC KEY-----\n" + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" }, { - "challenge": "C-A4-b-ZKjpgY0EJZmDIbUsTaZs3iPq-iW9XWQ18zd_k-biw8iKiWX7eLBjwUoHk", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrAMwX6JXw9RIH8f2+lG\nVpaqAT2nwyzbPtpoRgemX/6ig3R8W9yNWOMvK6tf0MAVeHiyQIPucNXehr1VzQ1/\nVIlA9KA7LXznXwXDZkLl1/XHeJGbd3eyEA8K7tT3bFrSTZx/mVhEuJN9eLb5tL9t\ngqes7pgYYe2iyhUCqQM3oh4K5SJGooQDX41/VXP8PxCem+71oG/LWogIVMLnU9Yu\n33oQY6xvF1YKNgfGqnyNh5rPduQplJy04o/b6Xs3mcX/NUOBMUcLXDxe6tRGyy/2\nUX+6thK9gU2dXheK03rwhMv5jmpIGP7FShSs/+9N3nUIA+g5sTgnm2xqch5jOYXV\nlwIDAQAB\n-----END PUBLIC KEY-----\n" + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" }, { - "challenge": "Q8nT85xO8wuYguKfPSVU60rmpO67ue-CvSFXxIVzjeh2AaYPQ3Vq7Jsu2Bk4ynAC", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoidJnb4gpd8Ua/QRpwjb\nCg+0pHhQ22bv0bX66fj8niLPV4o/TaQzyYD94zA7t6ulUmxj93w2U0EEmsufrBrt\nOgorVidL6MLuEOo0lWy0DHBsFDellFA75MTTIs76odhJshVfe9OSwFAakoMHiDAY\nLTmTEcaJsanUiHhIaxF5O/cTAFlkhOLFD0becK674OAnqqmabzJx2qlTMraR3yPi\nW1uLPdPBNeokBirk4WIO8XKtQJuOrGWhAxjIvGW20paRWgs0QI34ZgYQdnIjKwMO\n0dsr/vlHjfRs0OX2oVmBqSi+TNY240BC4HshQOSPBdR2HsC9vfQ0uax8+nEcZiNG\nCQIDAQAB\n-----END PUBLIC KEY-----\n" + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" } - ] + ], + "requiredApproverCount": 3 } } """ -sample_approved_challenges_output = """ -quorumParameters: - approvedTwoFactorPublicKeyPems: - - | - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoidJnb4gpd8Ua/QRpwjb - Cg+0pHhQ22bv0bX66fj8niLPV4o/TaQzyYD94zA7t6ulUmxj93w2U0EEmsufrBrt - OgorVidL6MLuEOo0lWy0DHBsFDellFA75MTTIs76odhJshVfe9OSwFAakoMHiDAY - LTmTEcaJsanUiHhIaxF5O/cTAFlkhOLFD0becK674OAnqqmabzJx2qlTMraR3yPi - W1uLPdPBNeokBirk4WIO8XKtQJuOrGWhAxjIvGW20paRWgs0QI34ZgYQdnIjKwMO - 0dsr/vlHjfRs0OX2oVmBqSi+TNY240BC4HshQOSPBdR2HsC9vfQ0uax8+nEcZiNG - CQIDAQAB - -----END PUBLIC KEY----- - - | - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKbCvQEFWx65Ma57Ul24 - 044s74u7H1nLQJ8WfBe33MKmOrsR31pO5dIfnJIs8d0vDFj3cm4MRRpIdI8PkOPE - oDDa9z0dz/t4qDpOKlRcxaFNZGoBs+j+TuC3rsrBxIxc+qSDn/WhDV2oAXaj3Jnw - 5DPCGljDObbzaDwTjovP3enfFCncCl76z0yGuKoyW4aqoK2ccHLRxNF69bYSzICE - CiUxUeHJTvoLuJ8fwGUIKYsBb9tBuzGsrM+5Mj+bskvJLM3cNOkW1p1TOjLTtMk2 - pDlY+9EdWMJ/6wbX88b7dl5T3aQOTzNrxjojpYPyNWdHIGXL1YwKNE3EkdN7kXkK - yQIDAQAB - -----END PUBLIC KEY----- - - | - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrAMwX6JXw9RIH8f2+lG - VpaqAT2nwyzbPtpoRgemX/6ig3R8W9yNWOMvK6tf0MAVeHiyQIPucNXehr1VzQ1/ - VIlA9KA7LXznXwXDZkLl1/XHeJGbd3eyEA8K7tT3bFrSTZx/mVhEuJN9eLb5tL9t - gqes7pgYYe2iyhUCqQM3oh4K5SJGooQDX41/VXP8PxCem+71oG/LWogIVMLnU9Yu - 33oQY6xvF1YKNgfGqnyNh5rPduQplJy04o/b6Xs3mcX/NUOBMUcLXDxe6tRGyy/2 - UX+6thK9gU2dXheK03rwhMv5jmpIGP7FShSs/+9N3nUIA+g5sTgnm2xqch5jOYXV - lwIDAQAB - -----END PUBLIC KEY----- -""" +sample_nonces = [ + "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", +] -# def create_parser(): -# parser = argparse.ArgumentParser(...) -# parser.add_argument... -# return parser +@dataclass +class QuorumParameters: + challenges: list[ykman_utils.Challenge] -@pytest.fixture() -def setup(): - parser = approve_proposal.parse_args(["proposal_resource", "my_proposal"]) + # def __init__(self, challenges: list[ykman_utils.Challenge]): + # self.challenges = challenges -def approve_mock_proposal(): - approve_proposal.approve_proposal() + # def to_dict(self): + # return {"challenges": self.challenges} -test_resource = "projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal" -mock_completed_process = subprocess.CompletedProcess -def test_get_challenges_mocked(mocker): - mock_response = mocker.MagicMock() - mock_response.stdout = sample_sthi_output +sample_assigned_challenges = "" - # mock the challenge string returned by service - mocker.patch("subprocess.run", return_value=mock_response) - result = gcloud_commands.fetch_challenges(test_resource) - assert result.stdout == sample_sthi_output - assert type(result.stdout) is str - # mock the creation of the challenges directory - - - # sign challenges +@pytest.fixture() +def setup(): + parser = approve_proposal.parse_args(["proposal_resource", "my_proposal"]) - # mock sneding the signed challenges to gcloud +test_resource = "projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal" -# @patch("subprocess.run") -# def test_fetch_challenges_and_sign(): -# with patch.object(sys, "argv", ["approve_sthi_proposal.py", test_resource]): -# approve_sthi_proposal.approve_proposal() +mock_completed_process = subprocess.CompletedProcess -# mock_result = Mock() -# mock_result.returncode = 0 -# mock_result.stdout = sample_sthi_output -# mock_run.return_value = mock_result +def public_key_to_pem(public_key): + public_key_pem = public_key.public_bytes( + encoding=Encoding.PEM, + format=PublicFormat.SubjectPublicKeyInfo + ).decode('utf-8') + print("PUBLIC KEY--------------") + print(public_key_pem) + return public_key_pem -# self.assertEqual() +def create_json(public_key_pem_1, public_key_pem_2, public_key_pem_3): - # approve_sthi_proposal.approve_proposal() + my_json_string = json.dumps({ "quorumParameters": { + "challenges": [ + { + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": public_key_pem_1 + }, + { + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": public_key_pem_2 + }, + { + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": public_key_pem_3 + } + ], + "requiredApproverCount": 3 + }}) + + return my_json_string + + +def create_fake_fetch_response(num_keys=3): + """ + Generates a fake fetch response with a specified number of RSA key pairs. + + Args: + num_keys: The number of RSA key pairs to generate. + + Returns: + A tuple containing: + - A JSON object with the public keys. + - A dictionary mapping public key PEMs to private keys. + """ + pub_to_priv_key = {} + public_key_pems = [] + + for _ in range(num_keys): + private_key, public_key = ykman_fake.generate_rsa_keys() + public_key_pem = public_key_to_pem(public_key) + pub_to_priv_key[public_key_pem] = private_key + public_key_pems.append(public_key_pem) + + challenge_json = create_json(*public_key_pems) # Use * to unpack the list + return challenge_json, pub_to_priv_key + + + +mock_signed_challenges = [] + +def sign_challenges_with_capture(challenges:list[ykman_utils.Challenge], pub_to_priv_key): + signed_challenges = [] + for challenge in challenges: + private_key = pub_to_priv_key[challenge.public_key_pem] + signed_challenge = ykman_fake.sign_data(private_key, challenge.challenge) + signed_challenges.append( + ykman_utils.ChallengeReply( + challenge.challenge, + signed_challenge, + challenge.public_key_pem + ) + ) + mock_signed_challenges.extend(signed_challenges) + return signed_challenges + +def verify_with_fake(pub_to_priv_key, signed_challenges): + for signed_challenge in signed_challenges: + priv_key = pub_to_priv_key[signed_challenge.public_key_pem] + assert True == ykman_fake.verify_signature(priv_key.public_key(), signed_challenge.unsigned_challenge, signed_challenge.signed_challenge) + print("Signed verified successfully") + +def test_get_challenges_mocked(mocker, monkeypatch): + + # Verify signed challenges + monkeypatch.setattr( + "gcloud_commands.send_signed_challenges", + lambda signed_challenges, proposal_resource: verify_with_fake(pub_to_priv_key, mock_signed_challenges) + ) + + # monkeypatch sign challenges + monkeypatch.setattr( + "ykman_utils.sign_challenges", + lambda challenges: sign_challenges_with_capture(challenges, pub_to_priv_key) + ) + + # mock the challenge string returned by service + challenge_json, pub_to_priv_key = create_fake_fetch_response() + mock_response = mocker.MagicMock() + mock_response.stdout = challenge_json + mocker.patch("subprocess.run", return_value=mock_response) + + # monkeypatch parse args + mock_args = argparse.Namespace(proposal_resource="test_resource") + monkeypatch.setattr( + "approve_proposal.parse_args", + lambda args: mock_args + ) + + approve_proposal.approve_proposal() + + # assert challenge files created + challenge_files = ['challenges/challenge1.txt', 'challenges/challenge2.txt', 'challenges/challenge3.txt'] + for file_path in challenge_files: + assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." if __name__ == "__main__": - approve_proposal.parse_only_challenges(sample_sthi_output) - challenges = ykman_utils.populate_challenges_from_files() - for challenge in challenges: - print(challenge.challenge) - print(challenge.public_key_pem) - signed_challenged_files = [] - signed_challenges = ykman_utils.sign_proposal(challenges, signed_challenged_files) - for signed_files in signed_challenged_files: - print(signed_files) - # banana = 1 + 1 - - + # Parse challenges into files + unsigned_challenges = approve_proposal.parse_challenges_into_files( + sample_sthi_output + ) + created_signed_files = ['signed_challenges/signed_challenge1.txt', 'signed_challenges/signed_challenge2.txt', 'signed_challenges/signed_challenge3.txt'] + for file_path in created_signed_files: + assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." + + # assert signed challenge files created + signed_challenge_files = ['signed_challenges/signed_challenge1.txt', 'signed_challenges/signed_challenge2.txt', 'signed_challenges/signed_challenge3.txt'] + for file_path in signed_challenge_files: + assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." + + + # Parse files into challenge list + challenges = ykman_utils.populate_challenges_from_files() + for challenge in challenges: + print(challenge.challenge) + print(challenge.public_key_pem) + unsigned_challenges.append(challenge.challenge) + signed_challenged_files = [] + signed_challenges = ykman_utils.sign_challenges( + challenges, signed_challenged_files + ) + for signed_challenge in signed_challenges: + print(signed_challenge.signed_challenge) + print(signed_challenge.public_key_pem) + print("--challenge_replies=" + str(signed_challenged_files)) + ykman_utils.verify_challenge_signatures(signed_challenges) diff --git a/kms/singletenanthsm/brandonluong/asr/BUILD b/kms/singletenanthsm/brandonluong/asr/BUILD new file mode 100644 index 00000000000..de934c5e31b --- /dev/null +++ b/kms/singletenanthsm/brandonluong/asr/BUILD @@ -0,0 +1,57 @@ +cc_library( + name = "hsm_functions", + srcs = ["hsm_functions.cc"], + hdrs = ["hsm_functions.h"], + deps = [ + "//cloud/security/hawksbill/cavium:cavium_api_interface_header", + "//testing/base/public:gunit_headers", + "//third_party/absl/log:check", + "//third_party/absl/status", + "//third_party/absl/status:statusor", + "//third_party/absl/strings", + "//third_party/cavium_hsm/v3_4__14:cavium_hsm", + "//third_party/cavium_hsm/v3_4__14/utils:openssl_util", + "//third_party/openssl:crypto", + "//util/task:status", + ], +) + +cc_test( + name = "hsm_functions_test", + srcs = ["hsm_functions_test.cc"], + deps = [ + ":hsm_functions", + "//base:raw_logging", + "//testing/base/public:gunit_main", + "//third_party/absl/log", + "//third_party/absl/status:statusor", + "//third_party/absl/strings", + "//third_party/crunchy/algs/openssl:errors", + "//third_party/openssl:crypto", + ], +) + +cc_binary( + name = "hsm_functions_perf_test", + srcs = ["hsm_functions_perf_test.cc"], + deps = [ + ":hsm_functions", + "//testing/base/public:gunit", + "//third_party/openssl:crypto", + ], +) + +cc_test( + name = "cavium_layers_test", + srcs = ["cavium_layers_test.cc"], + deps = [ + ":hsm_functions", + "//base:raw_logging", + "//cloud/security/hawksbill/cavium:cavium_api_error", + "//cloud/security/hawksbill/cavium:cavium_api_error_enum", + "//cloud/security/hawksbill/cavium:cavium_api_shim", + "//cloud/security/hawksbill/cavium:lib_cavium", + "//testing/base/public:gunit_main", + "//third_party/absl/strings", + ], +) diff --git a/kms/singletenanthsm/brandonluong/asr/hsm_functions.cc b/kms/singletenanthsm/brandonluong/asr/hsm_functions.cc new file mode 100644 index 00000000000..6aa554b9f4a --- /dev/null +++ b/kms/singletenanthsm/brandonluong/asr/hsm_functions.cc @@ -0,0 +1,507 @@ +#include "experimental/users/brandonluong/asr/hsm_functions.h" + +#include +#include + +#include "third_party/absl/log/check.h" +#include "third_party/absl/status/status.h" +#include "third_party/absl/status/statusor.h" +#include "third_party/absl/strings/escaping.h" +#include "third_party/absl/strings/substitute.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_args.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_defines.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_errors.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_mgmt.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_structs.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_wrappers.h" +#include "third_party/cavium_hsm/v3_4__14/utils/openssl_util.h" +#include "third_party/openssl/bn.h" +#include "third_party/openssl/ec.h" +#include "third_party/openssl/ec_key.h" +#include "third_party/openssl/rand.h" +#include "third_party/openssl/rsa.h" + +#define CHECK_SUCCESS(expr) \ + { \ + int32_t ret = expr; \ + CHECK_EQ(ret, 0) << Cfm2ResultAsString(ret); \ + } + +namespace third_party_cavium_hsm { + +std::vector RandomLabel() { + uint8_t rand[16]; + RAND_bytes(rand, sizeof(rand)); + std::string hex = absl::BytesToHexString( + absl::string_view(reinterpret_cast(rand), sizeof(rand))); + CHECK_EQ(hex.size(), 32); + return std::vector(hex.begin(), hex.end()); +} + +uint32_t Test_Init() { + uint32_t application_handle; + Cfm2Initialize2(167936, DIRECT, &application_handle); + + return application_handle; +} + +uint32_t Initialize() { + uint32_t application_handle; + CHECK_SUCCESS(Cfm2Initialize2(0, DIRECT, &application_handle)); + + uint32_t session_handle; + CHECK_SUCCESS(Cfm2OpenSession2(application_handle, &session_handle)); + + constexpr absl::string_view kUsername = "crypto_user"; + constexpr absl::string_view kPassword = "user123"; + + std::vector encrypted_password(PSWD_ENC_KEY_MODULUS, 0); + uint32_t password_size = encrypted_password.size(); + CHECK_SUCCESS(utils::encrypt_pswd( + session_handle, + reinterpret_cast(const_cast(kPassword.data())), + kPassword.size(), encrypted_password.data(), &password_size)); + encrypted_password.resize(password_size); + + CHECK_SUCCESS(Cfm2LoginHSM2( + session_handle, CN_CRYPTO_USER, + reinterpret_cast(const_cast(kUsername.data())), + kUsername.size(), encrypted_password.data(), encrypted_password.size(), + /*signature=*/nullptr)); + + return application_handle; +} + +uint32_t OpenSession(uint32_t application_handle) { + uint32_t session_handle; + CHECK_SUCCESS(Cfm2OpenSession2(application_handle, &session_handle)); + return session_handle; +} + +void CloseSession(uint32_t session_handle) { + CHECK_SUCCESS(Cfm2CloseSession(session_handle)); +} + +uint64_t GenerateParkingKey(uint32_t session_handle) { + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + args.ulSessionHandle = session_handle; + args.key.info.ucKeyLocation = STORAGE_FLASH; + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + args.key.info.bParking = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + CHECK_SUCCESS(CfmGenerateKey(&args)); + return args.key.ulKeyHandle; +} + +uint64_t GenerateAesWrappingKey(uint32_t session_handle) { + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + args.ulSessionHandle = session_handle; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 0; + args.key.info.bParkable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + + default_attributes(&args.key.info); + CHECK_SUCCESS(CfmGenerateKey(&args)); + return args.key.ulKeyHandle; +} + +uint64_t GenerateExtractableSymmetricKey(uint32_t session_handle, + SymmetricKeyType key_type) { + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + args.ulSessionHandle = session_handle; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 1; + args.key.info.bParkable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + switch (key_type) { + case SymmetricKeyType::kAes256: + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + break; + case SymmetricKeyType::kHkdfSha256: + args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; + args.key.info.ulKeyLen = 32; + args.key.info.ucDerive = 1; + break; + } + + default_attributes(&args.key.info); + CHECK_SUCCESS(CfmGenerateKey(&args)); + return args.key.ulKeyHandle; +} + +KeyPair GenerateEcdhKeypair(uint32_t session_handle, int curve) { + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + args.ulSessionHandle = session_handle; + + args.pubkey.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.pubkey.info.bParkable = 1; + args.pubkey.info.ulKeyClass = OBJ_CLASS_PUBLIC_KEY; + args.pubkey.info.ulCurveID = curve; + args.pubkey.info.ulMValue = 1; + args.pubkey.info.pLabel = label.data(); + args.pubkey.info.ulLabelLen = label.size(); + args.pubkey.info.ulKeyType = KEY_TYPE_EC; + args.pubkey.info.ucDerive = 1; + default_attributes(&args.pubkey.info); + + args.privkey.info.ulKeyClass = OBJ_CLASS_PRIVATE_KEY; + args.privkey.info.ulCurveID = args.pubkey.info.ulCurveID; + args.privkey.info.ulKeyType = args.pubkey.info.ulKeyType; + args.privkey.info.ucDerive = 1; + + copy_public_attr_to_priv_attr(&args); + + CHECK_SUCCESS(CfmGenerateKeyPair(&args)); + return {.pub = args.pubkey.ulKeyHandle, .prv = args.privkey.ulKeyHandle}; +} + +std::vector ParkKey(uint32_t session_handle, + uint64_t parking_key_handle, + uint64_t target_key_handle) { + uint8_t buffer[6000]; // as hardcoded in Cfm2Util + + parkOpArgs args = {0}; + args.pParkedObject = buffer; + args.ulParkedObjectLen = sizeof(buffer); + args.ulSessionHandle = session_handle; + args.ulParkingKeyHandle = parking_key_handle; + args.ulObjectHandle = target_key_handle; + CHECK_SUCCESS(CfmParkObject(&args)); + + uint8_t* parked_key_start = GET_KEY1_ATTR(args.pParkedObject); + return std::vector(parked_key_start, + parked_key_start + args.ulParkedObjectLen); +} + +uint64_t UnparkKey(uint32_t session_handle, uint64_t parking_key_handle, + std::vector& parked_key) { + unparkOpArgs args = {0}; + args.pParkedObject = parked_key.data(); + args.ulParkedObjectLen = parked_key.size(); + args.ulSessionHandle = session_handle; + args.ulParkingKeyHandle = parking_key_handle; + args.ephemeralStorage = 1; + CHECK_SUCCESS(CfmUnparkObject(&args)); + return args.ulObjectHandle; +} + +void InitializeWrapArgs(third_party_cavium_interface::WrapArguments pArgs) { + // size fails at 8925, Cfm2Util hardcoded max is 8192 + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + // input args + pArgs.common.session_handle = session_handle; + pArgs.common.wrapping_key_handle = wrapping_key_handle; + pArgs.common.mech = aes_key_wrap_pad; + pArgs.common.key.ul_key_handle = target_key_handle.value(); + pArgs.common.key_input_output = + third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; + + // output args + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); +} + +std::vector WrapKeyAesKwp(uint32_t session_handle, + uint64_t wrapping_key_handle, + uint64_t target_key_handle) { + uint8_t buffer[MAX_DATA_LENGTH]; // as hardcoded in Cfm2Util + + wrapArgs args = {0}; + args.aes_key_len = AES_256_KEY_SIZE; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.key.ulKeyHandle = target_key_handle; + args.pKey = buffer; + args.ulKeyLen = sizeof(buffer); + args.ulSessionHandle = session_handle; + CHECK_SUCCESS(CfmWrap(&args)); + + return std::vector(buffer, buffer + args.ulKeyLen); +} + +void WrapKeySmallBuffer(uint32_t session_handle, uint64_t wrapping_key_handle, + uint64_t target_key_handle, int wrapped_key_len) { + uint8_t buffer[7000]; // as hardcoded in Cfm2Util + + wrapArgs args = {0}; + args.aes_key_len = AES_256_KEY_SIZE; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.key.ulKeyHandle = target_key_handle; + args.pKey = buffer; + args.ulKeyLen = 7050; + args.ulSessionHandle = session_handle; + CHECK_SUCCESS(CfmWrap(&args)); + // CfmWrap(&args); + + // return std::vector(buffer, buffer + args.ulKeyLen); +} + +std::vector WrapKeyRsaOaep(uint32_t session_handle, + uint64_t wrapping_key_handle, + uint64_t target_key_handle) { + uint8_t buffer[MAX_DATA_LENGTH]; // as hardcoded in Cfm2Util + + wrapArgs args = {0}; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_RSA_OAEP_KEY_WRAP; + args.hash_type = HashType::SHA256_TYPE; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.key.ulKeyHandle = target_key_handle; + args.pKey = buffer; + args.ulKeyLen = sizeof(buffer); + args.ulSessionHandle = session_handle; + CHECK_SUCCESS(CfmWrap(&args)); + + return std::vector(buffer, buffer + args.ulKeyLen); +} + +absl::StatusOr UnwrapKeyAesKwp(uint32_t session_handle, + uint64_t wrapping_key_handle, + std::vector& wrapped_key, + SymmetricKeyType key_type) { + std::vector label = RandomLabel(); + + unwrapArgs args = {0}; + + args.aes_key_len = AES_256_KEY_SIZE; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.pKey = wrapped_key.data(); + args.ulKeyLen = wrapped_key.size(); + args.ulSessionHandle = session_handle; + + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + switch (key_type) { + case SymmetricKeyType::kAes256: + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + break; + case SymmetricKeyType::kHkdfSha256: + args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; + args.key.info.ulKeyLen = 32; + args.key.info.ucDerive = 1; + break; + } + default_attributes(&args.key.info); + int32_t ret = CfmUnwrap(&args); + if (ret == 0) { + return args.key.ulKeyHandle; + } else { + return absl::InternalError(absl::Substitute("Cavium Error")); + } +} + +absl::StatusOr UnwrapKeyAesKnp(uint32_t session_handle, + uint64_t wrapping_key_handle, + std::vector& wrapped_key, + SymmetricKeyType key_type) { + std::vector label = RandomLabel(); + + unwrapArgs args = {0}; + + args.aes_key_len = AES_256_KEY_SIZE; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_AES_KEY_WRAP; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.pKey = wrapped_key.data(); + args.ulKeyLen = wrapped_key.size(); + args.ulSessionHandle = session_handle; + + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + switch (key_type) { + case SymmetricKeyType::kAes256: + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + break; + case SymmetricKeyType::kHkdfSha256: + args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; + args.key.info.ulKeyLen = 32; + args.key.info.ucDerive = 1; + break; + } + default_attributes(&args.key.info); + int32_t ret = CfmUnwrap(&args); + if (ret == 0) { + return args.key.ulKeyHandle; + } else { + return absl::InternalError(absl::Substitute("Cavium Error")); + } +} + +uint64_t DeriveAes256KeyHkdfSha256(uint32_t session_handle, uint64_t key_handle, + std::vector info) { + std::vector label = RandomLabel(); + + deriveKeyArgs args = {0}; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + default_attributes(&args.key.info); + + args.ulSessionHandle = session_handle; + args.hBaseKey = key_handle; + constexpr uint64_t kCkdSha256Kdf = 0x06UL; + args.ulDeriveMech = + CRYPTO_MECH_LIQ_SEC(DERIVE_MECH_HKDF) + (kCkdSha256Kdf << 16); + args.prfCtx = info.data(); + args.ulPrfCtxLen = info.size(); + args.ulDKMLengthMethod = SP800_108_DKM_LENGTH_SUM_OF_KEYS; + + CHECK_SUCCESS(CfmDeriveKey(&args)); + return args.key.ulKeyHandle; +} + +uint64_t DeriveAes256KeyEcdhHkdfSha256(uint32_t session_handle, + uint64_t local_private_key_handle, + EC_KEY* remote_public_key) { + std::vector label = RandomLabel(); + + deriveKeyArgs args = {0}; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + default_attributes(&args.key.info); + + args.ulSessionHandle = session_handle; + args.hBaseKey = local_private_key_handle; + constexpr uint64_t kCkdSha256Kdf = 0x06UL; + args.ulDeriveMech = + CRYPTO_MECH_LIQ_SEC(DERIVE_MECH_ECDH_HKDF) + (kCkdSha256Kdf << 16); + args.ulDKMLengthMethod = SP800_108_DKM_LENGTH_SUM_OF_KEYS; + + std::vector serialized_remote_public_key; + + serialized_remote_public_key.resize( + EC_POINT_point2oct(EC_KEY_get0_group(remote_public_key), + EC_KEY_get0_public_key(remote_public_key), + POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr)); + CHECK_EQ(EC_POINT_point2oct(EC_KEY_get0_group(remote_public_key), + EC_KEY_get0_public_key(remote_public_key), + POINT_CONVERSION_UNCOMPRESSED, + serialized_remote_public_key.data(), + serialized_remote_public_key.size(), nullptr), + serialized_remote_public_key.size()); + args.pPubKey = serialized_remote_public_key.data(); + args.ulPubKeyLen = serialized_remote_public_key.size(); + + CHECK_SUCCESS(CfmDeriveKey(&args)); + return args.key.ulKeyHandle; +} + +bssl::UniquePtr ExportEcPublicKey(uint32_t session_handle, + uint64_t key_handle, int curve_id) { + bssl::UniquePtr group(EC_GROUP_new_by_curve_name(curve_id)); + CHECK_NE(group, nullptr); + + uint32_t buf_length = 1024; + std::vector buf; + buf.resize(buf_length); + + CHECK_SUCCESS( + Cfm2ExportPublicKey(session_handle, key_handle, buf.data(), &buf_length)); + + bssl::UniquePtr key(EC_KEY_new()); + CHECK_NE(key, nullptr); + CHECK_EQ(EC_KEY_set_group(key.get(), group.get()), 1); + + bssl::UniquePtr point(EC_POINT_new(group.get())); + CHECK_NE(point, nullptr); + CHECK_EQ(EC_POINT_oct2point(group.get(), point.get(), buf.data(), buf_length, + nullptr), + 1); + + CHECK_EQ(EC_KEY_set_public_key(key.get(), point.get()), 1); + + return key; +} + +void DeleteKey(uint32_t session_handle, uint64_t key_handle) { + CHECK_SUCCESS(Cfm2DeleteKey(session_handle, key_handle)); +} + +uint64_t ImportRsaPublicKey(uint32_t session_handle, const RSA* rsa_pub) { + CHECK_NE(rsa_pub, nullptr); + + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + + args.ulSessionHandle = session_handle; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_PUBLIC_KEY; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + args.key.info.ulKeyType = KEY_TYPE_RSA; + + const BIGNUM *n, *e, *d; + RSA_get0_key(rsa_pub, &n, &e, &d); + CHECK_NE(n, nullptr); + CHECK_NE(e, nullptr); + + std::vector modulus; + modulus.resize(BN_num_bytes(n)); + BN_bn2bin(n, modulus.data()); + args.key.info.pModulus = modulus.data(); + args.key.info.ulModLenInBits = modulus.size() * 8; + + uint64_t pub_exp; + CHECK_EQ(BN_get_u64(e, &pub_exp), 1); + args.key.info.ulPubExp = pub_exp; + + default_attributes(&args.key.info); + + CHECK_SUCCESS(CfmCreateObject(&args)); + return args.key.ulKeyHandle; +} + +} // namespace third_party_cavium_hsm diff --git a/kms/singletenanthsm/brandonluong/asr/hsm_functions.h b/kms/singletenanthsm/brandonluong/asr/hsm_functions.h new file mode 100644 index 00000000000..53f3e3d4776 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/asr/hsm_functions.h @@ -0,0 +1,87 @@ +#ifndef EXPERIMENTAL_USERS_BRANDONLUONG_ASR_HSM_FUNCTIONS_H_ +#define EXPERIMENTAL_USERS_BRANDONLUONG_ASR_HSM_FUNCTIONS_H_ + +#include +#include + +#include "cloud/security/hawksbill/cavium/cavium_api_interface.h" +#include "third_party/absl/status/statusor.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_structs.h" +#include "third_party/openssl/ec_key.h" +#include "third_party/openssl/rsa.h" + +namespace third_party_cavium_hsm { + +std::vector RandomLabel(); + +uint32_t Test_Init(); + +uint32_t Initialize(); +void Finalize(uint32_t application_handle); + +uint32_t OpenSession(uint32_t application_handle); +void CloseSession(uint32_t session_handle); + +uint64_t GenerateParkingKey(uint32_t session_handle); + +void InitializeWrapArgs(third_party_cavium_interface::WrapArguments* pArgs); + +uint64_t GenerateAesWrappingKey(uint32_t session_handle); + +enum class SymmetricKeyType { kAes256, kHkdfSha256 }; + +uint64_t GenerateExtractableSymmetricKey(uint32_t session_handle, + SymmetricKeyType key_type); + +struct KeyPair { + uint64_t pub; + uint64_t prv; +}; + +KeyPair GenerateEcdhKeypair(uint32_t session_handle, int curve); + +std::vector ParkKey(uint32_t session_handle, + uint64_t parking_key_handle, + uint64_t target_key_handle); + +uint64_t UnparkKey(uint32_t session_handle, uint64_t parking_key_handle, + std::vector& parked_key); + +std::vector WrapKeyAesKwp(uint32_t session_handle, + uint64_t wrapping_key_handle, + uint64_t target_key_handle); + +void WrapKeySmallBuffer(uint32_t session_handle, uint64_t wrapping_key_handle, + uint64_t target_key_handle, int wrapped_key_len); + +uint64_t ImportRsaPublicKey(uint32_t session_handle, const RSA* rsa_pub); + +std::vector WrapKeyRsaOaep(uint32_t session_handle, + uint64_t wrapping_key_handle, + uint64_t target_key_handle); + +absl::StatusOr UnwrapKeyAesKwp(uint32_t session_handle, + uint64_t wrapping_key_handle, + std::vector& wrapped_key, + SymmetricKeyType key_type); + +absl::StatusOr UnwrapKeyAesKnp(uint32_t session_handle, + uint64_t wrapping_key_handle, + std::vector& wrapped_key, + SymmetricKeyType key_type); + +uint64_t DeriveAes256KeyHkdfSha256(uint32_t session_handle, uint64_t key_handle, + std::vector info); + +uint64_t DeriveAes256KeyEcdhHkdfSha256(uint32_t session_handle, + uint64_t local_private_key_handle, + EC_KEY* remote_public_key); + +bssl::UniquePtr ExportEcPublicKey(uint32_t session_handle, + uint64_t key_handle, int curve_id); + +void DeleteKey(uint32_t session_handle, uint64_t key_handle); + +} // namespace third_party_cavium_hsm + +#endif // EXPERIMENTAL_USERS_BRANDONLUONG_ASR_HSM_FUNCTIONS_H_ diff --git a/kms/singletenanthsm/brandonluong/asr/hsm_functions_test.cc b/kms/singletenanthsm/brandonluong/asr/hsm_functions_test.cc new file mode 100644 index 00000000000..c0fd65f53ec --- /dev/null +++ b/kms/singletenanthsm/brandonluong/asr/hsm_functions_test.cc @@ -0,0 +1,122 @@ +#include "experimental/users/brandonluong/asr/hsm_functions.h" + +#include + +#include + +#include "base/raw_logging.h" +#include "testing/base/public/gmock.h" +#include "testing/base/public/gunit.h" +#include "third_party/openssl/base.h" +#include "third_party/openssl/nid.h" + +namespace third_party_cavium_hsm { +namespace { + +TEST(HsmFunctionsTest, InitializeHSM) { + uint32_t app_handle = Initialize(); + ABSL_RAW_LOG(INFO, "app_handle %d", app_handle); + uint32_t session_handle = OpenSession(app_handle); + + ABSL_RAW_LOG(INFO, "session handle : %d\n app handle: %d\n", session_handle, + app_handle); +} + +// Test Parking and unparking DEK +TEST(HsmFunctionsTest, GenerateParkUnparkAes256) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t parking_key_handle = GenerateParkingKey(session_handle); + uint32_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + std::vector parked_key = + ParkKey(session_handle, parking_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + data_key_handle = UnparkKey(session_handle, parking_key_handle, parked_key); + + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, parking_key_handle); + CloseSession(session_handle); +} + +TEST(HsmFunctionsTest, SymmetricRewrapWorkflowSuccess) { + int curve_nid = NID_secp384r1; + + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t parking_key_handle = GenerateParkingKey(session_handle); + + // Create data encryption key(DEK). + uint32_t data_encryption_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + // Create region B's Data Key Wrapping Key(DKWK). + // Region A's DKWK would have already been used in CreateWrapDataKey and + // RewrapDataKey. It is necessary for this workflow. + uint64_t region_b_dkwk_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + // Park region B's DKWK. + std::vector region_b_parked_dkwk = + ParkKey(session_handle, parking_key_handle, region_b_dkwk_handle); + + // Create 2 ECDH keys. + KeyPair ecdh_key_region_a = GenerateEcdhKeypair(session_handle, curve_nid); + KeyPair ecdh_key_region_b = GenerateEcdhKeypair(session_handle, curve_nid); + + // Convert public keys into EC form. + // There will be 2 ECDH key pairs: one for region A and one for region B. + bssl::UniquePtr region_a_pub_key = + ExportEcPublicKey(session_handle, ecdh_key_region_a.pub, curve_nid); + bssl::UniquePtr region_b_pub_key = + ExportEcPublicKey(session_handle, ecdh_key_region_b.pub, curve_nid); + + // Derive the shared secret using (priA and pubB) or (priB and pubA). + // Then use HKDF + ECDH to get each region's wrapping key. + uint32_t region_a_wrapping_key_handle = DeriveAes256KeyEcdhHkdfSha256( + session_handle, ecdh_key_region_a.prv, region_b_pub_key.get()); + uint32_t region_b_wrapping_key_handle = DeriveAes256KeyEcdhHkdfSha256( + session_handle, ecdh_key_region_b.prv, region_a_pub_key.get()); + + // Wrap the DEK with region A's ecdh + hkdf key. + // This is the wrapped DEK from the output of RewrapDataKey. From here we can + // start the workflow of SymmetricRewrap. + std::vector region_a_wrapped_dek = WrapKeyAesKwp( + session_handle, region_a_wrapping_key_handle, data_encryption_key_handle); + + // Unwrap using region B's ecdh + hkdf key. + // DEK that is wrapped with region a's shared secret is able to be unwrapped + // by region b's shared secret. Note that the unwrapped DEK handle can be + // different from the original DEK handle after creation. + ASSERT_OK_AND_ASSIGN( + data_encryption_key_handle, + UnwrapKeyAesKwp(session_handle, region_b_wrapping_key_handle, + region_a_wrapped_dek, SymmetricKeyType::kAes256)); + + // Unpark region B's DKWK + uint64_t region_b_unparked_dkwk_handle = + UnparkKey(session_handle, parking_key_handle, region_b_parked_dkwk); + + // Wrap the DEK with region B's DKWK. + // This is the output of SymmetricRewrap. The DEK is wrapped within region + // b's DKWK. The DKWK is an AES 256 key. + std::vector aes_wrapped_dek = + WrapKeyAesKwp(session_handle, region_b_unparked_dkwk_handle, + data_encryption_key_handle); + + // Unwrap using region B's DKWK + EXPECT_OK(UnwrapKeyAesKwp(session_handle, region_b_unparked_dkwk_handle, + aes_wrapped_dek, SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, region_b_unparked_dkwk_handle); + DeleteKey(session_handle, region_b_wrapping_key_handle); + DeleteKey(session_handle, region_a_wrapping_key_handle); + DeleteKey(session_handle, data_encryption_key_handle); + DeleteKey(session_handle, parking_key_handle); + CloseSession(session_handle); +} + +} // namespace +} // namespace third_party_cavium_hsm diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/BUILD b/kms/singletenanthsm/brandonluong/cfmwrap/BUILD new file mode 100644 index 00000000000..09a29b0d5c5 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/cfmwrap/BUILD @@ -0,0 +1,60 @@ +cc_library( + name = "hsm_functions", + srcs = ["hsm_functions.cc"], + hdrs = ["hsm_functions.h"], + deps = [ + "//cloud/security/hawksbill/cavium:cavium_api_interface_header", + "//cloud/security/hawksbill/cavium:cavium_api_shim", + "//testing/base/public:gunit_headers", + "//third_party/absl/log:check", + "//third_party/absl/status", + "//third_party/absl/status:statusor", + "//third_party/absl/strings", + "//third_party/cavium_hsm/v3_4__14:cavium_hsm", + "//third_party/cavium_hsm/v3_4__14/utils:openssl_util", + "//third_party/openssl:crypto", + "//util/task:status", + ], +) + +cc_test( + name = "hsm_functions_test", + srcs = ["hsm_functions_test.cc"], + deps = [ + ":hsm_functions", + "//base:raw_logging", + "//cloud/security/hawksbill/cavium:cavium_api_shim", + "//cloud/security/hawksbill/cavium:lib_cavium", + "//testing/base/public:gunit_main", + "//third_party/absl/log", + "//third_party/absl/status:statusor", + "//third_party/absl/strings", + "//third_party/openssl:crypto", + "//util/task:status", + ], +) + +cc_binary( + name = "hsm_functions_perf_test", + srcs = ["hsm_functions_perf_test.cc"], + deps = [ + ":hsm_functions", + "//testing/base/public:gunit", + "//third_party/openssl:crypto", + ], +) + +cc_test( + name = "cavium_layers_test", + srcs = ["cavium_layers_test.cc"], + deps = [ + ":hsm_functions", + "//base:raw_logging", + "//cloud/security/hawksbill/cavium:cavium_api_error", + "//cloud/security/hawksbill/cavium:cavium_api_error_enum", + "//cloud/security/hawksbill/cavium:cavium_api_shim", + "//cloud/security/hawksbill/cavium:lib_cavium", + "//testing/base/public:gunit_main", + "//third_party/absl/strings", + ], +) diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/cavium_layers_test.cc b/kms/singletenanthsm/brandonluong/cfmwrap/cavium_layers_test.cc new file mode 100644 index 00000000000..a11ca6ef315 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/cfmwrap/cavium_layers_test.cc @@ -0,0 +1,635 @@ +#include +#include +#include + +#include "base/raw_logging.h" +#include "cloud/security/hawksbill/cavium/cavium_api_error.h" +#include "cloud/security/hawksbill/cavium/cavium_api_error_enum.h" +#include "cloud/security/hawksbill/cavium/cavium_api_interface.h" +#include "cloud/security/hawksbill/cavium/cavium_api_shim.h" +#include "experimental/users/brandonluong/cfmwrap/hsm_functions.h" +#include "testing/base/public/gmock.h" +#include "testing/base/public/gunit.h" +#include "third_party/absl/strings/string_view.h" + +namespace third_party_cavium_hsm { +namespace { + +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::status::StatusIs; + +third_party_cavium_interface::WrapArguments SetWrapArgumentsInputs( + uint32_t session_handle, uint32_t wrapping_key_handle, + uint32_t target_key_handle, uint32_t aes_key_wrap_pad) { + third_party_cavium_interface::WrapArguments pArgs{}; + // inputs + pArgs.common.session_handle = session_handle; + pArgs.common.wrapping_key_handle = wrapping_key_handle; + pArgs.common.mech = aes_key_wrap_pad; + pArgs.common.key.ul_key_handle = target_key_handle; + pArgs.common.key_input_output = + third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; + + return pArgs; +} + +// Set wrapped_key_len to length 8 and buffer length to 8192. Cavium returns a +// buffer too small error. +TEST(CfmWrapWrappedKeyLength, WrappedKeyLenSmallerThanWrappedKey) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap_pad); + + // output args for CfmWrap + uint8_t buffer[8912]; // as hardcoded in Cfm2Util + pArgs.wrapped_key = buffer; + // Set wrapped_key_len to minimum value of 8 to see if an error is returned. + pArgs.wrapped_key_len = 8; + + ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %lu", + sizeof(pArgs.wrapped_key_len)); + + uint64_t status = CaviumCfmWrap(&pArgs); + // EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + absl::Status status_error = + hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap"); + + EXPECT_THAT( + status_error, + StatusIs( + hawksbill::cavium::CaviumErrorSpace::Get(), + Eq(third_party_cavium_interface::CaviumErrorCode::kErrBufferTooSmall), + HasSubstr("ERR_BUFFER_TOO_SMALL"))); + ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); + + std::string output_key = + reinterpret_cast(const_cast(pArgs.wrapped_key)); + ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", + output_key.c_str(), sizeof(output_key)); + + std::vector wrapped_key_vector(output_key.begin(), output_key.end()); + + ABSL_RAW_LOG(INFO, "wrapped_key_len after wrap: %lu", + sizeof(pArgs.wrapped_key_len)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +// Set wrapped_key_len to 8000 and buffer length to 8192. The key was able to be +// wrapped and unwrapped successfully. wrapped_key_len was modified from 8000 +// to 40. NOTE: Sometimes this will randomly fail. +TEST(CfmWrapWrappedKeyLength, WrappedKeyLenSmallerThanBufferSize) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap_pad); + + // output args for CfmWrap + uint8_t buffer[8912]; // as hardcoded in Cfm2Util + pArgs.wrapped_key = buffer; + // Set wrapped_key_len to length less than buffer. + pArgs.wrapped_key_len = 8000; + int wrapped_key_len_original = pArgs.wrapped_key_len; + + ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %lu", + sizeof(pArgs.wrapped_key_len)); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); + + std::string output_key = + reinterpret_cast(const_cast(pArgs.wrapped_key)); + ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", + output_key.c_str(), sizeof(output_key)); + + EXPECT_NE(wrapped_key_len_original, pArgs.wrapped_key_len); + ABSL_RAW_LOG(INFO, "wrapped_key_len_original: %d", wrapped_key_len_original); + ABSL_RAW_LOG(INFO, "wrapped_key_len_after: %d", pArgs.wrapped_key_len); + + std::vector wrapped_key_vector(output_key.begin(), output_key.end()); + + // Wrapped key is able to be successfully unwrapped. Note that the unwrapped + // target key handle can be different from the handle after creation. + ASSERT_OK_AND_ASSIGN( + target_key_handle, + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_key_vector, + SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +// Using a buffer with a size that is less than the wrapped key size will result +// in a segmentation fault. +TEST(CfmWrapWrappedKeyLength, BufferLessThanWrappedKeySize) { + // uint32_t app_handle = Initialize(); + // uint32_t session_handle = OpenSession(app_handle); + // uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + // absl::StatusOr target_key_handle = + // GenerateExtractableSymmetricKey( + // session_handle, SymmetricKeyType::kAes256); + // // uint32_t aes_key_wrap_pad = 0x00001091; + + // WrapKeySmallBuffer(session_handle, wrapping_key_handle, + // target_key_handle.value(), 8192); + + // third_party_cavium_interface::WrapArguments pArgs = + // SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + // target_key_handle.value(), + // aes_key_wrap_pad); + + // output args for CfmWrap + // Set buffer to minimum length of 8. + // uint8_t buffer[8]; // as hardcoded in Cfm2Util + // pArgs.wrapped_key = buffer; + // pArgs.wrapped_key_len = 8192; + // int wrapped_key_len_original = pArgs.wrapped_key_len; + + // ABSL_RAW_LOG(INFO, "wrapped_key_len_original before wrap: %d", + // wrapped_key_len_original); + + // uint64_t status = CaviumCfmWrap(&pArgs); + // EXPECT_EQ(status, 0); + // ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + // absl::Status status_error = + // hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap"); + // EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, + // "CfmWrap")); +} + +// Try swapping target and wrapping key. They target and wrapping keys have +// differerent permissions. +TEST(CfmWrapm, SwapTargetKeyAndWrappingKeyDistinctPermissions) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + // uint32_t aes_key_wrap = 0x00001090; + + // swap target and wrapping key handles. + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, target_key_handle.value(), + wrapping_key_handle, aes_key_wrap_pad); + + // output args for CfmWrap + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + + ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %d", pArgs.wrapped_key_len); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_NE(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + absl::Status status_error = + hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap"); + EXPECT_THAT( + status_error, + StatusIs( + hawksbill::cavium::CaviumErrorSpace::Get(), + Eq(third_party_cavium_interface::CaviumErrorCode::kRetPolicyMismatch), + HasSubstr( + "This operation violates the current configured/FIPS policies"))); + ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); + + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + + ABSL_RAW_LOG(INFO, "wraped key vector size: %lu", sizeof(wrapped_key)); + + // AES_256_KEY_SIZE + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + std::string output_key = + reinterpret_cast(const_cast(pArgs.wrapped_key)); + ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", + output_key.c_str(), sizeof(output_key)); + + std::vector wrapped_key_vector(output_key.begin(), output_key.end()); + + ABSL_RAW_LOG(INFO, "wrapped_key_len after wrap: %d", pArgs.wrapped_key_len); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +TEST(CfmWrapOutputLength, + SwapTargetKeyAndWrappingKeySameExtractablePermissions) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + // wrapping key has same permissions as data key. + uint32_t wrapping_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + // uint32_t aes_key_wrap = 0x00001090; + + // swap target and wrapping key handles. + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, target_key_handle.value(), + wrapping_key_handle, aes_key_wrap_pad); + + // output args for CfmWrap + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + + ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %lu", + sizeof(pArgs.wrapped_key_len)); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); + + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + + ABSL_RAW_LOG(INFO, "wraped key vector size: %lu", sizeof(wrapped_key)); + + // AES_256_KEY_SIZE + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + std::string output_key = + reinterpret_cast(const_cast(pArgs.wrapped_key)); + ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", + output_key.c_str(), sizeof(output_key)); + + std::vector wrapped_key_vector(output_key.begin(), output_key.end()); + + ABSL_RAW_LOG(INFO, "wrapped_key_len after wrap: %lu", + sizeof(pArgs.wrapped_key_len)); + + // Wrapped key is able to be successfully unwrapped. Note that the unwrapped + // target key handle can be different from the handle after creation. + ASSERT_OK_AND_ASSIGN( + target_key_handle, + UnwrapKeyAesKwp(session_handle, target_key_handle.value(), + wrapped_key_vector, SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +TEST(CfmWrapOutputLength, CheckLengthNoPad) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + // uint32_t aes_key_wrap_pad = 0x00001091; + uint32_t aes_key_wrap = 0x00001090; + + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap); + + constexpr int max_request_size = 8192; + // output args for CfmWrap + uint8_t buffer[max_request_size]; // as hardcoded in Cfm2Util + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = max_request_size; + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + // AES_256_KEY_SIZE + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + absl::string_view output_key = + reinterpret_cast(const_cast(pArgs.wrapped_key)); + ABSL_RAW_LOG(INFO, "output_key: %s \n output_key size : %zul", + output_key.data(), output_key.size()); + + // Wrapped key is able to be successfully unwrapped. Note that the unwrapped + // target key handle can be different from the handle after creation. + ASSERT_OK_AND_ASSIGN(target_key_handle, + UnwrapKeyAesKnp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +TEST(CfmWrapOutputLength, CheckLengthWithPad) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + // uint32_t aes_key_wrap = 0x00001090; + + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap_pad); + + // output args for CfmWrap + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + + ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %lu", + sizeof(pArgs.wrapped_key_len)); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); + + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + + ABSL_RAW_LOG(INFO, "wraped key vector size: %lu", sizeof(wrapped_key)); + + // AES_256_KEY_SIZE + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + std::string output_key = + reinterpret_cast(const_cast(pArgs.wrapped_key)); + ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", + output_key.c_str(), sizeof(output_key)); + + std::vector wrapped_key_vector(output_key.begin(), output_key.end()); + + ABSL_RAW_LOG(INFO, "wrapped_key_len after wrap: %lu", + sizeof(pArgs.wrapped_key_len)); + + // Wrapped key is able to be successfully unwrapped. Note that the unwrapped + // target key handle can be different from the handle after creation. + ASSERT_OK_AND_ASSIGN( + target_key_handle, + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_key_vector, + SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +// This test is to check if the output wrapped material would have equal +// lengths. Ten target keys were generated and then wrapped. According to the +// logs most sizes are 401, but some are shorter. +TEST(CfmWrapOutoutLength, CheckIfWrappedKeyMaterialHasDistinctLength) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t aes_key_wrap_pad = 0x00001091; + // uint32_t aes_key_wrap = 0x00001090; + + int wrapped_key_lengths[10]; + for (int i = 0; i < 10; i++) { + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = + GenerateExtractableSymmetricKey(session_handle, + SymmetricKeyType::kAes256); + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap_pad); + + // output args for CfmWrap + uint8_t buffer[8192]; // as hardcoded in Cfm2Util + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK( + hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + + auto char_wrapped_key = + reinterpret_cast(const_cast(pArgs.wrapped_key)); + ABSL_RAW_LOG(INFO, "wrapped key: %s", char_wrapped_key); + ABSL_RAW_LOG(INFO, "wrapped key: %lu", sizeof(char_wrapped_key)); + + // AES_256_KEY_SIZE + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + // ABSL_RAW_LOG(INFO, "%d", pArgs.wrapped_key[i]); + } + + absl::string_view output_key = + reinterpret_cast(const_cast(pArgs.wrapped_key)); + ABSL_RAW_LOG(INFO, "output_key: %s \n output_key size : %zul", + output_key.data(), output_key.size()); + wrapped_key_lengths[i] = output_key.size(); + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + } + + for (int i = 0; i < 10; i++) { + ABSL_RAW_LOG(INFO, "wrapped_key_lengths: %d \n ", wrapped_key_lengths[i]); + } + CloseSession(session_handle); +} + +// Local testing for correctness of CaviumCfmWrap within +// cloud/security/hawksbill/cavium/cavium_api_interface.h +TEST(CaviumApiInterface, CfmWrapThenUnwrapSuccess) { + // size fails at 8925, Cfm2Util hardcoded max is 8192 + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap_pad); + + // output args for CfmWrap + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + // Wrapped key is able to be successfully unwrapped. Note that the unwrapped + // target key handle can be different from the handle after creation. + ASSERT_OK_AND_ASSIGN(target_key_handle, + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +// Local testing for correctness of CaviumCfmWrap within +// cloud/security/hawksbill/cavium/cavium_api_interface.h +TEST(CaviumApiInterface, RewrapFailsWithModifiedWrappedKey) { + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + // target key handle saved for key deletion. + uint32_t original_target_key_handle = target_key_handle.value(); + + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap_pad); + + // output args for CfmWrap + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + + // Modify wrappedkey + std::vector wrapped_key( + pArgs.wrapped_key, pArgs.wrapped_key + (pArgs.wrapped_key_len - 1)); + + target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + // Rewrap fails due to modified key + EXPECT_THAT(target_key_handle, + testing::status::StatusIs(absl::StatusCode::kInternal)); + + DeleteKey(session_handle, original_target_key_handle); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +// Testing CaviumShim layer +TEST(CaviumShim, CfmWrapThenUnwrapSuccess) { + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap_pad); + + // output args + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + hawksbill::cavium::CaviumApiShim cavium_api_shim; + uint64_t status = cavium_api_shim.CfmWrap(&pArgs); + + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + ASSERT_OK_AND_ASSIGN(target_key_handle, + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} +TEST(CaviumShim, RewrapFailsWithModifiedWrappedKey) { + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + uint32_t original_target_key_handle = target_key_handle.value(); + + third_party_cavium_interface::WrapArguments pArgs = + SetWrapArgumentsInputs(session_handle, wrapping_key_handle, + target_key_handle.value(), aes_key_wrap_pad); + + // output args + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + hawksbill::cavium::CaviumApiShim cavium_api_shim; + uint64_t status = cavium_api_shim.CfmWrap(&pArgs); + + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + + // Modify wrappedkey + std::vector wrapped_key( + pArgs.wrapped_key, pArgs.wrapped_key + (pArgs.wrapped_key_len - 1)); + + target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + EXPECT_THAT(target_key_handle, + testing::status::StatusIs(absl::StatusCode::kInternal)); + + DeleteKey(session_handle, original_target_key_handle); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +} // namespace +} // namespace third_party_cavium_hsm \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.cc b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.cc new file mode 100644 index 00000000000..b22f8b181e9 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.cc @@ -0,0 +1,507 @@ +#include "experimental/users/brandonluong/cfmwrap/hsm_functions.h" + +#include +#include + +#include "third_party/absl/log/check.h" +#include "third_party/absl/status/status.h" +#include "third_party/absl/status/statusor.h" +#include "third_party/absl/strings/escaping.h" +#include "third_party/absl/strings/substitute.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_args.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_defines.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_errors.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_mgmt.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_structs.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_wrappers.h" +#include "third_party/cavium_hsm/v3_4__14/utils/openssl_util.h" +#include "third_party/openssl/bn.h" +#include "third_party/openssl/ec.h" +#include "third_party/openssl/ec_key.h" +#include "third_party/openssl/rand.h" +#include "third_party/openssl/rsa.h" + +#define CHECK_SUCCESS(expr) \ + { \ + int32_t ret = expr; \ + CHECK_EQ(ret, 0) << Cfm2ResultAsString(ret); \ + } + +namespace third_party_cavium_hsm { + +std::vector RandomLabel() { + uint8_t rand[16]; + RAND_bytes(rand, sizeof(rand)); + std::string hex = absl::BytesToHexString( + absl::string_view(reinterpret_cast(rand), sizeof(rand))); + CHECK_EQ(hex.size(), 32); + return std::vector(hex.begin(), hex.end()); +} + +uint32_t Test_Init() { + uint32_t application_handle; + Cfm2Initialize2(167936, DIRECT, &application_handle); + + return application_handle; +} + +uint32_t Initialize() { + uint32_t application_handle; + CHECK_SUCCESS(Cfm2Initialize2(0, DIRECT, &application_handle)); + + uint32_t session_handle; + CHECK_SUCCESS(Cfm2OpenSession2(application_handle, &session_handle)); + + constexpr absl::string_view kUsername = "crypto_user"; + constexpr absl::string_view kPassword = "user123"; + + std::vector encrypted_password(PSWD_ENC_KEY_MODULUS, 0); + uint32_t password_size = encrypted_password.size(); + CHECK_SUCCESS(utils::encrypt_pswd( + session_handle, + reinterpret_cast(const_cast(kPassword.data())), + kPassword.size(), encrypted_password.data(), &password_size)); + encrypted_password.resize(password_size); + + CHECK_SUCCESS(Cfm2LoginHSM2( + session_handle, CN_CRYPTO_USER, + reinterpret_cast(const_cast(kUsername.data())), + kUsername.size(), encrypted_password.data(), encrypted_password.size(), + /*signature=*/nullptr)); + + return application_handle; +} + +uint32_t OpenSession(uint32_t application_handle) { + uint32_t session_handle; + CHECK_SUCCESS(Cfm2OpenSession2(application_handle, &session_handle)); + return session_handle; +} + +void CloseSession(uint32_t session_handle) { + CHECK_SUCCESS(Cfm2CloseSession(session_handle)); +} + +uint64_t GenerateParkingKey(uint32_t session_handle) { + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + args.ulSessionHandle = session_handle; + args.key.info.ucKeyLocation = STORAGE_FLASH; + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + args.key.info.bParking = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + CHECK_SUCCESS(CfmGenerateKey(&args)); + return args.key.ulKeyHandle; +} + +uint64_t GenerateAesWrappingKey(uint32_t session_handle) { + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + args.ulSessionHandle = session_handle; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 0; + args.key.info.bParkable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + + default_attributes(&args.key.info); + CHECK_SUCCESS(CfmGenerateKey(&args)); + return args.key.ulKeyHandle; +} + +uint64_t GenerateExtractableSymmetricKey(uint32_t session_handle, + SymmetricKeyType key_type) { + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + args.ulSessionHandle = session_handle; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 1; + args.key.info.bParkable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + switch (key_type) { + case SymmetricKeyType::kAes256: + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + break; + case SymmetricKeyType::kHkdfSha256: + args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; + args.key.info.ulKeyLen = 32; + args.key.info.ucDerive = 1; + break; + } + + default_attributes(&args.key.info); + CHECK_SUCCESS(CfmGenerateKey(&args)); + return args.key.ulKeyHandle; +} + +KeyPair GenerateEcdhKeypair(uint32_t session_handle, int curve) { + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + args.ulSessionHandle = session_handle; + + args.pubkey.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.pubkey.info.bParkable = 1; + args.pubkey.info.ulKeyClass = OBJ_CLASS_PUBLIC_KEY; + args.pubkey.info.ulCurveID = curve; + args.pubkey.info.ulMValue = 1; + args.pubkey.info.pLabel = label.data(); + args.pubkey.info.ulLabelLen = label.size(); + args.pubkey.info.ulKeyType = KEY_TYPE_EC; + args.pubkey.info.ucDerive = 1; + default_attributes(&args.pubkey.info); + + args.privkey.info.ulKeyClass = OBJ_CLASS_PRIVATE_KEY; + args.privkey.info.ulCurveID = args.pubkey.info.ulCurveID; + args.privkey.info.ulKeyType = args.pubkey.info.ulKeyType; + args.privkey.info.ucDerive = 1; + + copy_public_attr_to_priv_attr(&args); + + CHECK_SUCCESS(CfmGenerateKeyPair(&args)); + return {.pub = args.pubkey.ulKeyHandle, .prv = args.privkey.ulKeyHandle}; +} + +std::vector ParkKey(uint32_t session_handle, + uint64_t parking_key_handle, + uint64_t target_key_handle) { + uint8_t buffer[6000]; // as hardcoded in Cfm2Util + + parkOpArgs args = {0}; + args.pParkedObject = buffer; + args.ulParkedObjectLen = sizeof(buffer); + args.ulSessionHandle = session_handle; + args.ulParkingKeyHandle = parking_key_handle; + args.ulObjectHandle = target_key_handle; + CHECK_SUCCESS(CfmParkObject(&args)); + + uint8_t* parked_key_start = GET_KEY1_ATTR(args.pParkedObject); + return std::vector(parked_key_start, + parked_key_start + args.ulParkedObjectLen); +} + +uint64_t UnparkKey(uint32_t session_handle, uint64_t parking_key_handle, + std::vector& parked_key) { + unparkOpArgs args = {0}; + args.pParkedObject = parked_key.data(); + args.ulParkedObjectLen = parked_key.size(); + args.ulSessionHandle = session_handle; + args.ulParkingKeyHandle = parking_key_handle; + args.ephemeralStorage = 1; + CHECK_SUCCESS(CfmUnparkObject(&args)); + return args.ulObjectHandle; +} + +void InitializeWrapArgs(third_party_cavium_interface::WrapArguments pArgs) { + // size fails at 8925, Cfm2Util hardcoded max is 8192 + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + // input args + pArgs.common.session_handle = session_handle; + pArgs.common.wrapping_key_handle = wrapping_key_handle; + pArgs.common.mech = aes_key_wrap_pad; + pArgs.common.key.ul_key_handle = target_key_handle.value(); + pArgs.common.key_input_output = + third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; + + // output args + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); +} + +std::vector WrapKeyAesKwp(uint32_t session_handle, + uint64_t wrapping_key_handle, + uint64_t target_key_handle) { + uint8_t buffer[MAX_DATA_LENGTH]; // as hardcoded in Cfm2Util + + wrapArgs args = {0}; + args.aes_key_len = AES_256_KEY_SIZE; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.key.ulKeyHandle = target_key_handle; + args.pKey = buffer; + args.ulKeyLen = sizeof(buffer); + args.ulSessionHandle = session_handle; + CHECK_SUCCESS(CfmWrap(&args)); + + return std::vector(buffer, buffer + args.ulKeyLen); +} + +void WrapKeySmallBuffer(uint32_t session_handle, uint64_t wrapping_key_handle, + uint64_t target_key_handle, int wrapped_key_len) { + uint8_t buffer[7000]; // as hardcoded in Cfm2Util + + wrapArgs args = {0}; + args.aes_key_len = AES_256_KEY_SIZE; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.key.ulKeyHandle = target_key_handle; + args.pKey = buffer; + args.ulKeyLen = 7050; + args.ulSessionHandle = session_handle; + CHECK_SUCCESS(CfmWrap(&args)); + // CfmWrap(&args); + + // return std::vector(buffer, buffer + args.ulKeyLen); +} + +std::vector WrapKeyRsaOaep(uint32_t session_handle, + uint64_t wrapping_key_handle, + uint64_t target_key_handle) { + uint8_t buffer[MAX_DATA_LENGTH]; // as hardcoded in Cfm2Util + + wrapArgs args = {0}; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_RSA_OAEP_KEY_WRAP; + args.hash_type = HashType::SHA256_TYPE; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.key.ulKeyHandle = target_key_handle; + args.pKey = buffer; + args.ulKeyLen = sizeof(buffer); + args.ulSessionHandle = session_handle; + CHECK_SUCCESS(CfmWrap(&args)); + + return std::vector(buffer, buffer + args.ulKeyLen); +} + +absl::StatusOr UnwrapKeyAesKwp(uint32_t session_handle, + uint64_t wrapping_key_handle, + std::vector& wrapped_key, + SymmetricKeyType key_type) { + std::vector label = RandomLabel(); + + unwrapArgs args = {0}; + + args.aes_key_len = AES_256_KEY_SIZE; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.pKey = wrapped_key.data(); + args.ulKeyLen = wrapped_key.size(); + args.ulSessionHandle = session_handle; + + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + switch (key_type) { + case SymmetricKeyType::kAes256: + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + break; + case SymmetricKeyType::kHkdfSha256: + args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; + args.key.info.ulKeyLen = 32; + args.key.info.ucDerive = 1; + break; + } + default_attributes(&args.key.info); + int32_t ret = CfmUnwrap(&args); + if (ret == 0) { + return args.key.ulKeyHandle; + } else { + return absl::InternalError(absl::Substitute("Cavium Error")); + } +} + +absl::StatusOr UnwrapKeyAesKnp(uint32_t session_handle, + uint64_t wrapping_key_handle, + std::vector& wrapped_key, + SymmetricKeyType key_type) { + std::vector label = RandomLabel(); + + unwrapArgs args = {0}; + + args.aes_key_len = AES_256_KEY_SIZE; + args.KeyOutput = ENCRYPTED; + args.ulMech = CRYPTO_MECH_AES_KEY_WRAP; + args.ulWrappingKeyHandle = wrapping_key_handle; + args.pKey = wrapped_key.data(); + args.ulKeyLen = wrapped_key.size(); + args.ulSessionHandle = session_handle; + + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + switch (key_type) { + case SymmetricKeyType::kAes256: + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + break; + case SymmetricKeyType::kHkdfSha256: + args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; + args.key.info.ulKeyLen = 32; + args.key.info.ucDerive = 1; + break; + } + default_attributes(&args.key.info); + int32_t ret = CfmUnwrap(&args); + if (ret == 0) { + return args.key.ulKeyHandle; + } else { + return absl::InternalError(absl::Substitute("Cavium Error")); + } +} + +uint64_t DeriveAes256KeyHkdfSha256(uint32_t session_handle, uint64_t key_handle, + std::vector info) { + std::vector label = RandomLabel(); + + deriveKeyArgs args = {0}; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.bExtractable = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + default_attributes(&args.key.info); + + args.ulSessionHandle = session_handle; + args.hBaseKey = key_handle; + constexpr uint64_t kCkdSha256Kdf = 0x06UL; + args.ulDeriveMech = + CRYPTO_MECH_LIQ_SEC(DERIVE_MECH_HKDF) + (kCkdSha256Kdf << 16); + args.prfCtx = info.data(); + args.ulPrfCtxLen = info.size(); + args.ulDKMLengthMethod = SP800_108_DKM_LENGTH_SUM_OF_KEYS; + + CHECK_SUCCESS(CfmDeriveKey(&args)); + return args.key.ulKeyHandle; +} + +uint64_t DeriveAes256KeyEcdhHkdfSha256(uint32_t session_handle, + uint64_t local_private_key_handle, + EC_KEY* remote_public_key) { + std::vector label = RandomLabel(); + + deriveKeyArgs args = {0}; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; + args.key.info.ulMValue = 1; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + args.key.info.ulKeyType = KEY_TYPE_AES; + args.key.info.ulKeyLen = 32; + default_attributes(&args.key.info); + + args.ulSessionHandle = session_handle; + args.hBaseKey = local_private_key_handle; + constexpr uint64_t kCkdSha256Kdf = 0x06UL; + args.ulDeriveMech = + CRYPTO_MECH_LIQ_SEC(DERIVE_MECH_ECDH_HKDF) + (kCkdSha256Kdf << 16); + args.ulDKMLengthMethod = SP800_108_DKM_LENGTH_SUM_OF_KEYS; + + std::vector serialized_remote_public_key; + + serialized_remote_public_key.resize( + EC_POINT_point2oct(EC_KEY_get0_group(remote_public_key), + EC_KEY_get0_public_key(remote_public_key), + POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr)); + CHECK_EQ(EC_POINT_point2oct(EC_KEY_get0_group(remote_public_key), + EC_KEY_get0_public_key(remote_public_key), + POINT_CONVERSION_UNCOMPRESSED, + serialized_remote_public_key.data(), + serialized_remote_public_key.size(), nullptr), + serialized_remote_public_key.size()); + args.pPubKey = serialized_remote_public_key.data(); + args.ulPubKeyLen = serialized_remote_public_key.size(); + + CHECK_SUCCESS(CfmDeriveKey(&args)); + return args.key.ulKeyHandle; +} + +bssl::UniquePtr ExportEcPublicKey(uint32_t session_handle, + uint64_t key_handle, int curve_id) { + bssl::UniquePtr group(EC_GROUP_new_by_curve_name(curve_id)); + CHECK_NE(group, nullptr); + + uint32_t buf_length = 1024; + std::vector buf; + buf.resize(buf_length); + + CHECK_SUCCESS( + Cfm2ExportPublicKey(session_handle, key_handle, buf.data(), &buf_length)); + + bssl::UniquePtr key(EC_KEY_new()); + CHECK_NE(key, nullptr); + CHECK_EQ(EC_KEY_set_group(key.get(), group.get()), 1); + + bssl::UniquePtr point(EC_POINT_new(group.get())); + CHECK_NE(point, nullptr); + CHECK_EQ(EC_POINT_oct2point(group.get(), point.get(), buf.data(), buf_length, + nullptr), + 1); + + CHECK_EQ(EC_KEY_set_public_key(key.get(), point.get()), 1); + + return key; +} + +void DeleteKey(uint32_t session_handle, uint64_t key_handle) { + CHECK_SUCCESS(Cfm2DeleteKey(session_handle, key_handle)); +} + +uint64_t ImportRsaPublicKey(uint32_t session_handle, const RSA* rsa_pub) { + CHECK_NE(rsa_pub, nullptr); + + std::vector label = RandomLabel(); + + genKeyArgs args = {0}; + + args.ulSessionHandle = session_handle; + args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; + args.key.info.ulKeyClass = OBJ_CLASS_PUBLIC_KEY; + args.key.info.pLabel = label.data(); + args.key.info.ulLabelLen = label.size(); + args.key.info.ulKeyType = KEY_TYPE_RSA; + + const BIGNUM *n, *e, *d; + RSA_get0_key(rsa_pub, &n, &e, &d); + CHECK_NE(n, nullptr); + CHECK_NE(e, nullptr); + + std::vector modulus; + modulus.resize(BN_num_bytes(n)); + BN_bn2bin(n, modulus.data()); + args.key.info.pModulus = modulus.data(); + args.key.info.ulModLenInBits = modulus.size() * 8; + + uint64_t pub_exp; + CHECK_EQ(BN_get_u64(e, &pub_exp), 1); + args.key.info.ulPubExp = pub_exp; + + default_attributes(&args.key.info); + + CHECK_SUCCESS(CfmCreateObject(&args)); + return args.key.ulKeyHandle; +} + +} // namespace third_party_cavium_hsm diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.h b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.h new file mode 100644 index 00000000000..5081e863686 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.h @@ -0,0 +1,88 @@ +#ifndef EXPERIMENTAL_USERS_BRANDONLUONG_CFMWRAP_HSM_FUNCTIONS_H_ +#define EXPERIMENTAL_USERS_BRANDONLUONG_CFMWRAP_HSM_FUNCTIONS_H_ + +#include +#include + +#include "cloud/security/hawksbill/cavium/cavium_api_interface.h" +#include "cloud/security/hawksbill/cavium/cavium_api_shim.h" +#include "third_party/absl/status/statusor.h" +#include "third_party/cavium_hsm/v3_4__14/cavium_structs.h" +#include "third_party/openssl/ec_key.h" +#include "third_party/openssl/rsa.h" + +namespace third_party_cavium_hsm { + +std::vector RandomLabel(); + +uint32_t Test_Init(); + +uint32_t Initialize(); +void Finalize(uint32_t application_handle); + +uint32_t OpenSession(uint32_t application_handle); +void CloseSession(uint32_t session_handle); + +uint64_t GenerateParkingKey(uint32_t session_handle); + +void InitializeWrapArgs(third_party_cavium_interface::WrapArguments* pArgs); + +uint64_t GenerateAesWrappingKey(uint32_t session_handle); + +enum class SymmetricKeyType { kAes256, kHkdfSha256 }; + +uint64_t GenerateExtractableSymmetricKey(uint32_t session_handle, + SymmetricKeyType key_type); + +struct KeyPair { + uint64_t pub; + uint64_t prv; +}; + +KeyPair GenerateEcdhKeypair(uint32_t session_handle, int curve); + +std::vector ParkKey(uint32_t session_handle, + uint64_t parking_key_handle, + uint64_t target_key_handle); + +uint64_t UnparkKey(uint32_t session_handle, uint64_t parking_key_handle, + std::vector& parked_key); + +std::vector WrapKeyAesKwp(uint32_t session_handle, + uint64_t wrapping_key_handle, + uint64_t target_key_handle); + +void WrapKeySmallBuffer(uint32_t session_handle, uint64_t wrapping_key_handle, + uint64_t target_key_handle, int wrapped_key_len); + +uint64_t ImportRsaPublicKey(uint32_t session_handle, const RSA* rsa_pub); + +std::vector WrapKeyRsaOaep(uint32_t session_handle, + uint64_t wrapping_key_handle, + uint64_t target_key_handle); + +absl::StatusOr UnwrapKeyAesKwp(uint32_t session_handle, + uint64_t wrapping_key_handle, + std::vector& wrapped_key, + SymmetricKeyType key_type); + +absl::StatusOr UnwrapKeyAesKnp(uint32_t session_handle, + uint64_t wrapping_key_handle, + std::vector& wrapped_key, + SymmetricKeyType key_type); + +uint64_t DeriveAes256KeyHkdfSha256(uint32_t session_handle, uint64_t key_handle, + std::vector info); + +uint64_t DeriveAes256KeyEcdhHkdfSha256(uint32_t session_handle, + uint64_t local_private_key_handle, + EC_KEY* remote_public_key); + +bssl::UniquePtr ExportEcPublicKey(uint32_t session_handle, + uint64_t key_handle, int curve_id); + +void DeleteKey(uint32_t session_handle, uint64_t key_handle); + +} // namespace third_party_cavium_hsm + +#endif // EXPERIMENTAL_USERS_BRANDONLUONG_CFMWRAP_HSM_FUNCTIONS_H_ diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_perf_test.cc b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_perf_test.cc new file mode 100644 index 00000000000..e1ccb072031 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_perf_test.cc @@ -0,0 +1,360 @@ + +#include +#include + +#include "experimental/users/brandonluong/cfmwrap/hsm_functions..h" +#include "testing/base/public/benchmark.h" +#include "testing/base/public/gunit.h" +#include "third_party/openssl/aes.h" +#include "third_party/openssl/base.h" +#include "third_party/openssl/bn.h" +#include "third_party/openssl/ec.h" +#include "third_party/openssl/ec_key.h" +#include "third_party/openssl/evp.h" +#include "third_party/openssl/nid.h" +#include "third_party/openssl/rsa.h" + +namespace third_party_cavium_hsm { +namespace { + +#define HSM_BENCHMARK(expr) BENCHMARK(expr)->Threads(8)->UseRealTime(); + +uint32_t application_handle; + +// Initialize writes junk to stdout which makes the results unpretty, so make +// sure it only runs once. +void SetupGlobalState() { application_handle = Initialize(); } + +void BM_ParkSymmetricKey(benchmark::State& state) { + uint32_t session_handle = OpenSession(application_handle); + + uint64_t parking_key_handle = GenerateParkingKey(session_handle); + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + for (auto s : state) { + benchmark::DoNotOptimize( + ParkKey(session_handle, parking_key_handle, data_key_handle)); + } + + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, parking_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +HSM_BENCHMARK(BM_ParkSymmetricKey); + +void BM_WrapSymmetricKey(benchmark::State& state) { + uint32_t session_handle = OpenSession(application_handle); + + uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + for (auto s : state) { + benchmark::DoNotOptimize( + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle)); + } + + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +HSM_BENCHMARK(BM_WrapSymmetricKey); + +void BM_UnparkSymmetricKey(benchmark::State& state) { + uint32_t session_handle = OpenSession(application_handle); + + uint64_t parking_key_handle = GenerateParkingKey(session_handle); + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + std::vector parked_key = + ParkKey(session_handle, parking_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + for (auto s : state) { + data_key_handle = UnparkKey(session_handle, parking_key_handle, parked_key); + DeleteKey(session_handle, data_key_handle); + } + + DeleteKey(session_handle, parking_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +HSM_BENCHMARK(BM_UnparkSymmetricKey); + +void BM_UnwrapSymmetricKey(benchmark::State& state) { + uint32_t session_handle = OpenSession(application_handle); + + uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + std::vector wrapped_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + for (auto s : state) { + data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + DeleteKey(session_handle, data_key_handle); + } + + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +HSM_BENCHMARK(BM_UnwrapSymmetricKey); + +void GenerateAndWrapSymmetric(benchmark::State& state, + SymmetricKeyType key_type) { + uint32_t session_handle = OpenSession(application_handle); + uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + + for (auto s : state) { + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + benchmark::DoNotOptimize( + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle)); + + DeleteKey(session_handle, data_key_handle); + } + + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +void BM_GenerateAndWrapAes256(benchmark::State& state) { + GenerateAndWrapSymmetric(state, SymmetricKeyType::kAes256); +} + +HSM_BENCHMARK(BM_GenerateAndWrapAes256); + +void BM_GenerateAndWrapHkdfSha256(benchmark::State& state) { + GenerateAndWrapSymmetric(state, SymmetricKeyType::kHkdfSha256); +} + +HSM_BENCHMARK(BM_GenerateAndWrapHkdfSha256); + +void BM_UnwrapAes256(benchmark::State& state) { + uint32_t session_handle = OpenSession(application_handle); + uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + std::vector wrapped_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + for (auto s : state) { + data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + DeleteKey(session_handle, data_key_handle); + } + + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +HSM_BENCHMARK(BM_UnwrapAes256); + +void BM_UnwrapAndDeriveHkdfSha256(benchmark::State& state) { + uint32_t session_handle = OpenSession(application_handle); + uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + + uint64_t derivation_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kHkdfSha256); + std::vector wrapped_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, derivation_key_handle); + DeleteKey(session_handle, derivation_key_handle); + + for (auto s : state) { + derivation_key_handle = + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_key, + SymmetricKeyType::kHkdfSha256); + + uint64_t data_key_handle = DeriveAes256KeyHkdfSha256( + session_handle, derivation_key_handle, RandomLabel()); + + DeleteKey(session_handle, derivation_key_handle); + DeleteKey(session_handle, data_key_handle); + } + + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +HSM_BENCHMARK(BM_UnwrapAndDeriveHkdfSha256); + +void UnwrapAesKwpRewrapRsa(benchmark::State& state, int rsa_bits) { + bssl::UniquePtr f4(BN_new()); + CHECK_EQ(BN_set_u64(f4.get(), RSA_F4), 1); + bssl::UniquePtr rsa(RSA_new()); + CHECK_EQ(RSA_generate_key_ex(rsa.get(), rsa_bits, f4.get(), nullptr), 1); + + uint32_t session_handle = OpenSession(application_handle); + + uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + std::vector wrapped_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + for (auto s : state) { + data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + uint64_t public_key_handle = ImportRsaPublicKey(session_handle, rsa.get()); + + benchmark::DoNotOptimize( + WrapKeyRsaOaep(session_handle, public_key_handle, data_key_handle)); + + DeleteKey(session_handle, public_key_handle); + DeleteKey(session_handle, data_key_handle); + } + + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +void BM_UnwrapAesKwpRewrapRsa3072(benchmark::State& state) { + UnwrapAesKwpRewrapRsa(state, 3072); +} + +HSM_BENCHMARK(BM_UnwrapAesKwpRewrapRsa3072); + +void BM_UnwrapAesKwpRewrapRsa4096(benchmark::State& state) { + UnwrapAesKwpRewrapRsa(state, 4096); +} + +HSM_BENCHMARK(BM_UnwrapAesKwpRewrapRsa4096); + +void UnwrapAesKwpRewrapEcdh(benchmark::State& state, int curve_nid) { + bssl::UniquePtr group(EC_GROUP_new_by_curve_name(curve_nid)); + bssl::UniquePtr recipient_key(EC_KEY_new()); + CHECK_EQ(EC_KEY_set_group(recipient_key.get(), group.get()), 1); + CHECK_EQ(EC_KEY_generate_key_fips(recipient_key.get()), 1); + + uint32_t session_handle = OpenSession(application_handle); + + KeyPair sender_handles = GenerateEcdhKeypair(session_handle, curve_nid); + + // Assume this is done at HSM startup or something like that, rather than + // in band in each data plane operation. + bssl::UniquePtr agree_pub_key = + ExportEcPublicKey(session_handle, sender_handles.pub, curve_nid); + + uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + std::vector wrapped_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + for (auto s : state) { + data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + uint64_t derived_key_handle = DeriveAes256KeyEcdhHkdfSha256( + session_handle, sender_handles.prv, recipient_key.get()); + + benchmark::DoNotOptimize( + WrapKeyAesKwp(session_handle, derived_key_handle, data_key_handle)); + + DeleteKey(session_handle, derived_key_handle); + DeleteKey(session_handle, data_key_handle); + } + + DeleteKey(session_handle, wrapping_key_handle); + DeleteKey(session_handle, sender_handles.pub); + DeleteKey(session_handle, sender_handles.prv); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +void BM_UnwrapAesKwpRewrapEcdhP256(benchmark::State& state) { + UnwrapAesKwpRewrapEcdh(state, NID_X9_62_prime256v1); +} + +HSM_BENCHMARK(BM_UnwrapAesKwpRewrapEcdhP256); + +void BM_UnwrapAesKwpRewrapEcdhP384(benchmark::State& state) { + UnwrapAesKwpRewrapEcdh(state, NID_secp384r1); +} + +HSM_BENCHMARK(BM_UnwrapAesKwpRewrapEcdhP384); + +void BM_UnwrapAesKwpRewrapEcdhP521(benchmark::State& state) { + UnwrapAesKwpRewrapEcdh(state, NID_secp521r1); +} + +HSM_BENCHMARK(BM_UnwrapAesKwpRewrapEcdhP521); + +void BM_ReencryptAesKwp(benchmark::State& state) { + uint32_t session_handle = OpenSession(application_handle); + + uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + uint64_t session_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint64_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + std::vector wrapped_session_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, session_key_handle); + DeleteKey(session_handle, session_key_handle); + + std::vector wrapped_data_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + for (auto s : state) { + session_key_handle = + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_session_key, SymmetricKeyType::kAes256); + data_key_handle = + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_data_key, + SymmetricKeyType::kAes256); + + benchmark::DoNotOptimize( + WrapKeyAesKwp(session_handle, session_key_handle, data_key_handle)); + + DeleteKey(session_handle, session_key_handle); + DeleteKey(session_handle, data_key_handle); + } + + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); + + state.SetItemsProcessed(state.iterations()); +} + +HSM_BENCHMARK(BM_ReencryptAesKwp); + +} // namespace +} // namespace third_party_cavium_hsm + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + third_party_cavium_hsm::SetupGlobalState(); + RunSpecifiedBenchmarks(); + return 0; +} \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_test.cc b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_test.cc new file mode 100644 index 00000000000..9e9b4e3c2ea --- /dev/null +++ b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_test.cc @@ -0,0 +1,485 @@ +#include "experimental/users/brandonluong/cfmwrap/hsm_functions.h" + +#include + +#include "base/raw_logging.h" +#include "testing/base/public/gmock.h" +#include "testing/base/public/gunit.h" +#include "third_party/absl/log/log.h" +#include "third_party/absl/status/statusor.h" +#include "third_party/absl/strings/substitute.h" +#include "third_party/openssl/aes.h" +#include "third_party/openssl/base.h" +#include "third_party/openssl/bn.h" +#include "third_party/openssl/ec.h" +#include "third_party/openssl/ec_key.h" +#include "third_party/openssl/ecdh.h" +#include "third_party/openssl/evp.h" +#include "third_party/openssl/hkdf.h" +#include "third_party/openssl/rsa.h" + +namespace third_party_cavium_hsm { +namespace { + +TEST(HsmFunctionsTest, InitializeHSM) { + uint32_t app_handle = Initialize(); + ABSL_RAW_LOG(INFO, "app_handle %d", app_handle); + uint32_t session_handle = OpenSession(app_handle); + + // // uint32_t session_handle = OpenSession(app_handle); + ABSL_RAW_LOG(INFO, "session handle : %d\n app handle: %d\n", session_handle, + app_handle); +} + +TEST(HsmFunctionsTest, GenerateParkUnparkAes256) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t parking_key_handle = GenerateParkingKey(session_handle); + uint32_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + std::vector parked_key = + ParkKey(session_handle, parking_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + data_key_handle = UnparkKey(session_handle, parking_key_handle, parked_key); + + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, parking_key_handle); + CloseSession(session_handle); +} + +TEST(HsmFunctionsTest, GenerateParkUnparkHkdfSha256) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t parking_key_handle = GenerateParkingKey(session_handle); + uint32_t derivation_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kHkdfSha256); + + std::vector parked_key = + ParkKey(session_handle, parking_key_handle, derivation_key_handle); + DeleteKey(session_handle, derivation_key_handle); + + derivation_key_handle = + UnparkKey(session_handle, parking_key_handle, parked_key); + + uint32_t data_key_handle = DeriveAes256KeyHkdfSha256( + session_handle, derivation_key_handle, RandomLabel()); + + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, derivation_key_handle); + DeleteKey(session_handle, parking_key_handle); + CloseSession(session_handle); +} + +// Local testing for correctness of CaviumCfmWrap within +// cloud/security/hawksbill/cavium/cavium_api_interface.h +TEST(CaviumApiInterface, CfmWrapThenUnwrapSuccess) { + // size fails at 8925, Cfm2Util hardcoded max is 8192 + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + third_party_cavium_interface::WrapArguments pArgs{}; + // input args for CfmWrap + pArgs.common.session_handle = session_handle; + pArgs.common.wrapping_key_handle = wrapping_key_handle; + pArgs.common.mech = aes_key_wrap_pad; + pArgs.common.key.ul_key_handle = target_key_handle.value(); + pArgs.common.key_input_output = + third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; + + // output args for CfmWrap + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + // Wrapped key is able to be successfully unwrapped. Note that the unwrapped + // target key handle can be different from the handle after creation. + ASSERT_OK_AND_ASSIGN(target_key_handle, + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} +TEST(CaviumApiInterface, CreateWrappingKeyUnparkWrapUnwrap) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t parking_key_handle = GenerateParkingKey(session_handle); + // Create wrapping key + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + + // park wrapping key + std::vector parked_wrapping_key = + ParkKey(session_handle, parking_key_handle, wrapping_key_handle); + + LOG(INFO) << absl::Substitute("parked_wrapping_key size: $0", + parked_wrapping_key.size()); + + // unpark wrapping key + uint32_t unparked_wrapping_key_handle = + UnparkKey(session_handle, parking_key_handle, parked_wrapping_key); + + // create data key + absl::StatusOr data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + // wrap data key with wrapping key + uint8_t buffer[8924]; + uint32_t aes_key_wrap_pad = 0x00001091; + third_party_cavium_interface::WrapArguments pArgs{}; + // input args for CfmWrap + pArgs.common.session_handle = session_handle; + pArgs.common.wrapping_key_handle = unparked_wrapping_key_handle; + pArgs.common.mech = aes_key_wrap_pad; + pArgs.common.key.ul_key_handle = data_key_handle.value(); + pArgs.common.key_input_output = + third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + ASSERT_OK_AND_ASSIGN(data_key_handle, + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, data_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +// Local testing for correctness of CaviumCfmWrap within +// cloud/security/hawksbill/cavium/cavium_api_interface.h +TEST(CaviumApiInterface, RewrapFailsWithModifiedWrappedKey) { + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + // target key handle saved for key deletion. + uint32_t original_target_key_handle = target_key_handle.value(); + + third_party_cavium_interface::WrapArguments pArgs{}; + // input args for CfmWrap + pArgs.common.session_handle = session_handle; + pArgs.common.wrapping_key_handle = wrapping_key_handle; + pArgs.common.mech = aes_key_wrap_pad; + pArgs.common.key.ul_key_handle = target_key_handle.value(); + pArgs.common.key_input_output = + third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; + + // output args for CfmWrap + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + + uint64_t status = CaviumCfmWrap(&pArgs); + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + + // Modify wrappedkey + std::vector wrapped_key( + pArgs.wrapped_key, pArgs.wrapped_key + (pArgs.wrapped_key_len - 1)); + + target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + // Rewrap fails due to modified key + EXPECT_THAT(target_key_handle, + testing::status::StatusIs(absl::StatusCode::kInternal)); + + DeleteKey(session_handle, original_target_key_handle); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +// Testing CaviumShim layer +TEST(CaviumShim, CfmWrapThenUnwrapSuccess) { + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + third_party_cavium_interface::WrapArguments pArgs{}; + + // input args + pArgs.common.session_handle = session_handle; + pArgs.common.wrapping_key_handle = wrapping_key_handle; + pArgs.common.mech = aes_key_wrap_pad; + pArgs.common.key.ul_key_handle = target_key_handle.value(); + pArgs.common.key_input_output = + third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; + + // output args + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + hawksbill::cavium::CaviumApiShim cavium_api_shim; + uint64_t status = cavium_api_shim.CfmWrap(&pArgs); + + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + std::vector wrapped_key(pArgs.wrapped_key, + pArgs.wrapped_key + pArgs.wrapped_key_len); + for (int i = 0; i < wrapped_key.size(); i++) { + EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); + } + + target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + ASSERT_OK_AND_ASSIGN(target_key_handle, + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256)); + + DeleteKey(session_handle, target_key_handle.value()); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +TEST(Caviumshim, RewrapFailsWithModifiedWrappedKey) { + uint8_t buffer[8924]; // as hardcoded in Cfm2Util + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + uint32_t aes_key_wrap_pad = 0x00001091; + + uint32_t original_target_key_handle = target_key_handle.value(); + + third_party_cavium_interface::WrapArguments pArgs{}; + + // input args + pArgs.common.session_handle = session_handle; + pArgs.common.wrapping_key_handle = wrapping_key_handle; + pArgs.common.mech = aes_key_wrap_pad; + pArgs.common.key.ul_key_handle = target_key_handle.value(); + pArgs.common.key_input_output = + third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; + + // output args + pArgs.wrapped_key = buffer; + pArgs.wrapped_key_len = sizeof(buffer); + hawksbill::cavium::CaviumApiShim cavium_api_shim; + uint64_t status = cavium_api_shim.CfmWrap(&pArgs); + + EXPECT_EQ(status, 0); + ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); + EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); + + // Modify wrappedkey + std::vector wrapped_key( + pArgs.wrapped_key, pArgs.wrapped_key + (pArgs.wrapped_key_len - 1)); + + target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256); + + EXPECT_THAT(target_key_handle, + testing::status::StatusIs(absl::StatusCode::kInternal)); + + DeleteKey(session_handle, original_target_key_handle); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +TEST(HsmFunctionsTest, GenerateWrapUnwrapAes256) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + uint32_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + std::vector wrapped_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); + DeleteKey(session_handle, data_key_handle); + + data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, + wrapped_key, SymmetricKeyType::kAes256) + .value(); + + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +TEST(HsmFunctionsTest, GenerateWrapUnwrapHkdfSha256) { + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); + uint32_t derivation_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + std::vector wrapped_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, derivation_key_handle); + DeleteKey(session_handle, derivation_key_handle); + + derivation_key_handle = + UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_key, + SymmetricKeyType::kHkdfSha256) + .value(); + + uint32_t data_key_handle = DeriveAes256KeyHkdfSha256( + session_handle, derivation_key_handle, RandomLabel()); + + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, derivation_key_handle); + DeleteKey(session_handle, wrapping_key_handle); + CloseSession(session_handle); +} + +class RsaWrapTest : public testing::TestWithParam {}; + +TEST_P(RsaWrapTest, RsaWrap) { + int rsa_bits = GetParam(); + + bssl::UniquePtr f4(BN_new()); + ASSERT_EQ(BN_set_u64(f4.get(), RSA_F4), 1); + + bssl::UniquePtr rsa(RSA_new()); + ASSERT_EQ(RSA_generate_key_ex(rsa.get(), rsa_bits, f4.get(), nullptr), 1); + + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + + uint32_t public_key_handle = ImportRsaPublicKey(session_handle, rsa.get()); + uint32_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + std::vector wrapped_key = + WrapKeyRsaOaep(session_handle, public_key_handle, data_key_handle); + + bssl::UniquePtr pkey(EVP_PKEY_new()); + ASSERT_EQ(EVP_PKEY_set1_RSA(pkey.get(), rsa.get()), 1); + + bssl::UniquePtr ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); + ASSERT_EQ(EVP_PKEY_decrypt_init(ctx.get()), 1); + ASSERT_EQ(EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING), 1); + ASSERT_EQ(EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), EVP_sha256()), 1); + ASSERT_EQ(EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), EVP_sha256()), 1); + + std::vector recovered; + recovered.resize(RSA_size(rsa.get())); + size_t out_len = recovered.size(); + + ASSERT_EQ(EVP_PKEY_decrypt(ctx.get(), recovered.data(), &out_len, + wrapped_key.data(), wrapped_key.size()), + 1); + + ASSERT_EQ(out_len, 32); + + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, public_key_handle); + CloseSession(session_handle); +} + +INSTANTIATE_TEST_SUITE_P(RsaWrap, RsaWrapTest, testing::Values(3072, 4096)); + +class EcdhWrapTest : public testing::TestWithParam {}; + +TEST_P(EcdhWrapTest, EcdhWrap) { + int curve_nid = GetParam(); + + bssl::UniquePtr group(EC_GROUP_new_by_curve_name(curve_nid)); + bssl::UniquePtr ec(EC_KEY_new()); + ASSERT_EQ(EC_KEY_set_group(ec.get(), group.get()), 1); + ASSERT_EQ(EC_KEY_generate_key_fips(ec.get()), 1); + + bssl::UniquePtr pkey(EVP_PKEY_new()); + ASSERT_EQ(EVP_PKEY_set1_EC_KEY(pkey.get(), ec.get()), 1); + + uint32_t app_handle = Initialize(); + uint32_t session_handle = OpenSession(app_handle); + + KeyPair durable_agree_key = GenerateEcdhKeypair(session_handle, curve_nid); + + uint32_t data_key_handle = GenerateExtractableSymmetricKey( + session_handle, SymmetricKeyType::kAes256); + + uint32_t wrapping_key_handle = DeriveAes256KeyEcdhHkdfSha256( + session_handle, durable_agree_key.prv, ec.get()); + + std::vector wrapped_key = + WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); + + bssl::UniquePtr agree_pub_key = + ExportEcPublicKey(session_handle, durable_agree_key.pub, curve_nid); + + std::vector shared_secret; + constexpr size_t kMaxSharedSecretBytes = + 66; // == ceil(521 / 8) for P-521 key + shared_secret.resize(kMaxSharedSecretBytes); + + int retrieved_bytes = ECDH_compute_key( + shared_secret.data(), shared_secret.size(), + EC_KEY_get0_public_key(agree_pub_key.get()), ec.get(), nullptr); + CHECK_GE(retrieved_bytes, 32); + shared_secret.resize(retrieved_bytes); + + std::vector kwp_key; + kwp_key.resize(32); + ASSERT_EQ( + HKDF(kwp_key.data(), kwp_key.size(), EVP_sha256(), shared_secret.data(), + shared_secret.size(), nullptr, 0, nullptr, 0), + 1); + + AES_KEY k; + ASSERT_EQ(AES_set_decrypt_key(kwp_key.data(), 256, &k), 0); + + std::vector data_key; + data_key.resize(32); + size_t data_key_size = data_key.size(); + ASSERT_EQ(AES_unwrap_key_padded(&k, data_key.data(), &data_key_size, + data_key.size(), wrapped_key.data(), + wrapped_key.size()), + 1); + + DeleteKey(session_handle, wrapping_key_handle); + DeleteKey(session_handle, data_key_handle); + DeleteKey(session_handle, durable_agree_key.pub); + DeleteKey(session_handle, durable_agree_key.prv); + + CloseSession(session_handle); +} + +INSTANTIATE_TEST_SUITE_P(EcdhWrap, EcdhWrapTest, + testing::Values(NID_X9_62_prime256v1, NID_secp384r1, + NID_secp521r1)); + +} // namespace +} // namespace third_party_cavium_hsm \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/conflicts/conflict.txt b/kms/singletenanthsm/brandonluong/conflicts/conflict.txt new file mode 100644 index 00000000000..df790035801 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/conflicts/conflict.txt @@ -0,0 +1,6 @@ +It was the best of times :), +it was the worst of times:(, +it was the age of tcp/ip, +it was the age of foolishness, +it was the epoch of yes, +it was the epoch of no. diff --git a/kms/singletenanthsm/brandonluong/quotes/index.html b/kms/singletenanthsm/brandonluong/quotes/index.html new file mode 100644 index 00000000000..f305badb271 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/quotes/index.html @@ -0,0 +1,23 @@ + + + + + Quotes Sandbox + + + +

Quotes Codelab

+ +

This HTML is just an area where we can play around with Piper. The +idea is to add one or two html files with quotations, +and then edit this +index.html file to link to your quotation files. +When you're ready, submit the thing. +

+ + + My quote + + diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/README.rst b/kms/singletenanthsm/brandonluong/singletenanthsm/README.rst new file mode 100644 index 00000000000..a3fe9fee3d3 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/README.rst @@ -0,0 +1,105 @@ +Google Cloud Key Management Service Python Samples +=============================================================================== + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=kms/singletenanthsm/README.rst + + +This directory contains samples for Google Cloud Key Management Service. The `Cloud Key Management Service`_ allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service. + + + + +.. _Cloud Key Management Service: https://cloud.google.com/kms/docs/ + + + + + +Setup +------------------------------------------------------------------------------- + + +Install Dependencies +++++++++++++++++++++ + +#. Clone python-kms and change directory to the sample directory you want to use. + + .. code-block:: bash + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. + + .. _Python Development Environment Setup Guide: + https://cloud.google.com/python/setup + +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ + +Samples +------------------------------------------------------------------------------- +Create a custom gcloud build to access the Single Tenant HSM service. + +Approve a Single Tenant HSM Instance Proposal. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Creates custom gcloud build to access single tenant HSM service+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +To run this sample: + +.. code-block:: bash + + $ python3 setup.py + + usage: setup.py [-h] [--operation] + + This application creates a custom gcloud build to access the single tenant HSM service. + + positional arguments: + operation The type of setup operation you want to perform. This includes build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'. + + optional arguments: + -h, --help show this help message and exit + + + +Approves a Single Tenant HSM Instance Proposal. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +To run this sample: + +.. code-block:: bash + + $ python3 approve_proposal.py + + usage: approve_proposal.py [-h] [--proposal_resource PROPOSAL_RESOURCE] + + This application fetches and approves the single tenant HSM instance proposal + specified in the "proposal_resource" field. + + For more information, visit https://cloud.google.com/kms/docs/attest-key. + + positional arguments: + --proposal_resource PROPOSAL_RESOURCE + The full name of the single tenant HSM instance proposal that needs to be approved. + + + + optional arguments: + -h, --help show this help message and exit + + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal.py b/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal.py new file mode 100644 index 00000000000..6684547b15d --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import json +import os +import sys +from typing import List + +import gcloud_commands +import ykman_utils + + +def parse_challenges_into_files(sthi_output: str) -> List[bytes]: + """Parses the STHI output and writes the challenges and public keys to files. + + Args: + sthi_output: The output of the STHI command. + + Returns: + A list of the unsigned challenges. + """ + print("parsing challenges into files") + proposal_json = json.loads(sthi_output, strict=False) + challenges = proposal_json["quorumParameters"]["challenges"] + + directory_path = "challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + + challenge_count = 0 + unsigned_challenges = [] + for challenge in challenges: + challenge_count += 1 + print(challenge["challenge"] + "\n") + print(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f = open("challenges/challenge{0}.txt".format(challenge_count), "wb") + binary_challenge = ykman_utils.urlsafe_base64_to_binary( + challenge["challenge"] + ) + f.write(binary_challenge) + f.close() + + f = open("challenges/public_key{0}.pem".format(challenge_count), "w") + f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f.close() + unsigned_challenges.append( + ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"]) + ) + + return unsigned_challenges + + +def parse_args(args): + parser = argparse.ArgumentParser() + parser.add_argument("--proposal_resource", type=str, required=True) + return parser.parse_args(args) + + +def signed_challenges_to_files( + challenge_replies: list[ykman_utils.ChallengeReply], +) -> None: + """Writes the signed challenges and public keys to files. + + Args: + challenge_replies: A list of ChallengeReply objects. + + Returns: + A list of tuples containing the signed challenge file path and the public + key file path. + """ + signed_challenge_files = [] + challenge_count = 0 + for challenge_reply in challenge_replies: + challenge_count += 1 + print("challenge_count", challenge_count) + directory_path = "signed_challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + with open( + f"signed_challenges/public_key_{challenge_count}.pem", "w" + ) as public_key_file: + + # Write public key to file + public_key_file.write(challenge_reply.public_key_pem) + with open( + f"signed_challenges/signed_challenge{challenge_count}.bin", "wb" + ) as binary_file: + + # Write signed challenge to file + binary_file.write(challenge_reply.signed_challenge) + signed_challenge_files.append(( + f"signed_challenges/signed_challenge{challenge_count}.bin", + f"signed_challenges/public_key_{challenge_count}.pem", + )) + return signed_challenge_files + + +def approve_proposal(): + """Approves a proposal by fetching challenges, signing them, and sending them back to gcloud.""" + parser = parse_args(sys.argv[1:]) + + # Fetch challenges + process = gcloud_commands.fetch_challenges(parser.proposal_resource) + + # Parse challenges into files + unsigned_challenges = parse_challenges_into_files(process.stdout) + + # Sign challenges + signed_challenges = ykman_utils.sign_challenges(unsigned_challenges) + + # Parse signed challenges into files + signed_challenged_files = signed_challenges_to_files(signed_challenges) + + # Return signed challenges to gcloud + gcloud_commands.send_signed_challenges( + signed_challenged_files, parser.proposal_resource + ) + + +if __name__ == "__main__": + approve_proposal() diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal_test.py b/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal_test.py new file mode 100644 index 00000000000..416d8ab2ed7 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal_test.py @@ -0,0 +1,261 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import argparse +from dataclasses import dataclass +import json +import os +import subprocess + +import approve_proposal +from cryptography.hazmat.primitives.serialization import Encoding +from cryptography.hazmat.primitives.serialization import PublicFormat +import gcloud_commands +import pytest +import ykman_fake +import ykman_utils + + +# from approve_proposal import parse_args + +sample_sthi_output = """ +{ + "quorumParameters": { + "challenges": [ + { + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" + } + ], + "requiredApproverCount": 3 + } +} + +""" + +sample_nonces = [ + "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", +] + + +@dataclass +class QuorumParameters: + challenges: list[ykman_utils.Challenge] + + # def __init__(self, challenges: list[ykman_utils.Challenge]): + # self.challenges = challenges + + # def to_dict(self): + # return {"challenges": self.challenges} + + +sample_assigned_challenges = "" + + +@pytest.fixture() +def setup(): + parser = approve_proposal.parse_args(["proposal_resource", "my_proposal"]) + + +test_resource = "projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal" + +MockCompletedProcess = subprocess.CompletedProcess + + +def public_key_to_pem(public_key): + public_key_pem = public_key.public_bytes( + encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo + ).decode("utf-8") + print("PUBLIC KEY--------------") + print(public_key_pem) + return public_key_pem + + +def create_json(public_key_pem_1, public_key_pem_2, public_key_pem_3): + + my_json_string = json.dumps({ + "quorumParameters": { + "challenges": [ + { + "challenge": ( + "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1" + ), + "publicKeyPem": public_key_pem_1, + }, + { + "challenge": ( + "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb" + ), + "publicKeyPem": public_key_pem_2, + }, + { + "challenge": ( + "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY" + ), + "publicKeyPem": public_key_pem_3, + }, + ], + "requiredApproverCount": 3, + } + }) + + return my_json_string + + +def create_fake_fetch_response(num_keys=3): + """Generates a fake fetch response with a specified number of RSA key pairs. + + Args: + num_keys: The number of RSA key pairs to generate. + + Returns: + A tuple containing: + - A JSON object with the public keys. + - A dictionary mapping public key PEMs to private keys. + """ + pub_to_priv_key = {} + public_key_pems = [] + + for _ in range(num_keys): + private_key, public_key = ykman_fake.generate_rsa_keys() + public_key_pem = public_key_to_pem(public_key) + pub_to_priv_key[public_key_pem] = private_key + public_key_pems.append(public_key_pem) + + challenge_json = create_json(*public_key_pems) # Use * to unpack the list + return challenge_json, pub_to_priv_key + + +mock_signed_challenges = [] + + +def sign_challenges_with_capture( + challenges: list[ykman_utils.Challenge], pub_to_priv_key +): + signed_challenges = [] + for challenge in challenges: + private_key = pub_to_priv_key[challenge.public_key_pem] + signed_challenge = ykman_fake.sign_data(private_key, challenge.challenge) + signed_challenges.append( + ykman_utils.ChallengeReply( + challenge.challenge, signed_challenge, challenge.public_key_pem + ) + ) + mock_signed_challenges.extend(signed_challenges) + return signed_challenges + + +def verify_with_fake(pub_to_priv_key, signed_challenges): + for signed_challenge in signed_challenges: + priv_key = pub_to_priv_key[signed_challenge.public_key_pem] + assert True == ykman_fake.verify_signature( + priv_key.public_key(), + signed_challenge.unsigned_challenge, + signed_challenge.signed_challenge, + ) + print("Signed verified successfully") + + +def test_get_challenges_mocked(mocker, monkeypatch): + + # Verify signed challenges + monkeypatch.setattr( + "gcloud_commands.send_signed_challenges", + lambda signed_challenges, proposal_resource: verify_with_fake( + pub_to_priv_key, mock_signed_challenges + ), + ) + + # monkeypatch sign challenges + monkeypatch.setattr( + "ykman_utils.sign_challenges", + lambda challenges: sign_challenges_with_capture( + challenges, pub_to_priv_key + ), + ) + + # mock the challenge string returned by service + challenge_json, pub_to_priv_key = create_fake_fetch_response() + mock_response = mocker.MagicMock() + mock_response.stdout = challenge_json + mocker.patch("subprocess.run", return_value=mock_response) + + # monkeypatch parse args + mock_args = argparse.Namespace(proposal_resource="test_resource") + monkeypatch.setattr("approve_proposal.parse_args", lambda args: mock_args) + + approve_proposal.approve_proposal() + + # assert challenge files created + challenge_files = [ + "challenges/challenge1.txt", + "challenges/challenge2.txt", + "challenges/challenge3.txt", + ] + for file_path in challenge_files: + assert True == os.path.exists( + file_path + ), f"File '{file_path}' should exist but does not." + + # assert signed challenge files created + signed_challenge_files = [ + "signed_challenges/signed_challenge1.txt", + "signed_challenges/signed_challenge2.txt", + "signed_challenges/signed_challenge3.txt", + ] + for file_path in signed_challenge_files: + assert True == os.path.exists( + file_path + ), f"File '{file_path}' should exist but does not." + + +if __name__ == "__main__": + # Parse challenges into files + unsigned_challenges = approve_proposal.parse_challenges_into_files( + sample_sthi_output + ) + created_signed_files = [ + "signed_challenges/signed_challenge1.txt", + "signed_challenges/signed_challenge2.txt", + "signed_challenges/signed_challenge3.txt", + ] + for file_path in created_signed_files: + assert True == os.path.exists( + file_path + ), f"File '{file_path}' should exist but does not." + + # Parse files into challenge list + challenges = ykman_utils.populate_challenges_from_files() + for challenge in challenges: + print(challenge.challenge) + print(challenge.public_key_pem) + unsigned_challenges.append(challenge.challenge) + signed_challenged_files = [] + signed_challenges = ykman_utils.sign_challenges( + challenges, signed_challenged_files + ) + for signed_challenge in signed_challenges: + print(signed_challenge.signed_challenge) + print(signed_challenge.public_key_pem) + print("--challenge_replies=" + str(signed_challenged_files)) + ykman_utils.verify_challenge_signatures(signed_challenges) diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge1.txt new file mode 100644 index 00000000000..c891b41bd6d --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge1.txt @@ -0,0 +1 @@ +#?xpAky7.m*\hkG.jo(T!5 \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge2.txt new file mode 100644 index 00000000000..93616f55ed6 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge2.txt @@ -0,0 +1 @@ +:/~j;QQܿA3($̳N+;H'^7N;QX \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge3.txt new file mode 100644 index 00000000000..3f9fc362302 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge3.txt @@ -0,0 +1 @@ +4>y)v2Ugn&3R"0g/熎X \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key1.pem new file mode 100644 index 00000000000..56f9b3b26d8 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxD115YJWrQy/cgV1Ax8B +jbqlffcXvXybceL/jFd7ijmZbnjr3xjDVGFdedGpE6bT49SQa3vhb/bvIza7+vy7 +VuW3iFK9QABF6PcnX75UDMgn+cX8qVS9pu1wKqC0pb/Z99Fw2D8jnSY9z6AzHnGf +qbX0R1virFS5Qm/XDa2QudS6i954f3bkcRyopt4GuJO6osJPAhAm5aXLXWskT0jU +trTHjIA0aQopNa6dokgw7HV7lVm1jgUeAQXl2YOWZSGrh9sknW2CLSCjClo5uOTj +caTHyvIb9lCzTJPTN60Gyy2cxsTl/eFiZRDyK1TNyapXBy1b3kF/hBRubwJU+Hjq +NwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key2.pem new file mode 100644 index 00000000000..0e830e9c965 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA09Y7M3Jyh/n3bnjh9Q6m +oy64/CXbn0Ud2KriRYua/jYGrPsRvhbnYmrOGjL0ABPNQLO4T95KKrYgl5ZDjAQQ +e5o5e+RnSSkCO7osqCZM1t7ydaMeOn6Jx/XvKDc7IqdQ0+iqBiv/zwc+dHdjLhS6 +gbwb3aengOL0TMijDXiTZAZR1mil5y5hBfJBGXKRTGy6GnCW9HG4k+PKLpJ5drsD +pbINCwfqozv+OsoDfOR0yp0iFlf6PGhYVMS/nTtuCKuPkWerTlvmAVogqz26VMbL +Mxxa+xFig4aScgJ7r/sSIVZHwT5nau7A9zEdMC3dV72oY8br/CrpkjvfMmhBw351 +bQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key3.pem new file mode 100644 index 00000000000..9ee4a26b6cd --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrStPaIP4CkVGJgXlzTK +1ppk3xbdhd4kuKNONvdxC13VR7pw6oUo8dihB0cZAvAn/O/Ws8FrfIvQnyBDcttf +wLGe5TK4Vtj/G/mgf66iYTpDF+4bKVX9JuR3EJN4N90XqoyRfQvS9zUniuIzn4MD +I5vrAK0PVz6CZWXA0omHOobJWS1ZjdEz1N+uXkRh+M/u9/85h3WhLYbWfv3Jj/lE +Y8cuXf2An1AyRGYhceID1Ce1z/pkikJZrNEAtMdnR1pCKo/bD/WBPlatXzkPtwwJ +TloSm6ptxhKRzARdvlHOn62lCvF1kGWBbzpyaur6SloWVTtR0Eln2QJAA3pXpLrF +PwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands.py new file mode 100644 index 00000000000..fb850582237 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess + +command_build_custom_gcloud = """ + pushd /tmp + curl -o installer.sh https://sdk.cloud.google.com + chmod +x installer.sh + ./installer.sh --disable-prompts --install-dir ~/sthi + rm installer.sh + popd + alias sthigcloud=~/sthi/google-cloud-sdk/bin/gcloud + sthigcloud auth login + """ + + +command_add_components = """ + ~/sthi/google-cloud-sdk/bin/gcloud components repositories add https://storage.googleapis.com/single-tenant-hsm-private/components-2.json + ~/sthi/google-cloud-sdk/bin/gcloud components update + """ + + +def build_custom_gcloud(): + """Builds a custom gcloud binary.""" + try: + print("\nBuilding custom gcloud build") + process = subprocess.run( + command_build_custom_gcloud, + check=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud build executed successfully.") + print(process.stdout) + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + try: + print("\nAdding gcloud components") + process = subprocess.run( + command_add_components, + check=False, + capture_output=False, + text=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud components add executed successfully.") + print(process.stdout) + return process + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + print(f"Error executing gcloud components update: {e}") + + +command_gcloud_list_proposal = ( + "~/sthi/google-cloud-sdk/bin/gcloud kms single-tenant-hsm list " + "--location=projects/hawksbill-playground/locations/global" +) + +command_gcloud_describe_proposal = """ + ~/sthi/google-cloud-sdk/bin/gcloud \ + kms single-tenant-hsm proposal describe """ + + +def fetch_challenges(sthi_proposal_resource: str): + """Fetches challenges from the server.""" + + try: + print("\nfetching challenges") + process = subprocess.run( + command_gcloud_describe_proposal + + sthi_proposal_resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + # stderr=subprocess.STDOUT + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud command executed successfully.") + print(process.stdout) + return process + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + + +command_gcloud_approve_proposal = [ + "~/sthi/google-cloud-sdk/bin/gcloud", + "kms", + "single-tenant-hsm", + "proposal", + "approve", +] + + +def send_signed_challenges( + signed_challenged_files: list[str], proposal_resource: str +): + """Sends signed challenges to the server.""" + print("Sending signed challenges") + signed_challenge_str = ( + '--challenge_replies="' + str(signed_challenged_files) + '"' + ) + command_str = " ".join( + command_gcloud_approve_proposal + + [proposal_resource] + + [signed_challenge_str] + ) + print(command_str) + + try: + + process = subprocess.run( + command_str, + capture_output=True, + check=False, + text=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud command executed successfully.") + return process + + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands_test.py b/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands_test.py new file mode 100644 index 00000000000..9823aebd114 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands_test.py @@ -0,0 +1,321 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess +from unittest import mock +import gcloud_commands +import pytest + +test_proposal_resource = """projects/test_project/locations/\ +us-central1/singleTenantHsmInstances/my_sthi/proposals/my_proposal + """ +sample_fetch_challenge_output = """ +{ + "quorumParameters": { + "challenges": [ + { + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" + } + ], + "requiredApproverCount": 3 + } +} + +""" + + +# Test case 1: Successful build and components add +def test_build_custom_gcloud_success(mock_subprocess_run): + # Setup: Mock successful gcloud execution + mock_subprocess_run.side_effect = [ + subprocess.CompletedProcess( + args=gcloud_commands.command_build_custom_gcloud, + returncode=0, + stdout="gcloud build successful!", + stderr="", + ), + subprocess.CompletedProcess( + args=gcloud_commands.command_add_components, + returncode=0, + stdout="gcloud components add successful.", + stderr="", + ), + ] + + # Action: Call the function + result = gcloud_commands.build_custom_gcloud() + + # Assert: Verify the return value and that subprocess.run was called correctly + assert result.returncode == 0 + assert result.stdout == "gcloud components add successful." + assert mock_subprocess_run.call_count == 2 + mock_subprocess_run.assert_has_calls([ + mock.call( + gcloud_commands.command_build_custom_gcloud, check=True, shell=True + ), + mock.call( + gcloud_commands.command_add_components, + check=False, + shell=True, + capture_output=False, + text=True, + ), + ]) + + +# Test case 2: gcloud build fails +def test_build_custom_gcloud_build_error(mock_subprocess_run): + # Setup: Mock gcloud build command with a non-zero return code + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, + cmd=gcloud_commands.command_build_custom_gcloud, + output="", + stderr="Error: Build failed", + ) + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.build_custom_gcloud() + + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Build failed" + assert exc_info.value.cmd == gcloud_commands.command_build_custom_gcloud + assert mock_subprocess_run.call_count == 1 + + +# Test case 3: gcloud components add fails +def test_build_custom_gcloud_components_error(mock_subprocess_run): + # Setup: Mock gcloud build success and components add with error + mock_subprocess_run.side_effect = [ + subprocess.CompletedProcess( + args=gcloud_commands.command_build_custom_gcloud, + returncode=0, + stdout="gcloud build successful!", + stderr="", + ), + subprocess.CalledProcessError( + returncode=1, + cmd=gcloud_commands.command_add_components, + output="", + stderr="Error: Components add failed", + ), + ] + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.build_custom_gcloud() + + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Components add failed" + assert exc_info.value.cmd == gcloud_commands.command_add_components + assert mock_subprocess_run.call_count == 2 + + +@pytest.fixture +def mock_subprocess_run(monkeypatch): + mock_run = mock.create_autospec(subprocess.run) + monkeypatch.setattr(subprocess, "run", mock_run) + return mock_run + + +def test_fetch_challenges_success(mock_subprocess_run): + # Setup: Configure the mock to simulate a successful gcloud command + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=0, + stdout=sample_fetch_challenge_output, + stderr="", + ) + mock_subprocess_run.return_value = mock_process_result + + # Action: Call the function + resource = test_proposal_resource + result = gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the results + mock_subprocess_run.assert_called_once_with( + gcloud_commands.command_gcloud_describe_proposal + + resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + ) + assert result == mock_process_result + assert result.returncode == 0 + assert result.stdout == sample_fetch_challenge_output + assert not result.stderr + + +def test_fetch_challenges_error(mock_subprocess_run): + # Setup: Configure the mock to simulate a failed gcloud command + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, cmd="", output="", stderr="Error: Invalid resource" + ) + + # Action & Assert: Call the function and check for the expected exception + resource = "invalid-resource" + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.fetch_challenges(resource) + + # Verify the exception details + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Invalid resource" + + +def test_fetch_challenges_command_construction(mock_subprocess_run): + # Setup: + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=0, + stdout="{}", + stderr="", + ) + mock_subprocess_run.return_value = mock_process_result + resource = test_proposal_resource + + # Action: Call the function + gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the command + mock_subprocess_run.assert_called_once_with( + gcloud_commands.command_gcloud_describe_proposal + + resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + ) + + +def test_fetch_challenges_output_capture(mock_subprocess_run): + # Setup: + expected_stdout = "Expected Output" + expected_stderr = "Expected Error" + expected_returncode = 0 + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=expected_returncode, + stdout=expected_stdout, + stderr=expected_stderr, + ) + mock_subprocess_run.return_value = mock_process_result + resource = test_proposal_resource + # Action: Call the function + result = gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the captured output + assert result.stdout == expected_stdout + assert result.stderr == expected_stderr + assert result.returncode == expected_returncode + + +# Test case 1: Successful gcloud command +def test_send_signed_challenges_success(mock_subprocess_run): + # Setup: Mock successful gcloud execution + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=[], # Not checked in this test, but good practice to include + returncode=0, + stdout="gcloud command successful!", + stderr="", + ) + + # Action: Call the function + result = gcloud_commands.send_signed_challenges(signed_files, proposal) + + # Assert: Verify the return value and that subprocess.run was called correctly + assert result.returncode == 0 + assert result.stdout == "gcloud command successful!" + expected_command = " ".join( + gcloud_commands.command_gcloud_approve_proposal + + [proposal] + + [ + "--challenge_replies=\"[('signed_challenge.bin'," + " 'public_key_1.pem')]\"" + ] + ) + mock_subprocess_run.assert_called_once_with( + expected_command, + capture_output=True, + check=True, + text=True, + shell=True, + ) + + +# Test case 2: gcloud command returns an error code +def test_send_signed_challenges_gcloud_error(mock_subprocess_run): + # Setup: Mock gcloud command with a non-zero return code and stderr + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=[], + returncode=1, + stdout="", + stderr="Error: Invalid proposal resource", + ) + + # Action: Call the function + result = gcloud_commands.send_signed_challenges(signed_files, proposal) + + # Assert: Verify the return value + assert result.returncode == 1 + assert result.stderr == "Error: Invalid proposal resource" + + +# Test case 3: subprocess.run raises a CalledProcessError +def test_send_signed_challenges_called_process_error( + mock_subprocess_run +): + # Setup: Mock subprocess.run to raise a CalledProcessError + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=2, + cmd="test_command", + output="", + stderr="Called process error", + ) + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.send_signed_challenges(signed_files, proposal) + + assert exc_info.value.returncode == 2 + assert exc_info.value.stderr == "Called process error" + assert exc_info.value.cmd == "test_command" + + +# Test case 4: Signed challenge file list is empty. +def test_send_signed_challenges_empty_list(mock_subprocess_run): + + # Action: Call the function + with pytest.raises(ValueError, match="signed_challenged_files is empty"): + gcloud_commands.send_signed_challenges([], test_proposal_resource) diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_25167010.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_25167010.pem new file mode 100644 index 00000000000..7b0fe69d677 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_25167010.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArp0MggO5sHSIyfzgVfnR +xzG3S1D1XKVRFHHa5PHG+g64OZJ/cYNQux+OXKBuXs0rgKI+s+PVnYF9vJg3gatF +Xos+lN0Xv+wcuFTqMD0acPUNhX2QCq53Lb4u1R/EAZ0lzFVSu17N6Kde90R8EPgE +a8pc7pusgzT7wE7TAlXbRLYBp11n+dcgXGtYHHBECeBLiu7vvx8bUO6ZPLJClnwK +dAkIy9HCtlMUdukmIUrL5oMLUVMCdPn/8fELUbsFBK5ZE1nrbx6eKJKKXRl1BZ+s +11U54/xftPvqw9MgKKU4aWoY5Yz+xsDM27h1zvPtADXzTUsNY3oLpo/pYJqmwdhz +6wIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787103.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787103.pem new file mode 100644 index 00000000000..bd82c8f28b8 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787103.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Rx08jnCJMrypWQgp8a8 +zBj0SAjIqDlOucHmaax6gI0xDMxq1LSfXUHsuD5qtxttxn+WszKUar29/VRHY9Af +98HjvqIRTJSyvCwDr0w/9LEKXL4JubzP9+4PdeK5AsdhgaKazYRfjpUVS7cispoq +N5rl+kLEJS5SvPTbzXh71YLgFWtUXJJLz5SgbCReVI4eegfVTZ1JPrAC2x7TVu4P +qNXLmuX9ijqEhxvr4Cv7A4n7MJTpaXAeu1yYByslI3kF16rND2digyd7TSZ/LwQe +eTd5ElTXF6b41zhRm+FyCwWjcWjiJdXnUSZai2Td2n8gddsDmz5YwBbRKD9lBpRy +gQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787105.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787105.pem new file mode 100644 index 00000000000..3466ad79de9 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787105.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3llqGRlrEdw1AFPAi5yN +SLunBSGtodc6F4RBXkTjE6awpRsqJUw0Nrt6iy6zK6yWBBE/ecwcZ7B5DUGFalqZ +NphMUhKaEVlbxO3R5+z+6OcJR74VO/DIzGOfpGw9FjK1FhYBl7Ljhy8cCQbky3hJ +I8GPsrhTkM7+Ciphp5Hxt4glhnd7LY5NiPy4DKdzhw3mvPZzm3cRhGDTbiMPHHxB +h6fjp8uzUTnAmF6EWNwPRKLvpg3q3QnOAmwmtOcke5Yl6TPzAw1r1M6KhVxkFijr +f22HHrM0ykHOTWZZTU+IgPX4T/dqEQ13BrqJPg8tai6pMClDdl5zStIK004wfNct +FwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/requirements.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/requirements.txt new file mode 100644 index 00000000000..e29d4ff4c8e --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/requirements.txt @@ -0,0 +1,21 @@ +backports.tarfile==1.2.0 +cffi==1.17.1 +click==8.1.8 +cryptography==44.0.0 +fido2==1.2.0 +importlib_metadata==8.6.1 +iniconfig==2.0.0 +jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.1.0 +jeepney==0.8.0 +keyring==25.6.0 +more-itertools==10.6.0 +packaging==24.2 +pluggy==1.5.0 +pycparser==2.22 +pyscard==2.2.1 +pytest==8.3.4 +SecretStorage==3.3.3 +yubikey-manager==5.5.1 +zipp==3.21.0 diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/setup.py b/kms/singletenanthsm/brandonluong/singletenanthsm/setup.py new file mode 100644 index 00000000000..249e92820f2 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/setup.py @@ -0,0 +1,69 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import gcloud_commands +import ykman_utils + + +def validate_operation(operation: str): + if operation == "build_custom_gcloud": + try: + gcloud_commands.build_custom_gcloud() + except Exception as e: + raise Exception(f"Generating custom gcloud build failed {e}") + elif operation == "generate_rsa_keys": + try: + ykman_utils.generate_private_key() + except Exception as e: + raise Exception(f"Generating private keys failed {e}") + elif operation == "generate_gcloud_and_keys": + generate_private_keys_build_gcloud() + else: + raise Exception( + "Operation type not valid. Operation flag value must be" + " build_custom_gcloud, generate_rsa_keys, or generate_gcloud_and_keys" + ) + + +def generate_private_keys_build_gcloud(): + """Generates an RSA key on slot 82 of every yubikey + + connected to the local machine and builds the custom gcloud cli. + """ + try: + ykman_utils.generate_private_key() + except Exception as e: + raise Exception(f"Generating private keys failed {e}") + try: + gcloud_commands.build_custom_gcloud() + except Exception as e: + raise Exception(f"Generating custom gcloud build failed {e}") + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument( + "--operation", + type=str, + choices=[ + "build_custom_gcloud", + "generate_rsa_keys", + "generate_gcloud_and_keys", + ], + required=True, + ) + args = parser.parse_args() + validate_operation(args.operation) diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_1.pem new file mode 100644 index 00000000000..9ee4a26b6cd --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrStPaIP4CkVGJgXlzTK +1ppk3xbdhd4kuKNONvdxC13VR7pw6oUo8dihB0cZAvAn/O/Ws8FrfIvQnyBDcttf +wLGe5TK4Vtj/G/mgf66iYTpDF+4bKVX9JuR3EJN4N90XqoyRfQvS9zUniuIzn4MD +I5vrAK0PVz6CZWXA0omHOobJWS1ZjdEz1N+uXkRh+M/u9/85h3WhLYbWfv3Jj/lE +Y8cuXf2An1AyRGYhceID1Ce1z/pkikJZrNEAtMdnR1pCKo/bD/WBPlatXzkPtwwJ +TloSm6ptxhKRzARdvlHOn62lCvF1kGWBbzpyaur6SloWVTtR0Eln2QJAA3pXpLrF +PwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_2.pem new file mode 100644 index 00000000000..0e830e9c965 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA09Y7M3Jyh/n3bnjh9Q6m +oy64/CXbn0Ud2KriRYua/jYGrPsRvhbnYmrOGjL0ABPNQLO4T95KKrYgl5ZDjAQQ +e5o5e+RnSSkCO7osqCZM1t7ydaMeOn6Jx/XvKDc7IqdQ0+iqBiv/zwc+dHdjLhS6 +gbwb3aengOL0TMijDXiTZAZR1mil5y5hBfJBGXKRTGy6GnCW9HG4k+PKLpJ5drsD +pbINCwfqozv+OsoDfOR0yp0iFlf6PGhYVMS/nTtuCKuPkWerTlvmAVogqz26VMbL +Mxxa+xFig4aScgJ7r/sSIVZHwT5nau7A9zEdMC3dV72oY8br/CrpkjvfMmhBw351 +bQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_3.pem new file mode 100644 index 00000000000..56f9b3b26d8 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxD115YJWrQy/cgV1Ax8B +jbqlffcXvXybceL/jFd7ijmZbnjr3xjDVGFdedGpE6bT49SQa3vhb/bvIza7+vy7 +VuW3iFK9QABF6PcnX75UDMgn+cX8qVS9pu1wKqC0pb/Z99Fw2D8jnSY9z6AzHnGf +qbX0R1virFS5Qm/XDa2QudS6i954f3bkcRyopt4GuJO6osJPAhAm5aXLXWskT0jU +trTHjIA0aQopNa6dokgw7HV7lVm1jgUeAQXl2YOWZSGrh9sknW2CLSCjClo5uOTj +caTHyvIb9lCzTJPTN60Gyy2cxsTl/eFiZRDyK1TNyapXBy1b3kF/hBRubwJU+Hjq +NwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge1.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge1.txt new file mode 100644 index 00000000000..e2cc944c512 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge1.txt @@ -0,0 +1 @@ +HwY(5E *dzb-qw;cB[e'.9v'$u+L'bo6=9*K$P+˜d;'\ƼlS \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge3.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge3.txt new file mode 100644 index 0000000000000000000000000000000000000000..f07c9cef275f2ddd1d33cf6be1cb5e9e4a8a1a27 GIT binary patch literal 256 zcmV+b0ssDJIB1;+Q0il23-Pg+HcrX`4UC7u&-H>d60%EOID7>f5UW5aTG~TtMepV8 z10F#Iob>sZ4cxEaoZ?!QM=~OumBxrSdZ!e{)^aVA^k-fC<`GI*vGfcUnI6b}v0i@G zpX-A}{@S8(9ffCkwd864j2^v|$2d<#gfUAM<^A;2!P1t95@K(TDL(*GoPyfC3@@{RKO~JlsbjS zC9+HV)V3EH5~d+w3t5*=6F-k)g-aVomIp^odx?oXp1oU)p?{MRJg5^`rJ%4mR4JfH G)g9aJaC|oa literal 0 HcmV?d00001 diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_fake.py b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_fake.py new file mode 100644 index 00000000000..6e0a29ecb19 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_fake.py @@ -0,0 +1,53 @@ +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa + + +def generate_rsa_keys(key_size=2048): + """Generates an RSA key pair with the specified key size.""" + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=key_size, + ) + public_key = private_key.public_key() + return private_key, public_key + + +def sign_data(private_key, data, hash_algorithm=hashes.SHA256()): + """Signs the provided data using the private key with PKCS#1.5 padding.""" + if not isinstance(data, bytes): + raise TypeError("Data must be of type bytes") + signature_bytes = private_key.sign(data, padding.PKCS1v15(), hash_algorithm) + return signature_bytes + + +def verify_signature( + public_key, data, signature, hash_algorithm=hashes.SHA256() +): + """Verifies the signature of the data using the public key.""" + if not isinstance(data, bytes): + raise TypeError("Data must be of type bytes") + if not isinstance(signature, bytes): + raise TypeError("Signature must be of type bytes") + try: + public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm) + return True # Signature is valid + except InvalidSignature: + return False # Signature is invalid + + +if __name__ == "__main__": + private_key_instance, public_key_instance = generate_rsa_keys() + + # Data to sign (as bytes) + data_to_sign = b"This is the data to be signed." + signature_bytes = sign_data(private_key_instance, data_to_sign) + print(f"Signature generated: {signature_bytes.hex()}") + is_valid = verify_signature( + public_key_instance, data_to_sign, signature_bytes + ) + if is_valid: + print("Signature is VALID.") + else: + print("Signature is INVALID.") diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils.py new file mode 100644 index 00000000000..fe7d9a2f834 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +from dataclasses import dataclass +import pathlib +import re +import cryptography.exceptions +from cryptography.hazmat.primitives import _serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.serialization import load_pem_public_key +from ykman import piv +from ykman.device import list_all_devices +from yubikit.piv import PIN_POLICY, TOUCH_POLICY, hashes +from yubikit.piv import SmartCardConnection + +DEFAULT_MANAGEMENT_KEY = "010203040506070801020304050607080102030405060708" +DEFAULT_PIN = "123456" + + +def generate_private_key( + key_type=piv.KEY_TYPE.RSA2048, + management_key=DEFAULT_MANAGEMENT_KEY, + pin=DEFAULT_PIN, +): + """Generates a private key on the yubikey.""" + + devices = list_all_devices() + if not devices: + raise ValueError("no yubikeys found") + print(f"{len(devices)} yubikeys detected") + for yubikey, device_info in devices: + with yubikey.open_connection(SmartCardConnection) as connection: + piv_session = piv.PivSession(connection) + piv_session.authenticate( + piv.MANAGEMENT_KEY_TYPE.TDES, + bytes.fromhex(management_key), + ) + piv_session.verify_pin(pin) + + public_key = piv_session.generate_key( + piv.SLOT.RETIRED1, + key_type=key_type, + pin_policy=PIN_POLICY.DEFAULT, + touch_policy=TOUCH_POLICY.ALWAYS, + ) + if not public_key: + raise RuntimeError("failed to generate public key") + with open( + f"generated_public_keys/public_key_{device_info.serial}.pem", "wb" + ) as binary_file: + + # Write bytes to file + binary_file.write( + public_key.public_bytes( + encoding=_serialization.Encoding.PEM, + format=_serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ) + print( + f"Private key pair generated on device {device_info.serial} on key" + f" slot: {piv.SLOT.RETIRED1}" + ) + + +@dataclass +class Challenge: + """Represents a challenge with its associated public key.""" + + challenge: bytes + public_key_pem: str + + def to_dict(self): + return { + "challenge": base64.b64encode(self.challenge).decode("utf-8"), + "public_key_pem": self.public_key_pem, + } + + @staticmethod + def from_dict(data): + if not isinstance(data, dict): + return None + return Challenge( + challenge=base64.b64decode(data["challenge"]), + public_key_pem=data["public_key_pem"], + ) + + +class ChallengeReply: + + def __init__(self, unsigned_challenge, signed_challenge, public_key_pem): + self.unsigned_challenge = unsigned_challenge + self.signed_challenge = signed_challenge + self.public_key_pem = public_key_pem + + +def populate_challenges_from_files() -> list[Challenge]: + """Populates challenges and their corresponding public keys from files. + + This function searches for files matching the patterns + "challenges/public_key*.pem" + and "challenges/challenge*.bin" in the current working directory. It then + pairs each challenge with its corresponding public key based on matching + numeric IDs in the filenames. + + Returns: + list[Challenge]: A list of Challenge objects, each containing a challenge + and its associated public key. + """ + public_key_files = list(pathlib.Path.cwd().glob("challenges/public_key*.pem")) + print(public_key_files) + challenge_files = list(pathlib.Path.cwd().glob("challenges/challenge*.bin")) + print(challenge_files) + + challenges = [] + + for public_key_file in public_key_files: + challenge_id = re.findall(r"\d+", str(public_key_file)) + for challenge_file in challenge_files: + if challenge_id == re.findall(r"\d+", str(challenge_file)): + print(public_key_file) + file = open(public_key_file, "r") + public_key_pem = file.read() + file.close() + file = open(challenge_file, "rb") + challenge = file.read() + file.close() + challenges.append(Challenge(challenge, public_key_pem)) + return challenges + + +def sign_challenges(challenges: list[Challenge]) -> list[ChallengeReply]: + """Signs a proposal's challenges using a Yubikey.""" + if not challenges: + raise ValueError("Challenge list empty: No challenges to sign.") + signed_challenges = [] + devices = list_all_devices() + if not devices: + raise ValueError("no yubikeys found") + for yubikey, _ in devices: + with yubikey.open_connection(SmartCardConnection) as connection: + # Make PivSession and fetch public key from Signature slot. + piv_session = piv.PivSession(connection) + # authenticate + piv_session.authenticate( + piv.MANAGEMENT_KEY_TYPE.TDES, + bytes.fromhex("010203040506070801020304050607080102030405060708"), + ) + piv_session.verify_pin("123456") + + # Get the public key from slot 82. + slot_metadata = piv_session.get_slot_metadata(slot=piv.SLOT.RETIRED1) + print(slot_metadata.public_key.public_bytes) + + # Check to see if any of the challenge public keys matches with the + # public key from slot 82. + for challenge in challenges: + key_public_bytes = slot_metadata.public_key.public_bytes( + encoding=_serialization.Encoding.PEM, + format=_serialization.PublicFormat.SubjectPublicKeyInfo, + ) + print(key_public_bytes.decode()) + print(challenge.public_key_pem) + if key_public_bytes == challenge.public_key_pem.encode(): + + # sign the challenge + print("Press Yubikey to sign challenge") + signed_challenge = piv_session.sign( + slot=piv.SLOT.RETIRED1, + key_type=slot_metadata.key_type, + message=challenge.challenge, + hash_algorithm=hashes.SHA256(), + padding=padding.PKCS1v15(), + ) + + signed_challenges.append( + ChallengeReply( + challenge.challenge, + signed_challenge, + challenge.public_key_pem, + ) + ) + print("Challenge signed successfully") + if not signed_challenges: + raise RuntimeError( + "No matching public keys between Yubikey and challenges. Make sure" + " key is generated in correct slot" + ) + return signed_challenges + + +def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: + """Converts a URL-safe base64 encoded string to its binary equivalent. + + Args: + urlsafe_string: The URL-safe base64 encoded string. + + Returns: + The binary data as bytes, or None if an error occurs. + + Raises: + TypeError: If the input is not a string. + ValueError: If the input string is not valid URL-safe base64. + """ + try: + if not isinstance(urlsafe_string, str): + raise TypeError("Input must be a string") + # Add padding if necessary. Base64 requires padding to be a multiple of 4 + missing_padding = len(urlsafe_string) % 4 + if missing_padding: + urlsafe_string += "=" * (4 - missing_padding) + return base64.urlsafe_b64decode(urlsafe_string) + except base64.binascii.Error as e: + raise ValueError(f"Invalid URL-safe base64 string: {e}") from e + + +def verify_challenge_signatures(challenge_replies: list[ChallengeReply]): + """Verifies the signatures of a list of challenge replies. + + Args: + challenge_replies: A list of ChallengeReply objects. + + Raises: + ValueError: If the list of challenge replies is empty. + cryptography.exceptions.InvalidSignature: If a signature is invalid. + """ + if not challenge_replies: + raise ValueError("No signed challenges to verify") + for challenge_reply in challenge_replies: + public_key = load_pem_public_key(challenge_reply.public_key_pem.encode()) + try: + + public_key.verify( + challenge_reply.signed_challenge, + challenge_reply.unsigned_challenge, + padding.PKCS1v15(), + hashes.SHA256(), + ) + print("Signature verification success") + except cryptography.exceptions.InvalidSignature as e: + raise cryptography.exceptions.InvalidSignature( + f"Signature verification failed: {e}" + ) + + +if __name__ == "__main__": + generate_private_key() diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils_test.py new file mode 100644 index 00000000000..b1a6ab4abb0 --- /dev/null +++ b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils_test.py @@ -0,0 +1,109 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pathlib + +import cryptography.exceptions +import pytest +import ykman_utils + + +class Challenge: + + def __init__(self, challenge, public_key_pem): + self.challenge = challenge + self.public_key_pem = public_key_pem + + +class ChallengeReply: + + def __init__(self, signed_challenge, public_key_pem): + self.signed_challenge = signed_challenge + self.public_key_pem = public_key_pem + + +challenge_test_data = b"test_data" + + +def generate_test_challenge_files(): + # Create challenges list from challenges directory + challenge_list = ykman_utils.populate_challenges_from_files() + for challenge in challenge_list: + print(challenge.challenge) + print(challenge.public_key_pem) + # Sign challenges + signed_challenges = ykman_utils.sign_challenges(challenge_list) + # for signed_challenge in signed_challenge_files: + # print(signed_challenge.signed_challenge.decode()) + # print(signed_challenge.public_key_pem) + ykman_utils.verify_challenge_signatures( + signed_challenges, + b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN", + ) + + +# A yubikey connected to your local machine will be needed to run these tests. +# The generate_private_key() method will rewrite the key saved on slot +# 82(Retired1). +@pytest.fixture(autouse=True) +def key_setup(): + ykman_utils.generate_private_key() + + +def create_challenges(): + public_key_files = list( + pathlib.Path.cwd().glob("generated_public_keys/public_key*.pem") + ) + challenges = [] + + for public_key_file in public_key_files: + file = open(public_key_file, "r") + public_key_pem = file.read() + challenges.append(Challenge(challenge_test_data, public_key_pem)) + + return challenges + + +def test_sign_and_verify_challenges(): + signed_challenges = ykman_utils.sign_challenges(create_challenges()) + ykman_utils.verify_challenge_signatures(signed_challenges) + + +def test_verify_mismatching_data_fail(): + with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: + signed_challenges = ykman_utils.sign_challenges(create_challenges()) + signed_challenges[0].signed_challenge = b"mismatched_data" + ykman_utils.verify_challenge_signatures(signed_challenges) + assert "Signature verification failed" in str(exec_info.value) + + +def test_sign_empty_challenge_list_fail(): + with pytest.raises(Exception) as exec_info: + ykman_utils.sign_challenges([]) + assert "Challenge list empty" in str(exec_info.value) + + +def test_sign_no_matching_public_keys_fail(): + modified_challenges = create_challenges() + for challenge in modified_challenges: + challenge.public_key_pem = "modified_public_key" + with pytest.raises(Exception) as exec_info: + ykman_utils.sign_challenges(modified_challenges) + assert "No matching public keys" in str(exec_info.value) + + +def test_verify_empty_challenge_replies_fail(): + with pytest.raises(Exception) as exec_info: + ykman_utils.verify_challenge_signatures([]) + assert "No \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/challenges/challenge1.txt deleted file mode 100644 index 96148c4d51d..00000000000 --- a/kms/singletenanthsm/challenges/challenge1.txt +++ /dev/null @@ -1 +0,0 @@ --jgoZSrRHP_1WQt6f4cx911AkWfa9WzqVuuOLd7iw6iAwdkeVcBA_Q3sBqaP_5B6 \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/challenges/challenge2.txt deleted file mode 100644 index d5b9f823ad0..00000000000 --- a/kms/singletenanthsm/challenges/challenge2.txt +++ /dev/null @@ -1 +0,0 @@ -C-A4-b-ZKjpgY0EJZmDIbUsTaZs3iPq-iW9XWQ18zd_k-biw8iKiWX7eLBjwUoHk \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/challenges/challenge3.txt deleted file mode 100644 index 16f1a19270c..00000000000 --- a/kms/singletenanthsm/challenges/challenge3.txt +++ /dev/null @@ -1 +0,0 @@ -Q8nT85xO8wuYguKfPSVU60rmpO67ue-CvSFXxIVzjeh2AaYPQ3Vq7Jsu2Bk4ynAC \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem deleted file mode 100644 index d6c87b690b9..00000000000 --- a/kms/singletenanthsm/challenges/public_key1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKbCvQEFWx65Ma57Ul24 -044s74u7H1nLQJ8WfBe33MKmOrsR31pO5dIfnJIs8d0vDFj3cm4MRRpIdI8PkOPE -oDDa9z0dz/t4qDpOKlRcxaFNZGoBs+j+TuC3rsrBxIxc+qSDn/WhDV2oAXaj3Jnw -5DPCGljDObbzaDwTjovP3enfFCncCl76z0yGuKoyW4aqoK2ccHLRxNF69bYSzICE -CiUxUeHJTvoLuJ8fwGUIKYsBb9tBuzGsrM+5Mj+bskvJLM3cNOkW1p1TOjLTtMk2 -pDlY+9EdWMJ/6wbX88b7dl5T3aQOTzNrxjojpYPyNWdHIGXL1YwKNE3EkdN7kXkK -yQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem deleted file mode 100644 index b9b47aab8f4..00000000000 --- a/kms/singletenanthsm/challenges/public_key2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrAMwX6JXw9RIH8f2+lG -VpaqAT2nwyzbPtpoRgemX/6ig3R8W9yNWOMvK6tf0MAVeHiyQIPucNXehr1VzQ1/ -VIlA9KA7LXznXwXDZkLl1/XHeJGbd3eyEA8K7tT3bFrSTZx/mVhEuJN9eLb5tL9t -gqes7pgYYe2iyhUCqQM3oh4K5SJGooQDX41/VXP8PxCem+71oG/LWogIVMLnU9Yu -33oQY6xvF1YKNgfGqnyNh5rPduQplJy04o/b6Xs3mcX/NUOBMUcLXDxe6tRGyy/2 -UX+6thK9gU2dXheK03rwhMv5jmpIGP7FShSs/+9N3nUIA+g5sTgnm2xqch5jOYXV -lwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem deleted file mode 100644 index 126fffa463f..00000000000 --- a/kms/singletenanthsm/challenges/public_key3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoidJnb4gpd8Ua/QRpwjb -Cg+0pHhQ22bv0bX66fj8niLPV4o/TaQzyYD94zA7t6ulUmxj93w2U0EEmsufrBrt -OgorVidL6MLuEOo0lWy0DHBsFDellFA75MTTIs76odhJshVfe9OSwFAakoMHiDAY -LTmTEcaJsanUiHhIaxF5O/cTAFlkhOLFD0becK674OAnqqmabzJx2qlTMraR3yPi -W1uLPdPBNeokBirk4WIO8XKtQJuOrGWhAxjIvGW20paRWgs0QI34ZgYQdnIjKwMO -0dsr/vlHjfRs0OX2oVmBqSi+TNY240BC4HshQOSPBdR2HsC9vfQ0uax8+nEcZiNG -CQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/fetch_challenges.py b/kms/singletenanthsm/fetch_challenges.py deleted file mode 100644 index d09f749c41a..00000000000 --- a/kms/singletenanthsm/fetch_challenges.py +++ /dev/null @@ -1,13 +0,0 @@ -import gcloud_commands - - -test_proposal_resource = "projects/cloudkms-dev/locations/us-central1/singleTenantHsmInstances/brandonluong2/proposals/3389a96c-8a64-4bd5-9b97-6957f882416f" - -def test_fetch_challenges(): - process = gcloud_commands.fetch_challenges(test_proposal_resource) - return process - # assert not process.stderr - -if __name__ == "__main__": - challenges = test_fetch_challenges() - print(challenges) \ No newline at end of file diff --git a/kms/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/gcloud_commands.py index dd8bd6f8bc4..7b89c384fbd 100644 --- a/kms/singletenanthsm/gcloud_commands.py +++ b/kms/singletenanthsm/gcloud_commands.py @@ -29,10 +29,11 @@ command_add_components = """ - ~/sthi/google-cloud-sdk/bin/gcloud components repositories add https://storage.googleapis.com/sthi-test-bucket/components-2.json + ~/sthi/google-cloud-sdk/bin/gcloud components repositories add https://storage.googleapis.com/single-tenant-hsm-private/components-2.json ~/sthi/google-cloud-sdk/bin/gcloud components update """ + def build_custom_gcloud(): """Builds a custom gcloud binary.""" try: @@ -49,20 +50,7 @@ def build_custom_gcloud(): print("gcloud build executed successfully.") print(process.stdout) except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(f"Error executing gcloud build: {e}") - # try: - # print("\nAdding sthigcloud alias") - # process = subprocess.run( - # "alias sthigcloud=~/sthi/google-cloud-sdk/bin/gcloud", - # check=False, - # capture_output=False, - # executable="/bin/bash", - # text=True, - # shell=True - # ) - # except subprocess.CalledProcessError as e: - # raise subprocess.CalledProcessError(f"Error executing gcloud alias update: {e}") - # print(f"Error executing gcloud alias update: {e}") + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) try: print("\nAdding gcloud components") process = subprocess.run( @@ -80,7 +68,7 @@ def build_custom_gcloud(): print(process.stdout) return process except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(f"Error executing gcloud components update: {e}") + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) print(f"Error executing gcloud components update: {e}") @@ -93,17 +81,18 @@ def build_custom_gcloud(): ~/sthi/google-cloud-sdk/bin/gcloud \ kms single-tenant-hsm proposal describe """ -def fetch_challenges(sthi_proposal_resource:str): - """Fetches challenges from the server.""" - challenges = [] +def fetch_challenges(sthi_proposal_resource: str): + """Fetches challenges from the server.""" try: print("\nfetching challenges") process = subprocess.run( - command_gcloud_describe_proposal + sthi_proposal_resource + " --format=json", + command_gcloud_describe_proposal + + sthi_proposal_resource + + " --format=json", capture_output=True, - check=False, + check=True, text=True, shell=True, # stderr=subprocess.STDOUT @@ -116,9 +105,7 @@ def fetch_challenges(sthi_proposal_resource:str): print(process.stdout) return process except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(f"Error executing gcloud command: {e}") - - return challenges + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) command_gcloud_approve_proposal = [ "~/sthi/google-cloud-sdk/bin/gcloud", @@ -126,22 +113,41 @@ def fetch_challenges(sthi_proposal_resource:str): "single-tenant-hsm", "proposal", "approve", - "projects/hawksbill_playground/locations/global/singleTenantHsmInstances/my_instance/proposals/proposal1", ] -def send_signed_challenges(): + +def send_signed_challenges( + signed_challenged_files: list[str], proposal_resource: str +): + """Sends signed challenges to the server.""" + if signed_challenged_files is None or not signed_challenged_files: + raise ValueError("signed_challenged_files is empty") print("Sending signed challenges") + signed_challenge_str = ( + '--challenge_replies="' + str(signed_challenged_files) + '"' + ) + command_str = " ".join( + command_gcloud_approve_proposal + + [proposal_resource] + + [signed_challenge_str] + ) + print(command_str) + try: process = subprocess.run( - " ".join(command_gcloud_approve_proposal), + command_str, capture_output=True, - # check=True, + check=True, text=True, shell=True, ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") print("gcloud command executed successfully.") return process except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(f"Error executing gcloud command: {e}") \ No newline at end of file + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) diff --git a/kms/singletenanthsm/gcloud_commands_test.py b/kms/singletenanthsm/gcloud_commands_test.py index 1a104a4fbe9..9823aebd114 100644 --- a/kms/singletenanthsm/gcloud_commands_test.py +++ b/kms/singletenanthsm/gcloud_commands_test.py @@ -12,22 +12,310 @@ # See the License for the specific language governing permissions and # limitations under the License. +import subprocess +from unittest import mock import gcloud_commands import pytest -test_proposal_resource = """projects/hawksbill-playground/locations/\ -us-east1/singleTenantHsmInstances/my_sth +test_proposal_resource = """projects/test_project/locations/\ +us-central1/singleTenantHsmInstances/my_sthi/proposals/my_proposal """ +sample_fetch_challenge_output = """ +{ + "quorumParameters": { + "challenges": [ + { + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" + } + ], + "requiredApproverCount": 3 + } +} -# def test_build_custom(): -# process = gcloud_commands.build_custom_gcloud() -# assert not process.stderr +""" -# def test_fetch_challenges(): -# process = gcloud_commands.fetch_challenges(test_proposal_resource) -# # assert not process.stderr -# def test_send_signed_challenges(): -# process = gcloud_commands.send_signed_challenges() -# # assert not process.stderr +# Test case 1: Successful build and components add +def test_build_custom_gcloud_success(mock_subprocess_run): + # Setup: Mock successful gcloud execution + mock_subprocess_run.side_effect = [ + subprocess.CompletedProcess( + args=gcloud_commands.command_build_custom_gcloud, + returncode=0, + stdout="gcloud build successful!", + stderr="", + ), + subprocess.CompletedProcess( + args=gcloud_commands.command_add_components, + returncode=0, + stdout="gcloud components add successful.", + stderr="", + ), + ] + # Action: Call the function + result = gcloud_commands.build_custom_gcloud() + + # Assert: Verify the return value and that subprocess.run was called correctly + assert result.returncode == 0 + assert result.stdout == "gcloud components add successful." + assert mock_subprocess_run.call_count == 2 + mock_subprocess_run.assert_has_calls([ + mock.call( + gcloud_commands.command_build_custom_gcloud, check=True, shell=True + ), + mock.call( + gcloud_commands.command_add_components, + check=False, + shell=True, + capture_output=False, + text=True, + ), + ]) + + +# Test case 2: gcloud build fails +def test_build_custom_gcloud_build_error(mock_subprocess_run): + # Setup: Mock gcloud build command with a non-zero return code + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, + cmd=gcloud_commands.command_build_custom_gcloud, + output="", + stderr="Error: Build failed", + ) + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.build_custom_gcloud() + + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Build failed" + assert exc_info.value.cmd == gcloud_commands.command_build_custom_gcloud + assert mock_subprocess_run.call_count == 1 + + +# Test case 3: gcloud components add fails +def test_build_custom_gcloud_components_error(mock_subprocess_run): + # Setup: Mock gcloud build success and components add with error + mock_subprocess_run.side_effect = [ + subprocess.CompletedProcess( + args=gcloud_commands.command_build_custom_gcloud, + returncode=0, + stdout="gcloud build successful!", + stderr="", + ), + subprocess.CalledProcessError( + returncode=1, + cmd=gcloud_commands.command_add_components, + output="", + stderr="Error: Components add failed", + ), + ] + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.build_custom_gcloud() + + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Components add failed" + assert exc_info.value.cmd == gcloud_commands.command_add_components + assert mock_subprocess_run.call_count == 2 + + +@pytest.fixture +def mock_subprocess_run(monkeypatch): + mock_run = mock.create_autospec(subprocess.run) + monkeypatch.setattr(subprocess, "run", mock_run) + return mock_run + + +def test_fetch_challenges_success(mock_subprocess_run): + # Setup: Configure the mock to simulate a successful gcloud command + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=0, + stdout=sample_fetch_challenge_output, + stderr="", + ) + mock_subprocess_run.return_value = mock_process_result + + # Action: Call the function + resource = test_proposal_resource + result = gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the results + mock_subprocess_run.assert_called_once_with( + gcloud_commands.command_gcloud_describe_proposal + + resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + ) + assert result == mock_process_result + assert result.returncode == 0 + assert result.stdout == sample_fetch_challenge_output + assert not result.stderr + + +def test_fetch_challenges_error(mock_subprocess_run): + # Setup: Configure the mock to simulate a failed gcloud command + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, cmd="", output="", stderr="Error: Invalid resource" + ) + + # Action & Assert: Call the function and check for the expected exception + resource = "invalid-resource" + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.fetch_challenges(resource) + + # Verify the exception details + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Invalid resource" + + +def test_fetch_challenges_command_construction(mock_subprocess_run): + # Setup: + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=0, + stdout="{}", + stderr="", + ) + mock_subprocess_run.return_value = mock_process_result + resource = test_proposal_resource + + # Action: Call the function + gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the command + mock_subprocess_run.assert_called_once_with( + gcloud_commands.command_gcloud_describe_proposal + + resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + ) + + +def test_fetch_challenges_output_capture(mock_subprocess_run): + # Setup: + expected_stdout = "Expected Output" + expected_stderr = "Expected Error" + expected_returncode = 0 + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=expected_returncode, + stdout=expected_stdout, + stderr=expected_stderr, + ) + mock_subprocess_run.return_value = mock_process_result + resource = test_proposal_resource + # Action: Call the function + result = gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the captured output + assert result.stdout == expected_stdout + assert result.stderr == expected_stderr + assert result.returncode == expected_returncode + + +# Test case 1: Successful gcloud command +def test_send_signed_challenges_success(mock_subprocess_run): + # Setup: Mock successful gcloud execution + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=[], # Not checked in this test, but good practice to include + returncode=0, + stdout="gcloud command successful!", + stderr="", + ) + + # Action: Call the function + result = gcloud_commands.send_signed_challenges(signed_files, proposal) + + # Assert: Verify the return value and that subprocess.run was called correctly + assert result.returncode == 0 + assert result.stdout == "gcloud command successful!" + expected_command = " ".join( + gcloud_commands.command_gcloud_approve_proposal + + [proposal] + + [ + "--challenge_replies=\"[('signed_challenge.bin'," + " 'public_key_1.pem')]\"" + ] + ) + mock_subprocess_run.assert_called_once_with( + expected_command, + capture_output=True, + check=True, + text=True, + shell=True, + ) + + +# Test case 2: gcloud command returns an error code +def test_send_signed_challenges_gcloud_error(mock_subprocess_run): + # Setup: Mock gcloud command with a non-zero return code and stderr + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=[], + returncode=1, + stdout="", + stderr="Error: Invalid proposal resource", + ) + + # Action: Call the function + result = gcloud_commands.send_signed_challenges(signed_files, proposal) + + # Assert: Verify the return value + assert result.returncode == 1 + assert result.stderr == "Error: Invalid proposal resource" + + +# Test case 3: subprocess.run raises a CalledProcessError +def test_send_signed_challenges_called_process_error( + mock_subprocess_run +): + # Setup: Mock subprocess.run to raise a CalledProcessError + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=2, + cmd="test_command", + output="", + stderr="Called process error", + ) + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.send_signed_challenges(signed_files, proposal) + + assert exc_info.value.returncode == 2 + assert exc_info.value.stderr == "Called process error" + assert exc_info.value.cmd == "test_command" + + +# Test case 4: Signed challenge file list is empty. +def test_send_signed_challenges_empty_list(mock_subprocess_run): + + # Action: Call the function + with pytest.raises(ValueError, match="signed_challenged_files is empty"): + gcloud_commands.send_signed_challenges([], test_proposal_resource) diff --git a/kms/singletenanthsm/generated_public_keys/public_key_25167010_slot_82 (RETIRED1).pem b/kms/singletenanthsm/generated_public_keys/public_key_25167010_slot_82 (RETIRED1).pem deleted file mode 100644 index 0c00fb5c279..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_25167010_slot_82 (RETIRED1).pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwmED+fcYCwcVTzpmvwPc -m4RjHW+yZMWfLrlsPoAvA7uQDdeBSR8eDGrpG6yNm/Vpql6S6Ufog7G/m1eqd+XR -/33DP1qouHFqboB0wra+oq+Uk577YEJmNaKM/ej+N6v1NQLlQnXJWuKcTgXoseJb -i011JyLlstAF0DoNSipD7MqlDFS6QthFklunbUuTGvU/RS/+wTnLD/1tEHCAld/7 -NA9eSyANaysXjI7V40SXkXX1NJGkzv9kB3iGPVybpFI3lNlUf/5oQCNkawscPTOb -DYh3pa8i1uTEHB0tB2eBAKo0iuqJqAoTJZ+FQCc+33M4a8hFjmT0wRUA1H3b0YGP -LwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787103_slot_82 (RETIRED1).pem b/kms/singletenanthsm/generated_public_keys/public_key_28787103_slot_82 (RETIRED1).pem deleted file mode 100644 index faa921c46f4..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_28787103_slot_82 (RETIRED1).pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu5l1cXUL3vj718gyG/wg -Hh2CXIaU5QOfkm15te/QQsid/8cRPFIR+yMT2Yjg/lvD96Pj4xctnIex9rTJj2TW -VXgRSP04oOO4qBNTYS1YdAruzEy6ObIRtNMXBIgVKDSlQ+pq9BKku9gqFLvPd/rA -94yIFEXbY9ma4ZGuHadu6KYWHTp3x8X8aNICqbEdYEKu31xf1a/arn3D97Mn5GKJ -/c7UtWQz1C8mTU/JPw8AM2uphqckepkcQ/mpAcfVhLbTQa6sg7j61Rj0VL5UiZj5 -NPVnv7QYxGI0LWrS41aM/LjVldgFgQv74pZylWd1K+rSbtmMZjHgdywFYcaK2Ks+ -GQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787105_slot_82 (RETIRED1).pem b/kms/singletenanthsm/generated_public_keys/public_key_28787105_slot_82 (RETIRED1).pem deleted file mode 100644 index e2e7cbb2935..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_28787105_slot_82 (RETIRED1).pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2/QYPaWqTcZd4ZQ/LAUu -KLCdfEp6osmxH+CN2OnSQidvV18IT4oqJv4iG7Xr2b9jWnpAIE6m/kiPtat6/m9d -z/knPKOiEb4kiai+TwtGpJ+RzW8vtyMZ3tBd6OEhiY9ErhDxsL4pOhYblQ8RRVu/ -GbDi4d38EdfXMQTfkP4QKf8Q/Ko4kWD2cYoNgeCNmvo0cejZlUG7IJ/66gd87nlm -welUDfPGAkt+RCkhZErTzIjLH0yhRiPNcVXnmPPhM36X4SXos7kDd9KxG4KuQP23 -1pT/kDjSDY2SkgZ9AeIifEULxgg+8j8Mf/FwUBONWvOpQObMKGfyD1BN8Z58mY3Z -0wIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/setup.py b/kms/singletenanthsm/setup.py index 55602ec4d37..1d93e1d313a 100644 --- a/kms/singletenanthsm/setup.py +++ b/kms/singletenanthsm/setup.py @@ -52,6 +52,9 @@ def generate_private_keys_build_gcloud(): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('--operation',type=str, required=True) + parser.add_argument('--operation',type=str, + choices=['build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'], + required=True + ) args = parser.parse_args() validate_operation(args.operation) diff --git a/kms/singletenanthsm/sign_proposals_test.py b/kms/singletenanthsm/sign_proposals_test.py deleted file mode 100644 index 2fc4c333efd..00000000000 --- a/kms/singletenanthsm/sign_proposals_test.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# import pytest - -import cryptography.exceptions -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import _serialization -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric import ed25519 -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.asymmetric import x25519 -from cryptography.hazmat.primitives.serialization import load_pem_public_key -import kms.singletenanthsm.approve_proposal as approve_proposal - - -public_key = """-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyzXMN4kWOHPwCGiWHOZP -hpbiElngaVVll2UybTZq9ntJmyaVyYtzq2ypP5PkrOC0wfFsleU4T31ZAvgRFrcV -WLCCdFfr9TMBKkXz0VOXXFamDvyuKl7nKxjQnIqMWPd7Fo0JlDjCnrNew/KYpVZ7 -VIZ4vLpyPfBHmJnaFyW1CY7cebQM0lBNH5hBibFl92ZRgm5Gg8tEMBKiySQKlP7E -I6rnA1KTwuCirXG2U/uaYj3rhsZahsJvUdyrpQQGNtkjpMX3f+djBf6Zs3p2l8hE -DNMmpuRb3EB88Su8P6vuxoJgxc5M0rV9RfutnsE4mMWKD3WO1Q0IiU8HwErURl/G -RwIDAQAB ------END PUBLIC KEY----- -""" - - -class Challenge: - - def __init__(self, challenge, public_key_pem): - self.challenge = challenge - self.public_key_pem = public_key_pem - - -example_challenge_1 = Challenge(b"123", public_key) -example_challenge_2 = Challenge(b"456", "hello") - - -class ChallengeReply: - - def __init__(self, signed_challenge, public_key_pem): - self.signed_challenge = signed_challenge - self.public_key_pem = public_key_pem - - -def verify_challenge_signatures(signed_challenges): - if not signed_challenges: - raise Exception("No signed challenges to verify") - for signed_challenge in signed_challenges: - public_key = load_pem_public_key( - example_challenge_1.public_key_pem.encode() - ) - try: - public_key.verify( - signed_challenge, - example_challenge_1.challenge, - padding.PKCS1v15(), - hashes.SHA256(), - ) - print(f"Signature verification success") - except cryptography.exceptions.InvalidSignature as e: - print(f"Signature verification failed: {e}") - - -def test_sign_proposal_success(): - - # Populate challenges - challenges = [] - challenges.append(example_challenge_1) - challenges.append(example_challenge_2) - signed_challenges = approve_proposal.sign_proposal(challenges) - if approve_proposal: - print("proposals signed") - else: - print("proposals not signed") - - verify_challenge_signatures(signed_challenges) - - -def test_sign_proposal_missing_challenges(): - challenges = [] - - -def test_sign_proposal_no_matching_public_keys(): - - challenges = [] - - - - -def test_fetch_challenges(): - print("fetching challenges") - approve_proposal.fetch_challenges - print("challenges fetched successfully") - - -if __name__ == "__main__": - test_fetch_challenges() - - test_sign_proposal_success() diff --git a/kms/singletenanthsm/signed_challenges/challenge1.txt b/kms/singletenanthsm/signed_challenges/challenge1.txt deleted file mode 100644 index cf515f2b3cd..00000000000 --- a/kms/singletenanthsm/signed_challenges/challenge1.txt +++ /dev/null @@ -1,2 +0,0 @@ -$wEQݰ^sLum fgvwFJ p(T{LhՑck/.Vk`L 6ݤJ+scQJ7߮eCDH4,B7pФd%cRi'!78=CmS('G\wn2ular<ĖqŸ%OuUF[$P|e|cE -Kc!_ \ No newline at end of file diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem deleted file mode 100644 index b9b47aab8f4..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrAMwX6JXw9RIH8f2+lG -VpaqAT2nwyzbPtpoRgemX/6ig3R8W9yNWOMvK6tf0MAVeHiyQIPucNXehr1VzQ1/ -VIlA9KA7LXznXwXDZkLl1/XHeJGbd3eyEA8K7tT3bFrSTZx/mVhEuJN9eLb5tL9t -gqes7pgYYe2iyhUCqQM3oh4K5SJGooQDX41/VXP8PxCem+71oG/LWogIVMLnU9Yu -33oQY6xvF1YKNgfGqnyNh5rPduQplJy04o/b6Xs3mcX/NUOBMUcLXDxe6tRGyy/2 -UX+6thK9gU2dXheK03rwhMv5jmpIGP7FShSs/+9N3nUIA+g5sTgnm2xqch5jOYXV -lwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem deleted file mode 100644 index d6c87b690b9..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKbCvQEFWx65Ma57Ul24 -044s74u7H1nLQJ8WfBe33MKmOrsR31pO5dIfnJIs8d0vDFj3cm4MRRpIdI8PkOPE -oDDa9z0dz/t4qDpOKlRcxaFNZGoBs+j+TuC3rsrBxIxc+qSDn/WhDV2oAXaj3Jnw -5DPCGljDObbzaDwTjovP3enfFCncCl76z0yGuKoyW4aqoK2ccHLRxNF69bYSzICE -CiUxUeHJTvoLuJ8fwGUIKYsBb9tBuzGsrM+5Mj+bskvJLM3cNOkW1p1TOjLTtMk2 -pDlY+9EdWMJ/6wbX88b7dl5T3aQOTzNrxjojpYPyNWdHIGXL1YwKNE3EkdN7kXkK -yQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem deleted file mode 100644 index 126fffa463f..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoidJnb4gpd8Ua/QRpwjb -Cg+0pHhQ22bv0bX66fj8niLPV4o/TaQzyYD94zA7t6ulUmxj93w2U0EEmsufrBrt -OgorVidL6MLuEOo0lWy0DHBsFDellFA75MTTIs76odhJshVfe9OSwFAakoMHiDAY -LTmTEcaJsanUiHhIaxF5O/cTAFlkhOLFD0becK674OAnqqmabzJx2qlTMraR3yPi -W1uLPdPBNeokBirk4WIO8XKtQJuOrGWhAxjIvGW20paRWgs0QI34ZgYQdnIjKwMO -0dsr/vlHjfRs0OX2oVmBqSi+TNY240BC4HshQOSPBdR2HsC9vfQ0uax8+nEcZiNG -CQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.txt b/kms/singletenanthsm/signed_challenges/signed_challenge1.txt deleted file mode 100644 index bdf0886dd3f..00000000000 --- a/kms/singletenanthsm/signed_challenges/signed_challenge1.txt +++ /dev/null @@ -1,3 +0,0 @@ -N=ᣇ,\̸ -`hɓC.݋oD4C,@uisaR"mwKFu jT%[%FxU^a?rNӴN6`L~8~asCfzz}CBC0fbai zR?i^5(!-&w8NPBjOnCT31moY(#)xni(k##A3FvhPB>--LlcLw;2>Bk|mcf z^ff|v>$TyA@uS-`MZ}OB)~;;;O%BeAvNanqkGyA4wkoZbcy7=t8ukP$$U^S*1?bFWXU>P6@GKPnGsDbR73~LcR_V0Ez#Pc&330-X#kLt zhss(Xy`Yy9?PZO~8uF=DX@?f-n#=#MV*Bs=8M#(#^w_QOSoz6UXEAyh>a1Rugf>-0 zg`}uF5#5HUAp5(`y3+^Nj|CRL!r7xytz`^9YS7)Ig%|pK8P8tpj{4cY&W~lDD*dR4 z2x>$WR{GC$<^)Mg1WT)=qmrxvt7JU-_no#hY;%+0piqS96xGi+fS?OzYI|(~L|(Vu z8<`WLa-Zn_7bMsgROAkpP_TN9Fl+gU;y1F$Lyd823wGi!IbD&t_A!AY{_K@XVRc6s G^$v4uw1VIO diff --git a/kms/singletenanthsm/singletenanthsm/README.rst b/kms/singletenanthsm/singletenanthsm/README.rst new file mode 100644 index 00000000000..a3fe9fee3d3 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/README.rst @@ -0,0 +1,105 @@ +Google Cloud Key Management Service Python Samples +=============================================================================== + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=kms/singletenanthsm/README.rst + + +This directory contains samples for Google Cloud Key Management Service. The `Cloud Key Management Service`_ allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service. + + + + +.. _Cloud Key Management Service: https://cloud.google.com/kms/docs/ + + + + + +Setup +------------------------------------------------------------------------------- + + +Install Dependencies +++++++++++++++++++++ + +#. Clone python-kms and change directory to the sample directory you want to use. + + .. code-block:: bash + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. + + .. _Python Development Environment Setup Guide: + https://cloud.google.com/python/setup + +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ + +Samples +------------------------------------------------------------------------------- +Create a custom gcloud build to access the Single Tenant HSM service. + +Approve a Single Tenant HSM Instance Proposal. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Creates custom gcloud build to access single tenant HSM service+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +To run this sample: + +.. code-block:: bash + + $ python3 setup.py + + usage: setup.py [-h] [--operation] + + This application creates a custom gcloud build to access the single tenant HSM service. + + positional arguments: + operation The type of setup operation you want to perform. This includes build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'. + + optional arguments: + -h, --help show this help message and exit + + + +Approves a Single Tenant HSM Instance Proposal. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +To run this sample: + +.. code-block:: bash + + $ python3 approve_proposal.py + + usage: approve_proposal.py [-h] [--proposal_resource PROPOSAL_RESOURCE] + + This application fetches and approves the single tenant HSM instance proposal + specified in the "proposal_resource" field. + + For more information, visit https://cloud.google.com/kms/docs/attest-key. + + positional arguments: + --proposal_resource PROPOSAL_RESOURCE + The full name of the single tenant HSM instance proposal that needs to be approved. + + + + optional arguments: + -h, --help show this help message and exit + + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/approve_proposal.py b/kms/singletenanthsm/singletenanthsm/approve_proposal.py new file mode 100644 index 00000000000..953b4556e6d --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/approve_proposal.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import json +import os +import sys +from typing import List + +import gcloud_commands +import ykman_utils + + +def parse_challenges_into_files(sthi_output: str) -> List[bytes]: + """Parses the STHI output and writes the challenges and public keys to files. + + Args: + sthi_output: The output of the STHI command. + + Returns: + A list of the unsigned challenges. + """ + print("parsing challenges into files") + proposal_json = json.loads(sthi_output, strict=False) + challenges = proposal_json["quorumParameters"]["challenges"] + + directory_path = "challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + + challenge_count = 0 + unsigned_challenges = [] + for challenge in challenges: + challenge_count += 1 + print(challenge["challenge"] + "\n") + print(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f = open("challenges/challenge{0}.txt".format(challenge_count), "wb") + binary_challenge = ykman_utils.urlsafe_base64_to_binary( + challenge["challenge"] + ) + f.write(binary_challenge) + f.close() + + f = open("challenges/public_key{0}.pem".format(challenge_count), "w") + f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f.close() + unsigned_challenges.append(ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"])) + + return unsigned_challenges + + +def parse_args(args): + parser = argparse.ArgumentParser() + parser.add_argument("--proposal_resource", type=str, required=True) + return parser.parse_args(args) + + +def signed_challenges_to_files( + challenge_replies: list[ykman_utils.ChallengeReply], +) -> None: + """Writes the signed challenges and public keys to files. + + Args: + challenge_replies: A list of ChallengeReply objects. + + Returns: + A list of tuples containing the signed challenge file path and the public + key file path. + """ + signed_challenge_files = [] + challenge_count = 0 + for challenge_reply in challenge_replies: + challenge_count += 1 + print("challenge_count", challenge_count) + directory_path = "signed_challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + with open( + f"signed_challenges/public_key_{challenge_count}.pem", "w" + ) as public_key_file: + + # Write public key to file + public_key_file.write(challenge_reply.public_key_pem) + with open( + f"signed_challenges/signed_challenge{challenge_count}.bin", "wb" + ) as binary_file: + + # Write signed challenge to file + binary_file.write(challenge_reply.signed_challenge) + signed_challenge_files.append(( + f"signed_challenges/signed_challenge{challenge_count}.bin", + f"signed_challenges/public_key_{challenge_count}.pem", + )) + return signed_challenge_files + + +def approve_proposal(): + """Approves a proposal by fetching challenges, signing them, and sending them back to gcloud.""" + parser = parse_args(sys.argv[1:]) + + # Fetch challenges + process = gcloud_commands.fetch_challenges(parser.proposal_resource) + + # Parse challenges into files + unsigned_challenges = parse_challenges_into_files(process.stdout) + print(unsigned_challenges) + + # Sign challenges + signed_challenges = ykman_utils.sign_challenges(unsigned_challenges) + + # Parse signed challenges into files + signed_challenged_files = signed_challenges_to_files(signed_challenges) + + # Return signed challenges to gcloud + gcloud_commands.send_signed_challenges( + signed_challenged_files, parser.proposal_resource + ) + + +if __name__ == "__main__": + approve_proposal() diff --git a/kms/singletenanthsm/singletenanthsm/approve_proposal_test.py b/kms/singletenanthsm/singletenanthsm/approve_proposal_test.py new file mode 100644 index 00000000000..9a53298fd38 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/approve_proposal_test.py @@ -0,0 +1,241 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from dataclasses import dataclass + + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PublicFormat, +) + +import argparse +import json +import os +import subprocess +from unittest import mock +from unittest.mock import Mock +from unittest.mock import patch +# from approve_proposal import parse_args + +import approve_proposal +import gcloud_commands +import pytest +import ykman_fake +import ykman_utils + + +sample_sthi_output = """ +{ + "quorumParameters": { + "challenges": [ + { + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" + } + ], + "requiredApproverCount": 3 + } +} + +""" + +sample_nonces = [ + "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", +] + +@dataclass +class QuorumParameters: + challenges: list[ykman_utils.Challenge] + + # def __init__(self, challenges: list[ykman_utils.Challenge]): + # self.challenges = challenges + + # def to_dict(self): + # return {"challenges": self.challenges} + + + +sample_assigned_challenges = "" + + +@pytest.fixture() +def setup(): + parser = approve_proposal.parse_args(["proposal_resource", "my_proposal"]) + + +test_resource = "projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal" + +mock_completed_process = subprocess.CompletedProcess + +def public_key_to_pem(public_key): + public_key_pem = public_key.public_bytes( + encoding=Encoding.PEM, + format=PublicFormat.SubjectPublicKeyInfo + ).decode('utf-8') + print("PUBLIC KEY--------------") + print(public_key_pem) + return public_key_pem + +def create_json(public_key_pem_1, public_key_pem_2, public_key_pem_3): + + my_json_string = json.dumps({ "quorumParameters": { + "challenges": [ + { + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": public_key_pem_1 + }, + { + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": public_key_pem_2 + }, + { + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": public_key_pem_3 + } + ], + "requiredApproverCount": 3 + }}) + + return my_json_string + + +def create_fake_fetch_response(num_keys=3): + """ + Generates a fake fetch response with a specified number of RSA key pairs. + + Args: + num_keys: The number of RSA key pairs to generate. + + Returns: + A tuple containing: + - A JSON object with the public keys. + - A dictionary mapping public key PEMs to private keys. + """ + pub_to_priv_key = {} + public_key_pems = [] + + for _ in range(num_keys): + private_key, public_key = ykman_fake.generate_rsa_keys() + public_key_pem = public_key_to_pem(public_key) + pub_to_priv_key[public_key_pem] = private_key + public_key_pems.append(public_key_pem) + + challenge_json = create_json(*public_key_pems) # Use * to unpack the list + return challenge_json, pub_to_priv_key + + + +mock_signed_challenges = [] + +def sign_challenges_with_capture(challenges:list[ykman_utils.Challenge], pub_to_priv_key): + signed_challenges = [] + for challenge in challenges: + private_key = pub_to_priv_key[challenge.public_key_pem] + signed_challenge = ykman_fake.sign_data(private_key, challenge.challenge) + signed_challenges.append( + ykman_utils.ChallengeReply( + challenge.challenge, + signed_challenge, + challenge.public_key_pem + ) + ) + mock_signed_challenges.extend(signed_challenges) + return signed_challenges + +def verify_with_fake(pub_to_priv_key, signed_challenges): + for signed_challenge in signed_challenges: + priv_key = pub_to_priv_key[signed_challenge.public_key_pem] + assert True == ykman_fake.verify_signature(priv_key.public_key(), signed_challenge.unsigned_challenge, signed_challenge.signed_challenge) + print("Signed verified successfully") + +def test_get_challenges_mocked(mocker, monkeypatch): + + # Verify signed challenges + monkeypatch.setattr( + "gcloud_commands.send_signed_challenges", + lambda signed_challenges, proposal_resource: verify_with_fake(pub_to_priv_key, mock_signed_challenges) + ) + + # monkeypatch sign challenges + monkeypatch.setattr( + "ykman_utils.sign_challenges", + lambda challenges: sign_challenges_with_capture(challenges, pub_to_priv_key) + ) + + # mock the challenge string returned by service + challenge_json, pub_to_priv_key = create_fake_fetch_response() + mock_response = mocker.MagicMock() + mock_response.stdout = challenge_json + mocker.patch("subprocess.run", return_value=mock_response) + + # monkeypatch parse args + mock_args = argparse.Namespace(proposal_resource="test_resource") + monkeypatch.setattr( + "approve_proposal.parse_args", + lambda args: mock_args + ) + + approve_proposal.approve_proposal() + + # assert challenge files created + challenge_files = ['challenges/challenge1.txt', 'challenges/challenge2.txt', 'challenges/challenge3.txt'] + for file_path in challenge_files: + assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." + + # assert signed challenge files created + signed_challenge_files = ['signed_challenges/signed_challenge1.txt', 'signed_challenges/signed_challenge2.txt', 'signed_challenges/signed_challenge3.txt'] + for file_path in signed_challenge_files: + assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." + + +if __name__ == "__main__": + # Parse challenges into files + unsigned_challenges = approve_proposal.parse_challenges_into_files( + sample_sthi_output + ) + created_signed_files = ['signed_challenges/signed_challenge1.txt', 'signed_challenges/signed_challenge2.txt', 'signed_challenges/signed_challenge3.txt'] + for file_path in created_signed_files: + assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." + + # Parse files into challenge list + challenges = ykman_utils.populate_challenges_from_files() + for challenge in challenges: + print(challenge.challenge) + print(challenge.public_key_pem) + unsigned_challenges.append(challenge.challenge) + signed_challenged_files = [] + signed_challenges = ykman_utils.sign_challenges( + challenges, signed_challenged_files + ) + for signed_challenge in signed_challenges: + print(signed_challenge.signed_challenge) + print(signed_challenge.public_key_pem) + print("--challenge_replies=" + str(signed_challenged_files)) + ykman_utils.verify_challenge_signatures(signed_challenges) diff --git a/kms/singletenanthsm/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/singletenanthsm/challenges/challenge1.txt new file mode 100644 index 00000000000..c891b41bd6d --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/challenges/challenge1.txt @@ -0,0 +1 @@ +#?xpAky7.m*\hkG.jo(T!5 \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/singletenanthsm/challenges/challenge2.txt new file mode 100644 index 00000000000..93616f55ed6 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/challenges/challenge2.txt @@ -0,0 +1 @@ +:/~j;QQܿA3($̳N+;H'^7N;QX \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/singletenanthsm/challenges/challenge3.txt new file mode 100644 index 00000000000..3f9fc362302 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/challenges/challenge3.txt @@ -0,0 +1 @@ +4>y)v2Ugn&3R"0g/熎X \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/singletenanthsm/challenges/public_key1.pem new file mode 100644 index 00000000000..2cc261dfd64 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/challenges/public_key1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQPZpYJxzBkVzgTHei8t +qhbusqs2yFbLVSedd86Oxj6yI26YA3XJqPdTPooa0YBl0AVRR0qg1XYyOU/zH5tk +2jUrGtdvOY78Om93Yj6bjB0Enn3RuCNS6W1DLxVy8g0IUSiGKT8wH30Jvs6Tr69b +yQUh9Cl0e9qIb7ljhtwh9SHkE+87Bgo2Z1qTWByIOzzZOBqW7CSIImNFvvPDf1M2 +/tnR1szdH2GP2urqYc/u+cgjPoOkPvbnSyxxxBYGdx1Fijr43i8IO5uXv4O+GcEb +fb/o1/fJ9MqQODXONWB1naBrzFwRe/wU9rlfvpPOGc9xkUtgSt3jN8J4rlMQfxMl +TQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/singletenanthsm/challenges/public_key2.pem new file mode 100644 index 00000000000..9566d0d8b07 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/challenges/public_key2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ipgJ7IlOwDcxKFgz+ic +00DwTf63awWohOZDoDWVSxxTFBciB73ANnpv8ANX88VM1sMSxu+Xd5vsr93Ur04i +i1asQ2p8KFGQKsjggA2nnmfN0IL6iSquYM//D7E/YoWtdaTJ0/Jl+BqBobIHG65H +Kk0CikBTojjWmA9B1Cu9j3g5fCjBGdPGYQtaDVnukrRbLw30T8C5lG1MDxk/hLSB +lw7Bi1hTOsf82k0Q0csmihVArWOrKYOcoiWoKGLCsw2ZBMK0UiYHFXcNVXc045yA +A97+87bSvuunVk86L5572h32/GJyBRjKxDAoWNuJCBm0kzg/abo73hblDxxITO1+ +aQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/singletenanthsm/challenges/public_key3.pem new file mode 100644 index 00000000000..6e235f16207 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/challenges/public_key3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuf0l0LU1s9PF6jMylq+6 +bFSZATh1tRreNBPXcVPt/NTD/t90mzjOYitGvSGVNedyp+RiDC9HJjKD8tMtU550 +VMAocnToxONeDdb3o3LNEt5XsJjgpIIVo2d+nK4/Gk52RyKp6Cui7gtQpYhNxMF4 +uazwLWibD7IRUU3Bsyv15g4YNqNmrtZYENVmxZrV37qjwBBsQ8XbEDrs7nFwBJU1 +QNbqOFC89pGxNzVNsH42M64+3eoozrHIqjZzvFxwwUOq55KlrQDUjVMVUcoBnQAM +oZ0zOxA6ETMQS3y5ZMfVH50sbg4xlG2sT9nz9nRlybBKKDzAacmsUjssHDnB8ZIs +BQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/singletenanthsm/gcloud_commands.py new file mode 100644 index 00000000000..7b89c384fbd --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/gcloud_commands.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess + +command_build_custom_gcloud = """ + pushd /tmp + curl -o installer.sh https://sdk.cloud.google.com + chmod +x installer.sh + ./installer.sh --disable-prompts --install-dir ~/sthi + rm installer.sh + popd + alias sthigcloud=~/sthi/google-cloud-sdk/bin/gcloud + sthigcloud auth login + """ + + +command_add_components = """ + ~/sthi/google-cloud-sdk/bin/gcloud components repositories add https://storage.googleapis.com/single-tenant-hsm-private/components-2.json + ~/sthi/google-cloud-sdk/bin/gcloud components update + """ + + +def build_custom_gcloud(): + """Builds a custom gcloud binary.""" + try: + print("\nBuilding custom gcloud build") + process = subprocess.run( + command_build_custom_gcloud, + check=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud build executed successfully.") + print(process.stdout) + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + try: + print("\nAdding gcloud components") + process = subprocess.run( + command_add_components, + check=False, + capture_output=False, + text=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud components add executed successfully.") + print(process.stdout) + return process + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + print(f"Error executing gcloud components update: {e}") + + +command_gcloud_list_proposal = ( + "~/sthi/google-cloud-sdk/bin/gcloud kms single-tenant-hsm list " + "--location=projects/hawksbill-playground/locations/global" +) + +command_gcloud_describe_proposal = """ + ~/sthi/google-cloud-sdk/bin/gcloud \ + kms single-tenant-hsm proposal describe """ + + +def fetch_challenges(sthi_proposal_resource: str): + """Fetches challenges from the server.""" + + try: + print("\nfetching challenges") + process = subprocess.run( + command_gcloud_describe_proposal + + sthi_proposal_resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + # stderr=subprocess.STDOUT + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud command executed successfully.") + print(process.stdout) + return process + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + +command_gcloud_approve_proposal = [ + "~/sthi/google-cloud-sdk/bin/gcloud", + "kms", + "single-tenant-hsm", + "proposal", + "approve", +] + + +def send_signed_challenges( + signed_challenged_files: list[str], proposal_resource: str +): + """Sends signed challenges to the server.""" + if signed_challenged_files is None or not signed_challenged_files: + raise ValueError("signed_challenged_files is empty") + print("Sending signed challenges") + signed_challenge_str = ( + '--challenge_replies="' + str(signed_challenged_files) + '"' + ) + command_str = " ".join( + command_gcloud_approve_proposal + + [proposal_resource] + + [signed_challenge_str] + ) + print(command_str) + + try: + + process = subprocess.run( + command_str, + capture_output=True, + check=True, + text=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud command executed successfully.") + return process + + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) diff --git a/kms/singletenanthsm/singletenanthsm/gcloud_commands_test.py b/kms/singletenanthsm/singletenanthsm/gcloud_commands_test.py new file mode 100644 index 00000000000..9823aebd114 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/gcloud_commands_test.py @@ -0,0 +1,321 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess +from unittest import mock +import gcloud_commands +import pytest + +test_proposal_resource = """projects/test_project/locations/\ +us-central1/singleTenantHsmInstances/my_sthi/proposals/my_proposal + """ +sample_fetch_challenge_output = """ +{ + "quorumParameters": { + "challenges": [ + { + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" + }, + { + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" + } + ], + "requiredApproverCount": 3 + } +} + +""" + + +# Test case 1: Successful build and components add +def test_build_custom_gcloud_success(mock_subprocess_run): + # Setup: Mock successful gcloud execution + mock_subprocess_run.side_effect = [ + subprocess.CompletedProcess( + args=gcloud_commands.command_build_custom_gcloud, + returncode=0, + stdout="gcloud build successful!", + stderr="", + ), + subprocess.CompletedProcess( + args=gcloud_commands.command_add_components, + returncode=0, + stdout="gcloud components add successful.", + stderr="", + ), + ] + + # Action: Call the function + result = gcloud_commands.build_custom_gcloud() + + # Assert: Verify the return value and that subprocess.run was called correctly + assert result.returncode == 0 + assert result.stdout == "gcloud components add successful." + assert mock_subprocess_run.call_count == 2 + mock_subprocess_run.assert_has_calls([ + mock.call( + gcloud_commands.command_build_custom_gcloud, check=True, shell=True + ), + mock.call( + gcloud_commands.command_add_components, + check=False, + shell=True, + capture_output=False, + text=True, + ), + ]) + + +# Test case 2: gcloud build fails +def test_build_custom_gcloud_build_error(mock_subprocess_run): + # Setup: Mock gcloud build command with a non-zero return code + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, + cmd=gcloud_commands.command_build_custom_gcloud, + output="", + stderr="Error: Build failed", + ) + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.build_custom_gcloud() + + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Build failed" + assert exc_info.value.cmd == gcloud_commands.command_build_custom_gcloud + assert mock_subprocess_run.call_count == 1 + + +# Test case 3: gcloud components add fails +def test_build_custom_gcloud_components_error(mock_subprocess_run): + # Setup: Mock gcloud build success and components add with error + mock_subprocess_run.side_effect = [ + subprocess.CompletedProcess( + args=gcloud_commands.command_build_custom_gcloud, + returncode=0, + stdout="gcloud build successful!", + stderr="", + ), + subprocess.CalledProcessError( + returncode=1, + cmd=gcloud_commands.command_add_components, + output="", + stderr="Error: Components add failed", + ), + ] + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.build_custom_gcloud() + + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Components add failed" + assert exc_info.value.cmd == gcloud_commands.command_add_components + assert mock_subprocess_run.call_count == 2 + + +@pytest.fixture +def mock_subprocess_run(monkeypatch): + mock_run = mock.create_autospec(subprocess.run) + monkeypatch.setattr(subprocess, "run", mock_run) + return mock_run + + +def test_fetch_challenges_success(mock_subprocess_run): + # Setup: Configure the mock to simulate a successful gcloud command + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=0, + stdout=sample_fetch_challenge_output, + stderr="", + ) + mock_subprocess_run.return_value = mock_process_result + + # Action: Call the function + resource = test_proposal_resource + result = gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the results + mock_subprocess_run.assert_called_once_with( + gcloud_commands.command_gcloud_describe_proposal + + resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + ) + assert result == mock_process_result + assert result.returncode == 0 + assert result.stdout == sample_fetch_challenge_output + assert not result.stderr + + +def test_fetch_challenges_error(mock_subprocess_run): + # Setup: Configure the mock to simulate a failed gcloud command + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, cmd="", output="", stderr="Error: Invalid resource" + ) + + # Action & Assert: Call the function and check for the expected exception + resource = "invalid-resource" + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.fetch_challenges(resource) + + # Verify the exception details + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Invalid resource" + + +def test_fetch_challenges_command_construction(mock_subprocess_run): + # Setup: + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=0, + stdout="{}", + stderr="", + ) + mock_subprocess_run.return_value = mock_process_result + resource = test_proposal_resource + + # Action: Call the function + gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the command + mock_subprocess_run.assert_called_once_with( + gcloud_commands.command_gcloud_describe_proposal + + resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + ) + + +def test_fetch_challenges_output_capture(mock_subprocess_run): + # Setup: + expected_stdout = "Expected Output" + expected_stderr = "Expected Error" + expected_returncode = 0 + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=expected_returncode, + stdout=expected_stdout, + stderr=expected_stderr, + ) + mock_subprocess_run.return_value = mock_process_result + resource = test_proposal_resource + # Action: Call the function + result = gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the captured output + assert result.stdout == expected_stdout + assert result.stderr == expected_stderr + assert result.returncode == expected_returncode + + +# Test case 1: Successful gcloud command +def test_send_signed_challenges_success(mock_subprocess_run): + # Setup: Mock successful gcloud execution + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=[], # Not checked in this test, but good practice to include + returncode=0, + stdout="gcloud command successful!", + stderr="", + ) + + # Action: Call the function + result = gcloud_commands.send_signed_challenges(signed_files, proposal) + + # Assert: Verify the return value and that subprocess.run was called correctly + assert result.returncode == 0 + assert result.stdout == "gcloud command successful!" + expected_command = " ".join( + gcloud_commands.command_gcloud_approve_proposal + + [proposal] + + [ + "--challenge_replies=\"[('signed_challenge.bin'," + " 'public_key_1.pem')]\"" + ] + ) + mock_subprocess_run.assert_called_once_with( + expected_command, + capture_output=True, + check=True, + text=True, + shell=True, + ) + + +# Test case 2: gcloud command returns an error code +def test_send_signed_challenges_gcloud_error(mock_subprocess_run): + # Setup: Mock gcloud command with a non-zero return code and stderr + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=[], + returncode=1, + stdout="", + stderr="Error: Invalid proposal resource", + ) + + # Action: Call the function + result = gcloud_commands.send_signed_challenges(signed_files, proposal) + + # Assert: Verify the return value + assert result.returncode == 1 + assert result.stderr == "Error: Invalid proposal resource" + + +# Test case 3: subprocess.run raises a CalledProcessError +def test_send_signed_challenges_called_process_error( + mock_subprocess_run +): + # Setup: Mock subprocess.run to raise a CalledProcessError + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=2, + cmd="test_command", + output="", + stderr="Called process error", + ) + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.send_signed_challenges(signed_files, proposal) + + assert exc_info.value.returncode == 2 + assert exc_info.value.stderr == "Called process error" + assert exc_info.value.cmd == "test_command" + + +# Test case 4: Signed challenge file list is empty. +def test_send_signed_challenges_empty_list(mock_subprocess_run): + + # Action: Call the function + with pytest.raises(ValueError, match="signed_challenged_files is empty"): + gcloud_commands.send_signed_challenges([], test_proposal_resource) diff --git a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_25167010.pem b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_25167010.pem new file mode 100644 index 00000000000..7b0fe69d677 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_25167010.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArp0MggO5sHSIyfzgVfnR +xzG3S1D1XKVRFHHa5PHG+g64OZJ/cYNQux+OXKBuXs0rgKI+s+PVnYF9vJg3gatF +Xos+lN0Xv+wcuFTqMD0acPUNhX2QCq53Lb4u1R/EAZ0lzFVSu17N6Kde90R8EPgE +a8pc7pusgzT7wE7TAlXbRLYBp11n+dcgXGtYHHBECeBLiu7vvx8bUO6ZPLJClnwK +dAkIy9HCtlMUdukmIUrL5oMLUVMCdPn/8fELUbsFBK5ZE1nrbx6eKJKKXRl1BZ+s +11U54/xftPvqw9MgKKU4aWoY5Yz+xsDM27h1zvPtADXzTUsNY3oLpo/pYJqmwdhz +6wIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787103.pem b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787103.pem new file mode 100644 index 00000000000..bd82c8f28b8 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787103.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Rx08jnCJMrypWQgp8a8 +zBj0SAjIqDlOucHmaax6gI0xDMxq1LSfXUHsuD5qtxttxn+WszKUar29/VRHY9Af +98HjvqIRTJSyvCwDr0w/9LEKXL4JubzP9+4PdeK5AsdhgaKazYRfjpUVS7cispoq +N5rl+kLEJS5SvPTbzXh71YLgFWtUXJJLz5SgbCReVI4eegfVTZ1JPrAC2x7TVu4P +qNXLmuX9ijqEhxvr4Cv7A4n7MJTpaXAeu1yYByslI3kF16rND2digyd7TSZ/LwQe +eTd5ElTXF6b41zhRm+FyCwWjcWjiJdXnUSZai2Td2n8gddsDmz5YwBbRKD9lBpRy +gQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787105.pem b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787105.pem new file mode 100644 index 00000000000..3466ad79de9 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787105.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3llqGRlrEdw1AFPAi5yN +SLunBSGtodc6F4RBXkTjE6awpRsqJUw0Nrt6iy6zK6yWBBE/ecwcZ7B5DUGFalqZ +NphMUhKaEVlbxO3R5+z+6OcJR74VO/DIzGOfpGw9FjK1FhYBl7Ljhy8cCQbky3hJ +I8GPsrhTkM7+Ciphp5Hxt4glhnd7LY5NiPy4DKdzhw3mvPZzm3cRhGDTbiMPHHxB +h6fjp8uzUTnAmF6EWNwPRKLvpg3q3QnOAmwmtOcke5Yl6TPzAw1r1M6KhVxkFijr +f22HHrM0ykHOTWZZTU+IgPX4T/dqEQ13BrqJPg8tai6pMClDdl5zStIK004wfNct +FwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/requirements.txt b/kms/singletenanthsm/singletenanthsm/requirements.txt new file mode 100644 index 00000000000..e29d4ff4c8e --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/requirements.txt @@ -0,0 +1,21 @@ +backports.tarfile==1.2.0 +cffi==1.17.1 +click==8.1.8 +cryptography==44.0.0 +fido2==1.2.0 +importlib_metadata==8.6.1 +iniconfig==2.0.0 +jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.1.0 +jeepney==0.8.0 +keyring==25.6.0 +more-itertools==10.6.0 +packaging==24.2 +pluggy==1.5.0 +pycparser==2.22 +pyscard==2.2.1 +pytest==8.3.4 +SecretStorage==3.3.3 +yubikey-manager==5.5.1 +zipp==3.21.0 diff --git a/kms/singletenanthsm/singletenanthsm/setup.py b/kms/singletenanthsm/singletenanthsm/setup.py new file mode 100644 index 00000000000..1d93e1d313a --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/setup.py @@ -0,0 +1,60 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import ykman_utils +import gcloud_commands + +def validate_operation(operation: str): + if operation == "build_custom_gcloud": + try: + gcloud_commands.build_custom_gcloud() + except Exception as e: + raise Exception(f"Generating custom gcloud build failed {e}") + elif operation == "generate_rsa_keys": + try: + ykman_utils.generate_private_key() + except Exception as e: + raise Exception(f"Generating private keys failed {e}") + elif operation == "generate_gcloud_and_keys": + generate_private_keys_build_gcloud() + else: + raise Exception("Operation type not valid. Operation flag value must be build_custom_gcloud," + " generate_rsa_keys, or generate_gcloud_and_keys") + + + + +def generate_private_keys_build_gcloud(): + """Generates an RSA key on slot 82 of every yubikey + connected to the local machine and builds the custom gcloud cli. + """ + try: + ykman_utils.generate_private_key() + except Exception as e: + raise Exception(f"Generating private keys failed {e}") + try: + gcloud_commands.build_custom_gcloud() + except Exception as e: + raise Exception(f"Generating custom gcloud build failed {e}") + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument('--operation',type=str, + choices=['build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'], + required=True + ) + args = parser.parse_args() + validate_operation(args.operation) diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_1.pem new file mode 100644 index 00000000000..2cc261dfd64 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQPZpYJxzBkVzgTHei8t +qhbusqs2yFbLVSedd86Oxj6yI26YA3XJqPdTPooa0YBl0AVRR0qg1XYyOU/zH5tk +2jUrGtdvOY78Om93Yj6bjB0Enn3RuCNS6W1DLxVy8g0IUSiGKT8wH30Jvs6Tr69b +yQUh9Cl0e9qIb7ljhtwh9SHkE+87Bgo2Z1qTWByIOzzZOBqW7CSIImNFvvPDf1M2 +/tnR1szdH2GP2urqYc/u+cgjPoOkPvbnSyxxxBYGdx1Fijr43i8IO5uXv4O+GcEb +fb/o1/fJ9MqQODXONWB1naBrzFwRe/wU9rlfvpPOGc9xkUtgSt3jN8J4rlMQfxMl +TQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_2.pem new file mode 100644 index 00000000000..9566d0d8b07 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ipgJ7IlOwDcxKFgz+ic +00DwTf63awWohOZDoDWVSxxTFBciB73ANnpv8ANX88VM1sMSxu+Xd5vsr93Ur04i +i1asQ2p8KFGQKsjggA2nnmfN0IL6iSquYM//D7E/YoWtdaTJ0/Jl+BqBobIHG65H +Kk0CikBTojjWmA9B1Cu9j3g5fCjBGdPGYQtaDVnukrRbLw30T8C5lG1MDxk/hLSB +lw7Bi1hTOsf82k0Q0csmihVArWOrKYOcoiWoKGLCsw2ZBMK0UiYHFXcNVXc045yA +A97+87bSvuunVk86L5572h32/GJyBRjKxDAoWNuJCBm0kzg/abo73hblDxxITO1+ +aQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_3.pem new file mode 100644 index 00000000000..6e235f16207 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuf0l0LU1s9PF6jMylq+6 +bFSZATh1tRreNBPXcVPt/NTD/t90mzjOYitGvSGVNedyp+RiDC9HJjKD8tMtU550 +VMAocnToxONeDdb3o3LNEt5XsJjgpIIVo2d+nK4/Gk52RyKp6Cui7gtQpYhNxMF4 +uazwLWibD7IRUU3Bsyv15g4YNqNmrtZYENVmxZrV37qjwBBsQ8XbEDrs7nFwBJU1 +QNbqOFC89pGxNzVNsH42M64+3eoozrHIqjZzvFxwwUOq55KlrQDUjVMVUcoBnQAM +oZ0zOxA6ETMQS3y5ZMfVH50sbg4xlG2sT9nz9nRlybBKKDzAacmsUjssHDnB8ZIs +BQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.bin new file mode 100644 index 00000000000..24dd7dc3600 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.bin @@ -0,0 +1 @@ +^²m p34Ay绌VRGM_] ]6o-"ea5˞ı7G:`*2uTkbaN?x*p#uƻh+#`MVh)0 f+*(-*c39sYE?:k>XplK;8At1W}~99WR~z!_[::k5zk? Έ \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.txt b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.txt new file mode 100644 index 00000000000..e2cc944c512 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.txt @@ -0,0 +1 @@ +HwY(5E *dzb-qw;cB[e'.9v'$u+L'bo6=9*K$P+˜d;'-rf7eQ?~&Ss6Gf=3IRv#V)DquZ1f@Bkz*MPJb|Lf%$t0{FeN^)LRZH zMZ@bg=FP(ipgEj?N*Tpu_V5N|4GUfKmB*X71eSC0*WdW?VfRh1oJ*P{sP2!%ON%y= zja%iPH87bHnmumZTgEju-F!9(l3Cxzs5@Ap@}Ot`W_wWJxJPl*R{1!wFG$~Owl?I_ GA?1KNPJ_n) literal 0 HcmV?d00001 diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.txt b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.txt new file mode 100644 index 00000000000..51f099c09fd --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.txt @@ -0,0 +1,3 @@ +oSJHzv +zd+5N*?Z3=iّ:<-Wowwrsd+%vЄ˩'oqb~q)@*~B11_Zum~{S7?.LإFY(dȴcNMyl=%CZG%5 +ҥԭAdW8UL}7&B{E4xNTx4K=ǯeԡ{g/-fIHBiNOp ?n>\ƼlS \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.bin b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.bin new file mode 100644 index 00000000000..a5536c11806 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.bin @@ -0,0 +1 @@ +nO?B) z3cDv!qVY _xkL䬯YLR6aqZ[r+һԓ|U&w/FB4@Zy32HC[.[Ic501XMCZYR1V:&@n {[V9c0H㜅F.o*Eh swU`FrE>@ڌoEBٞѴ'`.Szϥ,ƕ V~tUw \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.txt b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.txt new file mode 100644 index 0000000000000000000000000000000000000000..f07c9cef275f2ddd1d33cf6be1cb5e9e4a8a1a27 GIT binary patch literal 256 zcmV+b0ssDJIB1;+Q0il23-Pg+HcrX`4UC7u&-H>d60%EOID7>f5UW5aTG~TtMepV8 z10F#Iob>sZ4cxEaoZ?!QM=~OumBxrSdZ!e{)^aVA^k-fC<`GI*vGfcUnI6b}v0i@G zpX-A}{@S8(9ffCkwd864j2^v|$2d<#gfUAM<^A;2!P1t95@K(TDL(*GoPyfC3@@{RKO~JlsbjS zC9+HV)V3EH5~d+w3t5*=6F-k)g-aVomIp^odx?oXp1oU)p?{MRJg5^`rJ%4mR4JfH G)g9aJaC|oa literal 0 HcmV?d00001 diff --git a/kms/singletenanthsm/singletenanthsm/ykman_fake.py b/kms/singletenanthsm/singletenanthsm/ykman_fake.py new file mode 100644 index 00000000000..9bc29efe523 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/ykman_fake.py @@ -0,0 +1,59 @@ +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import padding + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PrivateFormat, + PublicFormat, + load_pem_private_key, + load_pem_public_key, + BestAvailableEncryption +) + + +def generate_rsa_keys(key_size=2048): + """Generates a private and public RSA key pair with the specified key size.""" + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=key_size, + ) + public_key = private_key.public_key() + return private_key, public_key + + +def sign_data(private_key, data, hash_algorithm=hashes.SHA256()): + """Signs the provided data using the private key with PKCS#1.5 padding.""" + if not isinstance(data, bytes): + raise TypeError("Data must be of type bytes") + signature = private_key.sign(data, padding.PKCS1v15(), hash_algorithm) + return signature + + +def verify_signature(public_key, data, signature, hash_algorithm=hashes.SHA256()): + """Verifies the signature of the data using the public key.""" + if not isinstance(data, bytes): + raise TypeError("Data must be of type bytes") + if not isinstance(signature, bytes): + raise TypeError("Signature must be of type bytes") + try: + public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm) + return True # Signature is valid + except InvalidSignature: + return False # Signature is invalid + + + +if __name__ == "__main__": + private_key, public_key = generate_rsa_keys() + + # Data to sign (as bytes) + data_to_sign = b"This is the data to be signed." + signature = sign_data(private_key, data_to_sign) + print(f"Signature generated: {signature.hex()}") + is_valid = verify_signature(public_key, data_to_sign, signature) + if is_valid: + print("Signature is VALID.") + else: + print("Signature is INVALID.") \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/singletenanthsm/ykman_utils.py new file mode 100644 index 00000000000..66c545c9ba9 --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/ykman_utils.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass + + +import base64 +import pathlib +import cryptography.exceptions +import glob +import re +import os +import ykman + + + +from cryptography.hazmat.primitives import _serialization +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.hazmat.primitives.serialization import load_pem_public_key + + +from ykman import piv +from ykman.device import list_all_devices +from yubikit.piv import hashes +from yubikit.piv import PIN_POLICY, TOUCH_POLICY, hashes +from yubikit.piv import SmartCardConnection + +DEFAULT_MANAGEMENT_KEY = "010203040506070801020304050607080102030405060708" +DEFAULT_PIN = "123456" + + +def generate_private_key( + key_type=piv.KEY_TYPE.RSA2048, + management_key=DEFAULT_MANAGEMENT_KEY, + pin=DEFAULT_PIN, +): + """Generates a private key on the yubikey""" + + devices = list_all_devices() + if not devices: + raise Exception("no yubikeys found") + print(f"{len(devices)} yubikeys detected") + for yubikey, device_info in devices: + with yubikey.open_connection(SmartCardConnection) as connection: + piv_session = piv.PivSession(connection) + piv_session.authenticate( + piv.MANAGEMENT_KEY_TYPE.TDES, + bytes.fromhex(management_key), + ) + piv_session.verify_pin(pin) + + public_key = piv_session.generate_key( + piv.SLOT.RETIRED1, + key_type=key_type, + pin_policy=PIN_POLICY.DEFAULT, + touch_policy=TOUCH_POLICY.ALWAYS, + ) + if not public_key: + raise Exception("failed to generate public key") + with open( + f"generated_public_keys/public_key_{device_info.serial}.pem", "wb" + ) as binary_file: + + # Write bytes to file + binary_file.write( + public_key.public_bytes( + encoding=_serialization.Encoding.PEM, + format=_serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ) + print( + f"Private key pair generated on device {device_info.serial} on key" + f" slot: {piv.SLOT.RETIRED1}" + ) + +@dataclass +class Challenge: + challenge: bytes + public_key_pem: str + + def to_dict(self): + return { + "challenge": base64.b64encode(self.challenge).decode('utf-8'), + "public_key_pem": self.public_key_pem, + } + + @staticmethod + def from_dict(data): + if not isinstance(data, dict): + return None + return Challenge( + challenge=base64.b64decode(data['challenge']), + public_key_pem=data['public_key_pem'] + ) + + + +class ChallengeReply: + + def __init__(self, unsigned_challenge, signed_challenge, public_key_pem): + self.unsigned_challenge = unsigned_challenge + self.signed_challenge = signed_challenge + self.public_key_pem = public_key_pem + +def populate_challenges_from_files() -> list[Challenge]: + public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("challenges/public_key*.pem")] + print(public_key_files) + challenge_files = [challenge_file for challenge_file in pathlib.Path.cwd().glob("challenges/challenge*.txt")] + print(challenge_files) + + challenges = [] + + for public_key_file in public_key_files: + challenge_id = re.findall(r"\d+", str(public_key_file)) + for challenge_file in challenge_files: + if challenge_id == re.findall(r"\d+",str(challenge_file)): + print(public_key_file) + file = open(public_key_file, "r") + public_key_pem = file.read() + file.close() + file = open(challenge_file, "rb") + challenge = file.read() + file.close() + challenges.append(Challenge(challenge, public_key_pem )) + return challenges + + +def sign_challenges(challenges: list[Challenge]) -> list[ChallengeReply]: + """Signs a proposal's challenges using a Yubikey.""" + if not challenges: + raise Exception("Challenge list empty: No challenges to sign.") + signed_challenges = [] + devices = list_all_devices() + if not devices: + raise Exception("no yubikeys found") + for yubikey, _ in devices: + with yubikey.open_connection(SmartCardConnection) as connection: + # Make PivSession and fetch public key from Signature slot. + piv_session = piv.PivSession(connection) + # authenticate + piv_session.authenticate( + piv.MANAGEMENT_KEY_TYPE.TDES, + bytes.fromhex("010203040506070801020304050607080102030405060708"), + ) + piv_session.verify_pin("123456") + + # Get the public key from slot 82. + slot_metadata = piv_session.get_slot_metadata(slot=piv.SLOT.RETIRED1) + print(slot_metadata.public_key.public_bytes) + + # Check to see if any of the challenge public keys matches with the + # public key from slot 82. + for challenge in challenges: + key_public_bytes = slot_metadata.public_key.public_bytes( + encoding=_serialization.Encoding.PEM, + format=_serialization.PublicFormat.SubjectPublicKeyInfo, + ) + print(key_public_bytes.decode()) + print(challenge.public_key_pem) + if key_public_bytes == challenge.public_key_pem.encode(): + + # sign the challenge + print("Press Yubikey to sign challenge") + # print("key type: " + str(slot_metadata.key_type)) + # print(challenge.challenge) + # print(type(challenge.challenge)) + signed_challenge = piv_session.sign( + slot=piv.SLOT.RETIRED1, + key_type=slot_metadata.key_type, + message=challenge.challenge, + hash_algorithm=hashes.SHA256(), + padding=padding.PKCS1v15(), + ) + + signed_challenges.append( + ChallengeReply( + challenge.challenge, + signed_challenge, + challenge.public_key_pem + ) + ) + print("Challenge signed successfully") + if not signed_challenges: + raise Exception( + "No matching public keys between Yubikey and challenges. Make sure" + " key is generated in correct slot" + ) + return signed_challenges + +def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: + """ + Converts a URL-safe base64 encoded string to its binary equivalent. + + Args: + urlsafe_string: The URL-safe base64 encoded string. + + Returns: + The binary data as bytes, or None if an error occurs. + + Raises: + TypeError: If the input is not a string. + ValueError: If the input string is not valid URL-safe base64. + """ + try: + if not isinstance(urlsafe_string, str): + raise TypeError("Input must be a string") + # Add padding if necessary. Base64 requires padding to be a multiple of 4 + missing_padding = len(urlsafe_string) % 4 + if missing_padding: + urlsafe_string += '=' * (4 - missing_padding) + return base64.urlsafe_b64decode(urlsafe_string) + except base64.binascii.Error as e: + raise ValueError(f"Invalid URL-safe base64 string: {e}") from e + +def verify_challenge_signatures(challenge_replies: list[ChallengeReply]): + if not challenge_replies: + raise Exception("No signed challenges to verify") + for challenge_reply in challenge_replies: + public_key = load_pem_public_key( + challenge_reply.public_key_pem.encode() + ) + try: + + public_key.verify( + challenge_reply.signed_challenge, + challenge_reply.unsigned_challenge, + padding.PKCS1v15(), + hashes.SHA256(), + ) + print(f"Signature verification success") + except cryptography.exceptions.InvalidSignature as e: + raise cryptography.exceptions.InvalidSignature((f"Signature verification failed: {e}")) + + +if __name__ == "__main__": + generate_private_key() \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/singletenanthsm/ykman_utils_test.py new file mode 100644 index 00000000000..37b1cee124c --- /dev/null +++ b/kms/singletenanthsm/singletenanthsm/ykman_utils_test.py @@ -0,0 +1,98 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import cryptography.exceptions + + +import glob +import os +import pathlib +import pytest +import re + +import ykman_utils + +class Challenge: + + def __init__(self, challenge, public_key_pem): + self.challenge = challenge + self.public_key_pem = public_key_pem + +class ChallengeReply: + + def __init__(self, signed_challenge, public_key_pem): + self.signed_challenge = signed_challenge + self.public_key_pem = public_key_pem + +challenge_test_data = b"test_data" + +def generate_test_challenge_files(): + # Create challenges list from challenges directory + challenges = ykman_utils.populate_challenges_from_files() + for challenge in challenges: + print(challenge.challenge) + print(challenge.public_key_pem) + # Sign challenges + signed_challenges = ykman_utils.sign_challenges(challenges) + ykman_utils.verify_challenge_signatures(signed_challenges, b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN") + + + +# A yubikey connected to your local machine will be needed to run these tests. +# The generate_private_key() method will rewrite the key saved on slot 82(Retired1). +@pytest.fixture(autouse=True) +def key_setup(): + ykman_utils.generate_private_key() + +def challenges(): + public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("generated_public_keys/public_key*.pem")] + challenges = [] + + + for public_key_file in public_key_files: + file = open(public_key_file, "r") + public_key_pem = file.read() + challenges.append(Challenge(challenge_test_data, public_key_pem )) + + return challenges + +def test_sign_and_verify_challenges(): + signed_challenges = ykman_utils.sign_challenges(challenges()) + ykman_utils.verify_challenge_signatures(signed_challenges) + +def test_verify_mismatching_data_fail(): + with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: + signed_challenges = ykman_utils.sign_challenges(challenges()) + signed_challenges[0].signed_challenge = b"mismatched_data" + ykman_utils.verify_challenge_signatures(signed_challenges) + assert "Signature verification failed" in str(exec_info.value) + +def test_sign_empty_challenge_list_fail(): + with pytest.raises(Exception) as exec_info: + # empty_challenges = [] + signed_challenges = ykman_utils.sign_challenges([]) + assert "Challenge list empty" in str(exec_info.value) + +def test_sign_no_matching_public_keys_fail(): + modified_challenges = challenges() + for challenge in modified_challenges: + challenge.public_key_pem = "modified_public_key" + with pytest.raises(Exception) as exec_info: + signed_challenges = ykman_utils.sign_challenges(modified_challenges) + assert "No matching public keys" in str(exec_info.value) + +def test_verify_empty_challenge_replies_fail(): + with pytest.raises(Exception) as exec_info: + ykman_utils.verify_challenge_signatures([]) + assert "No signed challenges to verify" in str(exec_info) diff --git a/kms/singletenanthsm/ykman_fake.py b/kms/singletenanthsm/ykman_fake.py new file mode 100644 index 00000000000..9bc29efe523 --- /dev/null +++ b/kms/singletenanthsm/ykman_fake.py @@ -0,0 +1,59 @@ +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import padding + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PrivateFormat, + PublicFormat, + load_pem_private_key, + load_pem_public_key, + BestAvailableEncryption +) + + +def generate_rsa_keys(key_size=2048): + """Generates a private and public RSA key pair with the specified key size.""" + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=key_size, + ) + public_key = private_key.public_key() + return private_key, public_key + + +def sign_data(private_key, data, hash_algorithm=hashes.SHA256()): + """Signs the provided data using the private key with PKCS#1.5 padding.""" + if not isinstance(data, bytes): + raise TypeError("Data must be of type bytes") + signature = private_key.sign(data, padding.PKCS1v15(), hash_algorithm) + return signature + + +def verify_signature(public_key, data, signature, hash_algorithm=hashes.SHA256()): + """Verifies the signature of the data using the public key.""" + if not isinstance(data, bytes): + raise TypeError("Data must be of type bytes") + if not isinstance(signature, bytes): + raise TypeError("Signature must be of type bytes") + try: + public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm) + return True # Signature is valid + except InvalidSignature: + return False # Signature is invalid + + + +if __name__ == "__main__": + private_key, public_key = generate_rsa_keys() + + # Data to sign (as bytes) + data_to_sign = b"This is the data to be signed." + signature = sign_data(private_key, data_to_sign) + print(f"Signature generated: {signature.hex()}") + is_valid = verify_signature(public_key, data_to_sign, signature) + if is_valid: + print("Signature is VALID.") + else: + print("Signature is INVALID.") \ No newline at end of file diff --git a/kms/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/ykman_utils.py index bfaef58c9b7..fafab21fd7f 100644 --- a/kms/singletenanthsm/ykman_utils.py +++ b/kms/singletenanthsm/ykman_utils.py @@ -14,6 +14,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from dataclasses import dataclass + + +import base64 import pathlib import cryptography.exceptions import glob @@ -71,8 +75,14 @@ def generate_private_key( ) if not public_key: raise Exception("failed to generate public key") + directory_path = "generated_public_keys" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") with open( - f"generated_public_keys/public_key_{device_info.serial}_slot_{piv.SLOT.RETIRED1}.pem", "wb" + f"generated_public_keys/public_key_{device_info.serial}.pem", "wb" ) as binary_file: # Write bytes to file @@ -87,19 +97,36 @@ def generate_private_key( f" slot: {piv.SLOT.RETIRED1}" ) +@dataclass class Challenge: + challenge: bytes + public_key_pem: str + + def to_dict(self): + return { + "challenge": base64.b64encode(self.challenge).decode('utf-8'), + "public_key_pem": self.public_key_pem, + } + + @staticmethod + def from_dict(data): + if not isinstance(data, dict): + return None + return Challenge( + challenge=base64.b64decode(data['challenge']), + public_key_pem=data['public_key_pem'] + ) + - def __init__(self, challenge, public_key_pem): - self.challenge = challenge - self.public_key_pem = public_key_pem class ChallengeReply: - def __init__(self, signed_challenge, public_key_pem): + def __init__(self, unsigned_challenge, signed_challenge, public_key_pem): + self.unsigned_challenge = unsigned_challenge self.signed_challenge = signed_challenge self.public_key_pem = public_key_pem -def populate_challenges_from_files(): +def populate_challenges_from_files() -> list[Challenge]: public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("challenges/public_key*.pem")] print(public_key_files) challenge_files = [challenge_file for challenge_file in pathlib.Path.cwd().glob("challenges/challenge*.txt")] @@ -115,14 +142,14 @@ def populate_challenges_from_files(): file = open(public_key_file, "r") public_key_pem = file.read() file.close() - file = open(challenge_file, "r") + file = open(challenge_file, "rb") challenge = file.read() file.close() challenges.append(Challenge(challenge, public_key_pem )) return challenges -def sign_proposal(challenges, singed_challenge_files): +def sign_challenges(challenges: list[Challenge]) -> list[ChallengeReply]: """Signs a proposal's challenges using a Yubikey.""" if not challenges: raise Exception("Challenge list empty: No challenges to sign.") @@ -130,7 +157,6 @@ def sign_proposal(challenges, singed_challenge_files): devices = list_all_devices() if not devices: raise Exception("no yubikeys found") - challenge_count = 0 for yubikey, _ in devices: with yubikey.open_connection(SmartCardConnection) as connection: # Make PivSession and fetch public key from Signature slot. @@ -159,47 +185,24 @@ def sign_proposal(challenges, singed_challenge_files): # sign the challenge print("Press Yubikey to sign challenge") + # print("key type: " + str(slot_metadata.key_type)) + # print(challenge.challenge) + # print(type(challenge.challenge)) signed_challenge = piv_session.sign( slot=piv.SLOT.RETIRED1, key_type=slot_metadata.key_type, - message=challenge.challenge.encode('utf-8'), + message=challenge.challenge, hash_algorithm=hashes.SHA256(), padding=padding.PKCS1v15(), ) signed_challenges.append( ChallengeReply( + challenge.challenge, signed_challenge, challenge.public_key_pem ) ) - challenge_count += 1 - print("challenge_count", challenge_count) - directory_path = "signed_challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - with open( - f"signed_challenges/public_key_{challenge_count}.pem", "w" - ) as binary_file: - - # Write public key to file - binary_file.write( - challenge.public_key_pem - ) - with open( - f"signed_challenges/signed_challenge{challenge_count}.txt", "wb" - ) as binary_file: - - # Write public key to file - binary_file.write( - signed_challenge - ) - singed_challenge_files.append((f"signed_challenges/signed_challenge{challenge_count}.txt", - f"signed_challenges/public_key_{challenge_count}.pem") - ) print("Challenge signed successfully") if not signed_challenges: raise Exception( @@ -208,8 +211,32 @@ def sign_proposal(challenges, singed_challenge_files): ) return signed_challenges +def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: + """ + Converts a URL-safe base64 encoded string to its binary equivalent. + + Args: + urlsafe_string: The URL-safe base64 encoded string. -def verify_challenge_signatures(challenge_replies, data): + Returns: + The binary data as bytes, or None if an error occurs. + + Raises: + TypeError: If the input is not a string. + ValueError: If the input string is not valid URL-safe base64. + """ + try: + if not isinstance(urlsafe_string, str): + raise TypeError("Input must be a string") + # Add padding if necessary. Base64 requires padding to be a multiple of 4 + missing_padding = len(urlsafe_string) % 4 + if missing_padding: + urlsafe_string += '=' * (4 - missing_padding) + return base64.urlsafe_b64decode(urlsafe_string) + except base64.binascii.Error as e: + raise ValueError(f"Invalid URL-safe base64 string: {e}") from e + +def verify_challenge_signatures(challenge_replies: list[ChallengeReply]): if not challenge_replies: raise Exception("No signed challenges to verify") for challenge_reply in challenge_replies: @@ -217,12 +244,17 @@ def verify_challenge_signatures(challenge_replies, data): challenge_reply.public_key_pem.encode() ) try: + public_key.verify( challenge_reply.signed_challenge, - data, + challenge_reply.unsigned_challenge, padding.PKCS1v15(), hashes.SHA256(), ) print(f"Signature verification success") except cryptography.exceptions.InvalidSignature as e: raise cryptography.exceptions.InvalidSignature((f"Signature verification failed: {e}")) + + +if __name__ == "__main__": + generate_private_key() \ No newline at end of file diff --git a/kms/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/ykman_utils_test.py index 9aa52766b3f..37b1cee124c 100644 --- a/kms/singletenanthsm/ykman_utils_test.py +++ b/kms/singletenanthsm/ykman_utils_test.py @@ -37,40 +37,14 @@ def __init__(self, signed_challenge, public_key_pem): challenge_test_data = b"test_data" -# def populate_challenges_from_files(): -# public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("challenges/public_key*.pem")] -# print(public_key_files) -# challenge_files = [challenge_file for challenge_file in pathlib.Path.cwd().glob("challenges/challenge*.txt")] -# print(challenge_files) - -# challenges = [] - -# for public_key_file in public_key_files: -# challenge_id = re.findall(r"\d+", str(public_key_file)) -# for challenge_file in challenge_files: -# if challenge_id == re.findall(r"\d+",str(challenge_file)): -# print(public_key_file) -# file = open(public_key_file, "r") -# public_key_pem = file.read() -# file.close() -# file = open(challenge_file, "r") -# challenge = file.read() -# file.close() -# challenges.append(Challenge(challenge, public_key_pem )) -# return challenges - def generate_test_challenge_files(): # Create challenges list from challenges directory challenges = ykman_utils.populate_challenges_from_files() for challenge in challenges: print(challenge.challenge) print(challenge.public_key_pem) - # Signes challenges - signed_challenge_files = [] - signed_challenges = ykman_utils.sign_proposal(challenges, signed_challenge_files) - for signed_challenge in signed_challenge_files: - print(signed_challenge.signed_challenge.decode()) - print(signed_challenge.public_key_pem) + # Sign challenges + signed_challenges = ykman_utils.sign_challenges(challenges) ykman_utils.verify_challenge_signatures(signed_challenges, b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN") @@ -93,16 +67,21 @@ def challenges(): return challenges - - def test_sign_and_verify_challenges(): - signed_challenges = ykman_utils.sign_proposal(challenges(),[]) - ykman_utils.verify_challenge_signatures(signed_challenges, challenge_test_data) + signed_challenges = ykman_utils.sign_challenges(challenges()) + ykman_utils.verify_challenge_signatures(signed_challenges) + +def test_verify_mismatching_data_fail(): + with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: + signed_challenges = ykman_utils.sign_challenges(challenges()) + signed_challenges[0].signed_challenge = b"mismatched_data" + ykman_utils.verify_challenge_signatures(signed_challenges) + assert "Signature verification failed" in str(exec_info.value) def test_sign_empty_challenge_list_fail(): with pytest.raises(Exception) as exec_info: # empty_challenges = [] - signed_challenges = ykman_utils.sign_proposal([],[]) + signed_challenges = ykman_utils.sign_challenges([]) assert "Challenge list empty" in str(exec_info.value) def test_sign_no_matching_public_keys_fail(): @@ -110,24 +89,10 @@ def test_sign_no_matching_public_keys_fail(): for challenge in modified_challenges: challenge.public_key_pem = "modified_public_key" with pytest.raises(Exception) as exec_info: - signed_challenges = ykman_utils.sign_proposal(modified_challenges,[]) + signed_challenges = ykman_utils.sign_challenges(modified_challenges) assert "No matching public keys" in str(exec_info.value) def test_verify_empty_challenge_replies_fail(): with pytest.raises(Exception) as exec_info: - ykman_utils.verify_challenge_signatures([], challenge_test_data) + ykman_utils.verify_challenge_signatures([]) assert "No signed challenges to verify" in str(exec_info) - - -def test_verify_mismatching_data_fail(): - with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: - signed_challenges = ykman_utils.sign_proposal(challenges(),[]) - ykman_utils.verify_challenge_signatures(signed_challenges, b"mismatched_data") - assert "Signature verification failed" in str(exec_info.value) - - - -if __name__ == "__main__": - # ykman_utils.generate_private_key() - # test_sign_and_verify_challenges() - generate_test_challenge_files() \ No newline at end of file From 747646c5d9178f31d00e25c3e50db3a891f1b873 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Wed, 30 Apr 2025 15:53:13 -0400 Subject: [PATCH 05/19] Update README.rst --- kms/singletenanthsm/README.rst | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/kms/singletenanthsm/README.rst b/kms/singletenanthsm/README.rst index a3fe9fee3d3..959b45c507f 100644 --- a/kms/singletenanthsm/README.rst +++ b/kms/singletenanthsm/README.rst @@ -52,23 +52,22 @@ Install Dependencies Samples ------------------------------------------------------------------------------- -Create a custom gcloud build to access the Single Tenant HSM service. -Approve a Single Tenant HSM Instance Proposal. +Create a custom gcloud build to access the Single Tenant HSM service. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -Creates custom gcloud build to access single tenant HSM service+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +This application creates a custom gcloud build to access the single tenant HSM service. The operation can be specified depending if the user +wants to just generate rsa keys on all connected yubikeys(generate_rsa_keys), just generate the custom gcloud build to access the +single-tenant-hsm(build_custom_gcloud), or both generate keys and the custom gcloud build(generate_gcloud_and_keys). Yubikeys will need to be connected +to run the `generate_rsa_keys` and `generate_gcloud_and_keys` operations. -To run this sample: .. code-block:: bash $ python3 setup.py - usage: setup.py [-h] [--operation] - - This application creates a custom gcloud build to access the single tenant HSM service. + usage: setup.py [-h] [--operation] {build_custom_gcloud,generate_rsa_keys,generate_gcloud_and_keys} positional arguments: operation The type of setup operation you want to perform. This includes build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'. @@ -76,9 +75,15 @@ To run this sample: optional arguments: -h, --help show this help message and exit + # Below is an example of using the setup command to generate rsa private keys and the custom gcloud build: + + $ python3 setup.py --operation=generate_gcloud_and_keys + + -Approves a Single Tenant HSM Instance Proposal. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +Approves a Single Tenant HSM Instance Proposal. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ To run this sample: .. code-block:: bash @@ -96,10 +101,14 @@ To run this sample: --proposal_resource PROPOSAL_RESOURCE The full name of the single tenant HSM instance proposal that needs to be approved. - - optional arguments: -h, --help show this help message and exit + # Below is an example of using the approve script to fetch the challenges, sign the challenges, and send the signed challenges + # associated with the proposal 'my_proposal': + + $ python3 approve_proposal.py --proposal_resource=projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal + + -.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file +.. _Google Cloud SDK: https://cloud.google.com/sdk/ From 41528b3c5b42e28023f07409cc0e8974c6eccd03 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Fri, 2 May 2025 14:34:50 -0400 Subject: [PATCH 06/19] Added .nox file and lint changes --- kms/singletenanthsm/approve_proposal.py | 190 +++--- kms/singletenanthsm/approve_proposal_test.py | 283 ++++---- kms/singletenanthsm/brandonluong/asr/BUILD | 57 -- .../brandonluong/asr/hsm_functions.cc | 507 -------------- .../brandonluong/asr/hsm_functions.h | 87 --- .../brandonluong/asr/hsm_functions_test.cc | 122 ---- .../brandonluong/cfmwrap/BUILD | 60 -- .../cfmwrap/cavium_layers_test.cc | 635 ------------------ .../brandonluong/cfmwrap/hsm_functions.cc | 507 -------------- .../brandonluong/cfmwrap/hsm_functions.h | 88 --- .../cfmwrap/hsm_functions_perf_test.cc | 360 ---------- .../cfmwrap/hsm_functions_test.cc | 485 ------------- .../brandonluong/conflicts/conflict.txt | 6 - .../brandonluong/quotes/index.html | 23 - .../brandonluong/singletenanthsm/README.rst | 105 --- .../singletenanthsm/approve_proposal.py | 141 ---- .../singletenanthsm/approve_proposal_test.py | 261 ------- .../challenges/public_key1.pem | 9 - .../challenges/public_key2.pem | 9 - .../challenges/public_key3.pem | 9 - .../singletenanthsm/gcloud_commands.py | 152 ----- .../singletenanthsm/gcloud_commands_test.py | 321 --------- .../public_key_25167010.pem | 9 - .../public_key_28787103.pem | 9 - .../public_key_28787105.pem | 9 - .../singletenanthsm/requirements.txt | 21 - .../brandonluong/singletenanthsm/setup.py | 69 -- .../signed_challenges/public_key_1.pem | 9 - .../signed_challenges/public_key_2.pem | 9 - .../signed_challenges/public_key_3.pem | 9 - .../signed_challenges/signed_challenge1.txt | 1 - .../signed_challenges/signed_challenge2.txt | 3 - .../signed_challenges/signed_challenge3.txt | Bin 256 -> 0 bytes .../singletenanthsm/ykman_fake.py | 53 -- .../singletenanthsm/ykman_utils.py | 260 ------- .../singletenanthsm/ykman_utils_test.py | 109 --- .../challenges/challenge1.txt | 0 .../challenges/challenge2.txt | 0 .../challenges/challenge3.txt | 0 .../challenges/public_key1.pem | 9 + .../challenges/public_key2.pem | 9 + .../challenges/public_key3.pem | 9 + kms/singletenanthsm/gcloud_commands.py | 180 +++-- kms/singletenanthsm/gcloud_commands_test.py | 454 +++++++------ .../public_key_25167010.pem | 9 + .../public_key_28787103.pem | 9 + .../public_key_28787105.pem | 9 + kms/singletenanthsm/requirements.txt | 10 + kms/singletenanthsm/setup.py | 83 ++- .../signed_challenges/public_key_1.pem | 9 + .../signed_challenges/public_key_2.pem | 9 + .../signed_challenges/public_key_3.pem | 9 + .../signed_challenges/signed_challenge1.bin | Bin 0 -> 256 bytes .../signed_challenges/signed_challenge2.bin | Bin 0 -> 256 bytes .../signed_challenges/signed_challenge3.bin | 2 + .../singletenanthsm/README.rst | 105 --- .../singletenanthsm/approve_proposal.py | 140 ---- .../singletenanthsm/approve_proposal_test.py | 241 ------- .../singletenanthsm/challenges/challenge1.txt | 1 - .../singletenanthsm/challenges/challenge2.txt | 1 - .../singletenanthsm/challenges/challenge3.txt | 1 - .../challenges/public_key1.pem | 9 - .../challenges/public_key2.pem | 9 - .../challenges/public_key3.pem | 9 - .../singletenanthsm/gcloud_commands.py | 153 ----- .../singletenanthsm/gcloud_commands_test.py | 321 --------- .../public_key_25167010.pem | 9 - .../public_key_28787103.pem | 9 - .../public_key_28787105.pem | 9 - .../singletenanthsm/requirements.txt | 21 - kms/singletenanthsm/singletenanthsm/setup.py | 60 -- .../signed_challenges/public_key_1.pem | 9 - .../signed_challenges/public_key_2.pem | 9 - .../signed_challenges/public_key_3.pem | 9 - .../signed_challenges/signed_challenge1.bin | 1 - .../signed_challenges/signed_challenge1.txt | 1 - .../signed_challenges/signed_challenge2.bin | Bin 256 -> 0 bytes .../signed_challenges/signed_challenge2.txt | 3 - .../signed_challenges/signed_challenge3.bin | 1 - .../signed_challenges/signed_challenge3.txt | Bin 256 -> 0 bytes .../singletenanthsm/ykman_fake.py | 59 -- .../singletenanthsm/ykman_utils.py | 254 ------- .../singletenanthsm/ykman_utils_test.py | 98 --- kms/singletenanthsm/ykman_fake.py | 38 +- kms/singletenanthsm/ykman_utils.py | 352 +++++----- kms/singletenanthsm/ykman_utils_test.py | 55 +- 86 files changed, 918 insertions(+), 6866 deletions(-) delete mode 100644 kms/singletenanthsm/brandonluong/asr/BUILD delete mode 100644 kms/singletenanthsm/brandonluong/asr/hsm_functions.cc delete mode 100644 kms/singletenanthsm/brandonluong/asr/hsm_functions.h delete mode 100644 kms/singletenanthsm/brandonluong/asr/hsm_functions_test.cc delete mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/BUILD delete mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/cavium_layers_test.cc delete mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.cc delete mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.h delete mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_perf_test.cc delete mode 100644 kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_test.cc delete mode 100644 kms/singletenanthsm/brandonluong/conflicts/conflict.txt delete mode 100644 kms/singletenanthsm/brandonluong/quotes/index.html delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/README.rst delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal.py delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal_test.py delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key1.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key2.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key3.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands.py delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands_test.py delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_25167010.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787103.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787105.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/requirements.txt delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/setup.py delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_1.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_2.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_3.pem delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge1.txt delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge2.txt delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge3.txt delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/ykman_fake.py delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils.py delete mode 100644 kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils_test.py rename kms/singletenanthsm/{brandonluong/singletenanthsm => }/challenges/challenge1.txt (100%) rename kms/singletenanthsm/{brandonluong/singletenanthsm => }/challenges/challenge2.txt (100%) rename kms/singletenanthsm/{brandonluong/singletenanthsm => }/challenges/challenge3.txt (100%) create mode 100644 kms/singletenanthsm/challenges/public_key1.pem create mode 100644 kms/singletenanthsm/challenges/public_key2.pem create mode 100644 kms/singletenanthsm/challenges/public_key3.pem create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_25167010.pem create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787103.pem create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787105.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_1.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_2.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_3.pem create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge1.bin create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge2.bin create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge3.bin delete mode 100644 kms/singletenanthsm/singletenanthsm/README.rst delete mode 100644 kms/singletenanthsm/singletenanthsm/approve_proposal.py delete mode 100644 kms/singletenanthsm/singletenanthsm/approve_proposal_test.py delete mode 100644 kms/singletenanthsm/singletenanthsm/challenges/challenge1.txt delete mode 100644 kms/singletenanthsm/singletenanthsm/challenges/challenge2.txt delete mode 100644 kms/singletenanthsm/singletenanthsm/challenges/challenge3.txt delete mode 100644 kms/singletenanthsm/singletenanthsm/challenges/public_key1.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/challenges/public_key2.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/challenges/public_key3.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/gcloud_commands.py delete mode 100644 kms/singletenanthsm/singletenanthsm/gcloud_commands_test.py delete mode 100644 kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_25167010.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787103.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787105.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/requirements.txt delete mode 100644 kms/singletenanthsm/singletenanthsm/setup.py delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_1.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_2.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_3.pem delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.bin delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.txt delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.bin delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.txt delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.bin delete mode 100644 kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.txt delete mode 100644 kms/singletenanthsm/singletenanthsm/ykman_fake.py delete mode 100644 kms/singletenanthsm/singletenanthsm/ykman_utils.py delete mode 100644 kms/singletenanthsm/singletenanthsm/ykman_utils_test.py diff --git a/kms/singletenanthsm/approve_proposal.py b/kms/singletenanthsm/approve_proposal.py index 6684547b15d..a3f03e47748 100644 --- a/kms/singletenanthsm/approve_proposal.py +++ b/kms/singletenanthsm/approve_proposal.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2020 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,117 +25,117 @@ def parse_challenges_into_files(sthi_output: str) -> List[bytes]: - """Parses the STHI output and writes the challenges and public keys to files. - - Args: - sthi_output: The output of the STHI command. - - Returns: - A list of the unsigned challenges. - """ - print("parsing challenges into files") - proposal_json = json.loads(sthi_output, strict=False) - challenges = proposal_json["quorumParameters"]["challenges"] - - directory_path = "challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - - challenge_count = 0 - unsigned_challenges = [] - for challenge in challenges: - challenge_count += 1 - print(challenge["challenge"] + "\n") - print(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) - f = open("challenges/challenge{0}.txt".format(challenge_count), "wb") - binary_challenge = ykman_utils.urlsafe_base64_to_binary( - challenge["challenge"] - ) - f.write(binary_challenge) - f.close() - - f = open("challenges/public_key{0}.pem".format(challenge_count), "w") - f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) - f.close() - unsigned_challenges.append( - ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"]) - ) + """Parses the STHI output and writes the challenges and public keys to files. + + Args: + sthi_output: The output of the STHI command. + + Returns: + A list of the unsigned challenges. + """ + print("parsing challenges into files") + proposal_json = json.loads(sthi_output, strict=False) + challenges = proposal_json["quorumParameters"]["challenges"] + + directory_path = "challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + + challenge_count = 0 + unsigned_challenges = [] + for challenge in challenges: + challenge_count += 1 + print(challenge["challenge"] + "\n") + print(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f = open("challenges/challenge{0}.txt".format(challenge_count), "wb") + binary_challenge = ykman_utils.urlsafe_base64_to_binary(challenge["challenge"]) + f.write(binary_challenge) + f.close() + + f = open("challenges/public_key{0}.pem".format(challenge_count), "w") + f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f.close() + unsigned_challenges.append( + ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"]) + ) - return unsigned_challenges + return unsigned_challenges def parse_args(args): - parser = argparse.ArgumentParser() - parser.add_argument("--proposal_resource", type=str, required=True) - return parser.parse_args(args) + parser = argparse.ArgumentParser() + parser.add_argument("--proposal_resource", type=str, required=True) + return parser.parse_args(args) def signed_challenges_to_files( challenge_replies: list[ykman_utils.ChallengeReply], ) -> None: - """Writes the signed challenges and public keys to files. - - Args: - challenge_replies: A list of ChallengeReply objects. - - Returns: - A list of tuples containing the signed challenge file path and the public - key file path. - """ - signed_challenge_files = [] - challenge_count = 0 - for challenge_reply in challenge_replies: - challenge_count += 1 - print("challenge_count", challenge_count) - directory_path = "signed_challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - with open( - f"signed_challenges/public_key_{challenge_count}.pem", "w" - ) as public_key_file: - - # Write public key to file - public_key_file.write(challenge_reply.public_key_pem) - with open( - f"signed_challenges/signed_challenge{challenge_count}.bin", "wb" - ) as binary_file: - - # Write signed challenge to file - binary_file.write(challenge_reply.signed_challenge) - signed_challenge_files.append(( - f"signed_challenges/signed_challenge{challenge_count}.bin", - f"signed_challenges/public_key_{challenge_count}.pem", - )) - return signed_challenge_files + """Writes the signed challenges and public keys to files. + + Args: + challenge_replies: A list of ChallengeReply objects. + + Returns: + A list of tuples containing the signed challenge file path and the public + key file path. + """ + signed_challenge_files = [] + challenge_count = 0 + for challenge_reply in challenge_replies: + challenge_count += 1 + print("challenge_count", challenge_count) + directory_path = "signed_challenges" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + else: + print(f"Directory '{directory_path}' already exists.") + with open( + f"signed_challenges/public_key_{challenge_count}.pem", "w" + ) as public_key_file: + + # Write public key to file + public_key_file.write(challenge_reply.public_key_pem) + with open( + f"signed_challenges/signed_challenge{challenge_count}.bin", "wb" + ) as binary_file: + + # Write signed challenge to file + binary_file.write(challenge_reply.signed_challenge) + signed_challenge_files.append( + ( + f"signed_challenges/signed_challenge{challenge_count}.bin", + f"signed_challenges/public_key_{challenge_count}.pem", + ) + ) + return signed_challenge_files def approve_proposal(): - """Approves a proposal by fetching challenges, signing them, and sending them back to gcloud.""" - parser = parse_args(sys.argv[1:]) + """Approves a proposal by fetching challenges, signing them, and sending them back to gcloud.""" + parser = parse_args(sys.argv[1:]) - # Fetch challenges - process = gcloud_commands.fetch_challenges(parser.proposal_resource) + # Fetch challenges + process = gcloud_commands.fetch_challenges(parser.proposal_resource) - # Parse challenges into files - unsigned_challenges = parse_challenges_into_files(process.stdout) + # Parse challenges into files + unsigned_challenges = parse_challenges_into_files(process.stdout) - # Sign challenges - signed_challenges = ykman_utils.sign_challenges(unsigned_challenges) + # Sign challenges + signed_challenges = ykman_utils.sign_challenges(unsigned_challenges) - # Parse signed challenges into files - signed_challenged_files = signed_challenges_to_files(signed_challenges) + # Parse signed challenges into files + signed_challenged_files = signed_challenges_to_files(signed_challenges) - # Return signed challenges to gcloud - gcloud_commands.send_signed_challenges( - signed_challenged_files, parser.proposal_resource - ) + # Return signed challenges to gcloud + gcloud_commands.send_signed_challenges( + signed_challenged_files, parser.proposal_resource + ) if __name__ == "__main__": - approve_proposal() + approve_proposal() diff --git a/kms/singletenanthsm/approve_proposal_test.py b/kms/singletenanthsm/approve_proposal_test.py index b9e293fc9cb..1821f5469ae 100644 --- a/kms/singletenanthsm/approve_proposal_test.py +++ b/kms/singletenanthsm/approve_proposal_test.py @@ -11,32 +11,22 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +import argparse + from dataclasses import dataclass +import json + +import os -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.backends import default_backend -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.serialization import ( Encoding, PublicFormat, ) -import argparse -import json -import os -import subprocess -from unittest import mock -from unittest.mock import Mock -from unittest.mock import patch -# from approve_proposal import parse_args - import approve_proposal -import gcloud_commands -import pytest + import ykman_fake import ykman_utils @@ -64,66 +54,58 @@ """ + sample_nonces = [ - "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", ] + @dataclass class QuorumParameters: - challenges: list[ykman_utils.Challenge] - - # def __init__(self, challenges: list[ykman_utils.Challenge]): - # self.challenges = challenges - - # def to_dict(self): - # return {"challenges": self.challenges} + challenges: list[ykman_utils.Challenge] - -sample_assigned_challenges = "" - - -@pytest.fixture() -def setup(): - parser = approve_proposal.parse_args(["proposal_resource", "my_proposal"]) - +mock_signed_challenges = [] test_resource = "projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal" +# mock_completed_process = subprocess.CompletedProcess -mock_completed_process = subprocess.CompletedProcess def public_key_to_pem(public_key): - public_key_pem = public_key.public_bytes( - encoding=Encoding.PEM, - format=PublicFormat.SubjectPublicKeyInfo - ).decode('utf-8') - print("PUBLIC KEY--------------") - print(public_key_pem) - return public_key_pem + public_key_pem = public_key.public_bytes( + encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo + ).decode("utf-8") + print("PUBLIC KEY--------------") + print(public_key_pem) + return public_key_pem -def create_json(public_key_pem_1, public_key_pem_2, public_key_pem_3): - my_json_string = json.dumps({ "quorumParameters": { - "challenges": [ - { - "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "publicKeyPem": public_key_pem_1 - }, - { - "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", - "publicKeyPem": public_key_pem_2 - }, - { - "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "publicKeyPem": public_key_pem_3 - } - ], - "requiredApproverCount": 3 - }}) +def create_json(public_key_pem_1, public_key_pem_2, public_key_pem_3): - return my_json_string + my_json_string = json.dumps( + { + "quorumParameters": { + "challenges": [ + { + "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", + "publicKeyPem": public_key_pem_1, + }, + { + "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", + "publicKeyPem": public_key_pem_2, + }, + { + "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", + "publicKeyPem": public_key_pem_3, + }, + ], + "requiredApproverCount": 3, + } + } + ) + return my_json_string def create_fake_fetch_response(num_keys=3): @@ -151,91 +133,108 @@ def create_fake_fetch_response(num_keys=3): return challenge_json, pub_to_priv_key +def sign_challenges_with_capture( + challenges: list[ykman_utils.Challenge], pub_to_priv_key +): + signed_challenges = [] + for challenge in challenges: + private_key = pub_to_priv_key[challenge.public_key_pem] + signed_challenge = ykman_fake.sign_data(private_key, challenge.challenge) + signed_challenges.append( + ykman_utils.ChallengeReply( + challenge.challenge, signed_challenge, challenge.public_key_pem + ) + ) + mock_signed_challenges.extend(signed_challenges) + return signed_challenges -mock_signed_challenges = [] -def sign_challenges_with_capture(challenges:list[ykman_utils.Challenge], pub_to_priv_key): - signed_challenges = [] - for challenge in challenges: - private_key = pub_to_priv_key[challenge.public_key_pem] - signed_challenge = ykman_fake.sign_data(private_key, challenge.challenge) - signed_challenges.append( - ykman_utils.ChallengeReply( - challenge.challenge, - signed_challenge, - challenge.public_key_pem +def verify_with_fake(pub_to_priv_key, signed_challenges): + for signed_challenge in signed_challenges: + priv_key = pub_to_priv_key[signed_challenge.public_key_pem] + assert ykman_fake.verify_signature( + priv_key.public_key(), + signed_challenge.unsigned_challenge, + signed_challenge.signed_challenge, ) - ) - mock_signed_challenges.extend(signed_challenges) - return signed_challenges + print("Signed verified successfully") -def verify_with_fake(pub_to_priv_key, signed_challenges): - for signed_challenge in signed_challenges: - priv_key = pub_to_priv_key[signed_challenge.public_key_pem] - assert True == ykman_fake.verify_signature(priv_key.public_key(), signed_challenge.unsigned_challenge, signed_challenge.signed_challenge) - print("Signed verified successfully") def test_get_challenges_mocked(mocker, monkeypatch): - - # Verify signed challenges - monkeypatch.setattr( - "gcloud_commands.send_signed_challenges", - lambda signed_challenges, proposal_resource: verify_with_fake(pub_to_priv_key, mock_signed_challenges) - ) - - # monkeypatch sign challenges - monkeypatch.setattr( - "ykman_utils.sign_challenges", - lambda challenges: sign_challenges_with_capture(challenges, pub_to_priv_key) - ) - - # mock the challenge string returned by service - challenge_json, pub_to_priv_key = create_fake_fetch_response() - mock_response = mocker.MagicMock() - mock_response.stdout = challenge_json - mocker.patch("subprocess.run", return_value=mock_response) - - # monkeypatch parse args - mock_args = argparse.Namespace(proposal_resource="test_resource") - monkeypatch.setattr( - "approve_proposal.parse_args", - lambda args: mock_args - ) - - approve_proposal.approve_proposal() - - # assert challenge files created - challenge_files = ['challenges/challenge1.txt', 'challenges/challenge2.txt', 'challenges/challenge3.txt'] - for file_path in challenge_files: - assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." + # Verify signed challenges + monkeypatch.setattr( + "gcloud_commands.send_signed_challenges", + lambda signed_challenges, proposal_resource: verify_with_fake( + pub_to_priv_key, mock_signed_challenges + ), + ) + + # monkeypatch sign challenges + monkeypatch.setattr( + "ykman_utils.sign_challenges", + lambda challenges: sign_challenges_with_capture(challenges, pub_to_priv_key), + ) + + # mock the challenge string returned by service + challenge_json, pub_to_priv_key = create_fake_fetch_response() + mock_response = mocker.MagicMock() + mock_response.stdout = challenge_json + mocker.patch("subprocess.run", return_value=mock_response) + + # monkeypatch parse args + mock_args = argparse.Namespace(proposal_resource="test_resource") + monkeypatch.setattr("approve_proposal.parse_args", lambda args: mock_args) + + approve_proposal.approve_proposal() + + # assert challenge files created + challenge_files = [ + "challenges/challenge1.txt", + "challenges/challenge2.txt", + "challenges/challenge3.txt", + ] + for file_path in challenge_files: + assert os.path.exists( + file_path + ), f"File '{file_path}' should exist but does not." + if __name__ == "__main__": - # Parse challenges into files - unsigned_challenges = approve_proposal.parse_challenges_into_files( - sample_sthi_output - ) - created_signed_files = ['signed_challenges/signed_challenge1.txt', 'signed_challenges/signed_challenge2.txt', 'signed_challenges/signed_challenge3.txt'] - for file_path in created_signed_files: - assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." - - # assert signed challenge files created - signed_challenge_files = ['signed_challenges/signed_challenge1.txt', 'signed_challenges/signed_challenge2.txt', 'signed_challenges/signed_challenge3.txt'] - for file_path in signed_challenge_files: - assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." - - - # Parse files into challenge list - challenges = ykman_utils.populate_challenges_from_files() - for challenge in challenges: - print(challenge.challenge) - print(challenge.public_key_pem) - unsigned_challenges.append(challenge.challenge) - signed_challenged_files = [] - signed_challenges = ykman_utils.sign_challenges( - challenges, signed_challenged_files - ) - for signed_challenge in signed_challenges: - print(signed_challenge.signed_challenge) - print(signed_challenge.public_key_pem) - print("--challenge_replies=" + str(signed_challenged_files)) - ykman_utils.verify_challenge_signatures(signed_challenges) + # Parse challenges into files + unsigned_challenges = approve_proposal.parse_challenges_into_files( + sample_sthi_output + ) + created_signed_files = [ + "signed_challenges/signed_challenge1.txt", + "signed_challenges/signed_challenge2.txt", + "signed_challenges/signed_challenge3.txt", + ] + for file_path in created_signed_files: + assert os.path.exists( + file_path + ), f"File '{file_path}' should exist but does not." + + # assert signed challenge files created + signed_challenge_files = [ + "signed_challenges/signed_challenge1.txt", + "signed_challenges/signed_challenge2.txt", + "signed_challenges/signed_challenge3.txt", + ] + for file_path in signed_challenge_files: + assert os.path.exists( + file_path + ), f"File '{file_path}' should exist but does not." + + # Parse files into challenge list + challenges = ykman_utils.populate_challenges_from_files() + for challenge in challenges: + print(challenge.challenge) + print(challenge.public_key_pem) + unsigned_challenges.append(challenge.challenge) + signed_challenged_files = [] + signed_challenges = ykman_utils.sign_challenges(challenges, signed_challenged_files) + for signed_challenge in signed_challenges: + print(signed_challenge.signed_challenge) + print(signed_challenge.public_key_pem) + print("--challenge_replies=" + str(signed_challenged_files)) + ykman_utils.verify_challenge_signatures(signed_challenges) diff --git a/kms/singletenanthsm/brandonluong/asr/BUILD b/kms/singletenanthsm/brandonluong/asr/BUILD deleted file mode 100644 index de934c5e31b..00000000000 --- a/kms/singletenanthsm/brandonluong/asr/BUILD +++ /dev/null @@ -1,57 +0,0 @@ -cc_library( - name = "hsm_functions", - srcs = ["hsm_functions.cc"], - hdrs = ["hsm_functions.h"], - deps = [ - "//cloud/security/hawksbill/cavium:cavium_api_interface_header", - "//testing/base/public:gunit_headers", - "//third_party/absl/log:check", - "//third_party/absl/status", - "//third_party/absl/status:statusor", - "//third_party/absl/strings", - "//third_party/cavium_hsm/v3_4__14:cavium_hsm", - "//third_party/cavium_hsm/v3_4__14/utils:openssl_util", - "//third_party/openssl:crypto", - "//util/task:status", - ], -) - -cc_test( - name = "hsm_functions_test", - srcs = ["hsm_functions_test.cc"], - deps = [ - ":hsm_functions", - "//base:raw_logging", - "//testing/base/public:gunit_main", - "//third_party/absl/log", - "//third_party/absl/status:statusor", - "//third_party/absl/strings", - "//third_party/crunchy/algs/openssl:errors", - "//third_party/openssl:crypto", - ], -) - -cc_binary( - name = "hsm_functions_perf_test", - srcs = ["hsm_functions_perf_test.cc"], - deps = [ - ":hsm_functions", - "//testing/base/public:gunit", - "//third_party/openssl:crypto", - ], -) - -cc_test( - name = "cavium_layers_test", - srcs = ["cavium_layers_test.cc"], - deps = [ - ":hsm_functions", - "//base:raw_logging", - "//cloud/security/hawksbill/cavium:cavium_api_error", - "//cloud/security/hawksbill/cavium:cavium_api_error_enum", - "//cloud/security/hawksbill/cavium:cavium_api_shim", - "//cloud/security/hawksbill/cavium:lib_cavium", - "//testing/base/public:gunit_main", - "//third_party/absl/strings", - ], -) diff --git a/kms/singletenanthsm/brandonluong/asr/hsm_functions.cc b/kms/singletenanthsm/brandonluong/asr/hsm_functions.cc deleted file mode 100644 index 6aa554b9f4a..00000000000 --- a/kms/singletenanthsm/brandonluong/asr/hsm_functions.cc +++ /dev/null @@ -1,507 +0,0 @@ -#include "experimental/users/brandonluong/asr/hsm_functions.h" - -#include -#include - -#include "third_party/absl/log/check.h" -#include "third_party/absl/status/status.h" -#include "third_party/absl/status/statusor.h" -#include "third_party/absl/strings/escaping.h" -#include "third_party/absl/strings/substitute.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_args.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_defines.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_errors.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_mgmt.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_structs.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_wrappers.h" -#include "third_party/cavium_hsm/v3_4__14/utils/openssl_util.h" -#include "third_party/openssl/bn.h" -#include "third_party/openssl/ec.h" -#include "third_party/openssl/ec_key.h" -#include "third_party/openssl/rand.h" -#include "third_party/openssl/rsa.h" - -#define CHECK_SUCCESS(expr) \ - { \ - int32_t ret = expr; \ - CHECK_EQ(ret, 0) << Cfm2ResultAsString(ret); \ - } - -namespace third_party_cavium_hsm { - -std::vector RandomLabel() { - uint8_t rand[16]; - RAND_bytes(rand, sizeof(rand)); - std::string hex = absl::BytesToHexString( - absl::string_view(reinterpret_cast(rand), sizeof(rand))); - CHECK_EQ(hex.size(), 32); - return std::vector(hex.begin(), hex.end()); -} - -uint32_t Test_Init() { - uint32_t application_handle; - Cfm2Initialize2(167936, DIRECT, &application_handle); - - return application_handle; -} - -uint32_t Initialize() { - uint32_t application_handle; - CHECK_SUCCESS(Cfm2Initialize2(0, DIRECT, &application_handle)); - - uint32_t session_handle; - CHECK_SUCCESS(Cfm2OpenSession2(application_handle, &session_handle)); - - constexpr absl::string_view kUsername = "crypto_user"; - constexpr absl::string_view kPassword = "user123"; - - std::vector encrypted_password(PSWD_ENC_KEY_MODULUS, 0); - uint32_t password_size = encrypted_password.size(); - CHECK_SUCCESS(utils::encrypt_pswd( - session_handle, - reinterpret_cast(const_cast(kPassword.data())), - kPassword.size(), encrypted_password.data(), &password_size)); - encrypted_password.resize(password_size); - - CHECK_SUCCESS(Cfm2LoginHSM2( - session_handle, CN_CRYPTO_USER, - reinterpret_cast(const_cast(kUsername.data())), - kUsername.size(), encrypted_password.data(), encrypted_password.size(), - /*signature=*/nullptr)); - - return application_handle; -} - -uint32_t OpenSession(uint32_t application_handle) { - uint32_t session_handle; - CHECK_SUCCESS(Cfm2OpenSession2(application_handle, &session_handle)); - return session_handle; -} - -void CloseSession(uint32_t session_handle) { - CHECK_SUCCESS(Cfm2CloseSession(session_handle)); -} - -uint64_t GenerateParkingKey(uint32_t session_handle) { - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - args.ulSessionHandle = session_handle; - args.key.info.ucKeyLocation = STORAGE_FLASH; - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - args.key.info.bParking = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - CHECK_SUCCESS(CfmGenerateKey(&args)); - return args.key.ulKeyHandle; -} - -uint64_t GenerateAesWrappingKey(uint32_t session_handle) { - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - args.ulSessionHandle = session_handle; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 0; - args.key.info.bParkable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - - default_attributes(&args.key.info); - CHECK_SUCCESS(CfmGenerateKey(&args)); - return args.key.ulKeyHandle; -} - -uint64_t GenerateExtractableSymmetricKey(uint32_t session_handle, - SymmetricKeyType key_type) { - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - args.ulSessionHandle = session_handle; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 1; - args.key.info.bParkable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - switch (key_type) { - case SymmetricKeyType::kAes256: - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - break; - case SymmetricKeyType::kHkdfSha256: - args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; - args.key.info.ulKeyLen = 32; - args.key.info.ucDerive = 1; - break; - } - - default_attributes(&args.key.info); - CHECK_SUCCESS(CfmGenerateKey(&args)); - return args.key.ulKeyHandle; -} - -KeyPair GenerateEcdhKeypair(uint32_t session_handle, int curve) { - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - args.ulSessionHandle = session_handle; - - args.pubkey.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.pubkey.info.bParkable = 1; - args.pubkey.info.ulKeyClass = OBJ_CLASS_PUBLIC_KEY; - args.pubkey.info.ulCurveID = curve; - args.pubkey.info.ulMValue = 1; - args.pubkey.info.pLabel = label.data(); - args.pubkey.info.ulLabelLen = label.size(); - args.pubkey.info.ulKeyType = KEY_TYPE_EC; - args.pubkey.info.ucDerive = 1; - default_attributes(&args.pubkey.info); - - args.privkey.info.ulKeyClass = OBJ_CLASS_PRIVATE_KEY; - args.privkey.info.ulCurveID = args.pubkey.info.ulCurveID; - args.privkey.info.ulKeyType = args.pubkey.info.ulKeyType; - args.privkey.info.ucDerive = 1; - - copy_public_attr_to_priv_attr(&args); - - CHECK_SUCCESS(CfmGenerateKeyPair(&args)); - return {.pub = args.pubkey.ulKeyHandle, .prv = args.privkey.ulKeyHandle}; -} - -std::vector ParkKey(uint32_t session_handle, - uint64_t parking_key_handle, - uint64_t target_key_handle) { - uint8_t buffer[6000]; // as hardcoded in Cfm2Util - - parkOpArgs args = {0}; - args.pParkedObject = buffer; - args.ulParkedObjectLen = sizeof(buffer); - args.ulSessionHandle = session_handle; - args.ulParkingKeyHandle = parking_key_handle; - args.ulObjectHandle = target_key_handle; - CHECK_SUCCESS(CfmParkObject(&args)); - - uint8_t* parked_key_start = GET_KEY1_ATTR(args.pParkedObject); - return std::vector(parked_key_start, - parked_key_start + args.ulParkedObjectLen); -} - -uint64_t UnparkKey(uint32_t session_handle, uint64_t parking_key_handle, - std::vector& parked_key) { - unparkOpArgs args = {0}; - args.pParkedObject = parked_key.data(); - args.ulParkedObjectLen = parked_key.size(); - args.ulSessionHandle = session_handle; - args.ulParkingKeyHandle = parking_key_handle; - args.ephemeralStorage = 1; - CHECK_SUCCESS(CfmUnparkObject(&args)); - return args.ulObjectHandle; -} - -void InitializeWrapArgs(third_party_cavium_interface::WrapArguments pArgs) { - // size fails at 8925, Cfm2Util hardcoded max is 8192 - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - // input args - pArgs.common.session_handle = session_handle; - pArgs.common.wrapping_key_handle = wrapping_key_handle; - pArgs.common.mech = aes_key_wrap_pad; - pArgs.common.key.ul_key_handle = target_key_handle.value(); - pArgs.common.key_input_output = - third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; - - // output args - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); -} - -std::vector WrapKeyAesKwp(uint32_t session_handle, - uint64_t wrapping_key_handle, - uint64_t target_key_handle) { - uint8_t buffer[MAX_DATA_LENGTH]; // as hardcoded in Cfm2Util - - wrapArgs args = {0}; - args.aes_key_len = AES_256_KEY_SIZE; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.key.ulKeyHandle = target_key_handle; - args.pKey = buffer; - args.ulKeyLen = sizeof(buffer); - args.ulSessionHandle = session_handle; - CHECK_SUCCESS(CfmWrap(&args)); - - return std::vector(buffer, buffer + args.ulKeyLen); -} - -void WrapKeySmallBuffer(uint32_t session_handle, uint64_t wrapping_key_handle, - uint64_t target_key_handle, int wrapped_key_len) { - uint8_t buffer[7000]; // as hardcoded in Cfm2Util - - wrapArgs args = {0}; - args.aes_key_len = AES_256_KEY_SIZE; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.key.ulKeyHandle = target_key_handle; - args.pKey = buffer; - args.ulKeyLen = 7050; - args.ulSessionHandle = session_handle; - CHECK_SUCCESS(CfmWrap(&args)); - // CfmWrap(&args); - - // return std::vector(buffer, buffer + args.ulKeyLen); -} - -std::vector WrapKeyRsaOaep(uint32_t session_handle, - uint64_t wrapping_key_handle, - uint64_t target_key_handle) { - uint8_t buffer[MAX_DATA_LENGTH]; // as hardcoded in Cfm2Util - - wrapArgs args = {0}; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_RSA_OAEP_KEY_WRAP; - args.hash_type = HashType::SHA256_TYPE; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.key.ulKeyHandle = target_key_handle; - args.pKey = buffer; - args.ulKeyLen = sizeof(buffer); - args.ulSessionHandle = session_handle; - CHECK_SUCCESS(CfmWrap(&args)); - - return std::vector(buffer, buffer + args.ulKeyLen); -} - -absl::StatusOr UnwrapKeyAesKwp(uint32_t session_handle, - uint64_t wrapping_key_handle, - std::vector& wrapped_key, - SymmetricKeyType key_type) { - std::vector label = RandomLabel(); - - unwrapArgs args = {0}; - - args.aes_key_len = AES_256_KEY_SIZE; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.pKey = wrapped_key.data(); - args.ulKeyLen = wrapped_key.size(); - args.ulSessionHandle = session_handle; - - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - switch (key_type) { - case SymmetricKeyType::kAes256: - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - break; - case SymmetricKeyType::kHkdfSha256: - args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; - args.key.info.ulKeyLen = 32; - args.key.info.ucDerive = 1; - break; - } - default_attributes(&args.key.info); - int32_t ret = CfmUnwrap(&args); - if (ret == 0) { - return args.key.ulKeyHandle; - } else { - return absl::InternalError(absl::Substitute("Cavium Error")); - } -} - -absl::StatusOr UnwrapKeyAesKnp(uint32_t session_handle, - uint64_t wrapping_key_handle, - std::vector& wrapped_key, - SymmetricKeyType key_type) { - std::vector label = RandomLabel(); - - unwrapArgs args = {0}; - - args.aes_key_len = AES_256_KEY_SIZE; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_AES_KEY_WRAP; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.pKey = wrapped_key.data(); - args.ulKeyLen = wrapped_key.size(); - args.ulSessionHandle = session_handle; - - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - switch (key_type) { - case SymmetricKeyType::kAes256: - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - break; - case SymmetricKeyType::kHkdfSha256: - args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; - args.key.info.ulKeyLen = 32; - args.key.info.ucDerive = 1; - break; - } - default_attributes(&args.key.info); - int32_t ret = CfmUnwrap(&args); - if (ret == 0) { - return args.key.ulKeyHandle; - } else { - return absl::InternalError(absl::Substitute("Cavium Error")); - } -} - -uint64_t DeriveAes256KeyHkdfSha256(uint32_t session_handle, uint64_t key_handle, - std::vector info) { - std::vector label = RandomLabel(); - - deriveKeyArgs args = {0}; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - default_attributes(&args.key.info); - - args.ulSessionHandle = session_handle; - args.hBaseKey = key_handle; - constexpr uint64_t kCkdSha256Kdf = 0x06UL; - args.ulDeriveMech = - CRYPTO_MECH_LIQ_SEC(DERIVE_MECH_HKDF) + (kCkdSha256Kdf << 16); - args.prfCtx = info.data(); - args.ulPrfCtxLen = info.size(); - args.ulDKMLengthMethod = SP800_108_DKM_LENGTH_SUM_OF_KEYS; - - CHECK_SUCCESS(CfmDeriveKey(&args)); - return args.key.ulKeyHandle; -} - -uint64_t DeriveAes256KeyEcdhHkdfSha256(uint32_t session_handle, - uint64_t local_private_key_handle, - EC_KEY* remote_public_key) { - std::vector label = RandomLabel(); - - deriveKeyArgs args = {0}; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - default_attributes(&args.key.info); - - args.ulSessionHandle = session_handle; - args.hBaseKey = local_private_key_handle; - constexpr uint64_t kCkdSha256Kdf = 0x06UL; - args.ulDeriveMech = - CRYPTO_MECH_LIQ_SEC(DERIVE_MECH_ECDH_HKDF) + (kCkdSha256Kdf << 16); - args.ulDKMLengthMethod = SP800_108_DKM_LENGTH_SUM_OF_KEYS; - - std::vector serialized_remote_public_key; - - serialized_remote_public_key.resize( - EC_POINT_point2oct(EC_KEY_get0_group(remote_public_key), - EC_KEY_get0_public_key(remote_public_key), - POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr)); - CHECK_EQ(EC_POINT_point2oct(EC_KEY_get0_group(remote_public_key), - EC_KEY_get0_public_key(remote_public_key), - POINT_CONVERSION_UNCOMPRESSED, - serialized_remote_public_key.data(), - serialized_remote_public_key.size(), nullptr), - serialized_remote_public_key.size()); - args.pPubKey = serialized_remote_public_key.data(); - args.ulPubKeyLen = serialized_remote_public_key.size(); - - CHECK_SUCCESS(CfmDeriveKey(&args)); - return args.key.ulKeyHandle; -} - -bssl::UniquePtr ExportEcPublicKey(uint32_t session_handle, - uint64_t key_handle, int curve_id) { - bssl::UniquePtr group(EC_GROUP_new_by_curve_name(curve_id)); - CHECK_NE(group, nullptr); - - uint32_t buf_length = 1024; - std::vector buf; - buf.resize(buf_length); - - CHECK_SUCCESS( - Cfm2ExportPublicKey(session_handle, key_handle, buf.data(), &buf_length)); - - bssl::UniquePtr key(EC_KEY_new()); - CHECK_NE(key, nullptr); - CHECK_EQ(EC_KEY_set_group(key.get(), group.get()), 1); - - bssl::UniquePtr point(EC_POINT_new(group.get())); - CHECK_NE(point, nullptr); - CHECK_EQ(EC_POINT_oct2point(group.get(), point.get(), buf.data(), buf_length, - nullptr), - 1); - - CHECK_EQ(EC_KEY_set_public_key(key.get(), point.get()), 1); - - return key; -} - -void DeleteKey(uint32_t session_handle, uint64_t key_handle) { - CHECK_SUCCESS(Cfm2DeleteKey(session_handle, key_handle)); -} - -uint64_t ImportRsaPublicKey(uint32_t session_handle, const RSA* rsa_pub) { - CHECK_NE(rsa_pub, nullptr); - - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - - args.ulSessionHandle = session_handle; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_PUBLIC_KEY; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - args.key.info.ulKeyType = KEY_TYPE_RSA; - - const BIGNUM *n, *e, *d; - RSA_get0_key(rsa_pub, &n, &e, &d); - CHECK_NE(n, nullptr); - CHECK_NE(e, nullptr); - - std::vector modulus; - modulus.resize(BN_num_bytes(n)); - BN_bn2bin(n, modulus.data()); - args.key.info.pModulus = modulus.data(); - args.key.info.ulModLenInBits = modulus.size() * 8; - - uint64_t pub_exp; - CHECK_EQ(BN_get_u64(e, &pub_exp), 1); - args.key.info.ulPubExp = pub_exp; - - default_attributes(&args.key.info); - - CHECK_SUCCESS(CfmCreateObject(&args)); - return args.key.ulKeyHandle; -} - -} // namespace third_party_cavium_hsm diff --git a/kms/singletenanthsm/brandonluong/asr/hsm_functions.h b/kms/singletenanthsm/brandonluong/asr/hsm_functions.h deleted file mode 100644 index 53f3e3d4776..00000000000 --- a/kms/singletenanthsm/brandonluong/asr/hsm_functions.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef EXPERIMENTAL_USERS_BRANDONLUONG_ASR_HSM_FUNCTIONS_H_ -#define EXPERIMENTAL_USERS_BRANDONLUONG_ASR_HSM_FUNCTIONS_H_ - -#include -#include - -#include "cloud/security/hawksbill/cavium/cavium_api_interface.h" -#include "third_party/absl/status/statusor.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_structs.h" -#include "third_party/openssl/ec_key.h" -#include "third_party/openssl/rsa.h" - -namespace third_party_cavium_hsm { - -std::vector RandomLabel(); - -uint32_t Test_Init(); - -uint32_t Initialize(); -void Finalize(uint32_t application_handle); - -uint32_t OpenSession(uint32_t application_handle); -void CloseSession(uint32_t session_handle); - -uint64_t GenerateParkingKey(uint32_t session_handle); - -void InitializeWrapArgs(third_party_cavium_interface::WrapArguments* pArgs); - -uint64_t GenerateAesWrappingKey(uint32_t session_handle); - -enum class SymmetricKeyType { kAes256, kHkdfSha256 }; - -uint64_t GenerateExtractableSymmetricKey(uint32_t session_handle, - SymmetricKeyType key_type); - -struct KeyPair { - uint64_t pub; - uint64_t prv; -}; - -KeyPair GenerateEcdhKeypair(uint32_t session_handle, int curve); - -std::vector ParkKey(uint32_t session_handle, - uint64_t parking_key_handle, - uint64_t target_key_handle); - -uint64_t UnparkKey(uint32_t session_handle, uint64_t parking_key_handle, - std::vector& parked_key); - -std::vector WrapKeyAesKwp(uint32_t session_handle, - uint64_t wrapping_key_handle, - uint64_t target_key_handle); - -void WrapKeySmallBuffer(uint32_t session_handle, uint64_t wrapping_key_handle, - uint64_t target_key_handle, int wrapped_key_len); - -uint64_t ImportRsaPublicKey(uint32_t session_handle, const RSA* rsa_pub); - -std::vector WrapKeyRsaOaep(uint32_t session_handle, - uint64_t wrapping_key_handle, - uint64_t target_key_handle); - -absl::StatusOr UnwrapKeyAesKwp(uint32_t session_handle, - uint64_t wrapping_key_handle, - std::vector& wrapped_key, - SymmetricKeyType key_type); - -absl::StatusOr UnwrapKeyAesKnp(uint32_t session_handle, - uint64_t wrapping_key_handle, - std::vector& wrapped_key, - SymmetricKeyType key_type); - -uint64_t DeriveAes256KeyHkdfSha256(uint32_t session_handle, uint64_t key_handle, - std::vector info); - -uint64_t DeriveAes256KeyEcdhHkdfSha256(uint32_t session_handle, - uint64_t local_private_key_handle, - EC_KEY* remote_public_key); - -bssl::UniquePtr ExportEcPublicKey(uint32_t session_handle, - uint64_t key_handle, int curve_id); - -void DeleteKey(uint32_t session_handle, uint64_t key_handle); - -} // namespace third_party_cavium_hsm - -#endif // EXPERIMENTAL_USERS_BRANDONLUONG_ASR_HSM_FUNCTIONS_H_ diff --git a/kms/singletenanthsm/brandonluong/asr/hsm_functions_test.cc b/kms/singletenanthsm/brandonluong/asr/hsm_functions_test.cc deleted file mode 100644 index c0fd65f53ec..00000000000 --- a/kms/singletenanthsm/brandonluong/asr/hsm_functions_test.cc +++ /dev/null @@ -1,122 +0,0 @@ -#include "experimental/users/brandonluong/asr/hsm_functions.h" - -#include - -#include - -#include "base/raw_logging.h" -#include "testing/base/public/gmock.h" -#include "testing/base/public/gunit.h" -#include "third_party/openssl/base.h" -#include "third_party/openssl/nid.h" - -namespace third_party_cavium_hsm { -namespace { - -TEST(HsmFunctionsTest, InitializeHSM) { - uint32_t app_handle = Initialize(); - ABSL_RAW_LOG(INFO, "app_handle %d", app_handle); - uint32_t session_handle = OpenSession(app_handle); - - ABSL_RAW_LOG(INFO, "session handle : %d\n app handle: %d\n", session_handle, - app_handle); -} - -// Test Parking and unparking DEK -TEST(HsmFunctionsTest, GenerateParkUnparkAes256) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t parking_key_handle = GenerateParkingKey(session_handle); - uint32_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - std::vector parked_key = - ParkKey(session_handle, parking_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - data_key_handle = UnparkKey(session_handle, parking_key_handle, parked_key); - - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, parking_key_handle); - CloseSession(session_handle); -} - -TEST(HsmFunctionsTest, SymmetricRewrapWorkflowSuccess) { - int curve_nid = NID_secp384r1; - - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t parking_key_handle = GenerateParkingKey(session_handle); - - // Create data encryption key(DEK). - uint32_t data_encryption_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - // Create region B's Data Key Wrapping Key(DKWK). - // Region A's DKWK would have already been used in CreateWrapDataKey and - // RewrapDataKey. It is necessary for this workflow. - uint64_t region_b_dkwk_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - // Park region B's DKWK. - std::vector region_b_parked_dkwk = - ParkKey(session_handle, parking_key_handle, region_b_dkwk_handle); - - // Create 2 ECDH keys. - KeyPair ecdh_key_region_a = GenerateEcdhKeypair(session_handle, curve_nid); - KeyPair ecdh_key_region_b = GenerateEcdhKeypair(session_handle, curve_nid); - - // Convert public keys into EC form. - // There will be 2 ECDH key pairs: one for region A and one for region B. - bssl::UniquePtr region_a_pub_key = - ExportEcPublicKey(session_handle, ecdh_key_region_a.pub, curve_nid); - bssl::UniquePtr region_b_pub_key = - ExportEcPublicKey(session_handle, ecdh_key_region_b.pub, curve_nid); - - // Derive the shared secret using (priA and pubB) or (priB and pubA). - // Then use HKDF + ECDH to get each region's wrapping key. - uint32_t region_a_wrapping_key_handle = DeriveAes256KeyEcdhHkdfSha256( - session_handle, ecdh_key_region_a.prv, region_b_pub_key.get()); - uint32_t region_b_wrapping_key_handle = DeriveAes256KeyEcdhHkdfSha256( - session_handle, ecdh_key_region_b.prv, region_a_pub_key.get()); - - // Wrap the DEK with region A's ecdh + hkdf key. - // This is the wrapped DEK from the output of RewrapDataKey. From here we can - // start the workflow of SymmetricRewrap. - std::vector region_a_wrapped_dek = WrapKeyAesKwp( - session_handle, region_a_wrapping_key_handle, data_encryption_key_handle); - - // Unwrap using region B's ecdh + hkdf key. - // DEK that is wrapped with region a's shared secret is able to be unwrapped - // by region b's shared secret. Note that the unwrapped DEK handle can be - // different from the original DEK handle after creation. - ASSERT_OK_AND_ASSIGN( - data_encryption_key_handle, - UnwrapKeyAesKwp(session_handle, region_b_wrapping_key_handle, - region_a_wrapped_dek, SymmetricKeyType::kAes256)); - - // Unpark region B's DKWK - uint64_t region_b_unparked_dkwk_handle = - UnparkKey(session_handle, parking_key_handle, region_b_parked_dkwk); - - // Wrap the DEK with region B's DKWK. - // This is the output of SymmetricRewrap. The DEK is wrapped within region - // b's DKWK. The DKWK is an AES 256 key. - std::vector aes_wrapped_dek = - WrapKeyAesKwp(session_handle, region_b_unparked_dkwk_handle, - data_encryption_key_handle); - - // Unwrap using region B's DKWK - EXPECT_OK(UnwrapKeyAesKwp(session_handle, region_b_unparked_dkwk_handle, - aes_wrapped_dek, SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, region_b_unparked_dkwk_handle); - DeleteKey(session_handle, region_b_wrapping_key_handle); - DeleteKey(session_handle, region_a_wrapping_key_handle); - DeleteKey(session_handle, data_encryption_key_handle); - DeleteKey(session_handle, parking_key_handle); - CloseSession(session_handle); -} - -} // namespace -} // namespace third_party_cavium_hsm diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/BUILD b/kms/singletenanthsm/brandonluong/cfmwrap/BUILD deleted file mode 100644 index 09a29b0d5c5..00000000000 --- a/kms/singletenanthsm/brandonluong/cfmwrap/BUILD +++ /dev/null @@ -1,60 +0,0 @@ -cc_library( - name = "hsm_functions", - srcs = ["hsm_functions.cc"], - hdrs = ["hsm_functions.h"], - deps = [ - "//cloud/security/hawksbill/cavium:cavium_api_interface_header", - "//cloud/security/hawksbill/cavium:cavium_api_shim", - "//testing/base/public:gunit_headers", - "//third_party/absl/log:check", - "//third_party/absl/status", - "//third_party/absl/status:statusor", - "//third_party/absl/strings", - "//third_party/cavium_hsm/v3_4__14:cavium_hsm", - "//third_party/cavium_hsm/v3_4__14/utils:openssl_util", - "//third_party/openssl:crypto", - "//util/task:status", - ], -) - -cc_test( - name = "hsm_functions_test", - srcs = ["hsm_functions_test.cc"], - deps = [ - ":hsm_functions", - "//base:raw_logging", - "//cloud/security/hawksbill/cavium:cavium_api_shim", - "//cloud/security/hawksbill/cavium:lib_cavium", - "//testing/base/public:gunit_main", - "//third_party/absl/log", - "//third_party/absl/status:statusor", - "//third_party/absl/strings", - "//third_party/openssl:crypto", - "//util/task:status", - ], -) - -cc_binary( - name = "hsm_functions_perf_test", - srcs = ["hsm_functions_perf_test.cc"], - deps = [ - ":hsm_functions", - "//testing/base/public:gunit", - "//third_party/openssl:crypto", - ], -) - -cc_test( - name = "cavium_layers_test", - srcs = ["cavium_layers_test.cc"], - deps = [ - ":hsm_functions", - "//base:raw_logging", - "//cloud/security/hawksbill/cavium:cavium_api_error", - "//cloud/security/hawksbill/cavium:cavium_api_error_enum", - "//cloud/security/hawksbill/cavium:cavium_api_shim", - "//cloud/security/hawksbill/cavium:lib_cavium", - "//testing/base/public:gunit_main", - "//third_party/absl/strings", - ], -) diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/cavium_layers_test.cc b/kms/singletenanthsm/brandonluong/cfmwrap/cavium_layers_test.cc deleted file mode 100644 index a11ca6ef315..00000000000 --- a/kms/singletenanthsm/brandonluong/cfmwrap/cavium_layers_test.cc +++ /dev/null @@ -1,635 +0,0 @@ -#include -#include -#include - -#include "base/raw_logging.h" -#include "cloud/security/hawksbill/cavium/cavium_api_error.h" -#include "cloud/security/hawksbill/cavium/cavium_api_error_enum.h" -#include "cloud/security/hawksbill/cavium/cavium_api_interface.h" -#include "cloud/security/hawksbill/cavium/cavium_api_shim.h" -#include "experimental/users/brandonluong/cfmwrap/hsm_functions.h" -#include "testing/base/public/gmock.h" -#include "testing/base/public/gunit.h" -#include "third_party/absl/strings/string_view.h" - -namespace third_party_cavium_hsm { -namespace { - -using ::testing::Eq; -using ::testing::HasSubstr; -using ::testing::status::StatusIs; - -third_party_cavium_interface::WrapArguments SetWrapArgumentsInputs( - uint32_t session_handle, uint32_t wrapping_key_handle, - uint32_t target_key_handle, uint32_t aes_key_wrap_pad) { - third_party_cavium_interface::WrapArguments pArgs{}; - // inputs - pArgs.common.session_handle = session_handle; - pArgs.common.wrapping_key_handle = wrapping_key_handle; - pArgs.common.mech = aes_key_wrap_pad; - pArgs.common.key.ul_key_handle = target_key_handle; - pArgs.common.key_input_output = - third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; - - return pArgs; -} - -// Set wrapped_key_len to length 8 and buffer length to 8192. Cavium returns a -// buffer too small error. -TEST(CfmWrapWrappedKeyLength, WrappedKeyLenSmallerThanWrappedKey) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap_pad); - - // output args for CfmWrap - uint8_t buffer[8912]; // as hardcoded in Cfm2Util - pArgs.wrapped_key = buffer; - // Set wrapped_key_len to minimum value of 8 to see if an error is returned. - pArgs.wrapped_key_len = 8; - - ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %lu", - sizeof(pArgs.wrapped_key_len)); - - uint64_t status = CaviumCfmWrap(&pArgs); - // EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - absl::Status status_error = - hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap"); - - EXPECT_THAT( - status_error, - StatusIs( - hawksbill::cavium::CaviumErrorSpace::Get(), - Eq(third_party_cavium_interface::CaviumErrorCode::kErrBufferTooSmall), - HasSubstr("ERR_BUFFER_TOO_SMALL"))); - ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); - - std::string output_key = - reinterpret_cast(const_cast(pArgs.wrapped_key)); - ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", - output_key.c_str(), sizeof(output_key)); - - std::vector wrapped_key_vector(output_key.begin(), output_key.end()); - - ABSL_RAW_LOG(INFO, "wrapped_key_len after wrap: %lu", - sizeof(pArgs.wrapped_key_len)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -// Set wrapped_key_len to 8000 and buffer length to 8192. The key was able to be -// wrapped and unwrapped successfully. wrapped_key_len was modified from 8000 -// to 40. NOTE: Sometimes this will randomly fail. -TEST(CfmWrapWrappedKeyLength, WrappedKeyLenSmallerThanBufferSize) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap_pad); - - // output args for CfmWrap - uint8_t buffer[8912]; // as hardcoded in Cfm2Util - pArgs.wrapped_key = buffer; - // Set wrapped_key_len to length less than buffer. - pArgs.wrapped_key_len = 8000; - int wrapped_key_len_original = pArgs.wrapped_key_len; - - ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %lu", - sizeof(pArgs.wrapped_key_len)); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); - - std::string output_key = - reinterpret_cast(const_cast(pArgs.wrapped_key)); - ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", - output_key.c_str(), sizeof(output_key)); - - EXPECT_NE(wrapped_key_len_original, pArgs.wrapped_key_len); - ABSL_RAW_LOG(INFO, "wrapped_key_len_original: %d", wrapped_key_len_original); - ABSL_RAW_LOG(INFO, "wrapped_key_len_after: %d", pArgs.wrapped_key_len); - - std::vector wrapped_key_vector(output_key.begin(), output_key.end()); - - // Wrapped key is able to be successfully unwrapped. Note that the unwrapped - // target key handle can be different from the handle after creation. - ASSERT_OK_AND_ASSIGN( - target_key_handle, - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_key_vector, - SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -// Using a buffer with a size that is less than the wrapped key size will result -// in a segmentation fault. -TEST(CfmWrapWrappedKeyLength, BufferLessThanWrappedKeySize) { - // uint32_t app_handle = Initialize(); - // uint32_t session_handle = OpenSession(app_handle); - // uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - // absl::StatusOr target_key_handle = - // GenerateExtractableSymmetricKey( - // session_handle, SymmetricKeyType::kAes256); - // // uint32_t aes_key_wrap_pad = 0x00001091; - - // WrapKeySmallBuffer(session_handle, wrapping_key_handle, - // target_key_handle.value(), 8192); - - // third_party_cavium_interface::WrapArguments pArgs = - // SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - // target_key_handle.value(), - // aes_key_wrap_pad); - - // output args for CfmWrap - // Set buffer to minimum length of 8. - // uint8_t buffer[8]; // as hardcoded in Cfm2Util - // pArgs.wrapped_key = buffer; - // pArgs.wrapped_key_len = 8192; - // int wrapped_key_len_original = pArgs.wrapped_key_len; - - // ABSL_RAW_LOG(INFO, "wrapped_key_len_original before wrap: %d", - // wrapped_key_len_original); - - // uint64_t status = CaviumCfmWrap(&pArgs); - // EXPECT_EQ(status, 0); - // ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - // absl::Status status_error = - // hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap"); - // EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, - // "CfmWrap")); -} - -// Try swapping target and wrapping key. They target and wrapping keys have -// differerent permissions. -TEST(CfmWrapm, SwapTargetKeyAndWrappingKeyDistinctPermissions) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - // uint32_t aes_key_wrap = 0x00001090; - - // swap target and wrapping key handles. - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, target_key_handle.value(), - wrapping_key_handle, aes_key_wrap_pad); - - // output args for CfmWrap - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - - ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %d", pArgs.wrapped_key_len); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_NE(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - absl::Status status_error = - hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap"); - EXPECT_THAT( - status_error, - StatusIs( - hawksbill::cavium::CaviumErrorSpace::Get(), - Eq(third_party_cavium_interface::CaviumErrorCode::kRetPolicyMismatch), - HasSubstr( - "This operation violates the current configured/FIPS policies"))); - ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); - - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - - ABSL_RAW_LOG(INFO, "wraped key vector size: %lu", sizeof(wrapped_key)); - - // AES_256_KEY_SIZE - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - std::string output_key = - reinterpret_cast(const_cast(pArgs.wrapped_key)); - ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", - output_key.c_str(), sizeof(output_key)); - - std::vector wrapped_key_vector(output_key.begin(), output_key.end()); - - ABSL_RAW_LOG(INFO, "wrapped_key_len after wrap: %d", pArgs.wrapped_key_len); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -TEST(CfmWrapOutputLength, - SwapTargetKeyAndWrappingKeySameExtractablePermissions) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - // wrapping key has same permissions as data key. - uint32_t wrapping_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - // uint32_t aes_key_wrap = 0x00001090; - - // swap target and wrapping key handles. - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, target_key_handle.value(), - wrapping_key_handle, aes_key_wrap_pad); - - // output args for CfmWrap - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - - ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %lu", - sizeof(pArgs.wrapped_key_len)); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); - - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - - ABSL_RAW_LOG(INFO, "wraped key vector size: %lu", sizeof(wrapped_key)); - - // AES_256_KEY_SIZE - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - std::string output_key = - reinterpret_cast(const_cast(pArgs.wrapped_key)); - ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", - output_key.c_str(), sizeof(output_key)); - - std::vector wrapped_key_vector(output_key.begin(), output_key.end()); - - ABSL_RAW_LOG(INFO, "wrapped_key_len after wrap: %lu", - sizeof(pArgs.wrapped_key_len)); - - // Wrapped key is able to be successfully unwrapped. Note that the unwrapped - // target key handle can be different from the handle after creation. - ASSERT_OK_AND_ASSIGN( - target_key_handle, - UnwrapKeyAesKwp(session_handle, target_key_handle.value(), - wrapped_key_vector, SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -TEST(CfmWrapOutputLength, CheckLengthNoPad) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - // uint32_t aes_key_wrap_pad = 0x00001091; - uint32_t aes_key_wrap = 0x00001090; - - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap); - - constexpr int max_request_size = 8192; - // output args for CfmWrap - uint8_t buffer[max_request_size]; // as hardcoded in Cfm2Util - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = max_request_size; - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - // AES_256_KEY_SIZE - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - absl::string_view output_key = - reinterpret_cast(const_cast(pArgs.wrapped_key)); - ABSL_RAW_LOG(INFO, "output_key: %s \n output_key size : %zul", - output_key.data(), output_key.size()); - - // Wrapped key is able to be successfully unwrapped. Note that the unwrapped - // target key handle can be different from the handle after creation. - ASSERT_OK_AND_ASSIGN(target_key_handle, - UnwrapKeyAesKnp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -TEST(CfmWrapOutputLength, CheckLengthWithPad) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - // uint32_t aes_key_wrap = 0x00001090; - - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap_pad); - - // output args for CfmWrap - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - - ABSL_RAW_LOG(INFO, "wrapped_key_len before wrap: %lu", - sizeof(pArgs.wrapped_key_len)); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - ABSL_RAW_LOG(INFO, "buffer_before_cast size: %lu", sizeof(pArgs.wrapped_key)); - - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - - ABSL_RAW_LOG(INFO, "wraped key vector size: %lu", sizeof(wrapped_key)); - - // AES_256_KEY_SIZE - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - std::string output_key = - reinterpret_cast(const_cast(pArgs.wrapped_key)); - ABSL_RAW_LOG(INFO, "output_key: %s \n buffer_after_cast size : %zul", - output_key.c_str(), sizeof(output_key)); - - std::vector wrapped_key_vector(output_key.begin(), output_key.end()); - - ABSL_RAW_LOG(INFO, "wrapped_key_len after wrap: %lu", - sizeof(pArgs.wrapped_key_len)); - - // Wrapped key is able to be successfully unwrapped. Note that the unwrapped - // target key handle can be different from the handle after creation. - ASSERT_OK_AND_ASSIGN( - target_key_handle, - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_key_vector, - SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -// This test is to check if the output wrapped material would have equal -// lengths. Ten target keys were generated and then wrapped. According to the -// logs most sizes are 401, but some are shorter. -TEST(CfmWrapOutoutLength, CheckIfWrappedKeyMaterialHasDistinctLength) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t aes_key_wrap_pad = 0x00001091; - // uint32_t aes_key_wrap = 0x00001090; - - int wrapped_key_lengths[10]; - for (int i = 0; i < 10; i++) { - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = - GenerateExtractableSymmetricKey(session_handle, - SymmetricKeyType::kAes256); - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap_pad); - - // output args for CfmWrap - uint8_t buffer[8192]; // as hardcoded in Cfm2Util - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK( - hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - - auto char_wrapped_key = - reinterpret_cast(const_cast(pArgs.wrapped_key)); - ABSL_RAW_LOG(INFO, "wrapped key: %s", char_wrapped_key); - ABSL_RAW_LOG(INFO, "wrapped key: %lu", sizeof(char_wrapped_key)); - - // AES_256_KEY_SIZE - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - // ABSL_RAW_LOG(INFO, "%d", pArgs.wrapped_key[i]); - } - - absl::string_view output_key = - reinterpret_cast(const_cast(pArgs.wrapped_key)); - ABSL_RAW_LOG(INFO, "output_key: %s \n output_key size : %zul", - output_key.data(), output_key.size()); - wrapped_key_lengths[i] = output_key.size(); - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - } - - for (int i = 0; i < 10; i++) { - ABSL_RAW_LOG(INFO, "wrapped_key_lengths: %d \n ", wrapped_key_lengths[i]); - } - CloseSession(session_handle); -} - -// Local testing for correctness of CaviumCfmWrap within -// cloud/security/hawksbill/cavium/cavium_api_interface.h -TEST(CaviumApiInterface, CfmWrapThenUnwrapSuccess) { - // size fails at 8925, Cfm2Util hardcoded max is 8192 - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap_pad); - - // output args for CfmWrap - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - // Wrapped key is able to be successfully unwrapped. Note that the unwrapped - // target key handle can be different from the handle after creation. - ASSERT_OK_AND_ASSIGN(target_key_handle, - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -// Local testing for correctness of CaviumCfmWrap within -// cloud/security/hawksbill/cavium/cavium_api_interface.h -TEST(CaviumApiInterface, RewrapFailsWithModifiedWrappedKey) { - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - // target key handle saved for key deletion. - uint32_t original_target_key_handle = target_key_handle.value(); - - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap_pad); - - // output args for CfmWrap - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - - // Modify wrappedkey - std::vector wrapped_key( - pArgs.wrapped_key, pArgs.wrapped_key + (pArgs.wrapped_key_len - 1)); - - target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - // Rewrap fails due to modified key - EXPECT_THAT(target_key_handle, - testing::status::StatusIs(absl::StatusCode::kInternal)); - - DeleteKey(session_handle, original_target_key_handle); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -// Testing CaviumShim layer -TEST(CaviumShim, CfmWrapThenUnwrapSuccess) { - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap_pad); - - // output args - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - hawksbill::cavium::CaviumApiShim cavium_api_shim; - uint64_t status = cavium_api_shim.CfmWrap(&pArgs); - - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - ASSERT_OK_AND_ASSIGN(target_key_handle, - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} -TEST(CaviumShim, RewrapFailsWithModifiedWrappedKey) { - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - uint32_t original_target_key_handle = target_key_handle.value(); - - third_party_cavium_interface::WrapArguments pArgs = - SetWrapArgumentsInputs(session_handle, wrapping_key_handle, - target_key_handle.value(), aes_key_wrap_pad); - - // output args - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - hawksbill::cavium::CaviumApiShim cavium_api_shim; - uint64_t status = cavium_api_shim.CfmWrap(&pArgs); - - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - - // Modify wrappedkey - std::vector wrapped_key( - pArgs.wrapped_key, pArgs.wrapped_key + (pArgs.wrapped_key_len - 1)); - - target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - EXPECT_THAT(target_key_handle, - testing::status::StatusIs(absl::StatusCode::kInternal)); - - DeleteKey(session_handle, original_target_key_handle); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -} // namespace -} // namespace third_party_cavium_hsm \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.cc b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.cc deleted file mode 100644 index b22f8b181e9..00000000000 --- a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.cc +++ /dev/null @@ -1,507 +0,0 @@ -#include "experimental/users/brandonluong/cfmwrap/hsm_functions.h" - -#include -#include - -#include "third_party/absl/log/check.h" -#include "third_party/absl/status/status.h" -#include "third_party/absl/status/statusor.h" -#include "third_party/absl/strings/escaping.h" -#include "third_party/absl/strings/substitute.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_args.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_defines.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_errors.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_mgmt.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_structs.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_wrappers.h" -#include "third_party/cavium_hsm/v3_4__14/utils/openssl_util.h" -#include "third_party/openssl/bn.h" -#include "third_party/openssl/ec.h" -#include "third_party/openssl/ec_key.h" -#include "third_party/openssl/rand.h" -#include "third_party/openssl/rsa.h" - -#define CHECK_SUCCESS(expr) \ - { \ - int32_t ret = expr; \ - CHECK_EQ(ret, 0) << Cfm2ResultAsString(ret); \ - } - -namespace third_party_cavium_hsm { - -std::vector RandomLabel() { - uint8_t rand[16]; - RAND_bytes(rand, sizeof(rand)); - std::string hex = absl::BytesToHexString( - absl::string_view(reinterpret_cast(rand), sizeof(rand))); - CHECK_EQ(hex.size(), 32); - return std::vector(hex.begin(), hex.end()); -} - -uint32_t Test_Init() { - uint32_t application_handle; - Cfm2Initialize2(167936, DIRECT, &application_handle); - - return application_handle; -} - -uint32_t Initialize() { - uint32_t application_handle; - CHECK_SUCCESS(Cfm2Initialize2(0, DIRECT, &application_handle)); - - uint32_t session_handle; - CHECK_SUCCESS(Cfm2OpenSession2(application_handle, &session_handle)); - - constexpr absl::string_view kUsername = "crypto_user"; - constexpr absl::string_view kPassword = "user123"; - - std::vector encrypted_password(PSWD_ENC_KEY_MODULUS, 0); - uint32_t password_size = encrypted_password.size(); - CHECK_SUCCESS(utils::encrypt_pswd( - session_handle, - reinterpret_cast(const_cast(kPassword.data())), - kPassword.size(), encrypted_password.data(), &password_size)); - encrypted_password.resize(password_size); - - CHECK_SUCCESS(Cfm2LoginHSM2( - session_handle, CN_CRYPTO_USER, - reinterpret_cast(const_cast(kUsername.data())), - kUsername.size(), encrypted_password.data(), encrypted_password.size(), - /*signature=*/nullptr)); - - return application_handle; -} - -uint32_t OpenSession(uint32_t application_handle) { - uint32_t session_handle; - CHECK_SUCCESS(Cfm2OpenSession2(application_handle, &session_handle)); - return session_handle; -} - -void CloseSession(uint32_t session_handle) { - CHECK_SUCCESS(Cfm2CloseSession(session_handle)); -} - -uint64_t GenerateParkingKey(uint32_t session_handle) { - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - args.ulSessionHandle = session_handle; - args.key.info.ucKeyLocation = STORAGE_FLASH; - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - args.key.info.bParking = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - CHECK_SUCCESS(CfmGenerateKey(&args)); - return args.key.ulKeyHandle; -} - -uint64_t GenerateAesWrappingKey(uint32_t session_handle) { - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - args.ulSessionHandle = session_handle; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 0; - args.key.info.bParkable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - - default_attributes(&args.key.info); - CHECK_SUCCESS(CfmGenerateKey(&args)); - return args.key.ulKeyHandle; -} - -uint64_t GenerateExtractableSymmetricKey(uint32_t session_handle, - SymmetricKeyType key_type) { - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - args.ulSessionHandle = session_handle; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 1; - args.key.info.bParkable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - switch (key_type) { - case SymmetricKeyType::kAes256: - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - break; - case SymmetricKeyType::kHkdfSha256: - args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; - args.key.info.ulKeyLen = 32; - args.key.info.ucDerive = 1; - break; - } - - default_attributes(&args.key.info); - CHECK_SUCCESS(CfmGenerateKey(&args)); - return args.key.ulKeyHandle; -} - -KeyPair GenerateEcdhKeypair(uint32_t session_handle, int curve) { - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - args.ulSessionHandle = session_handle; - - args.pubkey.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.pubkey.info.bParkable = 1; - args.pubkey.info.ulKeyClass = OBJ_CLASS_PUBLIC_KEY; - args.pubkey.info.ulCurveID = curve; - args.pubkey.info.ulMValue = 1; - args.pubkey.info.pLabel = label.data(); - args.pubkey.info.ulLabelLen = label.size(); - args.pubkey.info.ulKeyType = KEY_TYPE_EC; - args.pubkey.info.ucDerive = 1; - default_attributes(&args.pubkey.info); - - args.privkey.info.ulKeyClass = OBJ_CLASS_PRIVATE_KEY; - args.privkey.info.ulCurveID = args.pubkey.info.ulCurveID; - args.privkey.info.ulKeyType = args.pubkey.info.ulKeyType; - args.privkey.info.ucDerive = 1; - - copy_public_attr_to_priv_attr(&args); - - CHECK_SUCCESS(CfmGenerateKeyPair(&args)); - return {.pub = args.pubkey.ulKeyHandle, .prv = args.privkey.ulKeyHandle}; -} - -std::vector ParkKey(uint32_t session_handle, - uint64_t parking_key_handle, - uint64_t target_key_handle) { - uint8_t buffer[6000]; // as hardcoded in Cfm2Util - - parkOpArgs args = {0}; - args.pParkedObject = buffer; - args.ulParkedObjectLen = sizeof(buffer); - args.ulSessionHandle = session_handle; - args.ulParkingKeyHandle = parking_key_handle; - args.ulObjectHandle = target_key_handle; - CHECK_SUCCESS(CfmParkObject(&args)); - - uint8_t* parked_key_start = GET_KEY1_ATTR(args.pParkedObject); - return std::vector(parked_key_start, - parked_key_start + args.ulParkedObjectLen); -} - -uint64_t UnparkKey(uint32_t session_handle, uint64_t parking_key_handle, - std::vector& parked_key) { - unparkOpArgs args = {0}; - args.pParkedObject = parked_key.data(); - args.ulParkedObjectLen = parked_key.size(); - args.ulSessionHandle = session_handle; - args.ulParkingKeyHandle = parking_key_handle; - args.ephemeralStorage = 1; - CHECK_SUCCESS(CfmUnparkObject(&args)); - return args.ulObjectHandle; -} - -void InitializeWrapArgs(third_party_cavium_interface::WrapArguments pArgs) { - // size fails at 8925, Cfm2Util hardcoded max is 8192 - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - // input args - pArgs.common.session_handle = session_handle; - pArgs.common.wrapping_key_handle = wrapping_key_handle; - pArgs.common.mech = aes_key_wrap_pad; - pArgs.common.key.ul_key_handle = target_key_handle.value(); - pArgs.common.key_input_output = - third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; - - // output args - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); -} - -std::vector WrapKeyAesKwp(uint32_t session_handle, - uint64_t wrapping_key_handle, - uint64_t target_key_handle) { - uint8_t buffer[MAX_DATA_LENGTH]; // as hardcoded in Cfm2Util - - wrapArgs args = {0}; - args.aes_key_len = AES_256_KEY_SIZE; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.key.ulKeyHandle = target_key_handle; - args.pKey = buffer; - args.ulKeyLen = sizeof(buffer); - args.ulSessionHandle = session_handle; - CHECK_SUCCESS(CfmWrap(&args)); - - return std::vector(buffer, buffer + args.ulKeyLen); -} - -void WrapKeySmallBuffer(uint32_t session_handle, uint64_t wrapping_key_handle, - uint64_t target_key_handle, int wrapped_key_len) { - uint8_t buffer[7000]; // as hardcoded in Cfm2Util - - wrapArgs args = {0}; - args.aes_key_len = AES_256_KEY_SIZE; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.key.ulKeyHandle = target_key_handle; - args.pKey = buffer; - args.ulKeyLen = 7050; - args.ulSessionHandle = session_handle; - CHECK_SUCCESS(CfmWrap(&args)); - // CfmWrap(&args); - - // return std::vector(buffer, buffer + args.ulKeyLen); -} - -std::vector WrapKeyRsaOaep(uint32_t session_handle, - uint64_t wrapping_key_handle, - uint64_t target_key_handle) { - uint8_t buffer[MAX_DATA_LENGTH]; // as hardcoded in Cfm2Util - - wrapArgs args = {0}; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_RSA_OAEP_KEY_WRAP; - args.hash_type = HashType::SHA256_TYPE; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.key.ulKeyHandle = target_key_handle; - args.pKey = buffer; - args.ulKeyLen = sizeof(buffer); - args.ulSessionHandle = session_handle; - CHECK_SUCCESS(CfmWrap(&args)); - - return std::vector(buffer, buffer + args.ulKeyLen); -} - -absl::StatusOr UnwrapKeyAesKwp(uint32_t session_handle, - uint64_t wrapping_key_handle, - std::vector& wrapped_key, - SymmetricKeyType key_type) { - std::vector label = RandomLabel(); - - unwrapArgs args = {0}; - - args.aes_key_len = AES_256_KEY_SIZE; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_AES_KEY_WRAP_PAD; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.pKey = wrapped_key.data(); - args.ulKeyLen = wrapped_key.size(); - args.ulSessionHandle = session_handle; - - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - switch (key_type) { - case SymmetricKeyType::kAes256: - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - break; - case SymmetricKeyType::kHkdfSha256: - args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; - args.key.info.ulKeyLen = 32; - args.key.info.ucDerive = 1; - break; - } - default_attributes(&args.key.info); - int32_t ret = CfmUnwrap(&args); - if (ret == 0) { - return args.key.ulKeyHandle; - } else { - return absl::InternalError(absl::Substitute("Cavium Error")); - } -} - -absl::StatusOr UnwrapKeyAesKnp(uint32_t session_handle, - uint64_t wrapping_key_handle, - std::vector& wrapped_key, - SymmetricKeyType key_type) { - std::vector label = RandomLabel(); - - unwrapArgs args = {0}; - - args.aes_key_len = AES_256_KEY_SIZE; - args.KeyOutput = ENCRYPTED; - args.ulMech = CRYPTO_MECH_AES_KEY_WRAP; - args.ulWrappingKeyHandle = wrapping_key_handle; - args.pKey = wrapped_key.data(); - args.ulKeyLen = wrapped_key.size(); - args.ulSessionHandle = session_handle; - - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - switch (key_type) { - case SymmetricKeyType::kAes256: - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - break; - case SymmetricKeyType::kHkdfSha256: - args.key.info.ulKeyType = KEY_TYPE_GENERIC_SECRET; - args.key.info.ulKeyLen = 32; - args.key.info.ucDerive = 1; - break; - } - default_attributes(&args.key.info); - int32_t ret = CfmUnwrap(&args); - if (ret == 0) { - return args.key.ulKeyHandle; - } else { - return absl::InternalError(absl::Substitute("Cavium Error")); - } -} - -uint64_t DeriveAes256KeyHkdfSha256(uint32_t session_handle, uint64_t key_handle, - std::vector info) { - std::vector label = RandomLabel(); - - deriveKeyArgs args = {0}; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.bExtractable = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - default_attributes(&args.key.info); - - args.ulSessionHandle = session_handle; - args.hBaseKey = key_handle; - constexpr uint64_t kCkdSha256Kdf = 0x06UL; - args.ulDeriveMech = - CRYPTO_MECH_LIQ_SEC(DERIVE_MECH_HKDF) + (kCkdSha256Kdf << 16); - args.prfCtx = info.data(); - args.ulPrfCtxLen = info.size(); - args.ulDKMLengthMethod = SP800_108_DKM_LENGTH_SUM_OF_KEYS; - - CHECK_SUCCESS(CfmDeriveKey(&args)); - return args.key.ulKeyHandle; -} - -uint64_t DeriveAes256KeyEcdhHkdfSha256(uint32_t session_handle, - uint64_t local_private_key_handle, - EC_KEY* remote_public_key) { - std::vector label = RandomLabel(); - - deriveKeyArgs args = {0}; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_SECRET_KEY; - args.key.info.ulMValue = 1; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - args.key.info.ulKeyType = KEY_TYPE_AES; - args.key.info.ulKeyLen = 32; - default_attributes(&args.key.info); - - args.ulSessionHandle = session_handle; - args.hBaseKey = local_private_key_handle; - constexpr uint64_t kCkdSha256Kdf = 0x06UL; - args.ulDeriveMech = - CRYPTO_MECH_LIQ_SEC(DERIVE_MECH_ECDH_HKDF) + (kCkdSha256Kdf << 16); - args.ulDKMLengthMethod = SP800_108_DKM_LENGTH_SUM_OF_KEYS; - - std::vector serialized_remote_public_key; - - serialized_remote_public_key.resize( - EC_POINT_point2oct(EC_KEY_get0_group(remote_public_key), - EC_KEY_get0_public_key(remote_public_key), - POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr)); - CHECK_EQ(EC_POINT_point2oct(EC_KEY_get0_group(remote_public_key), - EC_KEY_get0_public_key(remote_public_key), - POINT_CONVERSION_UNCOMPRESSED, - serialized_remote_public_key.data(), - serialized_remote_public_key.size(), nullptr), - serialized_remote_public_key.size()); - args.pPubKey = serialized_remote_public_key.data(); - args.ulPubKeyLen = serialized_remote_public_key.size(); - - CHECK_SUCCESS(CfmDeriveKey(&args)); - return args.key.ulKeyHandle; -} - -bssl::UniquePtr ExportEcPublicKey(uint32_t session_handle, - uint64_t key_handle, int curve_id) { - bssl::UniquePtr group(EC_GROUP_new_by_curve_name(curve_id)); - CHECK_NE(group, nullptr); - - uint32_t buf_length = 1024; - std::vector buf; - buf.resize(buf_length); - - CHECK_SUCCESS( - Cfm2ExportPublicKey(session_handle, key_handle, buf.data(), &buf_length)); - - bssl::UniquePtr key(EC_KEY_new()); - CHECK_NE(key, nullptr); - CHECK_EQ(EC_KEY_set_group(key.get(), group.get()), 1); - - bssl::UniquePtr point(EC_POINT_new(group.get())); - CHECK_NE(point, nullptr); - CHECK_EQ(EC_POINT_oct2point(group.get(), point.get(), buf.data(), buf_length, - nullptr), - 1); - - CHECK_EQ(EC_KEY_set_public_key(key.get(), point.get()), 1); - - return key; -} - -void DeleteKey(uint32_t session_handle, uint64_t key_handle) { - CHECK_SUCCESS(Cfm2DeleteKey(session_handle, key_handle)); -} - -uint64_t ImportRsaPublicKey(uint32_t session_handle, const RSA* rsa_pub) { - CHECK_NE(rsa_pub, nullptr); - - std::vector label = RandomLabel(); - - genKeyArgs args = {0}; - - args.ulSessionHandle = session_handle; - args.key.info.ucKeyLocation = STORAGE_EPHEMERAL; - args.key.info.ulKeyClass = OBJ_CLASS_PUBLIC_KEY; - args.key.info.pLabel = label.data(); - args.key.info.ulLabelLen = label.size(); - args.key.info.ulKeyType = KEY_TYPE_RSA; - - const BIGNUM *n, *e, *d; - RSA_get0_key(rsa_pub, &n, &e, &d); - CHECK_NE(n, nullptr); - CHECK_NE(e, nullptr); - - std::vector modulus; - modulus.resize(BN_num_bytes(n)); - BN_bn2bin(n, modulus.data()); - args.key.info.pModulus = modulus.data(); - args.key.info.ulModLenInBits = modulus.size() * 8; - - uint64_t pub_exp; - CHECK_EQ(BN_get_u64(e, &pub_exp), 1); - args.key.info.ulPubExp = pub_exp; - - default_attributes(&args.key.info); - - CHECK_SUCCESS(CfmCreateObject(&args)); - return args.key.ulKeyHandle; -} - -} // namespace third_party_cavium_hsm diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.h b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.h deleted file mode 100644 index 5081e863686..00000000000 --- a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef EXPERIMENTAL_USERS_BRANDONLUONG_CFMWRAP_HSM_FUNCTIONS_H_ -#define EXPERIMENTAL_USERS_BRANDONLUONG_CFMWRAP_HSM_FUNCTIONS_H_ - -#include -#include - -#include "cloud/security/hawksbill/cavium/cavium_api_interface.h" -#include "cloud/security/hawksbill/cavium/cavium_api_shim.h" -#include "third_party/absl/status/statusor.h" -#include "third_party/cavium_hsm/v3_4__14/cavium_structs.h" -#include "third_party/openssl/ec_key.h" -#include "third_party/openssl/rsa.h" - -namespace third_party_cavium_hsm { - -std::vector RandomLabel(); - -uint32_t Test_Init(); - -uint32_t Initialize(); -void Finalize(uint32_t application_handle); - -uint32_t OpenSession(uint32_t application_handle); -void CloseSession(uint32_t session_handle); - -uint64_t GenerateParkingKey(uint32_t session_handle); - -void InitializeWrapArgs(third_party_cavium_interface::WrapArguments* pArgs); - -uint64_t GenerateAesWrappingKey(uint32_t session_handle); - -enum class SymmetricKeyType { kAes256, kHkdfSha256 }; - -uint64_t GenerateExtractableSymmetricKey(uint32_t session_handle, - SymmetricKeyType key_type); - -struct KeyPair { - uint64_t pub; - uint64_t prv; -}; - -KeyPair GenerateEcdhKeypair(uint32_t session_handle, int curve); - -std::vector ParkKey(uint32_t session_handle, - uint64_t parking_key_handle, - uint64_t target_key_handle); - -uint64_t UnparkKey(uint32_t session_handle, uint64_t parking_key_handle, - std::vector& parked_key); - -std::vector WrapKeyAesKwp(uint32_t session_handle, - uint64_t wrapping_key_handle, - uint64_t target_key_handle); - -void WrapKeySmallBuffer(uint32_t session_handle, uint64_t wrapping_key_handle, - uint64_t target_key_handle, int wrapped_key_len); - -uint64_t ImportRsaPublicKey(uint32_t session_handle, const RSA* rsa_pub); - -std::vector WrapKeyRsaOaep(uint32_t session_handle, - uint64_t wrapping_key_handle, - uint64_t target_key_handle); - -absl::StatusOr UnwrapKeyAesKwp(uint32_t session_handle, - uint64_t wrapping_key_handle, - std::vector& wrapped_key, - SymmetricKeyType key_type); - -absl::StatusOr UnwrapKeyAesKnp(uint32_t session_handle, - uint64_t wrapping_key_handle, - std::vector& wrapped_key, - SymmetricKeyType key_type); - -uint64_t DeriveAes256KeyHkdfSha256(uint32_t session_handle, uint64_t key_handle, - std::vector info); - -uint64_t DeriveAes256KeyEcdhHkdfSha256(uint32_t session_handle, - uint64_t local_private_key_handle, - EC_KEY* remote_public_key); - -bssl::UniquePtr ExportEcPublicKey(uint32_t session_handle, - uint64_t key_handle, int curve_id); - -void DeleteKey(uint32_t session_handle, uint64_t key_handle); - -} // namespace third_party_cavium_hsm - -#endif // EXPERIMENTAL_USERS_BRANDONLUONG_CFMWRAP_HSM_FUNCTIONS_H_ diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_perf_test.cc b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_perf_test.cc deleted file mode 100644 index e1ccb072031..00000000000 --- a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_perf_test.cc +++ /dev/null @@ -1,360 +0,0 @@ - -#include -#include - -#include "experimental/users/brandonluong/cfmwrap/hsm_functions..h" -#include "testing/base/public/benchmark.h" -#include "testing/base/public/gunit.h" -#include "third_party/openssl/aes.h" -#include "third_party/openssl/base.h" -#include "third_party/openssl/bn.h" -#include "third_party/openssl/ec.h" -#include "third_party/openssl/ec_key.h" -#include "third_party/openssl/evp.h" -#include "third_party/openssl/nid.h" -#include "third_party/openssl/rsa.h" - -namespace third_party_cavium_hsm { -namespace { - -#define HSM_BENCHMARK(expr) BENCHMARK(expr)->Threads(8)->UseRealTime(); - -uint32_t application_handle; - -// Initialize writes junk to stdout which makes the results unpretty, so make -// sure it only runs once. -void SetupGlobalState() { application_handle = Initialize(); } - -void BM_ParkSymmetricKey(benchmark::State& state) { - uint32_t session_handle = OpenSession(application_handle); - - uint64_t parking_key_handle = GenerateParkingKey(session_handle); - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - for (auto s : state) { - benchmark::DoNotOptimize( - ParkKey(session_handle, parking_key_handle, data_key_handle)); - } - - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, parking_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -HSM_BENCHMARK(BM_ParkSymmetricKey); - -void BM_WrapSymmetricKey(benchmark::State& state) { - uint32_t session_handle = OpenSession(application_handle); - - uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - for (auto s : state) { - benchmark::DoNotOptimize( - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle)); - } - - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -HSM_BENCHMARK(BM_WrapSymmetricKey); - -void BM_UnparkSymmetricKey(benchmark::State& state) { - uint32_t session_handle = OpenSession(application_handle); - - uint64_t parking_key_handle = GenerateParkingKey(session_handle); - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - std::vector parked_key = - ParkKey(session_handle, parking_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - for (auto s : state) { - data_key_handle = UnparkKey(session_handle, parking_key_handle, parked_key); - DeleteKey(session_handle, data_key_handle); - } - - DeleteKey(session_handle, parking_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -HSM_BENCHMARK(BM_UnparkSymmetricKey); - -void BM_UnwrapSymmetricKey(benchmark::State& state) { - uint32_t session_handle = OpenSession(application_handle); - - uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - std::vector wrapped_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - for (auto s : state) { - data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - DeleteKey(session_handle, data_key_handle); - } - - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -HSM_BENCHMARK(BM_UnwrapSymmetricKey); - -void GenerateAndWrapSymmetric(benchmark::State& state, - SymmetricKeyType key_type) { - uint32_t session_handle = OpenSession(application_handle); - uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - - for (auto s : state) { - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - benchmark::DoNotOptimize( - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle)); - - DeleteKey(session_handle, data_key_handle); - } - - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -void BM_GenerateAndWrapAes256(benchmark::State& state) { - GenerateAndWrapSymmetric(state, SymmetricKeyType::kAes256); -} - -HSM_BENCHMARK(BM_GenerateAndWrapAes256); - -void BM_GenerateAndWrapHkdfSha256(benchmark::State& state) { - GenerateAndWrapSymmetric(state, SymmetricKeyType::kHkdfSha256); -} - -HSM_BENCHMARK(BM_GenerateAndWrapHkdfSha256); - -void BM_UnwrapAes256(benchmark::State& state) { - uint32_t session_handle = OpenSession(application_handle); - uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - std::vector wrapped_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - for (auto s : state) { - data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - DeleteKey(session_handle, data_key_handle); - } - - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -HSM_BENCHMARK(BM_UnwrapAes256); - -void BM_UnwrapAndDeriveHkdfSha256(benchmark::State& state) { - uint32_t session_handle = OpenSession(application_handle); - uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - - uint64_t derivation_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kHkdfSha256); - std::vector wrapped_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, derivation_key_handle); - DeleteKey(session_handle, derivation_key_handle); - - for (auto s : state) { - derivation_key_handle = - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_key, - SymmetricKeyType::kHkdfSha256); - - uint64_t data_key_handle = DeriveAes256KeyHkdfSha256( - session_handle, derivation_key_handle, RandomLabel()); - - DeleteKey(session_handle, derivation_key_handle); - DeleteKey(session_handle, data_key_handle); - } - - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -HSM_BENCHMARK(BM_UnwrapAndDeriveHkdfSha256); - -void UnwrapAesKwpRewrapRsa(benchmark::State& state, int rsa_bits) { - bssl::UniquePtr f4(BN_new()); - CHECK_EQ(BN_set_u64(f4.get(), RSA_F4), 1); - bssl::UniquePtr rsa(RSA_new()); - CHECK_EQ(RSA_generate_key_ex(rsa.get(), rsa_bits, f4.get(), nullptr), 1); - - uint32_t session_handle = OpenSession(application_handle); - - uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - std::vector wrapped_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - for (auto s : state) { - data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - uint64_t public_key_handle = ImportRsaPublicKey(session_handle, rsa.get()); - - benchmark::DoNotOptimize( - WrapKeyRsaOaep(session_handle, public_key_handle, data_key_handle)); - - DeleteKey(session_handle, public_key_handle); - DeleteKey(session_handle, data_key_handle); - } - - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -void BM_UnwrapAesKwpRewrapRsa3072(benchmark::State& state) { - UnwrapAesKwpRewrapRsa(state, 3072); -} - -HSM_BENCHMARK(BM_UnwrapAesKwpRewrapRsa3072); - -void BM_UnwrapAesKwpRewrapRsa4096(benchmark::State& state) { - UnwrapAesKwpRewrapRsa(state, 4096); -} - -HSM_BENCHMARK(BM_UnwrapAesKwpRewrapRsa4096); - -void UnwrapAesKwpRewrapEcdh(benchmark::State& state, int curve_nid) { - bssl::UniquePtr group(EC_GROUP_new_by_curve_name(curve_nid)); - bssl::UniquePtr recipient_key(EC_KEY_new()); - CHECK_EQ(EC_KEY_set_group(recipient_key.get(), group.get()), 1); - CHECK_EQ(EC_KEY_generate_key_fips(recipient_key.get()), 1); - - uint32_t session_handle = OpenSession(application_handle); - - KeyPair sender_handles = GenerateEcdhKeypair(session_handle, curve_nid); - - // Assume this is done at HSM startup or something like that, rather than - // in band in each data plane operation. - bssl::UniquePtr agree_pub_key = - ExportEcPublicKey(session_handle, sender_handles.pub, curve_nid); - - uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - std::vector wrapped_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - for (auto s : state) { - data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - uint64_t derived_key_handle = DeriveAes256KeyEcdhHkdfSha256( - session_handle, sender_handles.prv, recipient_key.get()); - - benchmark::DoNotOptimize( - WrapKeyAesKwp(session_handle, derived_key_handle, data_key_handle)); - - DeleteKey(session_handle, derived_key_handle); - DeleteKey(session_handle, data_key_handle); - } - - DeleteKey(session_handle, wrapping_key_handle); - DeleteKey(session_handle, sender_handles.pub); - DeleteKey(session_handle, sender_handles.prv); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -void BM_UnwrapAesKwpRewrapEcdhP256(benchmark::State& state) { - UnwrapAesKwpRewrapEcdh(state, NID_X9_62_prime256v1); -} - -HSM_BENCHMARK(BM_UnwrapAesKwpRewrapEcdhP256); - -void BM_UnwrapAesKwpRewrapEcdhP384(benchmark::State& state) { - UnwrapAesKwpRewrapEcdh(state, NID_secp384r1); -} - -HSM_BENCHMARK(BM_UnwrapAesKwpRewrapEcdhP384); - -void BM_UnwrapAesKwpRewrapEcdhP521(benchmark::State& state) { - UnwrapAesKwpRewrapEcdh(state, NID_secp521r1); -} - -HSM_BENCHMARK(BM_UnwrapAesKwpRewrapEcdhP521); - -void BM_ReencryptAesKwp(benchmark::State& state) { - uint32_t session_handle = OpenSession(application_handle); - - uint64_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - uint64_t session_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint64_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - std::vector wrapped_session_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, session_key_handle); - DeleteKey(session_handle, session_key_handle); - - std::vector wrapped_data_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - for (auto s : state) { - session_key_handle = - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_session_key, SymmetricKeyType::kAes256); - data_key_handle = - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_data_key, - SymmetricKeyType::kAes256); - - benchmark::DoNotOptimize( - WrapKeyAesKwp(session_handle, session_key_handle, data_key_handle)); - - DeleteKey(session_handle, session_key_handle); - DeleteKey(session_handle, data_key_handle); - } - - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); - - state.SetItemsProcessed(state.iterations()); -} - -HSM_BENCHMARK(BM_ReencryptAesKwp); - -} // namespace -} // namespace third_party_cavium_hsm - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - third_party_cavium_hsm::SetupGlobalState(); - RunSpecifiedBenchmarks(); - return 0; -} \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_test.cc b/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_test.cc deleted file mode 100644 index 9e9b4e3c2ea..00000000000 --- a/kms/singletenanthsm/brandonluong/cfmwrap/hsm_functions_test.cc +++ /dev/null @@ -1,485 +0,0 @@ -#include "experimental/users/brandonluong/cfmwrap/hsm_functions.h" - -#include - -#include "base/raw_logging.h" -#include "testing/base/public/gmock.h" -#include "testing/base/public/gunit.h" -#include "third_party/absl/log/log.h" -#include "third_party/absl/status/statusor.h" -#include "third_party/absl/strings/substitute.h" -#include "third_party/openssl/aes.h" -#include "third_party/openssl/base.h" -#include "third_party/openssl/bn.h" -#include "third_party/openssl/ec.h" -#include "third_party/openssl/ec_key.h" -#include "third_party/openssl/ecdh.h" -#include "third_party/openssl/evp.h" -#include "third_party/openssl/hkdf.h" -#include "third_party/openssl/rsa.h" - -namespace third_party_cavium_hsm { -namespace { - -TEST(HsmFunctionsTest, InitializeHSM) { - uint32_t app_handle = Initialize(); - ABSL_RAW_LOG(INFO, "app_handle %d", app_handle); - uint32_t session_handle = OpenSession(app_handle); - - // // uint32_t session_handle = OpenSession(app_handle); - ABSL_RAW_LOG(INFO, "session handle : %d\n app handle: %d\n", session_handle, - app_handle); -} - -TEST(HsmFunctionsTest, GenerateParkUnparkAes256) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t parking_key_handle = GenerateParkingKey(session_handle); - uint32_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - std::vector parked_key = - ParkKey(session_handle, parking_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - data_key_handle = UnparkKey(session_handle, parking_key_handle, parked_key); - - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, parking_key_handle); - CloseSession(session_handle); -} - -TEST(HsmFunctionsTest, GenerateParkUnparkHkdfSha256) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t parking_key_handle = GenerateParkingKey(session_handle); - uint32_t derivation_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kHkdfSha256); - - std::vector parked_key = - ParkKey(session_handle, parking_key_handle, derivation_key_handle); - DeleteKey(session_handle, derivation_key_handle); - - derivation_key_handle = - UnparkKey(session_handle, parking_key_handle, parked_key); - - uint32_t data_key_handle = DeriveAes256KeyHkdfSha256( - session_handle, derivation_key_handle, RandomLabel()); - - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, derivation_key_handle); - DeleteKey(session_handle, parking_key_handle); - CloseSession(session_handle); -} - -// Local testing for correctness of CaviumCfmWrap within -// cloud/security/hawksbill/cavium/cavium_api_interface.h -TEST(CaviumApiInterface, CfmWrapThenUnwrapSuccess) { - // size fails at 8925, Cfm2Util hardcoded max is 8192 - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - third_party_cavium_interface::WrapArguments pArgs{}; - // input args for CfmWrap - pArgs.common.session_handle = session_handle; - pArgs.common.wrapping_key_handle = wrapping_key_handle; - pArgs.common.mech = aes_key_wrap_pad; - pArgs.common.key.ul_key_handle = target_key_handle.value(); - pArgs.common.key_input_output = - third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; - - // output args for CfmWrap - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - // Wrapped key is able to be successfully unwrapped. Note that the unwrapped - // target key handle can be different from the handle after creation. - ASSERT_OK_AND_ASSIGN(target_key_handle, - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} -TEST(CaviumApiInterface, CreateWrappingKeyUnparkWrapUnwrap) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t parking_key_handle = GenerateParkingKey(session_handle); - // Create wrapping key - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - - // park wrapping key - std::vector parked_wrapping_key = - ParkKey(session_handle, parking_key_handle, wrapping_key_handle); - - LOG(INFO) << absl::Substitute("parked_wrapping_key size: $0", - parked_wrapping_key.size()); - - // unpark wrapping key - uint32_t unparked_wrapping_key_handle = - UnparkKey(session_handle, parking_key_handle, parked_wrapping_key); - - // create data key - absl::StatusOr data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - // wrap data key with wrapping key - uint8_t buffer[8924]; - uint32_t aes_key_wrap_pad = 0x00001091; - third_party_cavium_interface::WrapArguments pArgs{}; - // input args for CfmWrap - pArgs.common.session_handle = session_handle; - pArgs.common.wrapping_key_handle = unparked_wrapping_key_handle; - pArgs.common.mech = aes_key_wrap_pad; - pArgs.common.key.ul_key_handle = data_key_handle.value(); - pArgs.common.key_input_output = - third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - ASSERT_OK_AND_ASSIGN(data_key_handle, - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, data_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -// Local testing for correctness of CaviumCfmWrap within -// cloud/security/hawksbill/cavium/cavium_api_interface.h -TEST(CaviumApiInterface, RewrapFailsWithModifiedWrappedKey) { - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - // target key handle saved for key deletion. - uint32_t original_target_key_handle = target_key_handle.value(); - - third_party_cavium_interface::WrapArguments pArgs{}; - // input args for CfmWrap - pArgs.common.session_handle = session_handle; - pArgs.common.wrapping_key_handle = wrapping_key_handle; - pArgs.common.mech = aes_key_wrap_pad; - pArgs.common.key.ul_key_handle = target_key_handle.value(); - pArgs.common.key_input_output = - third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; - - // output args for CfmWrap - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - - uint64_t status = CaviumCfmWrap(&pArgs); - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - - // Modify wrappedkey - std::vector wrapped_key( - pArgs.wrapped_key, pArgs.wrapped_key + (pArgs.wrapped_key_len - 1)); - - target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - // Rewrap fails due to modified key - EXPECT_THAT(target_key_handle, - testing::status::StatusIs(absl::StatusCode::kInternal)); - - DeleteKey(session_handle, original_target_key_handle); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -// Testing CaviumShim layer -TEST(CaviumShim, CfmWrapThenUnwrapSuccess) { - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - third_party_cavium_interface::WrapArguments pArgs{}; - - // input args - pArgs.common.session_handle = session_handle; - pArgs.common.wrapping_key_handle = wrapping_key_handle; - pArgs.common.mech = aes_key_wrap_pad; - pArgs.common.key.ul_key_handle = target_key_handle.value(); - pArgs.common.key_input_output = - third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; - - // output args - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - hawksbill::cavium::CaviumApiShim cavium_api_shim; - uint64_t status = cavium_api_shim.CfmWrap(&pArgs); - - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - std::vector wrapped_key(pArgs.wrapped_key, - pArgs.wrapped_key + pArgs.wrapped_key_len); - for (int i = 0; i < wrapped_key.size(); i++) { - EXPECT_EQ(wrapped_key[i], pArgs.wrapped_key[i]); - } - - target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - ASSERT_OK_AND_ASSIGN(target_key_handle, - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256)); - - DeleteKey(session_handle, target_key_handle.value()); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -TEST(Caviumshim, RewrapFailsWithModifiedWrappedKey) { - uint8_t buffer[8924]; // as hardcoded in Cfm2Util - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - absl::StatusOr target_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - uint32_t aes_key_wrap_pad = 0x00001091; - - uint32_t original_target_key_handle = target_key_handle.value(); - - third_party_cavium_interface::WrapArguments pArgs{}; - - // input args - pArgs.common.session_handle = session_handle; - pArgs.common.wrapping_key_handle = wrapping_key_handle; - pArgs.common.mech = aes_key_wrap_pad; - pArgs.common.key.ul_key_handle = target_key_handle.value(); - pArgs.common.key_input_output = - third_party_cavium_interface::CaviumInputType::CAVIUM_ENCRYPTED; - - // output args - pArgs.wrapped_key = buffer; - pArgs.wrapped_key_len = sizeof(buffer); - hawksbill::cavium::CaviumApiShim cavium_api_shim; - uint64_t status = cavium_api_shim.CfmWrap(&pArgs); - - EXPECT_EQ(status, 0); - ABSL_RAW_LOG(INFO, "Cavium Return Status: %lu", status); - EXPECT_OK(hawksbill::cavium::CaviumApiShim::CaviumStatus(status, "CfmWrap")); - - // Modify wrappedkey - std::vector wrapped_key( - pArgs.wrapped_key, pArgs.wrapped_key + (pArgs.wrapped_key_len - 1)); - - target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - target_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256); - - EXPECT_THAT(target_key_handle, - testing::status::StatusIs(absl::StatusCode::kInternal)); - - DeleteKey(session_handle, original_target_key_handle); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -TEST(HsmFunctionsTest, GenerateWrapUnwrapAes256) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - uint32_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - std::vector wrapped_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); - DeleteKey(session_handle, data_key_handle); - - data_key_handle = UnwrapKeyAesKwp(session_handle, wrapping_key_handle, - wrapped_key, SymmetricKeyType::kAes256) - .value(); - - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -TEST(HsmFunctionsTest, GenerateWrapUnwrapHkdfSha256) { - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - uint32_t wrapping_key_handle = GenerateAesWrappingKey(session_handle); - uint32_t derivation_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - std::vector wrapped_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, derivation_key_handle); - DeleteKey(session_handle, derivation_key_handle); - - derivation_key_handle = - UnwrapKeyAesKwp(session_handle, wrapping_key_handle, wrapped_key, - SymmetricKeyType::kHkdfSha256) - .value(); - - uint32_t data_key_handle = DeriveAes256KeyHkdfSha256( - session_handle, derivation_key_handle, RandomLabel()); - - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, derivation_key_handle); - DeleteKey(session_handle, wrapping_key_handle); - CloseSession(session_handle); -} - -class RsaWrapTest : public testing::TestWithParam {}; - -TEST_P(RsaWrapTest, RsaWrap) { - int rsa_bits = GetParam(); - - bssl::UniquePtr f4(BN_new()); - ASSERT_EQ(BN_set_u64(f4.get(), RSA_F4), 1); - - bssl::UniquePtr rsa(RSA_new()); - ASSERT_EQ(RSA_generate_key_ex(rsa.get(), rsa_bits, f4.get(), nullptr), 1); - - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - - uint32_t public_key_handle = ImportRsaPublicKey(session_handle, rsa.get()); - uint32_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - std::vector wrapped_key = - WrapKeyRsaOaep(session_handle, public_key_handle, data_key_handle); - - bssl::UniquePtr pkey(EVP_PKEY_new()); - ASSERT_EQ(EVP_PKEY_set1_RSA(pkey.get(), rsa.get()), 1); - - bssl::UniquePtr ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); - ASSERT_EQ(EVP_PKEY_decrypt_init(ctx.get()), 1); - ASSERT_EQ(EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING), 1); - ASSERT_EQ(EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), EVP_sha256()), 1); - ASSERT_EQ(EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), EVP_sha256()), 1); - - std::vector recovered; - recovered.resize(RSA_size(rsa.get())); - size_t out_len = recovered.size(); - - ASSERT_EQ(EVP_PKEY_decrypt(ctx.get(), recovered.data(), &out_len, - wrapped_key.data(), wrapped_key.size()), - 1); - - ASSERT_EQ(out_len, 32); - - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, public_key_handle); - CloseSession(session_handle); -} - -INSTANTIATE_TEST_SUITE_P(RsaWrap, RsaWrapTest, testing::Values(3072, 4096)); - -class EcdhWrapTest : public testing::TestWithParam {}; - -TEST_P(EcdhWrapTest, EcdhWrap) { - int curve_nid = GetParam(); - - bssl::UniquePtr group(EC_GROUP_new_by_curve_name(curve_nid)); - bssl::UniquePtr ec(EC_KEY_new()); - ASSERT_EQ(EC_KEY_set_group(ec.get(), group.get()), 1); - ASSERT_EQ(EC_KEY_generate_key_fips(ec.get()), 1); - - bssl::UniquePtr pkey(EVP_PKEY_new()); - ASSERT_EQ(EVP_PKEY_set1_EC_KEY(pkey.get(), ec.get()), 1); - - uint32_t app_handle = Initialize(); - uint32_t session_handle = OpenSession(app_handle); - - KeyPair durable_agree_key = GenerateEcdhKeypair(session_handle, curve_nid); - - uint32_t data_key_handle = GenerateExtractableSymmetricKey( - session_handle, SymmetricKeyType::kAes256); - - uint32_t wrapping_key_handle = DeriveAes256KeyEcdhHkdfSha256( - session_handle, durable_agree_key.prv, ec.get()); - - std::vector wrapped_key = - WrapKeyAesKwp(session_handle, wrapping_key_handle, data_key_handle); - - bssl::UniquePtr agree_pub_key = - ExportEcPublicKey(session_handle, durable_agree_key.pub, curve_nid); - - std::vector shared_secret; - constexpr size_t kMaxSharedSecretBytes = - 66; // == ceil(521 / 8) for P-521 key - shared_secret.resize(kMaxSharedSecretBytes); - - int retrieved_bytes = ECDH_compute_key( - shared_secret.data(), shared_secret.size(), - EC_KEY_get0_public_key(agree_pub_key.get()), ec.get(), nullptr); - CHECK_GE(retrieved_bytes, 32); - shared_secret.resize(retrieved_bytes); - - std::vector kwp_key; - kwp_key.resize(32); - ASSERT_EQ( - HKDF(kwp_key.data(), kwp_key.size(), EVP_sha256(), shared_secret.data(), - shared_secret.size(), nullptr, 0, nullptr, 0), - 1); - - AES_KEY k; - ASSERT_EQ(AES_set_decrypt_key(kwp_key.data(), 256, &k), 0); - - std::vector data_key; - data_key.resize(32); - size_t data_key_size = data_key.size(); - ASSERT_EQ(AES_unwrap_key_padded(&k, data_key.data(), &data_key_size, - data_key.size(), wrapped_key.data(), - wrapped_key.size()), - 1); - - DeleteKey(session_handle, wrapping_key_handle); - DeleteKey(session_handle, data_key_handle); - DeleteKey(session_handle, durable_agree_key.pub); - DeleteKey(session_handle, durable_agree_key.prv); - - CloseSession(session_handle); -} - -INSTANTIATE_TEST_SUITE_P(EcdhWrap, EcdhWrapTest, - testing::Values(NID_X9_62_prime256v1, NID_secp384r1, - NID_secp521r1)); - -} // namespace -} // namespace third_party_cavium_hsm \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/conflicts/conflict.txt b/kms/singletenanthsm/brandonluong/conflicts/conflict.txt deleted file mode 100644 index df790035801..00000000000 --- a/kms/singletenanthsm/brandonluong/conflicts/conflict.txt +++ /dev/null @@ -1,6 +0,0 @@ -It was the best of times :), -it was the worst of times:(, -it was the age of tcp/ip, -it was the age of foolishness, -it was the epoch of yes, -it was the epoch of no. diff --git a/kms/singletenanthsm/brandonluong/quotes/index.html b/kms/singletenanthsm/brandonluong/quotes/index.html deleted file mode 100644 index f305badb271..00000000000 --- a/kms/singletenanthsm/brandonluong/quotes/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Quotes Sandbox - - - -

Quotes Codelab

- -

This HTML is just an area where we can play around with Piper. The -idea is to add one or two html files with quotations, -and then edit this -index.html file to link to your quotation files. -When you're ready, submit the thing. -

- - - My quote - - diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/README.rst b/kms/singletenanthsm/brandonluong/singletenanthsm/README.rst deleted file mode 100644 index a3fe9fee3d3..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/README.rst +++ /dev/null @@ -1,105 +0,0 @@ -Google Cloud Key Management Service Python Samples -=============================================================================== - -.. image:: https://gstatic.com/cloudssh/images/open-btn.png - :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=kms/singletenanthsm/README.rst - - -This directory contains samples for Google Cloud Key Management Service. The `Cloud Key Management Service`_ allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service. - - - - -.. _Cloud Key Management Service: https://cloud.google.com/kms/docs/ - - - - - -Setup -------------------------------------------------------------------------------- - - -Install Dependencies -++++++++++++++++++++ - -#. Clone python-kms and change directory to the sample directory you want to use. - - .. code-block:: bash - - $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git - -#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. - - .. _Python Development Environment Setup Guide: - https://cloud.google.com/python/setup - -#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. - - .. code-block:: bash - - $ virtualenv env - $ source env/bin/activate - -#. Install the dependencies needed to run the samples. - - .. code-block:: bash - - $ pip install -r requirements.txt - -.. _pip: https://pip.pypa.io/ -.. _virtualenv: https://virtualenv.pypa.io/ - -Samples -------------------------------------------------------------------------------- -Create a custom gcloud build to access the Single Tenant HSM service. - -Approve a Single Tenant HSM Instance Proposal. -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -Creates custom gcloud build to access single tenant HSM service+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - -To run this sample: - -.. code-block:: bash - - $ python3 setup.py - - usage: setup.py [-h] [--operation] - - This application creates a custom gcloud build to access the single tenant HSM service. - - positional arguments: - operation The type of setup operation you want to perform. This includes build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'. - - optional arguments: - -h, --help show this help message and exit - - - -Approves a Single Tenant HSM Instance Proposal. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -To run this sample: - -.. code-block:: bash - - $ python3 approve_proposal.py - - usage: approve_proposal.py [-h] [--proposal_resource PROPOSAL_RESOURCE] - - This application fetches and approves the single tenant HSM instance proposal - specified in the "proposal_resource" field. - - For more information, visit https://cloud.google.com/kms/docs/attest-key. - - positional arguments: - --proposal_resource PROPOSAL_RESOURCE - The full name of the single tenant HSM instance proposal that needs to be approved. - - - - optional arguments: - -h, --help show this help message and exit - - -.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal.py b/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal.py deleted file mode 100644 index 6684547b15d..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import json -import os -import sys -from typing import List - -import gcloud_commands -import ykman_utils - - -def parse_challenges_into_files(sthi_output: str) -> List[bytes]: - """Parses the STHI output and writes the challenges and public keys to files. - - Args: - sthi_output: The output of the STHI command. - - Returns: - A list of the unsigned challenges. - """ - print("parsing challenges into files") - proposal_json = json.loads(sthi_output, strict=False) - challenges = proposal_json["quorumParameters"]["challenges"] - - directory_path = "challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - - challenge_count = 0 - unsigned_challenges = [] - for challenge in challenges: - challenge_count += 1 - print(challenge["challenge"] + "\n") - print(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) - f = open("challenges/challenge{0}.txt".format(challenge_count), "wb") - binary_challenge = ykman_utils.urlsafe_base64_to_binary( - challenge["challenge"] - ) - f.write(binary_challenge) - f.close() - - f = open("challenges/public_key{0}.pem".format(challenge_count), "w") - f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) - f.close() - unsigned_challenges.append( - ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"]) - ) - - return unsigned_challenges - - -def parse_args(args): - parser = argparse.ArgumentParser() - parser.add_argument("--proposal_resource", type=str, required=True) - return parser.parse_args(args) - - -def signed_challenges_to_files( - challenge_replies: list[ykman_utils.ChallengeReply], -) -> None: - """Writes the signed challenges and public keys to files. - - Args: - challenge_replies: A list of ChallengeReply objects. - - Returns: - A list of tuples containing the signed challenge file path and the public - key file path. - """ - signed_challenge_files = [] - challenge_count = 0 - for challenge_reply in challenge_replies: - challenge_count += 1 - print("challenge_count", challenge_count) - directory_path = "signed_challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - with open( - f"signed_challenges/public_key_{challenge_count}.pem", "w" - ) as public_key_file: - - # Write public key to file - public_key_file.write(challenge_reply.public_key_pem) - with open( - f"signed_challenges/signed_challenge{challenge_count}.bin", "wb" - ) as binary_file: - - # Write signed challenge to file - binary_file.write(challenge_reply.signed_challenge) - signed_challenge_files.append(( - f"signed_challenges/signed_challenge{challenge_count}.bin", - f"signed_challenges/public_key_{challenge_count}.pem", - )) - return signed_challenge_files - - -def approve_proposal(): - """Approves a proposal by fetching challenges, signing them, and sending them back to gcloud.""" - parser = parse_args(sys.argv[1:]) - - # Fetch challenges - process = gcloud_commands.fetch_challenges(parser.proposal_resource) - - # Parse challenges into files - unsigned_challenges = parse_challenges_into_files(process.stdout) - - # Sign challenges - signed_challenges = ykman_utils.sign_challenges(unsigned_challenges) - - # Parse signed challenges into files - signed_challenged_files = signed_challenges_to_files(signed_challenges) - - # Return signed challenges to gcloud - gcloud_commands.send_signed_challenges( - signed_challenged_files, parser.proposal_resource - ) - - -if __name__ == "__main__": - approve_proposal() diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal_test.py b/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal_test.py deleted file mode 100644 index 416d8ab2ed7..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/approve_proposal_test.py +++ /dev/null @@ -1,261 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import argparse -from dataclasses import dataclass -import json -import os -import subprocess - -import approve_proposal -from cryptography.hazmat.primitives.serialization import Encoding -from cryptography.hazmat.primitives.serialization import PublicFormat -import gcloud_commands -import pytest -import ykman_fake -import ykman_utils - - -# from approve_proposal import parse_args - -sample_sthi_output = """ -{ - "quorumParameters": { - "challenges": [ - { - "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" - }, - { - "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" - }, - { - "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" - } - ], - "requiredApproverCount": 3 - } -} - -""" - -sample_nonces = [ - "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", -] - - -@dataclass -class QuorumParameters: - challenges: list[ykman_utils.Challenge] - - # def __init__(self, challenges: list[ykman_utils.Challenge]): - # self.challenges = challenges - - # def to_dict(self): - # return {"challenges": self.challenges} - - -sample_assigned_challenges = "" - - -@pytest.fixture() -def setup(): - parser = approve_proposal.parse_args(["proposal_resource", "my_proposal"]) - - -test_resource = "projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal" - -MockCompletedProcess = subprocess.CompletedProcess - - -def public_key_to_pem(public_key): - public_key_pem = public_key.public_bytes( - encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo - ).decode("utf-8") - print("PUBLIC KEY--------------") - print(public_key_pem) - return public_key_pem - - -def create_json(public_key_pem_1, public_key_pem_2, public_key_pem_3): - - my_json_string = json.dumps({ - "quorumParameters": { - "challenges": [ - { - "challenge": ( - "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1" - ), - "publicKeyPem": public_key_pem_1, - }, - { - "challenge": ( - "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb" - ), - "publicKeyPem": public_key_pem_2, - }, - { - "challenge": ( - "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY" - ), - "publicKeyPem": public_key_pem_3, - }, - ], - "requiredApproverCount": 3, - } - }) - - return my_json_string - - -def create_fake_fetch_response(num_keys=3): - """Generates a fake fetch response with a specified number of RSA key pairs. - - Args: - num_keys: The number of RSA key pairs to generate. - - Returns: - A tuple containing: - - A JSON object with the public keys. - - A dictionary mapping public key PEMs to private keys. - """ - pub_to_priv_key = {} - public_key_pems = [] - - for _ in range(num_keys): - private_key, public_key = ykman_fake.generate_rsa_keys() - public_key_pem = public_key_to_pem(public_key) - pub_to_priv_key[public_key_pem] = private_key - public_key_pems.append(public_key_pem) - - challenge_json = create_json(*public_key_pems) # Use * to unpack the list - return challenge_json, pub_to_priv_key - - -mock_signed_challenges = [] - - -def sign_challenges_with_capture( - challenges: list[ykman_utils.Challenge], pub_to_priv_key -): - signed_challenges = [] - for challenge in challenges: - private_key = pub_to_priv_key[challenge.public_key_pem] - signed_challenge = ykman_fake.sign_data(private_key, challenge.challenge) - signed_challenges.append( - ykman_utils.ChallengeReply( - challenge.challenge, signed_challenge, challenge.public_key_pem - ) - ) - mock_signed_challenges.extend(signed_challenges) - return signed_challenges - - -def verify_with_fake(pub_to_priv_key, signed_challenges): - for signed_challenge in signed_challenges: - priv_key = pub_to_priv_key[signed_challenge.public_key_pem] - assert True == ykman_fake.verify_signature( - priv_key.public_key(), - signed_challenge.unsigned_challenge, - signed_challenge.signed_challenge, - ) - print("Signed verified successfully") - - -def test_get_challenges_mocked(mocker, monkeypatch): - - # Verify signed challenges - monkeypatch.setattr( - "gcloud_commands.send_signed_challenges", - lambda signed_challenges, proposal_resource: verify_with_fake( - pub_to_priv_key, mock_signed_challenges - ), - ) - - # monkeypatch sign challenges - monkeypatch.setattr( - "ykman_utils.sign_challenges", - lambda challenges: sign_challenges_with_capture( - challenges, pub_to_priv_key - ), - ) - - # mock the challenge string returned by service - challenge_json, pub_to_priv_key = create_fake_fetch_response() - mock_response = mocker.MagicMock() - mock_response.stdout = challenge_json - mocker.patch("subprocess.run", return_value=mock_response) - - # monkeypatch parse args - mock_args = argparse.Namespace(proposal_resource="test_resource") - monkeypatch.setattr("approve_proposal.parse_args", lambda args: mock_args) - - approve_proposal.approve_proposal() - - # assert challenge files created - challenge_files = [ - "challenges/challenge1.txt", - "challenges/challenge2.txt", - "challenges/challenge3.txt", - ] - for file_path in challenge_files: - assert True == os.path.exists( - file_path - ), f"File '{file_path}' should exist but does not." - - # assert signed challenge files created - signed_challenge_files = [ - "signed_challenges/signed_challenge1.txt", - "signed_challenges/signed_challenge2.txt", - "signed_challenges/signed_challenge3.txt", - ] - for file_path in signed_challenge_files: - assert True == os.path.exists( - file_path - ), f"File '{file_path}' should exist but does not." - - -if __name__ == "__main__": - # Parse challenges into files - unsigned_challenges = approve_proposal.parse_challenges_into_files( - sample_sthi_output - ) - created_signed_files = [ - "signed_challenges/signed_challenge1.txt", - "signed_challenges/signed_challenge2.txt", - "signed_challenges/signed_challenge3.txt", - ] - for file_path in created_signed_files: - assert True == os.path.exists( - file_path - ), f"File '{file_path}' should exist but does not." - - # Parse files into challenge list - challenges = ykman_utils.populate_challenges_from_files() - for challenge in challenges: - print(challenge.challenge) - print(challenge.public_key_pem) - unsigned_challenges.append(challenge.challenge) - signed_challenged_files = [] - signed_challenges = ykman_utils.sign_challenges( - challenges, signed_challenged_files - ) - for signed_challenge in signed_challenges: - print(signed_challenge.signed_challenge) - print(signed_challenge.public_key_pem) - print("--challenge_replies=" + str(signed_challenged_files)) - ykman_utils.verify_challenge_signatures(signed_challenges) diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key1.pem deleted file mode 100644 index 56f9b3b26d8..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxD115YJWrQy/cgV1Ax8B -jbqlffcXvXybceL/jFd7ijmZbnjr3xjDVGFdedGpE6bT49SQa3vhb/bvIza7+vy7 -VuW3iFK9QABF6PcnX75UDMgn+cX8qVS9pu1wKqC0pb/Z99Fw2D8jnSY9z6AzHnGf -qbX0R1virFS5Qm/XDa2QudS6i954f3bkcRyopt4GuJO6osJPAhAm5aXLXWskT0jU -trTHjIA0aQopNa6dokgw7HV7lVm1jgUeAQXl2YOWZSGrh9sknW2CLSCjClo5uOTj -caTHyvIb9lCzTJPTN60Gyy2cxsTl/eFiZRDyK1TNyapXBy1b3kF/hBRubwJU+Hjq -NwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key2.pem deleted file mode 100644 index 0e830e9c965..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA09Y7M3Jyh/n3bnjh9Q6m -oy64/CXbn0Ud2KriRYua/jYGrPsRvhbnYmrOGjL0ABPNQLO4T95KKrYgl5ZDjAQQ -e5o5e+RnSSkCO7osqCZM1t7ydaMeOn6Jx/XvKDc7IqdQ0+iqBiv/zwc+dHdjLhS6 -gbwb3aengOL0TMijDXiTZAZR1mil5y5hBfJBGXKRTGy6GnCW9HG4k+PKLpJ5drsD -pbINCwfqozv+OsoDfOR0yp0iFlf6PGhYVMS/nTtuCKuPkWerTlvmAVogqz26VMbL -Mxxa+xFig4aScgJ7r/sSIVZHwT5nau7A9zEdMC3dV72oY8br/CrpkjvfMmhBw351 -bQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key3.pem deleted file mode 100644 index 9ee4a26b6cd..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/public_key3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrStPaIP4CkVGJgXlzTK -1ppk3xbdhd4kuKNONvdxC13VR7pw6oUo8dihB0cZAvAn/O/Ws8FrfIvQnyBDcttf -wLGe5TK4Vtj/G/mgf66iYTpDF+4bKVX9JuR3EJN4N90XqoyRfQvS9zUniuIzn4MD -I5vrAK0PVz6CZWXA0omHOobJWS1ZjdEz1N+uXkRh+M/u9/85h3WhLYbWfv3Jj/lE -Y8cuXf2An1AyRGYhceID1Ce1z/pkikJZrNEAtMdnR1pCKo/bD/WBPlatXzkPtwwJ -TloSm6ptxhKRzARdvlHOn62lCvF1kGWBbzpyaur6SloWVTtR0Eln2QJAA3pXpLrF -PwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands.py deleted file mode 100644 index fb850582237..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import subprocess - -command_build_custom_gcloud = """ - pushd /tmp - curl -o installer.sh https://sdk.cloud.google.com - chmod +x installer.sh - ./installer.sh --disable-prompts --install-dir ~/sthi - rm installer.sh - popd - alias sthigcloud=~/sthi/google-cloud-sdk/bin/gcloud - sthigcloud auth login - """ - - -command_add_components = """ - ~/sthi/google-cloud-sdk/bin/gcloud components repositories add https://storage.googleapis.com/single-tenant-hsm-private/components-2.json - ~/sthi/google-cloud-sdk/bin/gcloud components update - """ - - -def build_custom_gcloud(): - """Builds a custom gcloud binary.""" - try: - print("\nBuilding custom gcloud build") - process = subprocess.run( - command_build_custom_gcloud, - check=True, - shell=True, - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud build executed successfully.") - print(process.stdout) - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - try: - print("\nAdding gcloud components") - process = subprocess.run( - command_add_components, - check=False, - capture_output=False, - text=True, - shell=True, - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud components add executed successfully.") - print(process.stdout) - return process - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - print(f"Error executing gcloud components update: {e}") - - -command_gcloud_list_proposal = ( - "~/sthi/google-cloud-sdk/bin/gcloud kms single-tenant-hsm list " - "--location=projects/hawksbill-playground/locations/global" -) - -command_gcloud_describe_proposal = """ - ~/sthi/google-cloud-sdk/bin/gcloud \ - kms single-tenant-hsm proposal describe """ - - -def fetch_challenges(sthi_proposal_resource: str): - """Fetches challenges from the server.""" - - try: - print("\nfetching challenges") - process = subprocess.run( - command_gcloud_describe_proposal - + sthi_proposal_resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - # stderr=subprocess.STDOUT - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud command executed successfully.") - print(process.stdout) - return process - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - - -command_gcloud_approve_proposal = [ - "~/sthi/google-cloud-sdk/bin/gcloud", - "kms", - "single-tenant-hsm", - "proposal", - "approve", -] - - -def send_signed_challenges( - signed_challenged_files: list[str], proposal_resource: str -): - """Sends signed challenges to the server.""" - print("Sending signed challenges") - signed_challenge_str = ( - '--challenge_replies="' + str(signed_challenged_files) + '"' - ) - command_str = " ".join( - command_gcloud_approve_proposal - + [proposal_resource] - + [signed_challenge_str] - ) - print(command_str) - - try: - - process = subprocess.run( - command_str, - capture_output=True, - check=False, - text=True, - shell=True, - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud command executed successfully.") - return process - - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands_test.py b/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands_test.py deleted file mode 100644 index 9823aebd114..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/gcloud_commands_test.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import subprocess -from unittest import mock -import gcloud_commands -import pytest - -test_proposal_resource = """projects/test_project/locations/\ -us-central1/singleTenantHsmInstances/my_sthi/proposals/my_proposal - """ -sample_fetch_challenge_output = """ -{ - "quorumParameters": { - "challenges": [ - { - "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" - }, - { - "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" - }, - { - "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" - } - ], - "requiredApproverCount": 3 - } -} - -""" - - -# Test case 1: Successful build and components add -def test_build_custom_gcloud_success(mock_subprocess_run): - # Setup: Mock successful gcloud execution - mock_subprocess_run.side_effect = [ - subprocess.CompletedProcess( - args=gcloud_commands.command_build_custom_gcloud, - returncode=0, - stdout="gcloud build successful!", - stderr="", - ), - subprocess.CompletedProcess( - args=gcloud_commands.command_add_components, - returncode=0, - stdout="gcloud components add successful.", - stderr="", - ), - ] - - # Action: Call the function - result = gcloud_commands.build_custom_gcloud() - - # Assert: Verify the return value and that subprocess.run was called correctly - assert result.returncode == 0 - assert result.stdout == "gcloud components add successful." - assert mock_subprocess_run.call_count == 2 - mock_subprocess_run.assert_has_calls([ - mock.call( - gcloud_commands.command_build_custom_gcloud, check=True, shell=True - ), - mock.call( - gcloud_commands.command_add_components, - check=False, - shell=True, - capture_output=False, - text=True, - ), - ]) - - -# Test case 2: gcloud build fails -def test_build_custom_gcloud_build_error(mock_subprocess_run): - # Setup: Mock gcloud build command with a non-zero return code - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=1, - cmd=gcloud_commands.command_build_custom_gcloud, - output="", - stderr="Error: Build failed", - ) - - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.build_custom_gcloud() - - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Build failed" - assert exc_info.value.cmd == gcloud_commands.command_build_custom_gcloud - assert mock_subprocess_run.call_count == 1 - - -# Test case 3: gcloud components add fails -def test_build_custom_gcloud_components_error(mock_subprocess_run): - # Setup: Mock gcloud build success and components add with error - mock_subprocess_run.side_effect = [ - subprocess.CompletedProcess( - args=gcloud_commands.command_build_custom_gcloud, - returncode=0, - stdout="gcloud build successful!", - stderr="", - ), - subprocess.CalledProcessError( - returncode=1, - cmd=gcloud_commands.command_add_components, - output="", - stderr="Error: Components add failed", - ), - ] - - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.build_custom_gcloud() - - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Components add failed" - assert exc_info.value.cmd == gcloud_commands.command_add_components - assert mock_subprocess_run.call_count == 2 - - -@pytest.fixture -def mock_subprocess_run(monkeypatch): - mock_run = mock.create_autospec(subprocess.run) - monkeypatch.setattr(subprocess, "run", mock_run) - return mock_run - - -def test_fetch_challenges_success(mock_subprocess_run): - # Setup: Configure the mock to simulate a successful gcloud command - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=0, - stdout=sample_fetch_challenge_output, - stderr="", - ) - mock_subprocess_run.return_value = mock_process_result - - # Action: Call the function - resource = test_proposal_resource - result = gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the results - mock_subprocess_run.assert_called_once_with( - gcloud_commands.command_gcloud_describe_proposal - + resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - ) - assert result == mock_process_result - assert result.returncode == 0 - assert result.stdout == sample_fetch_challenge_output - assert not result.stderr - - -def test_fetch_challenges_error(mock_subprocess_run): - # Setup: Configure the mock to simulate a failed gcloud command - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=1, cmd="", output="", stderr="Error: Invalid resource" - ) - - # Action & Assert: Call the function and check for the expected exception - resource = "invalid-resource" - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.fetch_challenges(resource) - - # Verify the exception details - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Invalid resource" - - -def test_fetch_challenges_command_construction(mock_subprocess_run): - # Setup: - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=0, - stdout="{}", - stderr="", - ) - mock_subprocess_run.return_value = mock_process_result - resource = test_proposal_resource - - # Action: Call the function - gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the command - mock_subprocess_run.assert_called_once_with( - gcloud_commands.command_gcloud_describe_proposal - + resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - ) - - -def test_fetch_challenges_output_capture(mock_subprocess_run): - # Setup: - expected_stdout = "Expected Output" - expected_stderr = "Expected Error" - expected_returncode = 0 - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=expected_returncode, - stdout=expected_stdout, - stderr=expected_stderr, - ) - mock_subprocess_run.return_value = mock_process_result - resource = test_proposal_resource - # Action: Call the function - result = gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the captured output - assert result.stdout == expected_stdout - assert result.stderr == expected_stderr - assert result.returncode == expected_returncode - - -# Test case 1: Successful gcloud command -def test_send_signed_challenges_success(mock_subprocess_run): - # Setup: Mock successful gcloud execution - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.return_value = subprocess.CompletedProcess( - args=[], # Not checked in this test, but good practice to include - returncode=0, - stdout="gcloud command successful!", - stderr="", - ) - - # Action: Call the function - result = gcloud_commands.send_signed_challenges(signed_files, proposal) - - # Assert: Verify the return value and that subprocess.run was called correctly - assert result.returncode == 0 - assert result.stdout == "gcloud command successful!" - expected_command = " ".join( - gcloud_commands.command_gcloud_approve_proposal - + [proposal] - + [ - "--challenge_replies=\"[('signed_challenge.bin'," - " 'public_key_1.pem')]\"" - ] - ) - mock_subprocess_run.assert_called_once_with( - expected_command, - capture_output=True, - check=True, - text=True, - shell=True, - ) - - -# Test case 2: gcloud command returns an error code -def test_send_signed_challenges_gcloud_error(mock_subprocess_run): - # Setup: Mock gcloud command with a non-zero return code and stderr - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.return_value = subprocess.CompletedProcess( - args=[], - returncode=1, - stdout="", - stderr="Error: Invalid proposal resource", - ) - - # Action: Call the function - result = gcloud_commands.send_signed_challenges(signed_files, proposal) - - # Assert: Verify the return value - assert result.returncode == 1 - assert result.stderr == "Error: Invalid proposal resource" - - -# Test case 3: subprocess.run raises a CalledProcessError -def test_send_signed_challenges_called_process_error( - mock_subprocess_run -): - # Setup: Mock subprocess.run to raise a CalledProcessError - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=2, - cmd="test_command", - output="", - stderr="Called process error", - ) - - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.send_signed_challenges(signed_files, proposal) - - assert exc_info.value.returncode == 2 - assert exc_info.value.stderr == "Called process error" - assert exc_info.value.cmd == "test_command" - - -# Test case 4: Signed challenge file list is empty. -def test_send_signed_challenges_empty_list(mock_subprocess_run): - - # Action: Call the function - with pytest.raises(ValueError, match="signed_challenged_files is empty"): - gcloud_commands.send_signed_challenges([], test_proposal_resource) diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_25167010.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_25167010.pem deleted file mode 100644 index 7b0fe69d677..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_25167010.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArp0MggO5sHSIyfzgVfnR -xzG3S1D1XKVRFHHa5PHG+g64OZJ/cYNQux+OXKBuXs0rgKI+s+PVnYF9vJg3gatF -Xos+lN0Xv+wcuFTqMD0acPUNhX2QCq53Lb4u1R/EAZ0lzFVSu17N6Kde90R8EPgE -a8pc7pusgzT7wE7TAlXbRLYBp11n+dcgXGtYHHBECeBLiu7vvx8bUO6ZPLJClnwK -dAkIy9HCtlMUdukmIUrL5oMLUVMCdPn/8fELUbsFBK5ZE1nrbx6eKJKKXRl1BZ+s -11U54/xftPvqw9MgKKU4aWoY5Yz+xsDM27h1zvPtADXzTUsNY3oLpo/pYJqmwdhz -6wIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787103.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787103.pem deleted file mode 100644 index bd82c8f28b8..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787103.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Rx08jnCJMrypWQgp8a8 -zBj0SAjIqDlOucHmaax6gI0xDMxq1LSfXUHsuD5qtxttxn+WszKUar29/VRHY9Af -98HjvqIRTJSyvCwDr0w/9LEKXL4JubzP9+4PdeK5AsdhgaKazYRfjpUVS7cispoq -N5rl+kLEJS5SvPTbzXh71YLgFWtUXJJLz5SgbCReVI4eegfVTZ1JPrAC2x7TVu4P -qNXLmuX9ijqEhxvr4Cv7A4n7MJTpaXAeu1yYByslI3kF16rND2digyd7TSZ/LwQe -eTd5ElTXF6b41zhRm+FyCwWjcWjiJdXnUSZai2Td2n8gddsDmz5YwBbRKD9lBpRy -gQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787105.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787105.pem deleted file mode 100644 index 3466ad79de9..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/generated_public_keys/public_key_28787105.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3llqGRlrEdw1AFPAi5yN -SLunBSGtodc6F4RBXkTjE6awpRsqJUw0Nrt6iy6zK6yWBBE/ecwcZ7B5DUGFalqZ -NphMUhKaEVlbxO3R5+z+6OcJR74VO/DIzGOfpGw9FjK1FhYBl7Ljhy8cCQbky3hJ -I8GPsrhTkM7+Ciphp5Hxt4glhnd7LY5NiPy4DKdzhw3mvPZzm3cRhGDTbiMPHHxB -h6fjp8uzUTnAmF6EWNwPRKLvpg3q3QnOAmwmtOcke5Yl6TPzAw1r1M6KhVxkFijr -f22HHrM0ykHOTWZZTU+IgPX4T/dqEQ13BrqJPg8tai6pMClDdl5zStIK004wfNct -FwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/requirements.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/requirements.txt deleted file mode 100644 index e29d4ff4c8e..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -backports.tarfile==1.2.0 -cffi==1.17.1 -click==8.1.8 -cryptography==44.0.0 -fido2==1.2.0 -importlib_metadata==8.6.1 -iniconfig==2.0.0 -jaraco.classes==3.4.0 -jaraco.context==6.0.1 -jaraco.functools==4.1.0 -jeepney==0.8.0 -keyring==25.6.0 -more-itertools==10.6.0 -packaging==24.2 -pluggy==1.5.0 -pycparser==2.22 -pyscard==2.2.1 -pytest==8.3.4 -SecretStorage==3.3.3 -yubikey-manager==5.5.1 -zipp==3.21.0 diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/setup.py b/kms/singletenanthsm/brandonluong/singletenanthsm/setup.py deleted file mode 100644 index 249e92820f2..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/setup.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import gcloud_commands -import ykman_utils - - -def validate_operation(operation: str): - if operation == "build_custom_gcloud": - try: - gcloud_commands.build_custom_gcloud() - except Exception as e: - raise Exception(f"Generating custom gcloud build failed {e}") - elif operation == "generate_rsa_keys": - try: - ykman_utils.generate_private_key() - except Exception as e: - raise Exception(f"Generating private keys failed {e}") - elif operation == "generate_gcloud_and_keys": - generate_private_keys_build_gcloud() - else: - raise Exception( - "Operation type not valid. Operation flag value must be" - " build_custom_gcloud, generate_rsa_keys, or generate_gcloud_and_keys" - ) - - -def generate_private_keys_build_gcloud(): - """Generates an RSA key on slot 82 of every yubikey - - connected to the local machine and builds the custom gcloud cli. - """ - try: - ykman_utils.generate_private_key() - except Exception as e: - raise Exception(f"Generating private keys failed {e}") - try: - gcloud_commands.build_custom_gcloud() - except Exception as e: - raise Exception(f"Generating custom gcloud build failed {e}") - - -if __name__ == "__main__": - - parser = argparse.ArgumentParser() - parser.add_argument( - "--operation", - type=str, - choices=[ - "build_custom_gcloud", - "generate_rsa_keys", - "generate_gcloud_and_keys", - ], - required=True, - ) - args = parser.parse_args() - validate_operation(args.operation) diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_1.pem deleted file mode 100644 index 9ee4a26b6cd..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrStPaIP4CkVGJgXlzTK -1ppk3xbdhd4kuKNONvdxC13VR7pw6oUo8dihB0cZAvAn/O/Ws8FrfIvQnyBDcttf -wLGe5TK4Vtj/G/mgf66iYTpDF+4bKVX9JuR3EJN4N90XqoyRfQvS9zUniuIzn4MD -I5vrAK0PVz6CZWXA0omHOobJWS1ZjdEz1N+uXkRh+M/u9/85h3WhLYbWfv3Jj/lE -Y8cuXf2An1AyRGYhceID1Ce1z/pkikJZrNEAtMdnR1pCKo/bD/WBPlatXzkPtwwJ -TloSm6ptxhKRzARdvlHOn62lCvF1kGWBbzpyaur6SloWVTtR0Eln2QJAA3pXpLrF -PwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_2.pem deleted file mode 100644 index 0e830e9c965..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA09Y7M3Jyh/n3bnjh9Q6m -oy64/CXbn0Ud2KriRYua/jYGrPsRvhbnYmrOGjL0ABPNQLO4T95KKrYgl5ZDjAQQ -e5o5e+RnSSkCO7osqCZM1t7ydaMeOn6Jx/XvKDc7IqdQ0+iqBiv/zwc+dHdjLhS6 -gbwb3aengOL0TMijDXiTZAZR1mil5y5hBfJBGXKRTGy6GnCW9HG4k+PKLpJ5drsD -pbINCwfqozv+OsoDfOR0yp0iFlf6PGhYVMS/nTtuCKuPkWerTlvmAVogqz26VMbL -Mxxa+xFig4aScgJ7r/sSIVZHwT5nau7A9zEdMC3dV72oY8br/CrpkjvfMmhBw351 -bQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_3.pem deleted file mode 100644 index 56f9b3b26d8..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/public_key_3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxD115YJWrQy/cgV1Ax8B -jbqlffcXvXybceL/jFd7ijmZbnjr3xjDVGFdedGpE6bT49SQa3vhb/bvIza7+vy7 -VuW3iFK9QABF6PcnX75UDMgn+cX8qVS9pu1wKqC0pb/Z99Fw2D8jnSY9z6AzHnGf -qbX0R1virFS5Qm/XDa2QudS6i954f3bkcRyopt4GuJO6osJPAhAm5aXLXWskT0jU -trTHjIA0aQopNa6dokgw7HV7lVm1jgUeAQXl2YOWZSGrh9sknW2CLSCjClo5uOTj -caTHyvIb9lCzTJPTN60Gyy2cxsTl/eFiZRDyK1TNyapXBy1b3kF/hBRubwJU+Hjq -NwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge1.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge1.txt deleted file mode 100644 index e2cc944c512..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge1.txt +++ /dev/null @@ -1 +0,0 @@ -HwY(5E *dzb-qw;cB[e'.9v'$u+L'bo6=9*K$P+˜d;'\ƼlS \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge3.txt b/kms/singletenanthsm/brandonluong/singletenanthsm/signed_challenges/signed_challenge3.txt deleted file mode 100644 index f07c9cef275f2ddd1d33cf6be1cb5e9e4a8a1a27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssDJIB1;+Q0il23-Pg+HcrX`4UC7u&-H>d60%EOID7>f5UW5aTG~TtMepV8 z10F#Iob>sZ4cxEaoZ?!QM=~OumBxrSdZ!e{)^aVA^k-fC<`GI*vGfcUnI6b}v0i@G zpX-A}{@S8(9ffCkwd864j2^v|$2d<#gfUAM<^A;2!P1t95@K(TDL(*GoPyfC3@@{RKO~JlsbjS zC9+HV)V3EH5~d+w3t5*=6F-k)g-aVomIp^odx?oXp1oU)p?{MRJg5^`rJ%4mR4JfH G)g9aJaC|oa diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_fake.py b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_fake.py deleted file mode 100644 index 6e0a29ecb19..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_fake.py +++ /dev/null @@ -1,53 +0,0 @@ -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.asymmetric import rsa - - -def generate_rsa_keys(key_size=2048): - """Generates an RSA key pair with the specified key size.""" - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=key_size, - ) - public_key = private_key.public_key() - return private_key, public_key - - -def sign_data(private_key, data, hash_algorithm=hashes.SHA256()): - """Signs the provided data using the private key with PKCS#1.5 padding.""" - if not isinstance(data, bytes): - raise TypeError("Data must be of type bytes") - signature_bytes = private_key.sign(data, padding.PKCS1v15(), hash_algorithm) - return signature_bytes - - -def verify_signature( - public_key, data, signature, hash_algorithm=hashes.SHA256() -): - """Verifies the signature of the data using the public key.""" - if not isinstance(data, bytes): - raise TypeError("Data must be of type bytes") - if not isinstance(signature, bytes): - raise TypeError("Signature must be of type bytes") - try: - public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm) - return True # Signature is valid - except InvalidSignature: - return False # Signature is invalid - - -if __name__ == "__main__": - private_key_instance, public_key_instance = generate_rsa_keys() - - # Data to sign (as bytes) - data_to_sign = b"This is the data to be signed." - signature_bytes = sign_data(private_key_instance, data_to_sign) - print(f"Signature generated: {signature_bytes.hex()}") - is_valid = verify_signature( - public_key_instance, data_to_sign, signature_bytes - ) - if is_valid: - print("Signature is VALID.") - else: - print("Signature is INVALID.") diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils.py deleted file mode 100644 index fe7d9a2f834..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import base64 -from dataclasses import dataclass -import pathlib -import re -import cryptography.exceptions -from cryptography.hazmat.primitives import _serialization -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.serialization import load_pem_public_key -from ykman import piv -from ykman.device import list_all_devices -from yubikit.piv import PIN_POLICY, TOUCH_POLICY, hashes -from yubikit.piv import SmartCardConnection - -DEFAULT_MANAGEMENT_KEY = "010203040506070801020304050607080102030405060708" -DEFAULT_PIN = "123456" - - -def generate_private_key( - key_type=piv.KEY_TYPE.RSA2048, - management_key=DEFAULT_MANAGEMENT_KEY, - pin=DEFAULT_PIN, -): - """Generates a private key on the yubikey.""" - - devices = list_all_devices() - if not devices: - raise ValueError("no yubikeys found") - print(f"{len(devices)} yubikeys detected") - for yubikey, device_info in devices: - with yubikey.open_connection(SmartCardConnection) as connection: - piv_session = piv.PivSession(connection) - piv_session.authenticate( - piv.MANAGEMENT_KEY_TYPE.TDES, - bytes.fromhex(management_key), - ) - piv_session.verify_pin(pin) - - public_key = piv_session.generate_key( - piv.SLOT.RETIRED1, - key_type=key_type, - pin_policy=PIN_POLICY.DEFAULT, - touch_policy=TOUCH_POLICY.ALWAYS, - ) - if not public_key: - raise RuntimeError("failed to generate public key") - with open( - f"generated_public_keys/public_key_{device_info.serial}.pem", "wb" - ) as binary_file: - - # Write bytes to file - binary_file.write( - public_key.public_bytes( - encoding=_serialization.Encoding.PEM, - format=_serialization.PublicFormat.SubjectPublicKeyInfo, - ) - ) - print( - f"Private key pair generated on device {device_info.serial} on key" - f" slot: {piv.SLOT.RETIRED1}" - ) - - -@dataclass -class Challenge: - """Represents a challenge with its associated public key.""" - - challenge: bytes - public_key_pem: str - - def to_dict(self): - return { - "challenge": base64.b64encode(self.challenge).decode("utf-8"), - "public_key_pem": self.public_key_pem, - } - - @staticmethod - def from_dict(data): - if not isinstance(data, dict): - return None - return Challenge( - challenge=base64.b64decode(data["challenge"]), - public_key_pem=data["public_key_pem"], - ) - - -class ChallengeReply: - - def __init__(self, unsigned_challenge, signed_challenge, public_key_pem): - self.unsigned_challenge = unsigned_challenge - self.signed_challenge = signed_challenge - self.public_key_pem = public_key_pem - - -def populate_challenges_from_files() -> list[Challenge]: - """Populates challenges and their corresponding public keys from files. - - This function searches for files matching the patterns - "challenges/public_key*.pem" - and "challenges/challenge*.bin" in the current working directory. It then - pairs each challenge with its corresponding public key based on matching - numeric IDs in the filenames. - - Returns: - list[Challenge]: A list of Challenge objects, each containing a challenge - and its associated public key. - """ - public_key_files = list(pathlib.Path.cwd().glob("challenges/public_key*.pem")) - print(public_key_files) - challenge_files = list(pathlib.Path.cwd().glob("challenges/challenge*.bin")) - print(challenge_files) - - challenges = [] - - for public_key_file in public_key_files: - challenge_id = re.findall(r"\d+", str(public_key_file)) - for challenge_file in challenge_files: - if challenge_id == re.findall(r"\d+", str(challenge_file)): - print(public_key_file) - file = open(public_key_file, "r") - public_key_pem = file.read() - file.close() - file = open(challenge_file, "rb") - challenge = file.read() - file.close() - challenges.append(Challenge(challenge, public_key_pem)) - return challenges - - -def sign_challenges(challenges: list[Challenge]) -> list[ChallengeReply]: - """Signs a proposal's challenges using a Yubikey.""" - if not challenges: - raise ValueError("Challenge list empty: No challenges to sign.") - signed_challenges = [] - devices = list_all_devices() - if not devices: - raise ValueError("no yubikeys found") - for yubikey, _ in devices: - with yubikey.open_connection(SmartCardConnection) as connection: - # Make PivSession and fetch public key from Signature slot. - piv_session = piv.PivSession(connection) - # authenticate - piv_session.authenticate( - piv.MANAGEMENT_KEY_TYPE.TDES, - bytes.fromhex("010203040506070801020304050607080102030405060708"), - ) - piv_session.verify_pin("123456") - - # Get the public key from slot 82. - slot_metadata = piv_session.get_slot_metadata(slot=piv.SLOT.RETIRED1) - print(slot_metadata.public_key.public_bytes) - - # Check to see if any of the challenge public keys matches with the - # public key from slot 82. - for challenge in challenges: - key_public_bytes = slot_metadata.public_key.public_bytes( - encoding=_serialization.Encoding.PEM, - format=_serialization.PublicFormat.SubjectPublicKeyInfo, - ) - print(key_public_bytes.decode()) - print(challenge.public_key_pem) - if key_public_bytes == challenge.public_key_pem.encode(): - - # sign the challenge - print("Press Yubikey to sign challenge") - signed_challenge = piv_session.sign( - slot=piv.SLOT.RETIRED1, - key_type=slot_metadata.key_type, - message=challenge.challenge, - hash_algorithm=hashes.SHA256(), - padding=padding.PKCS1v15(), - ) - - signed_challenges.append( - ChallengeReply( - challenge.challenge, - signed_challenge, - challenge.public_key_pem, - ) - ) - print("Challenge signed successfully") - if not signed_challenges: - raise RuntimeError( - "No matching public keys between Yubikey and challenges. Make sure" - " key is generated in correct slot" - ) - return signed_challenges - - -def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: - """Converts a URL-safe base64 encoded string to its binary equivalent. - - Args: - urlsafe_string: The URL-safe base64 encoded string. - - Returns: - The binary data as bytes, or None if an error occurs. - - Raises: - TypeError: If the input is not a string. - ValueError: If the input string is not valid URL-safe base64. - """ - try: - if not isinstance(urlsafe_string, str): - raise TypeError("Input must be a string") - # Add padding if necessary. Base64 requires padding to be a multiple of 4 - missing_padding = len(urlsafe_string) % 4 - if missing_padding: - urlsafe_string += "=" * (4 - missing_padding) - return base64.urlsafe_b64decode(urlsafe_string) - except base64.binascii.Error as e: - raise ValueError(f"Invalid URL-safe base64 string: {e}") from e - - -def verify_challenge_signatures(challenge_replies: list[ChallengeReply]): - """Verifies the signatures of a list of challenge replies. - - Args: - challenge_replies: A list of ChallengeReply objects. - - Raises: - ValueError: If the list of challenge replies is empty. - cryptography.exceptions.InvalidSignature: If a signature is invalid. - """ - if not challenge_replies: - raise ValueError("No signed challenges to verify") - for challenge_reply in challenge_replies: - public_key = load_pem_public_key(challenge_reply.public_key_pem.encode()) - try: - - public_key.verify( - challenge_reply.signed_challenge, - challenge_reply.unsigned_challenge, - padding.PKCS1v15(), - hashes.SHA256(), - ) - print("Signature verification success") - except cryptography.exceptions.InvalidSignature as e: - raise cryptography.exceptions.InvalidSignature( - f"Signature verification failed: {e}" - ) - - -if __name__ == "__main__": - generate_private_key() diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils_test.py deleted file mode 100644 index b1a6ab4abb0..00000000000 --- a/kms/singletenanthsm/brandonluong/singletenanthsm/ykman_utils_test.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pathlib - -import cryptography.exceptions -import pytest -import ykman_utils - - -class Challenge: - - def __init__(self, challenge, public_key_pem): - self.challenge = challenge - self.public_key_pem = public_key_pem - - -class ChallengeReply: - - def __init__(self, signed_challenge, public_key_pem): - self.signed_challenge = signed_challenge - self.public_key_pem = public_key_pem - - -challenge_test_data = b"test_data" - - -def generate_test_challenge_files(): - # Create challenges list from challenges directory - challenge_list = ykman_utils.populate_challenges_from_files() - for challenge in challenge_list: - print(challenge.challenge) - print(challenge.public_key_pem) - # Sign challenges - signed_challenges = ykman_utils.sign_challenges(challenge_list) - # for signed_challenge in signed_challenge_files: - # print(signed_challenge.signed_challenge.decode()) - # print(signed_challenge.public_key_pem) - ykman_utils.verify_challenge_signatures( - signed_challenges, - b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN", - ) - - -# A yubikey connected to your local machine will be needed to run these tests. -# The generate_private_key() method will rewrite the key saved on slot -# 82(Retired1). -@pytest.fixture(autouse=True) -def key_setup(): - ykman_utils.generate_private_key() - - -def create_challenges(): - public_key_files = list( - pathlib.Path.cwd().glob("generated_public_keys/public_key*.pem") - ) - challenges = [] - - for public_key_file in public_key_files: - file = open(public_key_file, "r") - public_key_pem = file.read() - challenges.append(Challenge(challenge_test_data, public_key_pem)) - - return challenges - - -def test_sign_and_verify_challenges(): - signed_challenges = ykman_utils.sign_challenges(create_challenges()) - ykman_utils.verify_challenge_signatures(signed_challenges) - - -def test_verify_mismatching_data_fail(): - with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: - signed_challenges = ykman_utils.sign_challenges(create_challenges()) - signed_challenges[0].signed_challenge = b"mismatched_data" - ykman_utils.verify_challenge_signatures(signed_challenges) - assert "Signature verification failed" in str(exec_info.value) - - -def test_sign_empty_challenge_list_fail(): - with pytest.raises(Exception) as exec_info: - ykman_utils.sign_challenges([]) - assert "Challenge list empty" in str(exec_info.value) - - -def test_sign_no_matching_public_keys_fail(): - modified_challenges = create_challenges() - for challenge in modified_challenges: - challenge.public_key_pem = "modified_public_key" - with pytest.raises(Exception) as exec_info: - ykman_utils.sign_challenges(modified_challenges) - assert "No matching public keys" in str(exec_info.value) - - -def test_verify_empty_challenge_replies_fail(): - with pytest.raises(Exception) as exec_info: - ykman_utils.verify_challenge_signatures([]) - assert "No \ No newline at end of file diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/challenges/challenge1.txt similarity index 100% rename from kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge1.txt rename to kms/singletenanthsm/challenges/challenge1.txt diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/challenges/challenge2.txt similarity index 100% rename from kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge2.txt rename to kms/singletenanthsm/challenges/challenge2.txt diff --git a/kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/challenges/challenge3.txt similarity index 100% rename from kms/singletenanthsm/brandonluong/singletenanthsm/challenges/challenge3.txt rename to kms/singletenanthsm/challenges/challenge3.txt diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem new file mode 100644 index 00000000000..e9d057231ec --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxpORpfP7RbgLHZ4w3e/D +Yc6WwEU+fj2I8RolMPafYZFKp2GewsjKX1C44FK4N1Bzd2PnIcJc7dcBd3LxyZXd +9Oj54ReLKNTLIenfUp3BuH21H/jagB+kNOQIyoK/xK5zDCcfkSbhDzet9y5jMsIq +b9uKAmWB127qmkPu95ozUXGWAWsLIahx/wb7mH3Dm2iOqjFrqXMqp2tXcNmAYzux +2Uure5WZQU+QKYTQvnccWkeZs/RTO7j9r/KqKBrHNtSIeRl1leb66PaRCa41xTHy +lp5194BMbceZn8tcUbU67Q3InBTaJu2CIq9IUytUwZBcOfhAqHwOPQ6cZPWUiCRk +LwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem new file mode 100644 index 00000000000..869be1e18f2 --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1xT9BLxfqHi/fWMm0UeI +WBzBA3mtd6gpHtbEhfEJ0IWaUO+X6GJm/u2kHPdJpyf84PlgMfMy14+ZFVMQKM8H +95XEpFah4oMA7zqVOusoLeV1CGSK4/wSVZzsv1OaODU/OCM+wZx0A+L7b9buQpjj +YxgO3SBoe/4LtadaekWBKEGbKNllD/UUqcfiFYfXn0mouXbaj60F9t7+F5dnyRL2 +2T9pvewPd2AuoP5buWuUIqfnOnVMWuWpj14bCimRb28kDNlLTMWgN89bIOAWCbRy +c9Dl7x1oyFB3oKQMPwR6vN6xDSWE+2wWl1xwI4PO0zyZ9DlbXiTvBdadtlvIQwvn +rwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem new file mode 100644 index 00000000000..171afc73420 --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1xabdpYzO/SQjVMJTZZ +RaklZIqMlcKLy2L9C2Y3yruJ4tZ2P8ZgV4dZt6DEl9PTmX8W5GHUoWk/w/wrm8XS +0ViX3t6s1/wgO85yBDrgTG9qRSsM2ELrPq9OucfYTy47Wedp8ALUSdw8JlyNeedq +mDr00k4JjgnIx+OrFt2XApSuHi9DrfbvMFVco5fJshpeLDEgtNoV7OQeDop/6Jnp +/EQtCnWcdhjxuP8sx2IbTwN58W89PwMVVYOQrYlexsECpbompa6X7UCAPAMTJQ+t +15JxJv34TzAEUj/CZS3FOEy24mchPL386UiXvTWD8iuXMfV2YCg9sliJNpTi2SI6 +gQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/gcloud_commands.py index 7b89c384fbd..ec9a0f28763 100644 --- a/kms/singletenanthsm/gcloud_commands.py +++ b/kms/singletenanthsm/gcloud_commands.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2020 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -35,41 +35,41 @@ def build_custom_gcloud(): - """Builds a custom gcloud binary.""" - try: - print("\nBuilding custom gcloud build") - process = subprocess.run( - command_build_custom_gcloud, - check=True, - shell=True, - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud build executed successfully.") - print(process.stdout) - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - try: - print("\nAdding gcloud components") - process = subprocess.run( - command_add_components, - check=False, - capture_output=False, - text=True, - shell=True, - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud components add executed successfully.") - print(process.stdout) - return process - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - print(f"Error executing gcloud components update: {e}") + """Builds a custom gcloud binary.""" + try: + print("\nBuilding custom gcloud build") + process = subprocess.run( + command_build_custom_gcloud, + check=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud build executed successfully.") + print(process.stdout) + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + try: + print("\nAdding gcloud components") + process = subprocess.run( + command_add_components, + check=False, + capture_output=False, + text=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud components add executed successfully.") + print(process.stdout) + return process + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + print(f"Error executing gcloud components update: {e}") command_gcloud_list_proposal = ( @@ -83,29 +83,30 @@ def build_custom_gcloud(): def fetch_challenges(sthi_proposal_resource: str): - """Fetches challenges from the server.""" - - try: - print("\nfetching challenges") - process = subprocess.run( - command_gcloud_describe_proposal - + sthi_proposal_resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - # stderr=subprocess.STDOUT - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud command executed successfully.") - print(process.stdout) - return process - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + """Fetches challenges from the server.""" + + try: + print("\nfetching challenges") + process = subprocess.run( + command_gcloud_describe_proposal + + sthi_proposal_resource + + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + # stderr=subprocess.STDOUT + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud command executed successfully.") + print(process.stdout) + return process + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + command_gcloud_approve_proposal = [ "~/sthi/google-cloud-sdk/bin/gcloud", @@ -116,38 +117,31 @@ def fetch_challenges(sthi_proposal_resource: str): ] -def send_signed_challenges( - signed_challenged_files: list[str], proposal_resource: str -): - """Sends signed challenges to the server.""" - if signed_challenged_files is None or not signed_challenged_files: - raise ValueError("signed_challenged_files is empty") - print("Sending signed challenges") - signed_challenge_str = ( - '--challenge_replies="' + str(signed_challenged_files) + '"' - ) - command_str = " ".join( - command_gcloud_approve_proposal - + [proposal_resource] - + [signed_challenge_str] - ) - print(command_str) - - try: - - process = subprocess.run( - command_str, - capture_output=True, - check=True, - text=True, - shell=True, +def send_signed_challenges(signed_challenged_files: list[str], proposal_resource: str): + """Sends signed challenges to the server.""" + if signed_challenged_files is None or not signed_challenged_files: + raise ValueError("signed_challenged_files is empty") + print("Sending signed challenges") + signed_challenge_str = '--challenge_replies="' + str(signed_challenged_files) + '"' + command_str = " ".join( + command_gcloud_approve_proposal + [proposal_resource] + [signed_challenge_str] ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud command executed successfully.") - return process - - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) + print(command_str) + + try: + process = subprocess.run( + command_str, + capture_output=True, + check=True, + text=True, + shell=True, + ) + print(f"Return Test: {process}") + print(f"Return Code: {process.returncode}") + print(f"Standard Output: {process.stdout}") + print(f"Standard Error: {process.stderr}") + print("gcloud command executed successfully.") + return process + + except subprocess.CalledProcessError as e: + raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) diff --git a/kms/singletenanthsm/gcloud_commands_test.py b/kms/singletenanthsm/gcloud_commands_test.py index 9823aebd114..bc1b6751ae0 100644 --- a/kms/singletenanthsm/gcloud_commands_test.py +++ b/kms/singletenanthsm/gcloud_commands_test.py @@ -14,9 +14,12 @@ import subprocess from unittest import mock -import gcloud_commands + import pytest +import gcloud_commands + + test_proposal_resource = """projects/test_project/locations/\ us-central1/singleTenantHsmInstances/my_sthi/proposals/my_proposal """ @@ -46,276 +49,269 @@ # Test case 1: Successful build and components add def test_build_custom_gcloud_success(mock_subprocess_run): - # Setup: Mock successful gcloud execution - mock_subprocess_run.side_effect = [ - subprocess.CompletedProcess( - args=gcloud_commands.command_build_custom_gcloud, - returncode=0, - stdout="gcloud build successful!", - stderr="", - ), - subprocess.CompletedProcess( - args=gcloud_commands.command_add_components, - returncode=0, - stdout="gcloud components add successful.", - stderr="", - ), - ] - - # Action: Call the function - result = gcloud_commands.build_custom_gcloud() - - # Assert: Verify the return value and that subprocess.run was called correctly - assert result.returncode == 0 - assert result.stdout == "gcloud components add successful." - assert mock_subprocess_run.call_count == 2 - mock_subprocess_run.assert_has_calls([ - mock.call( - gcloud_commands.command_build_custom_gcloud, check=True, shell=True - ), - mock.call( - gcloud_commands.command_add_components, - check=False, - shell=True, - capture_output=False, - text=True, - ), - ]) + # Setup: Mock successful gcloud execution + mock_subprocess_run.side_effect = [ + subprocess.CompletedProcess( + args=gcloud_commands.command_build_custom_gcloud, + returncode=0, + stdout="gcloud build successful!", + stderr="", + ), + subprocess.CompletedProcess( + args=gcloud_commands.command_add_components, + returncode=0, + stdout="gcloud components add successful.", + stderr="", + ), + ] + + # Action: Call the function + result = gcloud_commands.build_custom_gcloud() + + # Assert: Verify the return value and that subprocess.run was called correctly + assert result.returncode == 0 + assert result.stdout == "gcloud components add successful." + assert mock_subprocess_run.call_count == 2 + mock_subprocess_run.assert_has_calls( + [ + mock.call( + gcloud_commands.command_build_custom_gcloud, check=True, shell=True + ), + mock.call( + gcloud_commands.command_add_components, + check=False, + shell=True, + capture_output=False, + text=True, + ), + ] + ) # Test case 2: gcloud build fails def test_build_custom_gcloud_build_error(mock_subprocess_run): - # Setup: Mock gcloud build command with a non-zero return code - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=1, - cmd=gcloud_commands.command_build_custom_gcloud, - output="", - stderr="Error: Build failed", - ) + # Setup: Mock gcloud build command with a non-zero return code + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, + cmd=gcloud_commands.command_build_custom_gcloud, + output="", + stderr="Error: Build failed", + ) - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.build_custom_gcloud() + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.build_custom_gcloud() - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Build failed" - assert exc_info.value.cmd == gcloud_commands.command_build_custom_gcloud - assert mock_subprocess_run.call_count == 1 + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Build failed" + assert exc_info.value.cmd == gcloud_commands.command_build_custom_gcloud + assert mock_subprocess_run.call_count == 1 # Test case 3: gcloud components add fails def test_build_custom_gcloud_components_error(mock_subprocess_run): - # Setup: Mock gcloud build success and components add with error - mock_subprocess_run.side_effect = [ - subprocess.CompletedProcess( - args=gcloud_commands.command_build_custom_gcloud, - returncode=0, - stdout="gcloud build successful!", - stderr="", - ), - subprocess.CalledProcessError( - returncode=1, - cmd=gcloud_commands.command_add_components, - output="", - stderr="Error: Components add failed", - ), - ] - - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.build_custom_gcloud() - - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Components add failed" - assert exc_info.value.cmd == gcloud_commands.command_add_components - assert mock_subprocess_run.call_count == 2 + # Setup: Mock gcloud build success and components add with error + mock_subprocess_run.side_effect = [ + subprocess.CompletedProcess( + args=gcloud_commands.command_build_custom_gcloud, + returncode=0, + stdout="gcloud build successful!", + stderr="", + ), + subprocess.CalledProcessError( + returncode=1, + cmd=gcloud_commands.command_add_components, + output="", + stderr="Error: Components add failed", + ), + ] + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.build_custom_gcloud() + + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Components add failed" + assert exc_info.value.cmd == gcloud_commands.command_add_components + assert mock_subprocess_run.call_count == 2 @pytest.fixture def mock_subprocess_run(monkeypatch): - mock_run = mock.create_autospec(subprocess.run) - monkeypatch.setattr(subprocess, "run", mock_run) - return mock_run + mock_run = mock.create_autospec(subprocess.run) + monkeypatch.setattr(subprocess, "run", mock_run) + return mock_run def test_fetch_challenges_success(mock_subprocess_run): - # Setup: Configure the mock to simulate a successful gcloud command - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=0, - stdout=sample_fetch_challenge_output, - stderr="", - ) - mock_subprocess_run.return_value = mock_process_result - - # Action: Call the function - resource = test_proposal_resource - result = gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the results - mock_subprocess_run.assert_called_once_with( - gcloud_commands.command_gcloud_describe_proposal - + resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - ) - assert result == mock_process_result - assert result.returncode == 0 - assert result.stdout == sample_fetch_challenge_output - assert not result.stderr + # Setup: Configure the mock to simulate a successful gcloud command + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=0, + stdout=sample_fetch_challenge_output, + stderr="", + ) + mock_subprocess_run.return_value = mock_process_result + + # Action: Call the function + resource = test_proposal_resource + result = gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the results + mock_subprocess_run.assert_called_once_with( + gcloud_commands.command_gcloud_describe_proposal + resource + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + ) + assert result == mock_process_result + assert result.returncode == 0 + assert result.stdout == sample_fetch_challenge_output + assert not result.stderr def test_fetch_challenges_error(mock_subprocess_run): - # Setup: Configure the mock to simulate a failed gcloud command - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=1, cmd="", output="", stderr="Error: Invalid resource" - ) - - # Action & Assert: Call the function and check for the expected exception - resource = "invalid-resource" - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.fetch_challenges(resource) + # Setup: Configure the mock to simulate a failed gcloud command + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, cmd="", output="", stderr="Error: Invalid resource" + ) + + # Action & Assert: Call the function and check for the expected exception + resource = "invalid-resource" + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.fetch_challenges(resource) - # Verify the exception details - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Invalid resource" + # Verify the exception details + assert exc_info.value.returncode == 1 + assert exc_info.value.stderr == "Error: Invalid resource" def test_fetch_challenges_command_construction(mock_subprocess_run): - # Setup: - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=0, - stdout="{}", - stderr="", - ) - mock_subprocess_run.return_value = mock_process_result - resource = test_proposal_resource - - # Action: Call the function - gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the command - mock_subprocess_run.assert_called_once_with( - gcloud_commands.command_gcloud_describe_proposal - + resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - ) + # Setup: + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=0, + stdout="{}", + stderr="", + ) + mock_subprocess_run.return_value = mock_process_result + resource = test_proposal_resource + + # Action: Call the function + gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the command + mock_subprocess_run.assert_called_once_with( + gcloud_commands.command_gcloud_describe_proposal + resource + " --format=json", + capture_output=True, + check=True, + text=True, + shell=True, + ) def test_fetch_challenges_output_capture(mock_subprocess_run): - # Setup: - expected_stdout = "Expected Output" - expected_stderr = "Expected Error" - expected_returncode = 0 - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=expected_returncode, - stdout=expected_stdout, - stderr=expected_stderr, - ) - mock_subprocess_run.return_value = mock_process_result - resource = test_proposal_resource - # Action: Call the function - result = gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the captured output - assert result.stdout == expected_stdout - assert result.stderr == expected_stderr - assert result.returncode == expected_returncode + # Setup: + expected_stdout = "Expected Output" + expected_stderr = "Expected Error" + expected_returncode = 0 + mock_process_result = subprocess.CompletedProcess( + args=[], + returncode=expected_returncode, + stdout=expected_stdout, + stderr=expected_stderr, + ) + mock_subprocess_run.return_value = mock_process_result + resource = test_proposal_resource + # Action: Call the function + result = gcloud_commands.fetch_challenges(resource) + + # Assertions: Verify the captured output + assert result.stdout == expected_stdout + assert result.stderr == expected_stderr + assert result.returncode == expected_returncode # Test case 1: Successful gcloud command def test_send_signed_challenges_success(mock_subprocess_run): - # Setup: Mock successful gcloud execution - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.return_value = subprocess.CompletedProcess( - args=[], # Not checked in this test, but good practice to include - returncode=0, - stdout="gcloud command successful!", - stderr="", - ) - - # Action: Call the function - result = gcloud_commands.send_signed_challenges(signed_files, proposal) - - # Assert: Verify the return value and that subprocess.run was called correctly - assert result.returncode == 0 - assert result.stdout == "gcloud command successful!" - expected_command = " ".join( - gcloud_commands.command_gcloud_approve_proposal - + [proposal] - + [ - "--challenge_replies=\"[('signed_challenge.bin'," - " 'public_key_1.pem')]\"" - ] - ) - mock_subprocess_run.assert_called_once_with( - expected_command, - capture_output=True, - check=True, - text=True, - shell=True, - ) + # Setup: Mock successful gcloud execution + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=[], # Not checked in this test, but good practice to include + returncode=0, + stdout="gcloud command successful!", + stderr="", + ) + + # Action: Call the function + result = gcloud_commands.send_signed_challenges(signed_files, proposal) + + # Assert: Verify the return value and that subprocess.run was called correctly + assert result.returncode == 0 + assert result.stdout == "gcloud command successful!" + expected_command = " ".join( + gcloud_commands.command_gcloud_approve_proposal + + [proposal] + + ["--challenge_replies=\"[('signed_challenge.bin'," " 'public_key_1.pem')]\""] + ) + mock_subprocess_run.assert_called_once_with( + expected_command, + capture_output=True, + check=True, + text=True, + shell=True, + ) # Test case 2: gcloud command returns an error code def test_send_signed_challenges_gcloud_error(mock_subprocess_run): - # Setup: Mock gcloud command with a non-zero return code and stderr - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.return_value = subprocess.CompletedProcess( - args=[], - returncode=1, - stdout="", - stderr="Error: Invalid proposal resource", - ) + # Setup: Mock gcloud command with a non-zero return code and stderr + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=[], + returncode=1, + stdout="", + stderr="Error: Invalid proposal resource", + ) - # Action: Call the function - result = gcloud_commands.send_signed_challenges(signed_files, proposal) + # Action: Call the function + result = gcloud_commands.send_signed_challenges(signed_files, proposal) - # Assert: Verify the return value - assert result.returncode == 1 - assert result.stderr == "Error: Invalid proposal resource" + # Assert: Verify the return value + assert result.returncode == 1 + assert result.stderr == "Error: Invalid proposal resource" # Test case 3: subprocess.run raises a CalledProcessError -def test_send_signed_challenges_called_process_error( - mock_subprocess_run -): - # Setup: Mock subprocess.run to raise a CalledProcessError - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=2, - cmd="test_command", - output="", - stderr="Called process error", - ) - - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.send_signed_challenges(signed_files, proposal) - - assert exc_info.value.returncode == 2 - assert exc_info.value.stderr == "Called process error" - assert exc_info.value.cmd == "test_command" +def test_send_signed_challenges_called_process_error(mock_subprocess_run): + # Setup: Mock subprocess.run to raise a CalledProcessError + signed_files = [("signed_challenge.bin", "public_key_1.pem")] + proposal = "my-proposal" + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=2, + cmd="test_command", + output="", + stderr="Called process error", + ) + + # Action & Assert: Call the function and verify that the + # CalledProcessError is re-raised + with pytest.raises(subprocess.CalledProcessError) as exc_info: + gcloud_commands.send_signed_challenges(signed_files, proposal) + + assert exc_info.value.returncode == 2 + assert exc_info.value.stderr == "Called process error" + assert exc_info.value.cmd == "test_command" # Test case 4: Signed challenge file list is empty. def test_send_signed_challenges_empty_list(mock_subprocess_run): - # Action: Call the function - with pytest.raises(ValueError, match="signed_challenged_files is empty"): - gcloud_commands.send_signed_challenges([], test_proposal_resource) + # Action: Call the function + with pytest.raises(ValueError, match="signed_challenged_files is empty"): + gcloud_commands.send_signed_challenges([], test_proposal_resource) diff --git a/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem b/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem new file mode 100644 index 00000000000..a8ec68c9879 --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxifB3Vg5IHjB8k/1/4Vy +MVAGVFrbQkocwsDEvlCn6MeO/btD1SCcvHQLOnA61HI6rux3XLu9MQF1KkiBNwFS +K3CdcjE7O46jTawmyNU3WMpQYaSbLHVzQB2h0Uif1tWgXYKPhibYOgQ3rt9sswJ0 +y4/6xo1VGK1crOWrSwJxIuMbcePggcIwCI7VIOlSYOqxW/4qW2gJVQTfPp1wDkY9 +QjRk7hUiixt+lsPSLKecsnhk1Mkn35KMvaHRWVeX1YV8t0dPRzjJ/wAH/FhXDNZA +4jC2mjGnpsViz53zXGtRAh+zsbAxXkoYcBqEqMq+GOF/xSx8qFpmcY88pvi5zNnv +rwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem b/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem new file mode 100644 index 00000000000..81a17ceb95a --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1Q0PTAITlnLnaPTIh7A +KcK3pNU2WXogylKsrCeDOLniNWzBlKf57ocQSyEhtNEjneYdDULW+bYhXAahn6oS +yxEmGDRP/9U48YfSShkcDarfL0n2V+kuffq3lB+VP7mHtyfes037rithC86c2/jN +Y7hS/BriMk9/Y8dvPr2SezolrUyO740yr1VOtM0zzoe8jhn1QrCQKy9wm0l/XW9B +aS+dCJUJY52TzuES0Z2E3785IHyvcxoPDDJZIbQVPalU7FBfZCSzDstEIHgJR7nR +EN3Aoj17BkNtn395SmSOlkNn4cdpN+jUyXYVKvnmqq+2pGqqNR/mZhNvAEpYt4Pg +FwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem b/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem new file mode 100644 index 00000000000..c66c1da7c96 --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+NnEwM4y1msfN/6izLG +8/P1p0Ct8Qz0McCdbrB3qSXURLuri89iX1sjcWpbAbaihPpd1yLf9Lv836/Lx55M +8viX5k14DYPmaR1INB3m2OSh12VFV2rM5RQTUVBiYwjm+QNpyybpQ3S0dr89D1iD +KExucMtWKaxPW+I7ceY/OwqskPDSV5P8KHXi1RTOirNNfO9X0MjXc2clLYudxE9d +0jP/5dpURAODBHt7P1xke0uI5CN9YNC5k2pt0MijBiVULJM7Fj+YqyEGqzmdEc4A +Q6GVfIix3o8OQJ5ms6GwZgDvMvhRCm6e3c7IJHNFTQa+0U+hOSkPamFT/fTqn14+ +vQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/requirements.txt b/kms/singletenanthsm/requirements.txt index e29d4ff4c8e..78114f03a63 100644 --- a/kms/singletenanthsm/requirements.txt +++ b/kms/singletenanthsm/requirements.txt @@ -1,8 +1,14 @@ +argcomplete==3.6.2 +attrs==25.3.0 backports.tarfile==1.2.0 cffi==1.17.1 click==8.1.8 +colorlog==6.9.0 cryptography==44.0.0 +dependency-groups==1.3.0 +distlib==0.3.9 fido2==1.2.0 +filelock==3.18.0 importlib_metadata==8.6.1 iniconfig==2.0.0 jaraco.classes==3.4.0 @@ -11,11 +17,15 @@ jaraco.functools==4.1.0 jeepney==0.8.0 keyring==25.6.0 more-itertools==10.6.0 +nox==2025.2.9 packaging==24.2 +platformdirs==4.3.7 pluggy==1.5.0 pycparser==2.22 pyscard==2.2.1 pytest==8.3.4 +pytest-mock==3.14.0 SecretStorage==3.3.3 +virtualenv==20.30.0 yubikey-manager==5.5.1 zipp==3.21.0 diff --git a/kms/singletenanthsm/setup.py b/kms/singletenanthsm/setup.py index 1d93e1d313a..d0a976e3862 100644 --- a/kms/singletenanthsm/setup.py +++ b/kms/singletenanthsm/setup.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + # Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,50 +13,57 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - import argparse -import ykman_utils + import gcloud_commands +import ykman_utils + def validate_operation(operation: str): - if operation == "build_custom_gcloud": + if operation == "build_custom_gcloud": + try: + gcloud_commands.build_custom_gcloud() + except Exception as e: + raise Exception(f"Generating custom gcloud build failed {e}") + elif operation == "generate_rsa_keys": + try: + ykman_utils.generate_private_key() + except Exception as e: + raise Exception(f"Generating private keys failed {e}") + elif operation == "generate_gcloud_and_keys": + generate_private_keys_build_gcloud() + else: + raise Exception( + "Operation type not valid. Operation flag value must be build_custom_gcloud," + " generate_rsa_keys, or generate_gcloud_and_keys" + ) + + +def generate_private_keys_build_gcloud(): + """Generates an RSA key on slot 82 of every yubikey + connected to the local machine and builds the custom gcloud cli. + """ try: - gcloud_commands.build_custom_gcloud() + ykman_utils.generate_private_key() except Exception as e: - raise Exception(f"Generating custom gcloud build failed {e}") - elif operation == "generate_rsa_keys": - try: - ykman_utils.generate_private_key() + raise Exception(f"Generating private keys failed {e}") + try: + gcloud_commands.build_custom_gcloud() except Exception as e: - raise Exception(f"Generating private keys failed {e}") - elif operation == "generate_gcloud_and_keys": - generate_private_keys_build_gcloud() - else: - raise Exception("Operation type not valid. Operation flag value must be build_custom_gcloud," - " generate_rsa_keys, or generate_gcloud_and_keys") - - + raise Exception(f"Generating custom gcloud build failed {e}") -def generate_private_keys_build_gcloud(): - """Generates an RSA key on slot 82 of every yubikey - connected to the local machine and builds the custom gcloud cli. - """ - try: - ykman_utils.generate_private_key() - except Exception as e: - raise Exception(f"Generating private keys failed {e}") - try: - gcloud_commands.build_custom_gcloud() - except Exception as e: - raise Exception(f"Generating custom gcloud build failed {e}") - if __name__ == "__main__": - - parser = argparse.ArgumentParser() - parser.add_argument('--operation',type=str, - choices=['build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'], - required=True - ) - args = parser.parse_args() - validate_operation(args.operation) + parser = argparse.ArgumentParser() + parser.add_argument( + "--operation", + type=str, + choices=[ + "build_custom_gcloud", + "generate_rsa_keys", + "generate_gcloud_and_keys", + ], + required=True, + ) + args = parser.parse_args() + validate_operation(args.operation) diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem new file mode 100644 index 00000000000..e9d057231ec --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxpORpfP7RbgLHZ4w3e/D +Yc6WwEU+fj2I8RolMPafYZFKp2GewsjKX1C44FK4N1Bzd2PnIcJc7dcBd3LxyZXd +9Oj54ReLKNTLIenfUp3BuH21H/jagB+kNOQIyoK/xK5zDCcfkSbhDzet9y5jMsIq +b9uKAmWB127qmkPu95ozUXGWAWsLIahx/wb7mH3Dm2iOqjFrqXMqp2tXcNmAYzux +2Uure5WZQU+QKYTQvnccWkeZs/RTO7j9r/KqKBrHNtSIeRl1leb66PaRCa41xTHy +lp5194BMbceZn8tcUbU67Q3InBTaJu2CIq9IUytUwZBcOfhAqHwOPQ6cZPWUiCRk +LwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem new file mode 100644 index 00000000000..869be1e18f2 --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1xT9BLxfqHi/fWMm0UeI +WBzBA3mtd6gpHtbEhfEJ0IWaUO+X6GJm/u2kHPdJpyf84PlgMfMy14+ZFVMQKM8H +95XEpFah4oMA7zqVOusoLeV1CGSK4/wSVZzsv1OaODU/OCM+wZx0A+L7b9buQpjj +YxgO3SBoe/4LtadaekWBKEGbKNllD/UUqcfiFYfXn0mouXbaj60F9t7+F5dnyRL2 +2T9pvewPd2AuoP5buWuUIqfnOnVMWuWpj14bCimRb28kDNlLTMWgN89bIOAWCbRy +c9Dl7x1oyFB3oKQMPwR6vN6xDSWE+2wWl1xwI4PO0zyZ9DlbXiTvBdadtlvIQwvn +rwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem new file mode 100644 index 00000000000..171afc73420 --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1xabdpYzO/SQjVMJTZZ +RaklZIqMlcKLy2L9C2Y3yruJ4tZ2P8ZgV4dZt6DEl9PTmX8W5GHUoWk/w/wrm8XS +0ViX3t6s1/wgO85yBDrgTG9qRSsM2ELrPq9OucfYTy47Wedp8ALUSdw8JlyNeedq +mDr00k4JjgnIx+OrFt2XApSuHi9DrfbvMFVco5fJshpeLDEgtNoV7OQeDop/6Jnp +/EQtCnWcdhjxuP8sx2IbTwN58W89PwMVVYOQrYlexsECpbompa6X7UCAPAMTJQ+t +15JxJv34TzAEUj/CZS3FOEy24mchPL386UiXvTWD8iuXMfV2YCg9sliJNpTi2SI6 +gQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/signed_challenges/signed_challenge1.bin new file mode 100644 index 0000000000000000000000000000000000000000..2d8a674566bbb0cfc611c8f1fb7e8d09eff616a7 GIT binary patch literal 256 zcmV+b0ssD}*NwT(A0yy*h7C*JE&i`SV~{`~p3(~THR_he+6~y+W8)3b<=(wrKHa#R z3lK^!_xS8tIU2%u`sIx(oLwUcQ48w8-L_hNdEQjRJePu%PeuPY=#FCV)sM)GZT$N9 zHy`IYDXF|8Eeo>pE{mCqO@aW3VYo)^chNr4;PuPaHkY=qYD+vm)yBorNCh;&FWBw! z13^=#79QK!0a9&EEl8s>_sPKxnKm*^IX28vZtq7{eeh7LV&#(17(6ir0Lt0qYItS>QYlk>*DJ3#Y)$Bw?Xf0!{-BR!7dz!bd%Ow9W%84H1{Ip*E9c9bV{{ z~!ՕqtՅ }7O List[bytes]: - """Parses the STHI output and writes the challenges and public keys to files. - - Args: - sthi_output: The output of the STHI command. - - Returns: - A list of the unsigned challenges. - """ - print("parsing challenges into files") - proposal_json = json.loads(sthi_output, strict=False) - challenges = proposal_json["quorumParameters"]["challenges"] - - directory_path = "challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - - challenge_count = 0 - unsigned_challenges = [] - for challenge in challenges: - challenge_count += 1 - print(challenge["challenge"] + "\n") - print(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) - f = open("challenges/challenge{0}.txt".format(challenge_count), "wb") - binary_challenge = ykman_utils.urlsafe_base64_to_binary( - challenge["challenge"] - ) - f.write(binary_challenge) - f.close() - - f = open("challenges/public_key{0}.pem".format(challenge_count), "w") - f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) - f.close() - unsigned_challenges.append(ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"])) - - return unsigned_challenges - - -def parse_args(args): - parser = argparse.ArgumentParser() - parser.add_argument("--proposal_resource", type=str, required=True) - return parser.parse_args(args) - - -def signed_challenges_to_files( - challenge_replies: list[ykman_utils.ChallengeReply], -) -> None: - """Writes the signed challenges and public keys to files. - - Args: - challenge_replies: A list of ChallengeReply objects. - - Returns: - A list of tuples containing the signed challenge file path and the public - key file path. - """ - signed_challenge_files = [] - challenge_count = 0 - for challenge_reply in challenge_replies: - challenge_count += 1 - print("challenge_count", challenge_count) - directory_path = "signed_challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - with open( - f"signed_challenges/public_key_{challenge_count}.pem", "w" - ) as public_key_file: - - # Write public key to file - public_key_file.write(challenge_reply.public_key_pem) - with open( - f"signed_challenges/signed_challenge{challenge_count}.bin", "wb" - ) as binary_file: - - # Write signed challenge to file - binary_file.write(challenge_reply.signed_challenge) - signed_challenge_files.append(( - f"signed_challenges/signed_challenge{challenge_count}.bin", - f"signed_challenges/public_key_{challenge_count}.pem", - )) - return signed_challenge_files - - -def approve_proposal(): - """Approves a proposal by fetching challenges, signing them, and sending them back to gcloud.""" - parser = parse_args(sys.argv[1:]) - - # Fetch challenges - process = gcloud_commands.fetch_challenges(parser.proposal_resource) - - # Parse challenges into files - unsigned_challenges = parse_challenges_into_files(process.stdout) - print(unsigned_challenges) - - # Sign challenges - signed_challenges = ykman_utils.sign_challenges(unsigned_challenges) - - # Parse signed challenges into files - signed_challenged_files = signed_challenges_to_files(signed_challenges) - - # Return signed challenges to gcloud - gcloud_commands.send_signed_challenges( - signed_challenged_files, parser.proposal_resource - ) - - -if __name__ == "__main__": - approve_proposal() diff --git a/kms/singletenanthsm/singletenanthsm/approve_proposal_test.py b/kms/singletenanthsm/singletenanthsm/approve_proposal_test.py deleted file mode 100644 index 9a53298fd38..00000000000 --- a/kms/singletenanthsm/singletenanthsm/approve_proposal_test.py +++ /dev/null @@ -1,241 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from dataclasses import dataclass - - -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.backends import default_backend -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.serialization import ( - Encoding, - PublicFormat, -) - -import argparse -import json -import os -import subprocess -from unittest import mock -from unittest.mock import Mock -from unittest.mock import patch -# from approve_proposal import parse_args - -import approve_proposal -import gcloud_commands -import pytest -import ykman_fake -import ykman_utils - - -sample_sthi_output = """ -{ - "quorumParameters": { - "challenges": [ - { - "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" - }, - { - "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" - }, - { - "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" - } - ], - "requiredApproverCount": 3 - } -} - -""" - -sample_nonces = [ - "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", -] - -@dataclass -class QuorumParameters: - challenges: list[ykman_utils.Challenge] - - # def __init__(self, challenges: list[ykman_utils.Challenge]): - # self.challenges = challenges - - # def to_dict(self): - # return {"challenges": self.challenges} - - - -sample_assigned_challenges = "" - - -@pytest.fixture() -def setup(): - parser = approve_proposal.parse_args(["proposal_resource", "my_proposal"]) - - -test_resource = "projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi/proposals/my_proposal" - -mock_completed_process = subprocess.CompletedProcess - -def public_key_to_pem(public_key): - public_key_pem = public_key.public_bytes( - encoding=Encoding.PEM, - format=PublicFormat.SubjectPublicKeyInfo - ).decode('utf-8') - print("PUBLIC KEY--------------") - print(public_key_pem) - return public_key_pem - -def create_json(public_key_pem_1, public_key_pem_2, public_key_pem_3): - - my_json_string = json.dumps({ "quorumParameters": { - "challenges": [ - { - "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "publicKeyPem": public_key_pem_1 - }, - { - "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", - "publicKeyPem": public_key_pem_2 - }, - { - "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "publicKeyPem": public_key_pem_3 - } - ], - "requiredApproverCount": 3 - }}) - - return my_json_string - - -def create_fake_fetch_response(num_keys=3): - """ - Generates a fake fetch response with a specified number of RSA key pairs. - - Args: - num_keys: The number of RSA key pairs to generate. - - Returns: - A tuple containing: - - A JSON object with the public keys. - - A dictionary mapping public key PEMs to private keys. - """ - pub_to_priv_key = {} - public_key_pems = [] - - for _ in range(num_keys): - private_key, public_key = ykman_fake.generate_rsa_keys() - public_key_pem = public_key_to_pem(public_key) - pub_to_priv_key[public_key_pem] = private_key - public_key_pems.append(public_key_pem) - - challenge_json = create_json(*public_key_pems) # Use * to unpack the list - return challenge_json, pub_to_priv_key - - - -mock_signed_challenges = [] - -def sign_challenges_with_capture(challenges:list[ykman_utils.Challenge], pub_to_priv_key): - signed_challenges = [] - for challenge in challenges: - private_key = pub_to_priv_key[challenge.public_key_pem] - signed_challenge = ykman_fake.sign_data(private_key, challenge.challenge) - signed_challenges.append( - ykman_utils.ChallengeReply( - challenge.challenge, - signed_challenge, - challenge.public_key_pem - ) - ) - mock_signed_challenges.extend(signed_challenges) - return signed_challenges - -def verify_with_fake(pub_to_priv_key, signed_challenges): - for signed_challenge in signed_challenges: - priv_key = pub_to_priv_key[signed_challenge.public_key_pem] - assert True == ykman_fake.verify_signature(priv_key.public_key(), signed_challenge.unsigned_challenge, signed_challenge.signed_challenge) - print("Signed verified successfully") - -def test_get_challenges_mocked(mocker, monkeypatch): - - # Verify signed challenges - monkeypatch.setattr( - "gcloud_commands.send_signed_challenges", - lambda signed_challenges, proposal_resource: verify_with_fake(pub_to_priv_key, mock_signed_challenges) - ) - - # monkeypatch sign challenges - monkeypatch.setattr( - "ykman_utils.sign_challenges", - lambda challenges: sign_challenges_with_capture(challenges, pub_to_priv_key) - ) - - # mock the challenge string returned by service - challenge_json, pub_to_priv_key = create_fake_fetch_response() - mock_response = mocker.MagicMock() - mock_response.stdout = challenge_json - mocker.patch("subprocess.run", return_value=mock_response) - - # monkeypatch parse args - mock_args = argparse.Namespace(proposal_resource="test_resource") - monkeypatch.setattr( - "approve_proposal.parse_args", - lambda args: mock_args - ) - - approve_proposal.approve_proposal() - - # assert challenge files created - challenge_files = ['challenges/challenge1.txt', 'challenges/challenge2.txt', 'challenges/challenge3.txt'] - for file_path in challenge_files: - assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." - - # assert signed challenge files created - signed_challenge_files = ['signed_challenges/signed_challenge1.txt', 'signed_challenges/signed_challenge2.txt', 'signed_challenges/signed_challenge3.txt'] - for file_path in signed_challenge_files: - assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." - - -if __name__ == "__main__": - # Parse challenges into files - unsigned_challenges = approve_proposal.parse_challenges_into_files( - sample_sthi_output - ) - created_signed_files = ['signed_challenges/signed_challenge1.txt', 'signed_challenges/signed_challenge2.txt', 'signed_challenges/signed_challenge3.txt'] - for file_path in created_signed_files: - assert True == os.path.exists(file_path), f"File '{file_path}' should exist but does not." - - # Parse files into challenge list - challenges = ykman_utils.populate_challenges_from_files() - for challenge in challenges: - print(challenge.challenge) - print(challenge.public_key_pem) - unsigned_challenges.append(challenge.challenge) - signed_challenged_files = [] - signed_challenges = ykman_utils.sign_challenges( - challenges, signed_challenged_files - ) - for signed_challenge in signed_challenges: - print(signed_challenge.signed_challenge) - print(signed_challenge.public_key_pem) - print("--challenge_replies=" + str(signed_challenged_files)) - ykman_utils.verify_challenge_signatures(signed_challenges) diff --git a/kms/singletenanthsm/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/singletenanthsm/challenges/challenge1.txt deleted file mode 100644 index c891b41bd6d..00000000000 --- a/kms/singletenanthsm/singletenanthsm/challenges/challenge1.txt +++ /dev/null @@ -1 +0,0 @@ -#?xpAky7.m*\hkG.jo(T!5 \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/singletenanthsm/challenges/challenge2.txt deleted file mode 100644 index 93616f55ed6..00000000000 --- a/kms/singletenanthsm/singletenanthsm/challenges/challenge2.txt +++ /dev/null @@ -1 +0,0 @@ -:/~j;QQܿA3($̳N+;H'^7N;QX \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/singletenanthsm/challenges/challenge3.txt deleted file mode 100644 index 3f9fc362302..00000000000 --- a/kms/singletenanthsm/singletenanthsm/challenges/challenge3.txt +++ /dev/null @@ -1 +0,0 @@ -4>y)v2Ugn&3R"0g/熎X \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/singletenanthsm/challenges/public_key1.pem deleted file mode 100644 index 2cc261dfd64..00000000000 --- a/kms/singletenanthsm/singletenanthsm/challenges/public_key1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQPZpYJxzBkVzgTHei8t -qhbusqs2yFbLVSedd86Oxj6yI26YA3XJqPdTPooa0YBl0AVRR0qg1XYyOU/zH5tk -2jUrGtdvOY78Om93Yj6bjB0Enn3RuCNS6W1DLxVy8g0IUSiGKT8wH30Jvs6Tr69b -yQUh9Cl0e9qIb7ljhtwh9SHkE+87Bgo2Z1qTWByIOzzZOBqW7CSIImNFvvPDf1M2 -/tnR1szdH2GP2urqYc/u+cgjPoOkPvbnSyxxxBYGdx1Fijr43i8IO5uXv4O+GcEb -fb/o1/fJ9MqQODXONWB1naBrzFwRe/wU9rlfvpPOGc9xkUtgSt3jN8J4rlMQfxMl -TQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/singletenanthsm/challenges/public_key2.pem deleted file mode 100644 index 9566d0d8b07..00000000000 --- a/kms/singletenanthsm/singletenanthsm/challenges/public_key2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ipgJ7IlOwDcxKFgz+ic -00DwTf63awWohOZDoDWVSxxTFBciB73ANnpv8ANX88VM1sMSxu+Xd5vsr93Ur04i -i1asQ2p8KFGQKsjggA2nnmfN0IL6iSquYM//D7E/YoWtdaTJ0/Jl+BqBobIHG65H -Kk0CikBTojjWmA9B1Cu9j3g5fCjBGdPGYQtaDVnukrRbLw30T8C5lG1MDxk/hLSB -lw7Bi1hTOsf82k0Q0csmihVArWOrKYOcoiWoKGLCsw2ZBMK0UiYHFXcNVXc045yA -A97+87bSvuunVk86L5572h32/GJyBRjKxDAoWNuJCBm0kzg/abo73hblDxxITO1+ -aQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/singletenanthsm/challenges/public_key3.pem deleted file mode 100644 index 6e235f16207..00000000000 --- a/kms/singletenanthsm/singletenanthsm/challenges/public_key3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuf0l0LU1s9PF6jMylq+6 -bFSZATh1tRreNBPXcVPt/NTD/t90mzjOYitGvSGVNedyp+RiDC9HJjKD8tMtU550 -VMAocnToxONeDdb3o3LNEt5XsJjgpIIVo2d+nK4/Gk52RyKp6Cui7gtQpYhNxMF4 -uazwLWibD7IRUU3Bsyv15g4YNqNmrtZYENVmxZrV37qjwBBsQ8XbEDrs7nFwBJU1 -QNbqOFC89pGxNzVNsH42M64+3eoozrHIqjZzvFxwwUOq55KlrQDUjVMVUcoBnQAM -oZ0zOxA6ETMQS3y5ZMfVH50sbg4xlG2sT9nz9nRlybBKKDzAacmsUjssHDnB8ZIs -BQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/singletenanthsm/gcloud_commands.py deleted file mode 100644 index 7b89c384fbd..00000000000 --- a/kms/singletenanthsm/singletenanthsm/gcloud_commands.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import subprocess - -command_build_custom_gcloud = """ - pushd /tmp - curl -o installer.sh https://sdk.cloud.google.com - chmod +x installer.sh - ./installer.sh --disable-prompts --install-dir ~/sthi - rm installer.sh - popd - alias sthigcloud=~/sthi/google-cloud-sdk/bin/gcloud - sthigcloud auth login - """ - - -command_add_components = """ - ~/sthi/google-cloud-sdk/bin/gcloud components repositories add https://storage.googleapis.com/single-tenant-hsm-private/components-2.json - ~/sthi/google-cloud-sdk/bin/gcloud components update - """ - - -def build_custom_gcloud(): - """Builds a custom gcloud binary.""" - try: - print("\nBuilding custom gcloud build") - process = subprocess.run( - command_build_custom_gcloud, - check=True, - shell=True, - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud build executed successfully.") - print(process.stdout) - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - try: - print("\nAdding gcloud components") - process = subprocess.run( - command_add_components, - check=False, - capture_output=False, - text=True, - shell=True, - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud components add executed successfully.") - print(process.stdout) - return process - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - print(f"Error executing gcloud components update: {e}") - - -command_gcloud_list_proposal = ( - "~/sthi/google-cloud-sdk/bin/gcloud kms single-tenant-hsm list " - "--location=projects/hawksbill-playground/locations/global" -) - -command_gcloud_describe_proposal = """ - ~/sthi/google-cloud-sdk/bin/gcloud \ - kms single-tenant-hsm proposal describe """ - - -def fetch_challenges(sthi_proposal_resource: str): - """Fetches challenges from the server.""" - - try: - print("\nfetching challenges") - process = subprocess.run( - command_gcloud_describe_proposal - + sthi_proposal_resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - # stderr=subprocess.STDOUT - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud command executed successfully.") - print(process.stdout) - return process - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - -command_gcloud_approve_proposal = [ - "~/sthi/google-cloud-sdk/bin/gcloud", - "kms", - "single-tenant-hsm", - "proposal", - "approve", -] - - -def send_signed_challenges( - signed_challenged_files: list[str], proposal_resource: str -): - """Sends signed challenges to the server.""" - if signed_challenged_files is None or not signed_challenged_files: - raise ValueError("signed_challenged_files is empty") - print("Sending signed challenges") - signed_challenge_str = ( - '--challenge_replies="' + str(signed_challenged_files) + '"' - ) - command_str = " ".join( - command_gcloud_approve_proposal - + [proposal_resource] - + [signed_challenge_str] - ) - print(command_str) - - try: - - process = subprocess.run( - command_str, - capture_output=True, - check=True, - text=True, - shell=True, - ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud command executed successfully.") - return process - - except subprocess.CalledProcessError as e: - raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) diff --git a/kms/singletenanthsm/singletenanthsm/gcloud_commands_test.py b/kms/singletenanthsm/singletenanthsm/gcloud_commands_test.py deleted file mode 100644 index 9823aebd114..00000000000 --- a/kms/singletenanthsm/singletenanthsm/gcloud_commands_test.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import subprocess -from unittest import mock -import gcloud_commands -import pytest - -test_proposal_resource = """projects/test_project/locations/\ -us-central1/singleTenantHsmInstances/my_sthi/proposals/my_proposal - """ -sample_fetch_challenge_output = """ -{ - "quorumParameters": { - "challenges": [ - { - "challenge": "tiOz64M_rJ34yOvweHBBltRrm3k34bou4m2JKlz9BmhrR7yU6S6ram8o1VQhyPU1", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3WK/NpZ4DJ68lOR7JINL\nyODwrRanATJNepJi1LYDDO4ZqQvaOvbv8RR47YBlHYAwEDuUC0Vy9g03T0G7V/TV\nTFNQU+I2wIm6VQFFbhjFYYCECILHPNwRp8XN0VKSiTqj5ilPa2wdPsBEgwNKlILn\nv9iTx9IdyFeMmCqIWgeFX5sHddvgq5Dep7kBRVh7ZM1+hOS8kw2qmZgKX8Zwgz3E\n0En/2r+3YgWtMxTz6iqW/Op0UagrlR5EgysjrNgakJEJQA/x23SataJOpVvSE9pH\nSCyzrIaseg1gtz5huDVO5GOK3Xg/VUr2n3sk98MQtHWWaEfcpstSrrefjTC4IYN5\n2QIDAQAB\n-----END PUBLIC KEY-----\n" - }, - { - "challenge": "6bfZOoD9L35qO1GIzVHcv9sX0UEzKCTru8yz1U7NK4o7y0gnXoU3Ak47sFFY4Yzb", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpxT5iX72pkd/m8Fb3mg\nMkCQMoWb3FKAjHsutKpUEMA0ts1atZe7WFBRcCxV2mTDeWFpSwWjuYYSNNrEgk9e\nBRiLJ/36hCewnzw9PZMPcnWv+QLbyLsr4jAEVHk2pWln2HkVbAmK2OWEhvlUjxyT\nfB0b1UsBP3uy5f+SLb8iltvwWZGauT64JrLpbIwhk6SbXOCZSZtsXVZ5mVPEIxik\nZ4iBT3r+9Fc3fgKN/16bjdHw+qbWxovEYejG10Yp1yO4QjSzkxQsXTFvsWxaTKF2\ncZa5GF19b9ZkY3SRxHF6emA720F+N4oeGuV0Zu/ACYfMqRUSkh5GiOpv6VxvuXRD\n0wIDAQAB\n-----END PUBLIC KEY-----\n" - }, - { - "challenge": "NNH3Pt3F-OvaeYR_Dynp_nbHMuLaVYBnkG7uJtwz2-lShyLaHNjOyjBnL-eGjoRY", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrrPGkxbk08x5CpkUk5y\nfWBmfiE4qU4IWaSO9HCBv5uRWJvDqXkKjkcBptwmGFsnzT+owfSe+21nWLOLZqwW\nmPbV0bW3e7l3ZUw/4fUga+KJDR5OfkkXWSos1cEMhxsSMnGykhx2/ge9bqY0Edbr\nzckOT2un87ThdawveS3hOxTczE+JcgzoI+CUxlPV0c9yJ5iNFZXf1p7wj3Rq2I8X\nAl4XyMP/+0TLR5+UTrrxLC4ds4m9EjMPRv4aNJFqzBfb3WBM/DFVvNR82Mt2pfF8\nlv6RyZU/vls6vjDl42NK3hckOhEGqQpPmifKgPCaOwdLHg68CjQZ54GWGqyFGzNx\nHwIDAQAB\n-----END PUBLIC KEY-----\n" - } - ], - "requiredApproverCount": 3 - } -} - -""" - - -# Test case 1: Successful build and components add -def test_build_custom_gcloud_success(mock_subprocess_run): - # Setup: Mock successful gcloud execution - mock_subprocess_run.side_effect = [ - subprocess.CompletedProcess( - args=gcloud_commands.command_build_custom_gcloud, - returncode=0, - stdout="gcloud build successful!", - stderr="", - ), - subprocess.CompletedProcess( - args=gcloud_commands.command_add_components, - returncode=0, - stdout="gcloud components add successful.", - stderr="", - ), - ] - - # Action: Call the function - result = gcloud_commands.build_custom_gcloud() - - # Assert: Verify the return value and that subprocess.run was called correctly - assert result.returncode == 0 - assert result.stdout == "gcloud components add successful." - assert mock_subprocess_run.call_count == 2 - mock_subprocess_run.assert_has_calls([ - mock.call( - gcloud_commands.command_build_custom_gcloud, check=True, shell=True - ), - mock.call( - gcloud_commands.command_add_components, - check=False, - shell=True, - capture_output=False, - text=True, - ), - ]) - - -# Test case 2: gcloud build fails -def test_build_custom_gcloud_build_error(mock_subprocess_run): - # Setup: Mock gcloud build command with a non-zero return code - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=1, - cmd=gcloud_commands.command_build_custom_gcloud, - output="", - stderr="Error: Build failed", - ) - - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.build_custom_gcloud() - - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Build failed" - assert exc_info.value.cmd == gcloud_commands.command_build_custom_gcloud - assert mock_subprocess_run.call_count == 1 - - -# Test case 3: gcloud components add fails -def test_build_custom_gcloud_components_error(mock_subprocess_run): - # Setup: Mock gcloud build success and components add with error - mock_subprocess_run.side_effect = [ - subprocess.CompletedProcess( - args=gcloud_commands.command_build_custom_gcloud, - returncode=0, - stdout="gcloud build successful!", - stderr="", - ), - subprocess.CalledProcessError( - returncode=1, - cmd=gcloud_commands.command_add_components, - output="", - stderr="Error: Components add failed", - ), - ] - - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.build_custom_gcloud() - - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Components add failed" - assert exc_info.value.cmd == gcloud_commands.command_add_components - assert mock_subprocess_run.call_count == 2 - - -@pytest.fixture -def mock_subprocess_run(monkeypatch): - mock_run = mock.create_autospec(subprocess.run) - monkeypatch.setattr(subprocess, "run", mock_run) - return mock_run - - -def test_fetch_challenges_success(mock_subprocess_run): - # Setup: Configure the mock to simulate a successful gcloud command - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=0, - stdout=sample_fetch_challenge_output, - stderr="", - ) - mock_subprocess_run.return_value = mock_process_result - - # Action: Call the function - resource = test_proposal_resource - result = gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the results - mock_subprocess_run.assert_called_once_with( - gcloud_commands.command_gcloud_describe_proposal - + resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - ) - assert result == mock_process_result - assert result.returncode == 0 - assert result.stdout == sample_fetch_challenge_output - assert not result.stderr - - -def test_fetch_challenges_error(mock_subprocess_run): - # Setup: Configure the mock to simulate a failed gcloud command - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=1, cmd="", output="", stderr="Error: Invalid resource" - ) - - # Action & Assert: Call the function and check for the expected exception - resource = "invalid-resource" - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.fetch_challenges(resource) - - # Verify the exception details - assert exc_info.value.returncode == 1 - assert exc_info.value.stderr == "Error: Invalid resource" - - -def test_fetch_challenges_command_construction(mock_subprocess_run): - # Setup: - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=0, - stdout="{}", - stderr="", - ) - mock_subprocess_run.return_value = mock_process_result - resource = test_proposal_resource - - # Action: Call the function - gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the command - mock_subprocess_run.assert_called_once_with( - gcloud_commands.command_gcloud_describe_proposal - + resource - + " --format=json", - capture_output=True, - check=True, - text=True, - shell=True, - ) - - -def test_fetch_challenges_output_capture(mock_subprocess_run): - # Setup: - expected_stdout = "Expected Output" - expected_stderr = "Expected Error" - expected_returncode = 0 - mock_process_result = subprocess.CompletedProcess( - args=[], - returncode=expected_returncode, - stdout=expected_stdout, - stderr=expected_stderr, - ) - mock_subprocess_run.return_value = mock_process_result - resource = test_proposal_resource - # Action: Call the function - result = gcloud_commands.fetch_challenges(resource) - - # Assertions: Verify the captured output - assert result.stdout == expected_stdout - assert result.stderr == expected_stderr - assert result.returncode == expected_returncode - - -# Test case 1: Successful gcloud command -def test_send_signed_challenges_success(mock_subprocess_run): - # Setup: Mock successful gcloud execution - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.return_value = subprocess.CompletedProcess( - args=[], # Not checked in this test, but good practice to include - returncode=0, - stdout="gcloud command successful!", - stderr="", - ) - - # Action: Call the function - result = gcloud_commands.send_signed_challenges(signed_files, proposal) - - # Assert: Verify the return value and that subprocess.run was called correctly - assert result.returncode == 0 - assert result.stdout == "gcloud command successful!" - expected_command = " ".join( - gcloud_commands.command_gcloud_approve_proposal - + [proposal] - + [ - "--challenge_replies=\"[('signed_challenge.bin'," - " 'public_key_1.pem')]\"" - ] - ) - mock_subprocess_run.assert_called_once_with( - expected_command, - capture_output=True, - check=True, - text=True, - shell=True, - ) - - -# Test case 2: gcloud command returns an error code -def test_send_signed_challenges_gcloud_error(mock_subprocess_run): - # Setup: Mock gcloud command with a non-zero return code and stderr - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.return_value = subprocess.CompletedProcess( - args=[], - returncode=1, - stdout="", - stderr="Error: Invalid proposal resource", - ) - - # Action: Call the function - result = gcloud_commands.send_signed_challenges(signed_files, proposal) - - # Assert: Verify the return value - assert result.returncode == 1 - assert result.stderr == "Error: Invalid proposal resource" - - -# Test case 3: subprocess.run raises a CalledProcessError -def test_send_signed_challenges_called_process_error( - mock_subprocess_run -): - # Setup: Mock subprocess.run to raise a CalledProcessError - signed_files = [("signed_challenge.bin", "public_key_1.pem")] - proposal = "my-proposal" - mock_subprocess_run.side_effect = subprocess.CalledProcessError( - returncode=2, - cmd="test_command", - output="", - stderr="Called process error", - ) - - # Action & Assert: Call the function and verify that the - # CalledProcessError is re-raised - with pytest.raises(subprocess.CalledProcessError) as exc_info: - gcloud_commands.send_signed_challenges(signed_files, proposal) - - assert exc_info.value.returncode == 2 - assert exc_info.value.stderr == "Called process error" - assert exc_info.value.cmd == "test_command" - - -# Test case 4: Signed challenge file list is empty. -def test_send_signed_challenges_empty_list(mock_subprocess_run): - - # Action: Call the function - with pytest.raises(ValueError, match="signed_challenged_files is empty"): - gcloud_commands.send_signed_challenges([], test_proposal_resource) diff --git a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_25167010.pem b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_25167010.pem deleted file mode 100644 index 7b0fe69d677..00000000000 --- a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_25167010.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArp0MggO5sHSIyfzgVfnR -xzG3S1D1XKVRFHHa5PHG+g64OZJ/cYNQux+OXKBuXs0rgKI+s+PVnYF9vJg3gatF -Xos+lN0Xv+wcuFTqMD0acPUNhX2QCq53Lb4u1R/EAZ0lzFVSu17N6Kde90R8EPgE -a8pc7pusgzT7wE7TAlXbRLYBp11n+dcgXGtYHHBECeBLiu7vvx8bUO6ZPLJClnwK -dAkIy9HCtlMUdukmIUrL5oMLUVMCdPn/8fELUbsFBK5ZE1nrbx6eKJKKXRl1BZ+s -11U54/xftPvqw9MgKKU4aWoY5Yz+xsDM27h1zvPtADXzTUsNY3oLpo/pYJqmwdhz -6wIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787103.pem b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787103.pem deleted file mode 100644 index bd82c8f28b8..00000000000 --- a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787103.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Rx08jnCJMrypWQgp8a8 -zBj0SAjIqDlOucHmaax6gI0xDMxq1LSfXUHsuD5qtxttxn+WszKUar29/VRHY9Af -98HjvqIRTJSyvCwDr0w/9LEKXL4JubzP9+4PdeK5AsdhgaKazYRfjpUVS7cispoq -N5rl+kLEJS5SvPTbzXh71YLgFWtUXJJLz5SgbCReVI4eegfVTZ1JPrAC2x7TVu4P -qNXLmuX9ijqEhxvr4Cv7A4n7MJTpaXAeu1yYByslI3kF16rND2digyd7TSZ/LwQe -eTd5ElTXF6b41zhRm+FyCwWjcWjiJdXnUSZai2Td2n8gddsDmz5YwBbRKD9lBpRy -gQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787105.pem b/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787105.pem deleted file mode 100644 index 3466ad79de9..00000000000 --- a/kms/singletenanthsm/singletenanthsm/generated_public_keys/public_key_28787105.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3llqGRlrEdw1AFPAi5yN -SLunBSGtodc6F4RBXkTjE6awpRsqJUw0Nrt6iy6zK6yWBBE/ecwcZ7B5DUGFalqZ -NphMUhKaEVlbxO3R5+z+6OcJR74VO/DIzGOfpGw9FjK1FhYBl7Ljhy8cCQbky3hJ -I8GPsrhTkM7+Ciphp5Hxt4glhnd7LY5NiPy4DKdzhw3mvPZzm3cRhGDTbiMPHHxB -h6fjp8uzUTnAmF6EWNwPRKLvpg3q3QnOAmwmtOcke5Yl6TPzAw1r1M6KhVxkFijr -f22HHrM0ykHOTWZZTU+IgPX4T/dqEQ13BrqJPg8tai6pMClDdl5zStIK004wfNct -FwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/requirements.txt b/kms/singletenanthsm/singletenanthsm/requirements.txt deleted file mode 100644 index e29d4ff4c8e..00000000000 --- a/kms/singletenanthsm/singletenanthsm/requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -backports.tarfile==1.2.0 -cffi==1.17.1 -click==8.1.8 -cryptography==44.0.0 -fido2==1.2.0 -importlib_metadata==8.6.1 -iniconfig==2.0.0 -jaraco.classes==3.4.0 -jaraco.context==6.0.1 -jaraco.functools==4.1.0 -jeepney==0.8.0 -keyring==25.6.0 -more-itertools==10.6.0 -packaging==24.2 -pluggy==1.5.0 -pycparser==2.22 -pyscard==2.2.1 -pytest==8.3.4 -SecretStorage==3.3.3 -yubikey-manager==5.5.1 -zipp==3.21.0 diff --git a/kms/singletenanthsm/singletenanthsm/setup.py b/kms/singletenanthsm/singletenanthsm/setup.py deleted file mode 100644 index 1d93e1d313a..00000000000 --- a/kms/singletenanthsm/singletenanthsm/setup.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import ykman_utils -import gcloud_commands - -def validate_operation(operation: str): - if operation == "build_custom_gcloud": - try: - gcloud_commands.build_custom_gcloud() - except Exception as e: - raise Exception(f"Generating custom gcloud build failed {e}") - elif operation == "generate_rsa_keys": - try: - ykman_utils.generate_private_key() - except Exception as e: - raise Exception(f"Generating private keys failed {e}") - elif operation == "generate_gcloud_and_keys": - generate_private_keys_build_gcloud() - else: - raise Exception("Operation type not valid. Operation flag value must be build_custom_gcloud," - " generate_rsa_keys, or generate_gcloud_and_keys") - - - - -def generate_private_keys_build_gcloud(): - """Generates an RSA key on slot 82 of every yubikey - connected to the local machine and builds the custom gcloud cli. - """ - try: - ykman_utils.generate_private_key() - except Exception as e: - raise Exception(f"Generating private keys failed {e}") - try: - gcloud_commands.build_custom_gcloud() - except Exception as e: - raise Exception(f"Generating custom gcloud build failed {e}") - -if __name__ == "__main__": - - parser = argparse.ArgumentParser() - parser.add_argument('--operation',type=str, - choices=['build_custom_gcloud','generate_rsa_keys','generate_gcloud_and_keys'], - required=True - ) - args = parser.parse_args() - validate_operation(args.operation) diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_1.pem deleted file mode 100644 index 2cc261dfd64..00000000000 --- a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQPZpYJxzBkVzgTHei8t -qhbusqs2yFbLVSedd86Oxj6yI26YA3XJqPdTPooa0YBl0AVRR0qg1XYyOU/zH5tk -2jUrGtdvOY78Om93Yj6bjB0Enn3RuCNS6W1DLxVy8g0IUSiGKT8wH30Jvs6Tr69b -yQUh9Cl0e9qIb7ljhtwh9SHkE+87Bgo2Z1qTWByIOzzZOBqW7CSIImNFvvPDf1M2 -/tnR1szdH2GP2urqYc/u+cgjPoOkPvbnSyxxxBYGdx1Fijr43i8IO5uXv4O+GcEb -fb/o1/fJ9MqQODXONWB1naBrzFwRe/wU9rlfvpPOGc9xkUtgSt3jN8J4rlMQfxMl -TQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_2.pem deleted file mode 100644 index 9566d0d8b07..00000000000 --- a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ipgJ7IlOwDcxKFgz+ic -00DwTf63awWohOZDoDWVSxxTFBciB73ANnpv8ANX88VM1sMSxu+Xd5vsr93Ur04i -i1asQ2p8KFGQKsjggA2nnmfN0IL6iSquYM//D7E/YoWtdaTJ0/Jl+BqBobIHG65H -Kk0CikBTojjWmA9B1Cu9j3g5fCjBGdPGYQtaDVnukrRbLw30T8C5lG1MDxk/hLSB -lw7Bi1hTOsf82k0Q0csmihVArWOrKYOcoiWoKGLCsw2ZBMK0UiYHFXcNVXc045yA -A97+87bSvuunVk86L5572h32/GJyBRjKxDAoWNuJCBm0kzg/abo73hblDxxITO1+ -aQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_3.pem deleted file mode 100644 index 6e235f16207..00000000000 --- a/kms/singletenanthsm/singletenanthsm/signed_challenges/public_key_3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuf0l0LU1s9PF6jMylq+6 -bFSZATh1tRreNBPXcVPt/NTD/t90mzjOYitGvSGVNedyp+RiDC9HJjKD8tMtU550 -VMAocnToxONeDdb3o3LNEt5XsJjgpIIVo2d+nK4/Gk52RyKp6Cui7gtQpYhNxMF4 -uazwLWibD7IRUU3Bsyv15g4YNqNmrtZYENVmxZrV37qjwBBsQ8XbEDrs7nFwBJU1 -QNbqOFC89pGxNzVNsH42M64+3eoozrHIqjZzvFxwwUOq55KlrQDUjVMVUcoBnQAM -oZ0zOxA6ETMQS3y5ZMfVH50sbg4xlG2sT9nz9nRlybBKKDzAacmsUjssHDnB8ZIs -BQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.bin deleted file mode 100644 index 24dd7dc3600..00000000000 --- a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.bin +++ /dev/null @@ -1 +0,0 @@ -^²m p34Ay绌VRGM_] ]6o-"ea5˞ı7G:`*2uTkbaN?x*p#uƻh+#`MVh)0 f+*(-*c39sYE?:k>XplK;8At1W}~99WR~z!_[::k5zk? Έ \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.txt b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.txt deleted file mode 100644 index e2cc944c512..00000000000 --- a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge1.txt +++ /dev/null @@ -1 +0,0 @@ -HwY(5E *dzb-qw;cB[e'.9v'$u+L'bo6=9*K$P+˜d;'-rf7eQ?~&Ss6Gf=3IRv#V)DquZ1f@Bkz*MPJb|Lf%$t0{FeN^)LRZH zMZ@bg=FP(ipgEj?N*Tpu_V5N|4GUfKmB*X71eSC0*WdW?VfRh1oJ*P{sP2!%ON%y= zja%iPH87bHnmumZTgEju-F!9(l3Cxzs5@Ap@}Ot`W_wWJxJPl*R{1!wFG$~Owl?I_ GA?1KNPJ_n) diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.txt b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.txt deleted file mode 100644 index 51f099c09fd..00000000000 --- a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge2.txt +++ /dev/null @@ -1,3 +0,0 @@ -oSJHzv -zd+5N*?Z3=iّ:<-Wowwrsd+%vЄ˩'oqb~q)@*~B11_Zum~{S7?.LإFY(dȴcNMyl=%CZG%5 -ҥԭAdW8UL}7&B{E4xNTx4K=ǯeԡ{g/-fIHBiNOp ?n>\ƼlS \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.bin b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.bin deleted file mode 100644 index a5536c11806..00000000000 --- a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.bin +++ /dev/null @@ -1 +0,0 @@ -nO?B) z3cDv!qVY _xkL䬯YLR6aqZ[r+һԓ|U&w/FB4@Zy32HC[.[Ic501XMCZYR1V:&@n {[V9c0H㜅F.o*Eh swU`FrE>@ڌoEBٞѴ'`.Szϥ,ƕ V~tUw \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.txt b/kms/singletenanthsm/singletenanthsm/signed_challenges/signed_challenge3.txt deleted file mode 100644 index f07c9cef275f2ddd1d33cf6be1cb5e9e4a8a1a27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssDJIB1;+Q0il23-Pg+HcrX`4UC7u&-H>d60%EOID7>f5UW5aTG~TtMepV8 z10F#Iob>sZ4cxEaoZ?!QM=~OumBxrSdZ!e{)^aVA^k-fC<`GI*vGfcUnI6b}v0i@G zpX-A}{@S8(9ffCkwd864j2^v|$2d<#gfUAM<^A;2!P1t95@K(TDL(*GoPyfC3@@{RKO~JlsbjS zC9+HV)V3EH5~d+w3t5*=6F-k)g-aVomIp^odx?oXp1oU)p?{MRJg5^`rJ%4mR4JfH G)g9aJaC|oa diff --git a/kms/singletenanthsm/singletenanthsm/ykman_fake.py b/kms/singletenanthsm/singletenanthsm/ykman_fake.py deleted file mode 100644 index 9bc29efe523..00000000000 --- a/kms/singletenanthsm/singletenanthsm/ykman_fake.py +++ /dev/null @@ -1,59 +0,0 @@ -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric import padding - -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives.serialization import ( - Encoding, - PrivateFormat, - PublicFormat, - load_pem_private_key, - load_pem_public_key, - BestAvailableEncryption -) - - -def generate_rsa_keys(key_size=2048): - """Generates a private and public RSA key pair with the specified key size.""" - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=key_size, - ) - public_key = private_key.public_key() - return private_key, public_key - - -def sign_data(private_key, data, hash_algorithm=hashes.SHA256()): - """Signs the provided data using the private key with PKCS#1.5 padding.""" - if not isinstance(data, bytes): - raise TypeError("Data must be of type bytes") - signature = private_key.sign(data, padding.PKCS1v15(), hash_algorithm) - return signature - - -def verify_signature(public_key, data, signature, hash_algorithm=hashes.SHA256()): - """Verifies the signature of the data using the public key.""" - if not isinstance(data, bytes): - raise TypeError("Data must be of type bytes") - if not isinstance(signature, bytes): - raise TypeError("Signature must be of type bytes") - try: - public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm) - return True # Signature is valid - except InvalidSignature: - return False # Signature is invalid - - - -if __name__ == "__main__": - private_key, public_key = generate_rsa_keys() - - # Data to sign (as bytes) - data_to_sign = b"This is the data to be signed." - signature = sign_data(private_key, data_to_sign) - print(f"Signature generated: {signature.hex()}") - is_valid = verify_signature(public_key, data_to_sign, signature) - if is_valid: - print("Signature is VALID.") - else: - print("Signature is INVALID.") \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/singletenanthsm/ykman_utils.py deleted file mode 100644 index 66c545c9ba9..00000000000 --- a/kms/singletenanthsm/singletenanthsm/ykman_utils.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from dataclasses import dataclass - - -import base64 -import pathlib -import cryptography.exceptions -import glob -import re -import os -import ykman - - - -from cryptography.hazmat.primitives import _serialization -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric import ed25519 -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric import x25519 -from cryptography.hazmat.primitives.serialization import load_pem_public_key - - -from ykman import piv -from ykman.device import list_all_devices -from yubikit.piv import hashes -from yubikit.piv import PIN_POLICY, TOUCH_POLICY, hashes -from yubikit.piv import SmartCardConnection - -DEFAULT_MANAGEMENT_KEY = "010203040506070801020304050607080102030405060708" -DEFAULT_PIN = "123456" - - -def generate_private_key( - key_type=piv.KEY_TYPE.RSA2048, - management_key=DEFAULT_MANAGEMENT_KEY, - pin=DEFAULT_PIN, -): - """Generates a private key on the yubikey""" - - devices = list_all_devices() - if not devices: - raise Exception("no yubikeys found") - print(f"{len(devices)} yubikeys detected") - for yubikey, device_info in devices: - with yubikey.open_connection(SmartCardConnection) as connection: - piv_session = piv.PivSession(connection) - piv_session.authenticate( - piv.MANAGEMENT_KEY_TYPE.TDES, - bytes.fromhex(management_key), - ) - piv_session.verify_pin(pin) - - public_key = piv_session.generate_key( - piv.SLOT.RETIRED1, - key_type=key_type, - pin_policy=PIN_POLICY.DEFAULT, - touch_policy=TOUCH_POLICY.ALWAYS, - ) - if not public_key: - raise Exception("failed to generate public key") - with open( - f"generated_public_keys/public_key_{device_info.serial}.pem", "wb" - ) as binary_file: - - # Write bytes to file - binary_file.write( - public_key.public_bytes( - encoding=_serialization.Encoding.PEM, - format=_serialization.PublicFormat.SubjectPublicKeyInfo, - ) - ) - print( - f"Private key pair generated on device {device_info.serial} on key" - f" slot: {piv.SLOT.RETIRED1}" - ) - -@dataclass -class Challenge: - challenge: bytes - public_key_pem: str - - def to_dict(self): - return { - "challenge": base64.b64encode(self.challenge).decode('utf-8'), - "public_key_pem": self.public_key_pem, - } - - @staticmethod - def from_dict(data): - if not isinstance(data, dict): - return None - return Challenge( - challenge=base64.b64decode(data['challenge']), - public_key_pem=data['public_key_pem'] - ) - - - -class ChallengeReply: - - def __init__(self, unsigned_challenge, signed_challenge, public_key_pem): - self.unsigned_challenge = unsigned_challenge - self.signed_challenge = signed_challenge - self.public_key_pem = public_key_pem - -def populate_challenges_from_files() -> list[Challenge]: - public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("challenges/public_key*.pem")] - print(public_key_files) - challenge_files = [challenge_file for challenge_file in pathlib.Path.cwd().glob("challenges/challenge*.txt")] - print(challenge_files) - - challenges = [] - - for public_key_file in public_key_files: - challenge_id = re.findall(r"\d+", str(public_key_file)) - for challenge_file in challenge_files: - if challenge_id == re.findall(r"\d+",str(challenge_file)): - print(public_key_file) - file = open(public_key_file, "r") - public_key_pem = file.read() - file.close() - file = open(challenge_file, "rb") - challenge = file.read() - file.close() - challenges.append(Challenge(challenge, public_key_pem )) - return challenges - - -def sign_challenges(challenges: list[Challenge]) -> list[ChallengeReply]: - """Signs a proposal's challenges using a Yubikey.""" - if not challenges: - raise Exception("Challenge list empty: No challenges to sign.") - signed_challenges = [] - devices = list_all_devices() - if not devices: - raise Exception("no yubikeys found") - for yubikey, _ in devices: - with yubikey.open_connection(SmartCardConnection) as connection: - # Make PivSession and fetch public key from Signature slot. - piv_session = piv.PivSession(connection) - # authenticate - piv_session.authenticate( - piv.MANAGEMENT_KEY_TYPE.TDES, - bytes.fromhex("010203040506070801020304050607080102030405060708"), - ) - piv_session.verify_pin("123456") - - # Get the public key from slot 82. - slot_metadata = piv_session.get_slot_metadata(slot=piv.SLOT.RETIRED1) - print(slot_metadata.public_key.public_bytes) - - # Check to see if any of the challenge public keys matches with the - # public key from slot 82. - for challenge in challenges: - key_public_bytes = slot_metadata.public_key.public_bytes( - encoding=_serialization.Encoding.PEM, - format=_serialization.PublicFormat.SubjectPublicKeyInfo, - ) - print(key_public_bytes.decode()) - print(challenge.public_key_pem) - if key_public_bytes == challenge.public_key_pem.encode(): - - # sign the challenge - print("Press Yubikey to sign challenge") - # print("key type: " + str(slot_metadata.key_type)) - # print(challenge.challenge) - # print(type(challenge.challenge)) - signed_challenge = piv_session.sign( - slot=piv.SLOT.RETIRED1, - key_type=slot_metadata.key_type, - message=challenge.challenge, - hash_algorithm=hashes.SHA256(), - padding=padding.PKCS1v15(), - ) - - signed_challenges.append( - ChallengeReply( - challenge.challenge, - signed_challenge, - challenge.public_key_pem - ) - ) - print("Challenge signed successfully") - if not signed_challenges: - raise Exception( - "No matching public keys between Yubikey and challenges. Make sure" - " key is generated in correct slot" - ) - return signed_challenges - -def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: - """ - Converts a URL-safe base64 encoded string to its binary equivalent. - - Args: - urlsafe_string: The URL-safe base64 encoded string. - - Returns: - The binary data as bytes, or None if an error occurs. - - Raises: - TypeError: If the input is not a string. - ValueError: If the input string is not valid URL-safe base64. - """ - try: - if not isinstance(urlsafe_string, str): - raise TypeError("Input must be a string") - # Add padding if necessary. Base64 requires padding to be a multiple of 4 - missing_padding = len(urlsafe_string) % 4 - if missing_padding: - urlsafe_string += '=' * (4 - missing_padding) - return base64.urlsafe_b64decode(urlsafe_string) - except base64.binascii.Error as e: - raise ValueError(f"Invalid URL-safe base64 string: {e}") from e - -def verify_challenge_signatures(challenge_replies: list[ChallengeReply]): - if not challenge_replies: - raise Exception("No signed challenges to verify") - for challenge_reply in challenge_replies: - public_key = load_pem_public_key( - challenge_reply.public_key_pem.encode() - ) - try: - - public_key.verify( - challenge_reply.signed_challenge, - challenge_reply.unsigned_challenge, - padding.PKCS1v15(), - hashes.SHA256(), - ) - print(f"Signature verification success") - except cryptography.exceptions.InvalidSignature as e: - raise cryptography.exceptions.InvalidSignature((f"Signature verification failed: {e}")) - - -if __name__ == "__main__": - generate_private_key() \ No newline at end of file diff --git a/kms/singletenanthsm/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/singletenanthsm/ykman_utils_test.py deleted file mode 100644 index 37b1cee124c..00000000000 --- a/kms/singletenanthsm/singletenanthsm/ykman_utils_test.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import cryptography.exceptions - - -import glob -import os -import pathlib -import pytest -import re - -import ykman_utils - -class Challenge: - - def __init__(self, challenge, public_key_pem): - self.challenge = challenge - self.public_key_pem = public_key_pem - -class ChallengeReply: - - def __init__(self, signed_challenge, public_key_pem): - self.signed_challenge = signed_challenge - self.public_key_pem = public_key_pem - -challenge_test_data = b"test_data" - -def generate_test_challenge_files(): - # Create challenges list from challenges directory - challenges = ykman_utils.populate_challenges_from_files() - for challenge in challenges: - print(challenge.challenge) - print(challenge.public_key_pem) - # Sign challenges - signed_challenges = ykman_utils.sign_challenges(challenges) - ykman_utils.verify_challenge_signatures(signed_challenges, b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN") - - - -# A yubikey connected to your local machine will be needed to run these tests. -# The generate_private_key() method will rewrite the key saved on slot 82(Retired1). -@pytest.fixture(autouse=True) -def key_setup(): - ykman_utils.generate_private_key() - -def challenges(): - public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("generated_public_keys/public_key*.pem")] - challenges = [] - - - for public_key_file in public_key_files: - file = open(public_key_file, "r") - public_key_pem = file.read() - challenges.append(Challenge(challenge_test_data, public_key_pem )) - - return challenges - -def test_sign_and_verify_challenges(): - signed_challenges = ykman_utils.sign_challenges(challenges()) - ykman_utils.verify_challenge_signatures(signed_challenges) - -def test_verify_mismatching_data_fail(): - with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: - signed_challenges = ykman_utils.sign_challenges(challenges()) - signed_challenges[0].signed_challenge = b"mismatched_data" - ykman_utils.verify_challenge_signatures(signed_challenges) - assert "Signature verification failed" in str(exec_info.value) - -def test_sign_empty_challenge_list_fail(): - with pytest.raises(Exception) as exec_info: - # empty_challenges = [] - signed_challenges = ykman_utils.sign_challenges([]) - assert "Challenge list empty" in str(exec_info.value) - -def test_sign_no_matching_public_keys_fail(): - modified_challenges = challenges() - for challenge in modified_challenges: - challenge.public_key_pem = "modified_public_key" - with pytest.raises(Exception) as exec_info: - signed_challenges = ykman_utils.sign_challenges(modified_challenges) - assert "No matching public keys" in str(exec_info.value) - -def test_verify_empty_challenge_replies_fail(): - with pytest.raises(Exception) as exec_info: - ykman_utils.verify_challenge_signatures([]) - assert "No signed challenges to verify" in str(exec_info) diff --git a/kms/singletenanthsm/ykman_fake.py b/kms/singletenanthsm/ykman_fake.py index 9bc29efe523..38a7d75eeaf 100644 --- a/kms/singletenanthsm/ykman_fake.py +++ b/kms/singletenanthsm/ykman_fake.py @@ -1,16 +1,23 @@ -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric import padding +#!/usr/bin/env python + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives.serialization import ( - Encoding, - PrivateFormat, - PublicFormat, - load_pem_private_key, - load_pem_public_key, - BestAvailableEncryption -) +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import rsa def generate_rsa_keys(key_size=2048): @@ -26,7 +33,7 @@ def generate_rsa_keys(key_size=2048): def sign_data(private_key, data, hash_algorithm=hashes.SHA256()): """Signs the provided data using the private key with PKCS#1.5 padding.""" if not isinstance(data, bytes): - raise TypeError("Data must be of type bytes") + raise TypeError("Data must be of type bytes") signature = private_key.sign(data, padding.PKCS1v15(), hash_algorithm) return signature @@ -34,9 +41,9 @@ def sign_data(private_key, data, hash_algorithm=hashes.SHA256()): def verify_signature(public_key, data, signature, hash_algorithm=hashes.SHA256()): """Verifies the signature of the data using the public key.""" if not isinstance(data, bytes): - raise TypeError("Data must be of type bytes") + raise TypeError("Data must be of type bytes") if not isinstance(signature, bytes): - raise TypeError("Signature must be of type bytes") + raise TypeError("Signature must be of type bytes") try: public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm) return True # Signature is valid @@ -44,7 +51,6 @@ def verify_signature(public_key, data, signature, hash_algorithm=hashes.SHA256() return False # Signature is invalid - if __name__ == "__main__": private_key, public_key = generate_rsa_keys() @@ -56,4 +62,4 @@ def verify_signature(public_key, data, signature, hash_algorithm=hashes.SHA256() if is_valid: print("Signature is VALID.") else: - print("Signature is INVALID.") \ No newline at end of file + print("Signature is INVALID.") diff --git a/kms/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/ykman_utils.py index fafab21fd7f..782dfcdf589 100644 --- a/kms/singletenanthsm/ykman_utils.py +++ b/kms/singletenanthsm/ykman_utils.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2020 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,33 +14,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass - - import base64 +from dataclasses import dataclass +import os import pathlib -import cryptography.exceptions -import glob import re -import os -import ykman - - +import cryptography.exceptions from cryptography.hazmat.primitives import _serialization -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.primitives.serialization import load_pem_public_key - - from ykman import piv from ykman.device import list_all_devices -from yubikit.piv import hashes -from yubikit.piv import PIN_POLICY, TOUCH_POLICY, hashes +from yubikit.piv import hashes, PIN_POLICY, TOUCH_POLICY from yubikit.piv import SmartCardConnection DEFAULT_MANAGEMENT_KEY = "010203040506070801020304050607080102030405060708" @@ -52,59 +38,61 @@ def generate_private_key( management_key=DEFAULT_MANAGEMENT_KEY, pin=DEFAULT_PIN, ): - """Generates a private key on the yubikey""" - - devices = list_all_devices() - if not devices: - raise Exception("no yubikeys found") - print(f"{len(devices)} yubikeys detected") - for yubikey, device_info in devices: - with yubikey.open_connection(SmartCardConnection) as connection: - piv_session = piv.PivSession(connection) - piv_session.authenticate( - piv.MANAGEMENT_KEY_TYPE.TDES, - bytes.fromhex(management_key), - ) - piv_session.verify_pin(pin) - - public_key = piv_session.generate_key( - piv.SLOT.RETIRED1, - key_type=key_type, - pin_policy=PIN_POLICY.DEFAULT, - touch_policy=TOUCH_POLICY.ALWAYS, - ) - if not public_key: - raise Exception("failed to generate public key") - directory_path = "generated_public_keys" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") - else: - print(f"Directory '{directory_path}' already exists.") - with open( - f"generated_public_keys/public_key_{device_info.serial}.pem", "wb" - ) as binary_file: - - # Write bytes to file - binary_file.write( - public_key.public_bytes( - encoding=_serialization.Encoding.PEM, - format=_serialization.PublicFormat.SubjectPublicKeyInfo, + """Generates a private key on the yubikey.""" + + devices = list_all_devices() + if not devices: + raise ValueError("no yubikeys found") + print(f"{len(devices)} yubikeys detected") + for yubikey, device_info in devices: + with yubikey.open_connection(SmartCardConnection) as connection: + piv_session = piv.PivSession(connection) + piv_session.authenticate( + piv.MANAGEMENT_KEY_TYPE.TDES, + bytes.fromhex(management_key), ) - ) - print( - f"Private key pair generated on device {device_info.serial} on key" - f" slot: {piv.SLOT.RETIRED1}" - ) + piv_session.verify_pin(pin) + + public_key = piv_session.generate_key( + piv.SLOT.RETIRED1, + key_type=key_type, + pin_policy=PIN_POLICY.DEFAULT, + touch_policy=TOUCH_POLICY.ALWAYS, + ) + if not public_key: + raise RuntimeError("failed to generate public key") + directory_path = "generated_public_keys" + if not os.path.exists(directory_path): + os.mkdir(directory_path) + print(f"Directory '{directory_path}' created.") + + with open( + f"generated_public_keys/public_key_{device_info.serial}.pem", "wb" + ) as binary_file: + + # Write bytes to file + binary_file.write( + public_key.public_bytes( + encoding=_serialization.Encoding.PEM, + format=_serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ) + print( + f"Private key pair generated on device {device_info.serial} on key" + f" slot: {piv.SLOT.RETIRED1}" + ) + @dataclass class Challenge: + """Represents a challenge with its associated public key.""" + challenge: bytes public_key_pem: str def to_dict(self): return { - "challenge": base64.b64encode(self.challenge).decode('utf-8'), + "challenge": base64.b64encode(self.challenge).decode("utf-8"), "public_key_pem": self.public_key_pem, } @@ -113,107 +101,116 @@ def from_dict(data): if not isinstance(data, dict): return None return Challenge( - challenge=base64.b64decode(data['challenge']), - public_key_pem=data['public_key_pem'] + challenge=base64.b64decode(data["challenge"]), + public_key_pem=data["public_key_pem"], ) - class ChallengeReply: - + def __init__(self, unsigned_challenge, signed_challenge, public_key_pem): self.unsigned_challenge = unsigned_challenge self.signed_challenge = signed_challenge self.public_key_pem = public_key_pem + def populate_challenges_from_files() -> list[Challenge]: - public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("challenges/public_key*.pem")] - print(public_key_files) - challenge_files = [challenge_file for challenge_file in pathlib.Path.cwd().glob("challenges/challenge*.txt")] - print(challenge_files) - - challenges = [] - - for public_key_file in public_key_files: - challenge_id = re.findall(r"\d+", str(public_key_file)) - for challenge_file in challenge_files: - if challenge_id == re.findall(r"\d+",str(challenge_file)): - print(public_key_file) - file = open(public_key_file, "r") - public_key_pem = file.read() - file.close() - file = open(challenge_file, "rb") - challenge = file.read() - file.close() - challenges.append(Challenge(challenge, public_key_pem )) - return challenges + """Populates challenges and their corresponding public keys from files. + + This function searches for files matching the patterns + "challenges/public_key*.pem" + and "challenges/challenge*.bin" in the current working directory. It then + pairs each challenge with its corresponding public key based on matching + numeric IDs in the filenames. + + Returns: + list[Challenge]: A list of Challenge objects, each containing a challenge + and its associated public key. + """ + public_key_files = list(pathlib.Path.cwd().glob("challenges/public_key*.pem")) + print(public_key_files) + challenge_files = list(pathlib.Path.cwd().glob("challenges/challenge*.bin")) + print(challenge_files) + + challenges = [] + + for public_key_file in public_key_files: + challenge_id = re.findall(r"\d+", str(public_key_file)) + for challenge_file in challenge_files: + if challenge_id == re.findall(r"\d+", str(challenge_file)): + print(public_key_file) + file = open(public_key_file, "r") + public_key_pem = file.read() + file.close() + file = open(challenge_file, "rb") + challenge = file.read() + file.close() + challenges.append(Challenge(challenge, public_key_pem)) + return challenges def sign_challenges(challenges: list[Challenge]) -> list[ChallengeReply]: - """Signs a proposal's challenges using a Yubikey.""" - if not challenges: - raise Exception("Challenge list empty: No challenges to sign.") - signed_challenges = [] - devices = list_all_devices() - if not devices: - raise Exception("no yubikeys found") - for yubikey, _ in devices: - with yubikey.open_connection(SmartCardConnection) as connection: - # Make PivSession and fetch public key from Signature slot. - piv_session = piv.PivSession(connection) - # authenticate - piv_session.authenticate( - piv.MANAGEMENT_KEY_TYPE.TDES, - bytes.fromhex("010203040506070801020304050607080102030405060708"), - ) - piv_session.verify_pin("123456") - - # Get the public key from slot 82. - slot_metadata = piv_session.get_slot_metadata(slot=piv.SLOT.RETIRED1) - print(slot_metadata.public_key.public_bytes) - - # Check to see if any of the challenge public keys matches with the - # public key from slot 82. - for challenge in challenges: - key_public_bytes = slot_metadata.public_key.public_bytes( - encoding=_serialization.Encoding.PEM, - format=_serialization.PublicFormat.SubjectPublicKeyInfo, - ) - print(key_public_bytes.decode()) - print(challenge.public_key_pem) - if key_public_bytes == challenge.public_key_pem.encode(): - - # sign the challenge - print("Press Yubikey to sign challenge") - # print("key type: " + str(slot_metadata.key_type)) - # print(challenge.challenge) - # print(type(challenge.challenge)) - signed_challenge = piv_session.sign( - slot=piv.SLOT.RETIRED1, - key_type=slot_metadata.key_type, - message=challenge.challenge, - hash_algorithm=hashes.SHA256(), - padding=padding.PKCS1v15(), - ) - - signed_challenges.append( - ChallengeReply( - challenge.challenge, - signed_challenge, - challenge.public_key_pem + """Signs a proposal's challenges using a Yubikey.""" + if not challenges: + raise ValueError("Challenge list empty: No challenges to sign.") + signed_challenges = [] + devices = list_all_devices() + if not devices: + raise ValueError("no yubikeys found") + for yubikey, _ in devices: + with yubikey.open_connection(SmartCardConnection) as connection: + # Make PivSession and fetch public key from Signature slot. + piv_session = piv.PivSession(connection) + # authenticate + piv_session.authenticate( + piv.MANAGEMENT_KEY_TYPE.TDES, + bytes.fromhex("010203040506070801020304050607080102030405060708"), ) - ) - print("Challenge signed successfully") - if not signed_challenges: - raise Exception( - "No matching public keys between Yubikey and challenges. Make sure" - " key is generated in correct slot" - ) - return signed_challenges + piv_session.verify_pin("123456") + + # Get the public key from slot 82. + slot_metadata = piv_session.get_slot_metadata(slot=piv.SLOT.RETIRED1) + print(slot_metadata.public_key.public_bytes) + + # Check to see if any of the challenge public keys matches with the + # public key from slot 82. + for challenge in challenges: + key_public_bytes = slot_metadata.public_key.public_bytes( + encoding=_serialization.Encoding.PEM, + format=_serialization.PublicFormat.SubjectPublicKeyInfo, + ) + print(key_public_bytes.decode()) + print(challenge.public_key_pem) + if key_public_bytes == challenge.public_key_pem.encode(): + + # sign the challenge + print("Press Yubikey to sign challenge") + signed_challenge = piv_session.sign( + slot=piv.SLOT.RETIRED1, + key_type=slot_metadata.key_type, + message=challenge.challenge, + hash_algorithm=hashes.SHA256(), + padding=padding.PKCS1v15(), + ) + + signed_challenges.append( + ChallengeReply( + challenge.challenge, + signed_challenge, + challenge.public_key_pem, + ) + ) + print("Challenge signed successfully") + if not signed_challenges: + raise RuntimeError( + "No matching public keys between Yubikey and challenges. Make sure" + " key is generated in correct slot" + ) + return signed_challenges + def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: - """ - Converts a URL-safe base64 encoded string to its binary equivalent. + """Converts a URL-safe base64 encoded string to its binary equivalent. Args: urlsafe_string: The URL-safe base64 encoded string. @@ -226,35 +223,40 @@ def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: ValueError: If the input string is not valid URL-safe base64. """ try: - if not isinstance(urlsafe_string, str): - raise TypeError("Input must be a string") - # Add padding if necessary. Base64 requires padding to be a multiple of 4 - missing_padding = len(urlsafe_string) % 4 - if missing_padding: - urlsafe_string += '=' * (4 - missing_padding) - return base64.urlsafe_b64decode(urlsafe_string) + if not isinstance(urlsafe_string, str): + raise TypeError("Input must be a string") + # Add padding if necessary. Base64 requires padding to be a multiple of 4 + missing_padding = len(urlsafe_string) % 4 + if missing_padding: + urlsafe_string += "=" * (4 - missing_padding) + return base64.urlsafe_b64decode(urlsafe_string) except base64.binascii.Error as e: raise ValueError(f"Invalid URL-safe base64 string: {e}") from e + def verify_challenge_signatures(challenge_replies: list[ChallengeReply]): - if not challenge_replies: - raise Exception("No signed challenges to verify") - for challenge_reply in challenge_replies: - public_key = load_pem_public_key( - challenge_reply.public_key_pem.encode() - ) - try: + """Verifies the signatures of a list of challenge replies. + + Args: + challenge_replies: A list of ChallengeReply objects. - public_key.verify( - challenge_reply.signed_challenge, - challenge_reply.unsigned_challenge, - padding.PKCS1v15(), - hashes.SHA256(), - ) - print(f"Signature verification success") - except cryptography.exceptions.InvalidSignature as e: - raise cryptography.exceptions.InvalidSignature((f"Signature verification failed: {e}")) - - -if __name__ == "__main__": - generate_private_key() \ No newline at end of file + Raises: + ValueError: If the list of challenge replies is empty. + cryptography.exceptions.InvalidSignature: If a signature is invalid. + """ + if not challenge_replies: + raise ValueError("No signed challenges to verify") + for challenge_reply in challenge_replies: + public_key = load_pem_public_key(challenge_reply.public_key_pem.encode()) + try: + public_key.verify( + challenge_reply.signed_challenge, + challenge_reply.unsigned_challenge, + padding.PKCS1v15(), + hashes.SHA256(), + ) + print("Signature verification success") + except cryptography.exceptions.InvalidSignature as e: + raise cryptography.exceptions.InvalidSignature( + f"Signature verification failed: {e}" + ) diff --git a/kms/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/ykman_utils_test.py index 37b1cee124c..03f738cd2f0 100644 --- a/kms/singletenanthsm/ykman_utils_test.py +++ b/kms/singletenanthsm/ykman_utils_test.py @@ -12,31 +12,32 @@ # See the License for the specific language governing permissions and # limitations under the License. -import cryptography.exceptions +import pathlib +import cryptography.exceptions -import glob -import os -import pathlib import pytest -import re import ykman_utils + class Challenge: - def __init__(self, challenge, public_key_pem): - self.challenge = challenge - self.public_key_pem = public_key_pem + def __init__(self, challenge, public_key_pem): + self.challenge = challenge + self.public_key_pem = public_key_pem + class ChallengeReply: - + def __init__(self, signed_challenge, public_key_pem): self.signed_challenge = signed_challenge self.public_key_pem = public_key_pem + challenge_test_data = b"test_data" + def generate_test_challenge_files(): # Create challenges list from challenges directory challenges = ykman_utils.populate_challenges_from_files() @@ -45,8 +46,10 @@ def generate_test_challenge_files(): print(challenge.public_key_pem) # Sign challenges signed_challenges = ykman_utils.sign_challenges(challenges) - ykman_utils.verify_challenge_signatures(signed_challenges, b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN") - + ykman_utils.verify_challenge_signatures( + signed_challenges, + b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN", + ) # A yubikey connected to your local machine will be needed to run these tests. @@ -55,22 +58,26 @@ def generate_test_challenge_files(): def key_setup(): ykman_utils.generate_private_key() + def challenges(): - public_key_files = [key_file for key_file in pathlib.Path.cwd().glob("generated_public_keys/public_key*.pem")] - challenges = [] + public_key_files = [ + key_file + for key_file in pathlib.Path.cwd().glob("generated_public_keys/public_key*.pem") + ] + challenges = [] + + for public_key_file in public_key_files: + file = open(public_key_file, "r") + public_key_pem = file.read() + challenges.append(Challenge(challenge_test_data, public_key_pem)) + return challenges - - for public_key_file in public_key_files: - file = open(public_key_file, "r") - public_key_pem = file.read() - challenges.append(Challenge(challenge_test_data, public_key_pem )) - return challenges - def test_sign_and_verify_challenges(): signed_challenges = ykman_utils.sign_challenges(challenges()) ykman_utils.verify_challenge_signatures(signed_challenges) + def test_verify_mismatching_data_fail(): with pytest.raises(cryptography.exceptions.InvalidSignature) as exec_info: signed_challenges = ykman_utils.sign_challenges(challenges()) @@ -78,20 +85,22 @@ def test_verify_mismatching_data_fail(): ykman_utils.verify_challenge_signatures(signed_challenges) assert "Signature verification failed" in str(exec_info.value) + def test_sign_empty_challenge_list_fail(): with pytest.raises(Exception) as exec_info: - # empty_challenges = [] - signed_challenges = ykman_utils.sign_challenges([]) + ykman_utils.sign_challenges([]) assert "Challenge list empty" in str(exec_info.value) + def test_sign_no_matching_public_keys_fail(): modified_challenges = challenges() for challenge in modified_challenges: challenge.public_key_pem = "modified_public_key" with pytest.raises(Exception) as exec_info: - signed_challenges = ykman_utils.sign_challenges(modified_challenges) + ykman_utils.sign_challenges(modified_challenges) assert "No matching public keys" in str(exec_info.value) + def test_verify_empty_challenge_replies_fail(): with pytest.raises(Exception) as exec_info: ykman_utils.verify_challenge_signatures([]) From a071e2ed539f0c6b9289f6a780f6f3dfaac5985f Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Fri, 2 May 2025 14:39:21 -0400 Subject: [PATCH 07/19] removed generated challenge and key files --- kms/singletenanthsm/challenges/challenge1.txt | 1 - kms/singletenanthsm/challenges/challenge2.txt | 1 - kms/singletenanthsm/challenges/challenge3.txt | 1 - kms/singletenanthsm/challenges/public_key1.pem | 9 --------- kms/singletenanthsm/challenges/public_key2.pem | 9 --------- kms/singletenanthsm/challenges/public_key3.pem | 9 --------- .../generated_public_keys/public_key_25167010.pem | 9 --------- .../generated_public_keys/public_key_28787103.pem | 9 --------- .../generated_public_keys/public_key_28787105.pem | 9 --------- .../signed_challenges/public_key_1.pem | 9 --------- .../signed_challenges/public_key_2.pem | 9 --------- .../signed_challenges/public_key_3.pem | 9 --------- .../signed_challenges/signed_challenge1.bin | Bin 256 -> 0 bytes .../signed_challenges/signed_challenge2.bin | Bin 256 -> 0 bytes .../signed_challenges/signed_challenge3.bin | 2 -- 15 files changed, 86 deletions(-) delete mode 100644 kms/singletenanthsm/challenges/challenge1.txt delete mode 100644 kms/singletenanthsm/challenges/challenge2.txt delete mode 100644 kms/singletenanthsm/challenges/challenge3.txt delete mode 100644 kms/singletenanthsm/challenges/public_key1.pem delete mode 100644 kms/singletenanthsm/challenges/public_key2.pem delete mode 100644 kms/singletenanthsm/challenges/public_key3.pem delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_25167010.pem delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787103.pem delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787105.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_1.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_2.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_3.pem delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge1.bin delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge2.bin delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge3.bin diff --git a/kms/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/challenges/challenge1.txt deleted file mode 100644 index c891b41bd6d..00000000000 --- a/kms/singletenanthsm/challenges/challenge1.txt +++ /dev/null @@ -1 +0,0 @@ -#?xpAky7.m*\hkG.jo(T!5 \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/challenges/challenge2.txt deleted file mode 100644 index 93616f55ed6..00000000000 --- a/kms/singletenanthsm/challenges/challenge2.txt +++ /dev/null @@ -1 +0,0 @@ -:/~j;QQܿA3($̳N+;H'^7N;QX \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/challenges/challenge3.txt deleted file mode 100644 index 3f9fc362302..00000000000 --- a/kms/singletenanthsm/challenges/challenge3.txt +++ /dev/null @@ -1 +0,0 @@ -4>y)v2Ugn&3R"0g/熎X \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem deleted file mode 100644 index e9d057231ec..00000000000 --- a/kms/singletenanthsm/challenges/public_key1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxpORpfP7RbgLHZ4w3e/D -Yc6WwEU+fj2I8RolMPafYZFKp2GewsjKX1C44FK4N1Bzd2PnIcJc7dcBd3LxyZXd -9Oj54ReLKNTLIenfUp3BuH21H/jagB+kNOQIyoK/xK5zDCcfkSbhDzet9y5jMsIq -b9uKAmWB127qmkPu95ozUXGWAWsLIahx/wb7mH3Dm2iOqjFrqXMqp2tXcNmAYzux -2Uure5WZQU+QKYTQvnccWkeZs/RTO7j9r/KqKBrHNtSIeRl1leb66PaRCa41xTHy -lp5194BMbceZn8tcUbU67Q3InBTaJu2CIq9IUytUwZBcOfhAqHwOPQ6cZPWUiCRk -LwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem deleted file mode 100644 index 869be1e18f2..00000000000 --- a/kms/singletenanthsm/challenges/public_key2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1xT9BLxfqHi/fWMm0UeI -WBzBA3mtd6gpHtbEhfEJ0IWaUO+X6GJm/u2kHPdJpyf84PlgMfMy14+ZFVMQKM8H -95XEpFah4oMA7zqVOusoLeV1CGSK4/wSVZzsv1OaODU/OCM+wZx0A+L7b9buQpjj -YxgO3SBoe/4LtadaekWBKEGbKNllD/UUqcfiFYfXn0mouXbaj60F9t7+F5dnyRL2 -2T9pvewPd2AuoP5buWuUIqfnOnVMWuWpj14bCimRb28kDNlLTMWgN89bIOAWCbRy -c9Dl7x1oyFB3oKQMPwR6vN6xDSWE+2wWl1xwI4PO0zyZ9DlbXiTvBdadtlvIQwvn -rwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem deleted file mode 100644 index 171afc73420..00000000000 --- a/kms/singletenanthsm/challenges/public_key3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1xabdpYzO/SQjVMJTZZ -RaklZIqMlcKLy2L9C2Y3yruJ4tZ2P8ZgV4dZt6DEl9PTmX8W5GHUoWk/w/wrm8XS -0ViX3t6s1/wgO85yBDrgTG9qRSsM2ELrPq9OucfYTy47Wedp8ALUSdw8JlyNeedq -mDr00k4JjgnIx+OrFt2XApSuHi9DrfbvMFVco5fJshpeLDEgtNoV7OQeDop/6Jnp -/EQtCnWcdhjxuP8sx2IbTwN58W89PwMVVYOQrYlexsECpbompa6X7UCAPAMTJQ+t -15JxJv34TzAEUj/CZS3FOEy24mchPL386UiXvTWD8iuXMfV2YCg9sliJNpTi2SI6 -gQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem b/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem deleted file mode 100644 index a8ec68c9879..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxifB3Vg5IHjB8k/1/4Vy -MVAGVFrbQkocwsDEvlCn6MeO/btD1SCcvHQLOnA61HI6rux3XLu9MQF1KkiBNwFS -K3CdcjE7O46jTawmyNU3WMpQYaSbLHVzQB2h0Uif1tWgXYKPhibYOgQ3rt9sswJ0 -y4/6xo1VGK1crOWrSwJxIuMbcePggcIwCI7VIOlSYOqxW/4qW2gJVQTfPp1wDkY9 -QjRk7hUiixt+lsPSLKecsnhk1Mkn35KMvaHRWVeX1YV8t0dPRzjJ/wAH/FhXDNZA -4jC2mjGnpsViz53zXGtRAh+zsbAxXkoYcBqEqMq+GOF/xSx8qFpmcY88pvi5zNnv -rwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem b/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem deleted file mode 100644 index 81a17ceb95a..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1Q0PTAITlnLnaPTIh7A -KcK3pNU2WXogylKsrCeDOLniNWzBlKf57ocQSyEhtNEjneYdDULW+bYhXAahn6oS -yxEmGDRP/9U48YfSShkcDarfL0n2V+kuffq3lB+VP7mHtyfes037rithC86c2/jN -Y7hS/BriMk9/Y8dvPr2SezolrUyO740yr1VOtM0zzoe8jhn1QrCQKy9wm0l/XW9B -aS+dCJUJY52TzuES0Z2E3785IHyvcxoPDDJZIbQVPalU7FBfZCSzDstEIHgJR7nR -EN3Aoj17BkNtn395SmSOlkNn4cdpN+jUyXYVKvnmqq+2pGqqNR/mZhNvAEpYt4Pg -FwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem b/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem deleted file mode 100644 index c66c1da7c96..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+NnEwM4y1msfN/6izLG -8/P1p0Ct8Qz0McCdbrB3qSXURLuri89iX1sjcWpbAbaihPpd1yLf9Lv836/Lx55M -8viX5k14DYPmaR1INB3m2OSh12VFV2rM5RQTUVBiYwjm+QNpyybpQ3S0dr89D1iD -KExucMtWKaxPW+I7ceY/OwqskPDSV5P8KHXi1RTOirNNfO9X0MjXc2clLYudxE9d -0jP/5dpURAODBHt7P1xke0uI5CN9YNC5k2pt0MijBiVULJM7Fj+YqyEGqzmdEc4A -Q6GVfIix3o8OQJ5ms6GwZgDvMvhRCm6e3c7IJHNFTQa+0U+hOSkPamFT/fTqn14+ -vQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem deleted file mode 100644 index e9d057231ec..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxpORpfP7RbgLHZ4w3e/D -Yc6WwEU+fj2I8RolMPafYZFKp2GewsjKX1C44FK4N1Bzd2PnIcJc7dcBd3LxyZXd -9Oj54ReLKNTLIenfUp3BuH21H/jagB+kNOQIyoK/xK5zDCcfkSbhDzet9y5jMsIq -b9uKAmWB127qmkPu95ozUXGWAWsLIahx/wb7mH3Dm2iOqjFrqXMqp2tXcNmAYzux -2Uure5WZQU+QKYTQvnccWkeZs/RTO7j9r/KqKBrHNtSIeRl1leb66PaRCa41xTHy -lp5194BMbceZn8tcUbU67Q3InBTaJu2CIq9IUytUwZBcOfhAqHwOPQ6cZPWUiCRk -LwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem deleted file mode 100644 index 869be1e18f2..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1xT9BLxfqHi/fWMm0UeI -WBzBA3mtd6gpHtbEhfEJ0IWaUO+X6GJm/u2kHPdJpyf84PlgMfMy14+ZFVMQKM8H -95XEpFah4oMA7zqVOusoLeV1CGSK4/wSVZzsv1OaODU/OCM+wZx0A+L7b9buQpjj -YxgO3SBoe/4LtadaekWBKEGbKNllD/UUqcfiFYfXn0mouXbaj60F9t7+F5dnyRL2 -2T9pvewPd2AuoP5buWuUIqfnOnVMWuWpj14bCimRb28kDNlLTMWgN89bIOAWCbRy -c9Dl7x1oyFB3oKQMPwR6vN6xDSWE+2wWl1xwI4PO0zyZ9DlbXiTvBdadtlvIQwvn -rwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem deleted file mode 100644 index 171afc73420..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1xabdpYzO/SQjVMJTZZ -RaklZIqMlcKLy2L9C2Y3yruJ4tZ2P8ZgV4dZt6DEl9PTmX8W5GHUoWk/w/wrm8XS -0ViX3t6s1/wgO85yBDrgTG9qRSsM2ELrPq9OucfYTy47Wedp8ALUSdw8JlyNeedq -mDr00k4JjgnIx+OrFt2XApSuHi9DrfbvMFVco5fJshpeLDEgtNoV7OQeDop/6Jnp -/EQtCnWcdhjxuP8sx2IbTwN58W89PwMVVYOQrYlexsECpbompa6X7UCAPAMTJQ+t -15JxJv34TzAEUj/CZS3FOEy24mchPL386UiXvTWD8iuXMfV2YCg9sliJNpTi2SI6 -gQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/signed_challenges/signed_challenge1.bin deleted file mode 100644 index 2d8a674566bbb0cfc611c8f1fb7e8d09eff616a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssD}*NwT(A0yy*h7C*JE&i`SV~{`~p3(~THR_he+6~y+W8)3b<=(wrKHa#R z3lK^!_xS8tIU2%u`sIx(oLwUcQ48w8-L_hNdEQjRJePu%PeuPY=#FCV)sM)GZT$N9 zHy`IYDXF|8Eeo>pE{mCqO@aW3VYo)^chNr4;PuPaHkY=qYD+vm)yBorNCh;&FWBw! z13^=#79QK!0a9&EEl8s>_sPKxnKm*^IX28vZtq7{eeh7LV&#(17(6ir0Lt0qYItS>QYlk>*DJ3#Y)$Bw?Xf0!{-BR!7dz!bd%Ow9W%84H1{Ip*E9c9bV{{ z~!ՕqtՅ }7O Date: Fri, 2 May 2025 16:39:23 -0400 Subject: [PATCH 08/19] added pin and management key args --- kms/singletenanthsm/approve_proposal.py | 43 +++++++++++------- kms/singletenanthsm/challenges/challenge1.txt | 1 + kms/singletenanthsm/challenges/challenge2.txt | 1 + kms/singletenanthsm/challenges/challenge3.txt | 1 + .../challenges/public_key1.pem | 9 ++++ .../challenges/public_key2.pem | 9 ++++ .../challenges/public_key3.pem | 9 ++++ kms/singletenanthsm/gcloud_commands.py | 16 ++++--- kms/singletenanthsm/setup.py | 24 ++++++++-- .../signed_challenges/public_key_1.pem | 9 ++++ .../signed_challenges/public_key_2.pem | 9 ++++ .../signed_challenges/public_key_3.pem | 9 ++++ .../signed_challenges/signed_challenge1.bin | 2 + .../signed_challenges/signed_challenge2.bin | Bin 0 -> 256 bytes .../signed_challenges/signed_challenge3.bin | Bin 0 -> 256 bytes kms/singletenanthsm/ykman_utils.py | 10 ++-- 16 files changed, 122 insertions(+), 30 deletions(-) create mode 100644 kms/singletenanthsm/challenges/challenge1.txt create mode 100644 kms/singletenanthsm/challenges/challenge2.txt create mode 100644 kms/singletenanthsm/challenges/challenge3.txt create mode 100644 kms/singletenanthsm/challenges/public_key1.pem create mode 100644 kms/singletenanthsm/challenges/public_key2.pem create mode 100644 kms/singletenanthsm/challenges/public_key3.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_1.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_2.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_3.pem create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge1.bin create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge2.bin create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge3.bin diff --git a/kms/singletenanthsm/approve_proposal.py b/kms/singletenanthsm/approve_proposal.py index a3f03e47748..705c1c526c8 100644 --- a/kms/singletenanthsm/approve_proposal.py +++ b/kms/singletenanthsm/approve_proposal.py @@ -15,6 +15,7 @@ # limitations under the License. import argparse +import logging import json import os import sys @@ -33,31 +34,39 @@ def parse_challenges_into_files(sthi_output: str) -> List[bytes]: Returns: A list of the unsigned challenges. """ - print("parsing challenges into files") + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Parsing challenges into files") proposal_json = json.loads(sthi_output, strict=False) challenges = proposal_json["quorumParameters"]["challenges"] directory_path = "challenges" if not os.path.exists(directory_path): os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") + logger.info(f"Directory '{directory_path}' created.") else: - print(f"Directory '{directory_path}' already exists.") + logger.info(f"Directory '{directory_path}' already exists.") challenge_count = 0 unsigned_challenges = [] for challenge in challenges: challenge_count += 1 - print(challenge["challenge"] + "\n") - print(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) - f = open("challenges/challenge{0}.txt".format(challenge_count), "wb") - binary_challenge = ykman_utils.urlsafe_base64_to_binary(challenge["challenge"]) - f.write(binary_challenge) - f.close() - - f = open("challenges/public_key{0}.pem".format(challenge_count), "w") - f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) - f.close() + try: + with open("challenges/challenge{0}.txt".format(challenge_count), "wb") as f: + binary_challenge = ykman_utils.urlsafe_base64_to_binary(challenge["challenge"]) + f.write(binary_challenge) + except FileNotFoundError: + print(f"File not found: challenges/challenge{challenge_count}.txt") + except Exception as e: + print(f"An error occurred: {e}") + try: + with open("challenges/public_key{0}.pem".format(challenge_count), "w") as f: + f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + except FileNotFoundError: + print(f"File not found: challenges/public_key{challenge_count}.txt") + except Exception as e: + print(f"An error occurred: {e}") unsigned_challenges.append( ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"]) ) @@ -85,15 +94,17 @@ def signed_challenges_to_files( """ signed_challenge_files = [] challenge_count = 0 + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + for challenge_reply in challenge_replies: challenge_count += 1 - print("challenge_count", challenge_count) directory_path = "signed_challenges" if not os.path.exists(directory_path): os.mkdir(directory_path) - print(f"Directory '{directory_path}' created.") + logger.info(f"Directory '{directory_path}' created.") else: - print(f"Directory '{directory_path}' already exists.") + logger.info(f"Directory '{directory_path}' already exists.") with open( f"signed_challenges/public_key_{challenge_count}.pem", "w" ) as public_key_file: diff --git a/kms/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/challenges/challenge1.txt new file mode 100644 index 00000000000..c891b41bd6d --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge1.txt @@ -0,0 +1 @@ +#?xpAky7.m*\hkG.jo(T!5 \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/challenges/challenge2.txt new file mode 100644 index 00000000000..93616f55ed6 --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge2.txt @@ -0,0 +1 @@ +:/~j;QQܿA3($̳N+;H'^7N;QX \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/challenges/challenge3.txt new file mode 100644 index 00000000000..3f9fc362302 --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge3.txt @@ -0,0 +1 @@ +4>y)v2Ugn&3R"0g/熎X \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem new file mode 100644 index 00000000000..721b6babb89 --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtD7qkuyTSxuV8OKoKWAG +Xjfq6CzT1sq/VdTfDUIW9A+3K6d56XIfTSmLjq5IBuF4lH2Rps+LKJHuf6LYAD8U +5JvI9ZtjtySalGKwyN9t7ZhLkbVXhFbNo+pucCJkZAUbBWcaY82DuXu97sbEHY46 +FFeCc2X5Xt5WcFmyKofMLnYtiORt50kTOYYaVW/jRBhE726jK2X3VYrhlK55Zcwh +akiB5K2azRbvwophZLkmEgkjxY/JiSGKZW1VIVdjmLFyWLJfuSacUQ/QQOtCirW+ +ks/o1wCNh+MJgd5dlhkmI6mV0EITT4hNn06XMMXfZ62PIQBXz6oInlVZnFT0R58M +zwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem new file mode 100644 index 00000000000..6aa512f1638 --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApHhfnEUrlBaWacxltJ19 +1i+voxM+1IJIYTeQLQ/rZb6gwVABKDeoLP+vn//9rmmWTL7pjBubmIikWuROgbQ7 +y8Xvnyg/1q8wxKUbbduuAbS3s0mktV4nLMPBzvQ/GOBcZCfG0du+k7Vl/uvRgx9w +x8v5Zn1LIiei96k5EJr0YhAL9lNAh86e5HIjTqhRwa5b6S2oh2oslEXe9qCwdlVV +3sy+hpTSheiiUOcfHDOEiIWlR6r49MMqI0oav0NZ15eYa/oUB4MC9Zao+VqchItk +46o4cO+p85RXTAKt8rJub16Dl6C8V9oXRIoa3kRtv14QJE2OWV6Zp1nuDaEQwn5m +2wIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem new file mode 100644 index 00000000000..fa44a9d6257 --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx8SoBPVaK7b9fgDkbZPZ +94MxK8eDkoe4CJ/Hzi7PIHIVng5sA5ufdWqi1jGqoetc3LwaLlHVsduE3Iol4Ofy +vYgf61BaV8xMHTdgjLGTzPaxfgEH18W4epT6QOcD4m6Q1wCK93Q5UgWfDbdPVuSo +xcwffKOR3MnyO0M1WHWDy39rPyeXhISA9ZPHl3VwMkcixuGQtEsU1AidqNpj0POo +xdoJeXk7ZpA2MWudaldZdlcQVkeUr3NRGrc46CEKUbsFmPlx/p0aQyuLfxXHUwVZ +x8STwRf6ZEs2+tnlQpTYg/xv8+YJwFOB9+he4Qiz0ElAmrdHlt+AY3xGMRZgC9jw +RQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/gcloud_commands.py index ec9a0f28763..ce36576fdd6 100644 --- a/kms/singletenanthsm/gcloud_commands.py +++ b/kms/singletenanthsm/gcloud_commands.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import subprocess command_build_custom_gcloud = """ @@ -36,19 +37,22 @@ def build_custom_gcloud(): """Builds a custom gcloud binary.""" + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + try: print("\nBuilding custom gcloud build") process = subprocess.run( command_build_custom_gcloud, check=True, shell=True, + capture_output=True, # Capture output + text=True # Ensure output is text ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud build executed successfully.") - print(process.stdout) + logger.info(f"Return Code: {process.returncode}") + logger.info(f"Standard Error: {process.stderr}") + logger.info("gcloud build executed successfully.") + logger.info(process.stdout) except subprocess.CalledProcessError as e: raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) try: diff --git a/kms/singletenanthsm/setup.py b/kms/singletenanthsm/setup.py index d0a976e3862..25d75a8d957 100644 --- a/kms/singletenanthsm/setup.py +++ b/kms/singletenanthsm/setup.py @@ -19,7 +19,7 @@ import ykman_utils -def validate_operation(operation: str): +def validate_operation(operation: str, management_key: str, pin: str): if operation == "build_custom_gcloud": try: gcloud_commands.build_custom_gcloud() @@ -27,8 +27,12 @@ def validate_operation(operation: str): raise Exception(f"Generating custom gcloud build failed {e}") elif operation == "generate_rsa_keys": try: - ykman_utils.generate_private_key() + if not management_key or not pin: + raise ValueError("--management_key and --pin need to be specified for the generate_rsa_keys operation") + ykman_utils.generate_private_key(management_key=management_key, pin=pin) except Exception as e: + if not management_key or not pin: + raise ValueError("--management_key and --pin need to be specified for the generate_rsa_keys operation") raise Exception(f"Generating private keys failed {e}") elif operation == "generate_gcloud_and_keys": generate_private_keys_build_gcloud() @@ -39,12 +43,12 @@ def validate_operation(operation: str): ) -def generate_private_keys_build_gcloud(): +def generate_private_keys_build_gcloud(management_key: str, pin: str): """Generates an RSA key on slot 82 of every yubikey connected to the local machine and builds the custom gcloud cli. """ try: - ykman_utils.generate_private_key() + ykman_utils.generate_private_key(management_key=management_key, pin=pin) except Exception as e: raise Exception(f"Generating private keys failed {e}") try: @@ -65,5 +69,15 @@ def generate_private_keys_build_gcloud(): ], required=True, ) + parser.add_argument( + "--management_key", + type=str, + required=False, + ) + parser.add_argument( + "--pin", + type=str, + required=False, + ) args = parser.parse_args() - validate_operation(args.operation) + validate_operation(args.operation, args.management_key, args.pin) diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem new file mode 100644 index 00000000000..721b6babb89 --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtD7qkuyTSxuV8OKoKWAG +Xjfq6CzT1sq/VdTfDUIW9A+3K6d56XIfTSmLjq5IBuF4lH2Rps+LKJHuf6LYAD8U +5JvI9ZtjtySalGKwyN9t7ZhLkbVXhFbNo+pucCJkZAUbBWcaY82DuXu97sbEHY46 +FFeCc2X5Xt5WcFmyKofMLnYtiORt50kTOYYaVW/jRBhE726jK2X3VYrhlK55Zcwh +akiB5K2azRbvwophZLkmEgkjxY/JiSGKZW1VIVdjmLFyWLJfuSacUQ/QQOtCirW+ +ks/o1wCNh+MJgd5dlhkmI6mV0EITT4hNn06XMMXfZ62PIQBXz6oInlVZnFT0R58M +zwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem new file mode 100644 index 00000000000..6aa512f1638 --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApHhfnEUrlBaWacxltJ19 +1i+voxM+1IJIYTeQLQ/rZb6gwVABKDeoLP+vn//9rmmWTL7pjBubmIikWuROgbQ7 +y8Xvnyg/1q8wxKUbbduuAbS3s0mktV4nLMPBzvQ/GOBcZCfG0du+k7Vl/uvRgx9w +x8v5Zn1LIiei96k5EJr0YhAL9lNAh86e5HIjTqhRwa5b6S2oh2oslEXe9qCwdlVV +3sy+hpTSheiiUOcfHDOEiIWlR6r49MMqI0oav0NZ15eYa/oUB4MC9Zao+VqchItk +46o4cO+p85RXTAKt8rJub16Dl6C8V9oXRIoa3kRtv14QJE2OWV6Zp1nuDaEQwn5m +2wIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem new file mode 100644 index 00000000000..fa44a9d6257 --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx8SoBPVaK7b9fgDkbZPZ +94MxK8eDkoe4CJ/Hzi7PIHIVng5sA5ufdWqi1jGqoetc3LwaLlHVsduE3Iol4Ofy +vYgf61BaV8xMHTdgjLGTzPaxfgEH18W4epT6QOcD4m6Q1wCK93Q5UgWfDbdPVuSo +xcwffKOR3MnyO0M1WHWDy39rPyeXhISA9ZPHl3VwMkcixuGQtEsU1AidqNpj0POo +xdoJeXk7ZpA2MWudaldZdlcQVkeUr3NRGrc46CEKUbsFmPlx/p0aQyuLfxXHUwVZ +x8STwRf6ZEs2+tnlQpTYg/xv8+YJwFOB9+he4Qiz0ElAmrdHlt+AY3xGMRZgC9jw +RQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/signed_challenges/signed_challenge1.bin new file mode 100644 index 00000000000..788b9e7eed9 --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/signed_challenge1.bin @@ -0,0 +1,2 @@ +Xt50 w,9d'QS(Z41y8Z71$6=vHM +}p؃Nj eҵ|PXηΞ9^5UUӵеI`šP\p; @|)S/x葆G;"L6l*!N)^8T  C27 sF/m XXL˦[Dh{hB{5 \ No newline at end of file diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge2.bin b/kms/singletenanthsm/signed_challenges/signed_challenge2.bin new file mode 100644 index 0000000000000000000000000000000000000000..2afad69a3f14dfb6f9389ab13558d6a4a6baf2ed GIT binary patch literal 256 zcmV+b0ssDit-5%r_;XO`CDhNuI1xF6t7pH!6czLVo9&sEQHdGwyt455lU;)J%(c#I z_%Wbd^@#bYzY+{Nf?qgKX8}!%p(6VKoh)quU7{yc!*ke`UE>)})wJ8t(gR7f4OpQY z^0^!$f;H=i!{w42GdB4bwd*-pL4BEEs|g_NxU(+ii$;z%z|f5O94O#oF-6RP2sI5q z{bYDG*Ch`$kH_2pZ?O~)IURzzX}dpq0U59X%OqK|!t-&e{>`8Kqd(9QY*e!E;GZzf zy@puRPK=?e1>#5E4jbD(?(DqhTfqRS(#3(qY=tS&_NegL)N#8%xyb5YFe_Rcs3w%k Gv-Wdzl!crC literal 0 HcmV?d00001 diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge3.bin b/kms/singletenanthsm/signed_challenges/signed_challenge3.bin new file mode 100644 index 0000000000000000000000000000000000000000..f1800f3b1d14768512e79a8f69c963f79a9db793 GIT binary patch literal 256 zcmV+b0ssD3k&^{>b5}AUA0CZ*HffUDyTrM}Gt&TLC%C!!rh7IfN}P0JUtyofZI&iH zH1+z|2dK;1f6fV;tDvx>bl2++-%NrF?Hpb!3=PIK%@g@kSq}oAzOV_k9#XB_8i`>} z_t+dfZzq2XiR+Damg$^zXAOd`(*?$XidN)}ZW^hFzp%nPQ>4e}CD~trk%#+8Kc%Sb zaGZ{l=e@X;35$vkR4gHv`uUv1wwl=0=S%)&JRTy)Nr-+(DqRkO3vV$&n0qc;+ZA5k z)nsc(uQa;K6;GOT`a=HFjKdDWPryDl=%EmEH=Mjrw2O_vah`u2PEaj61CTH|ly4n> GUiZ9l)qHaR literal 0 HcmV?d00001 diff --git a/kms/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/ykman_utils.py index 782dfcdf589..d7b3a810505 100644 --- a/kms/singletenanthsm/ykman_utils.py +++ b/kms/singletenanthsm/ykman_utils.py @@ -149,7 +149,8 @@ def populate_challenges_from_files() -> list[Challenge]: return challenges -def sign_challenges(challenges: list[Challenge]) -> list[ChallengeReply]: +def sign_challenges(challenges: list[Challenge], management_key=DEFAULT_MANAGEMENT_KEY, + pin=DEFAULT_PIN) -> list[ChallengeReply]: """Signs a proposal's challenges using a Yubikey.""" if not challenges: raise ValueError("Challenge list empty: No challenges to sign.") @@ -164,9 +165,9 @@ def sign_challenges(challenges: list[Challenge]) -> list[ChallengeReply]: # authenticate piv_session.authenticate( piv.MANAGEMENT_KEY_TYPE.TDES, - bytes.fromhex("010203040506070801020304050607080102030405060708"), + bytes.fromhex(management_key), ) - piv_session.verify_pin("123456") + piv_session.verify_pin(pin) # Get the public key from slot 82. slot_metadata = piv_session.get_slot_metadata(slot=piv.SLOT.RETIRED1) @@ -225,6 +226,9 @@ def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: try: if not isinstance(urlsafe_string, str): raise TypeError("Input must be a string") + # Check if the input string contains only URL-safe base64 characters + if not re.match(r'^[a-zA-Z0-9_-]*$', urlsafe_string): + raise ValueError("Input string contains invalid characters") # Add padding if necessary. Base64 requires padding to be a multiple of 4 missing_padding = len(urlsafe_string) % 4 if missing_padding: From db604757698faa61f2fa42b22123958c2f04eb65 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Mon, 5 May 2025 16:15:52 -0400 Subject: [PATCH 09/19] Added pin and management key to approve command --- kms/singletenanthsm/approve_proposal.py | 76 ++++++++++++------ kms/singletenanthsm/approve_proposal_test.py | 15 +++- .../challenges/public_key1.pem | 14 ++-- .../challenges/public_key2.pem | 14 ++-- .../challenges/public_key3.pem | 14 ++-- kms/singletenanthsm/gcloud_commands.py | 50 +++++++----- kms/singletenanthsm/setup.py | 8 +- .../signed_challenges/public_key_1.pem | 14 ++-- .../signed_challenges/public_key_2.pem | 14 ++-- .../signed_challenges/public_key_3.pem | 14 ++-- .../signed_challenges/signed_challenge1.bin | Bin 256 -> 256 bytes .../signed_challenges/signed_challenge2.bin | Bin 256 -> 256 bytes .../signed_challenges/signed_challenge3.bin | Bin 256 -> 256 bytes kms/singletenanthsm/ykman_fake.py | 2 +- kms/singletenanthsm/ykman_utils.py | 7 +- kms/singletenanthsm/ykman_utils_test.py | 1 + 16 files changed, 145 insertions(+), 98 deletions(-) diff --git a/kms/singletenanthsm/approve_proposal.py b/kms/singletenanthsm/approve_proposal.py index 705c1c526c8..63c6b940e22 100644 --- a/kms/singletenanthsm/approve_proposal.py +++ b/kms/singletenanthsm/approve_proposal.py @@ -15,8 +15,8 @@ # limitations under the License. import argparse -import logging import json +import logging import os import sys from typing import List @@ -25,6 +25,25 @@ import ykman_utils +def make_directory(directory_path: str) -> None: + """Creates a directory with the passed in path if it does not already exist. + + Args: + directory_path: The path of the directory to be created. + + Returns: + None + """ + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + logger.info("Parsing challenges into files") + if not os.path.exists(directory_path): + os.mkdir(directory_path) + logger.info(f"Directory '{directory_path}' created.") + else: + logger.info(f"Directory '{directory_path}' already exists.") + + def parse_challenges_into_files(sthi_output: str) -> List[bytes]: """Parses the STHI output and writes the challenges and public keys to files. @@ -41,12 +60,7 @@ def parse_challenges_into_files(sthi_output: str) -> List[bytes]: proposal_json = json.loads(sthi_output, strict=False) challenges = proposal_json["quorumParameters"]["challenges"] - directory_path = "challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - logger.info(f"Directory '{directory_path}' created.") - else: - logger.info(f"Directory '{directory_path}' already exists.") + make_directory("challenges") challenge_count = 0 unsigned_challenges = [] @@ -54,19 +68,27 @@ def parse_challenges_into_files(sthi_output: str) -> List[bytes]: challenge_count += 1 try: with open("challenges/challenge{0}.txt".format(challenge_count), "wb") as f: - binary_challenge = ykman_utils.urlsafe_base64_to_binary(challenge["challenge"]) + binary_challenge = ykman_utils.urlsafe_base64_to_binary( + challenge["challenge"] + ) f.write(binary_challenge) except FileNotFoundError: - print(f"File not found: challenges/challenge{challenge_count}.txt") + logger.exception( + f"File not found: challenges/challenge{challenge_count}.txt" + ) except Exception as e: - print(f"An error occurred: {e}") + logger.exception(f"An error occurred: {e}") try: with open("challenges/public_key{0}.pem".format(challenge_count), "w") as f: - f.write(challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape")) + f.write( + challenge["publicKeyPem"].encode("utf-8").decode("unicode_escape") + ) except FileNotFoundError: - print(f"File not found: challenges/public_key{challenge_count}.txt") + logger.exception( + f"File not found: challenges/public_key{challenge_count}.txt" + ) except Exception as e: - print(f"An error occurred: {e}") + logger.exception(f"An error occurred: {e}") unsigned_challenges.append( ykman_utils.Challenge(binary_challenge, challenge["publicKeyPem"]) ) @@ -77,6 +99,16 @@ def parse_challenges_into_files(sthi_output: str) -> List[bytes]: def parse_args(args): parser = argparse.ArgumentParser() parser.add_argument("--proposal_resource", type=str, required=True) + parser.add_argument( + "--management_key", + type=str, + required=False, + ) + parser.add_argument( + "--pin", + type=str, + required=False, + ) return parser.parse_args(args) @@ -89,22 +121,14 @@ def signed_challenges_to_files( challenge_replies: A list of ChallengeReply objects. Returns: - A list of tuples containing the signed challenge file path and the public - key file path. + None """ signed_challenge_files = [] challenge_count = 0 - logging.basicConfig(level=logging.INFO) - logger = logging.getLogger(__name__) for challenge_reply in challenge_replies: challenge_count += 1 - directory_path = "signed_challenges" - if not os.path.exists(directory_path): - os.mkdir(directory_path) - logger.info(f"Directory '{directory_path}' created.") - else: - logger.info(f"Directory '{directory_path}' already exists.") + make_directory("signed_challenges") with open( f"signed_challenges/public_key_{challenge_count}.pem", "w" ) as public_key_file: @@ -137,7 +161,11 @@ def approve_proposal(): unsigned_challenges = parse_challenges_into_files(process.stdout) # Sign challenges - signed_challenges = ykman_utils.sign_challenges(unsigned_challenges) + signed_challenges = ykman_utils.sign_challenges( + challenges=unsigned_challenges, + management_key=parser.management_key, + pin=parser.pin, + ) # Parse signed challenges into files signed_challenged_files = signed_challenges_to_files(signed_challenges) diff --git a/kms/singletenanthsm/approve_proposal_test.py b/kms/singletenanthsm/approve_proposal_test.py index 1821f5469ae..8808d037496 100644 --- a/kms/singletenanthsm/approve_proposal_test.py +++ b/kms/singletenanthsm/approve_proposal_test.py @@ -172,7 +172,9 @@ def test_get_challenges_mocked(mocker, monkeypatch): # monkeypatch sign challenges monkeypatch.setattr( "ykman_utils.sign_challenges", - lambda challenges: sign_challenges_with_capture(challenges, pub_to_priv_key), + lambda challenges, **kwargs: sign_challenges_with_capture( + challenges, pub_to_priv_key + ), ) # mock the challenge string returned by service @@ -182,9 +184,14 @@ def test_get_challenges_mocked(mocker, monkeypatch): mocker.patch("subprocess.run", return_value=mock_response) # monkeypatch parse args - mock_args = argparse.Namespace(proposal_resource="test_resource") - monkeypatch.setattr("approve_proposal.parse_args", lambda args: mock_args) - + monkeypatch.setattr( + "approve_proposal.parse_args", + lambda args: argparse.Namespace( + proposal_resource="test_resource", + management_key="test_management_key", + pin="123456", + ), + ) approve_proposal.approve_proposal() # assert challenge files created diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem index 721b6babb89..944c8353b41 100644 --- a/kms/singletenanthsm/challenges/public_key1.pem +++ b/kms/singletenanthsm/challenges/public_key1.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtD7qkuyTSxuV8OKoKWAG -Xjfq6CzT1sq/VdTfDUIW9A+3K6d56XIfTSmLjq5IBuF4lH2Rps+LKJHuf6LYAD8U -5JvI9ZtjtySalGKwyN9t7ZhLkbVXhFbNo+pucCJkZAUbBWcaY82DuXu97sbEHY46 -FFeCc2X5Xt5WcFmyKofMLnYtiORt50kTOYYaVW/jRBhE726jK2X3VYrhlK55Zcwh -akiB5K2azRbvwophZLkmEgkjxY/JiSGKZW1VIVdjmLFyWLJfuSacUQ/QQOtCirW+ -ks/o1wCNh+MJgd5dlhkmI6mV0EITT4hNn06XMMXfZ62PIQBXz6oInlVZnFT0R58M -zwIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxnfF4KVlr7gTbUFqpyeM +7NEZa/NtHb/CMOIJ5EbsofeYNRWyd+hreMu/8YGszNSpXbvSPv6abpcR7a7XOXg4 +XdhLnbzHQEIJJXDDpiiAIqXRWJ/YYvllKUg0MM9D/dNYdQlggRCACBax/iEPvjTh +pLAAfqTpTT9Ud2X1ByBQg3Nvg3bixapgABNv6vNZDpgw8zQBXZ0XaeOID4C94Xhd +OVoYEjcZrSHe19pWqM7G242npO/AEBX5GfQXPphd3J3mlEjWWZ5jkgAMaR622fjV +ySXbcIwpGTKwllvlshO5G6NMzaPFm4Eo+QWfHiXgYKjiECCioHhgckL1ASK+xtFp +6QIDAQAB -----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem index 6aa512f1638..c52d669951d 100644 --- a/kms/singletenanthsm/challenges/public_key2.pem +++ b/kms/singletenanthsm/challenges/public_key2.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApHhfnEUrlBaWacxltJ19 -1i+voxM+1IJIYTeQLQ/rZb6gwVABKDeoLP+vn//9rmmWTL7pjBubmIikWuROgbQ7 -y8Xvnyg/1q8wxKUbbduuAbS3s0mktV4nLMPBzvQ/GOBcZCfG0du+k7Vl/uvRgx9w -x8v5Zn1LIiei96k5EJr0YhAL9lNAh86e5HIjTqhRwa5b6S2oh2oslEXe9qCwdlVV -3sy+hpTSheiiUOcfHDOEiIWlR6r49MMqI0oav0NZ15eYa/oUB4MC9Zao+VqchItk -46o4cO+p85RXTAKt8rJub16Dl6C8V9oXRIoa3kRtv14QJE2OWV6Zp1nuDaEQwn5m -2wIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAybbLbtJyJYwQrxd79/A5 +I0ZmckiKCW7sq5L3d2rwyAX8CiOadoo3ndwTTrz4jHWvVKleKBLyhoOY71ab0Gj8 +dAIMYsl6tKnFATI9/PIlYMg1ay9n/15aj9rc/Zbx996GMhEkeeqradd3i5OAjIJw +8D/rF8JtOzNWLlNQCc1tEO8eIRlRxtaWMtiCpuuIKtMHsqJ0jj6S2iFnsazGTXjN +KFNHINv41t0zsK11Bxy7W1a/701pszHJB9FvqD3m/h6rTCi5jEVjcRb/U2agsrl9 +YSG7A65/3UqHYG/WDGlZ9uAk1ED73ipubRn9RIxkdzIXzMiIh83zYLApPYHy6uib +sQIDAQAB -----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem index fa44a9d6257..4d9e5093382 100644 --- a/kms/singletenanthsm/challenges/public_key3.pem +++ b/kms/singletenanthsm/challenges/public_key3.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx8SoBPVaK7b9fgDkbZPZ -94MxK8eDkoe4CJ/Hzi7PIHIVng5sA5ufdWqi1jGqoetc3LwaLlHVsduE3Iol4Ofy -vYgf61BaV8xMHTdgjLGTzPaxfgEH18W4epT6QOcD4m6Q1wCK93Q5UgWfDbdPVuSo -xcwffKOR3MnyO0M1WHWDy39rPyeXhISA9ZPHl3VwMkcixuGQtEsU1AidqNpj0POo -xdoJeXk7ZpA2MWudaldZdlcQVkeUr3NRGrc46CEKUbsFmPlx/p0aQyuLfxXHUwVZ -x8STwRf6ZEs2+tnlQpTYg/xv8+YJwFOB9+he4Qiz0ElAmrdHlt+AY3xGMRZgC9jw -RQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA39BaWHpiIFwqdN+yu0Ka +p52YDTa/IvWMxZhJ4GnL0UStPAT5le5N2cQF76cHyw+k3iO9kd2xP3seOnGn6NaV +dmF3LKMQRrPJBcf1/DQGGaNOP+coMxA9l+axaksb3NaYxmHe8ZVXihbUCRUlNJ7v +b0MfofdyyxYiCe80tEX4y58r8QxmWFWAr+TLMUyMH/ULvgr/YivHpwN49egMlCwz +A01V/3MyrHqjVudJWvwVNPe8NKpWQIJJZzfluPGOViCCzvxlYlxNaKIP5wg1NGeP +2LHPy6c6CMKUYmIcGdPSrJ4tE1E+PMzJwv4bMU1BwY+Ggi7YkLWv+96Jcjan7mCx +ZwIDAQAB -----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/gcloud_commands.py index ce36576fdd6..6ff6fdf245b 100644 --- a/kms/singletenanthsm/gcloud_commands.py +++ b/kms/singletenanthsm/gcloud_commands.py @@ -46,14 +46,15 @@ def build_custom_gcloud(): command_build_custom_gcloud, check=True, shell=True, - capture_output=True, # Capture output - text=True # Ensure output is text + capture_output=True, + text=True, ) logger.info(f"Return Code: {process.returncode}") logger.info(f"Standard Error: {process.stderr}") logger.info("gcloud build executed successfully.") logger.info(process.stdout) except subprocess.CalledProcessError as e: + logger.exception(f"gcloud build failed: {e}") raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) try: print("\nAdding gcloud components") @@ -64,16 +65,16 @@ def build_custom_gcloud(): text=True, shell=True, ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud components add executed successfully.") - print(process.stdout) + logger.info(f"Return Test: {process}") + logger.info(f"Return Code: {process.returncode}") + logger.info(f"Standard Output: {process.stdout}") + logger.info(f"Standard Error: {process.stderr}") + logger.info("gcloud components add executed successfully.") + logger.info(process.stdout) return process except subprocess.CalledProcessError as e: + logger.info(f"Error executing gcloud components update: {e}") raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) - print(f"Error executing gcloud components update: {e}") command_gcloud_list_proposal = ( @@ -88,7 +89,8 @@ def build_custom_gcloud(): def fetch_challenges(sthi_proposal_resource: str): """Fetches challenges from the server.""" - + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) try: print("\nfetching challenges") process = subprocess.run( @@ -101,14 +103,15 @@ def fetch_challenges(sthi_proposal_resource: str): shell=True, # stderr=subprocess.STDOUT ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud command executed successfully.") - print(process.stdout) + logger.info(f"Return Test: {process}") + logger.info(f"Return Code: {process.returncode}") + logger.info(f"Standard Output: {process.stdout}") + logger.info(f"Standard Error: {process.stderr}") + logger.info("gcloud command executed successfully.") + logger.info(process.stdout) return process except subprocess.CalledProcessError as e: + logger.exception(f"Fetching challenges failed: {e}") raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) @@ -123,6 +126,8 @@ def fetch_challenges(sthi_proposal_resource: str): def send_signed_challenges(signed_challenged_files: list[str], proposal_resource: str): """Sends signed challenges to the server.""" + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) if signed_challenged_files is None or not signed_challenged_files: raise ValueError("signed_challenged_files is empty") print("Sending signed challenges") @@ -130,7 +135,7 @@ def send_signed_challenges(signed_challenged_files: list[str], proposal_resource command_str = " ".join( command_gcloud_approve_proposal + [proposal_resource] + [signed_challenge_str] ) - print(command_str) + logger.info(command_str) try: process = subprocess.run( @@ -140,12 +145,13 @@ def send_signed_challenges(signed_challenged_files: list[str], proposal_resource text=True, shell=True, ) - print(f"Return Test: {process}") - print(f"Return Code: {process.returncode}") - print(f"Standard Output: {process.stdout}") - print(f"Standard Error: {process.stderr}") - print("gcloud command executed successfully.") + logger.info(f"Return Test: {process}") + logger.info(f"Return Code: {process.returncode}") + logger.info(f"Standard Output: {process.stdout}") + logger.info(f"Standard Error: {process.stderr}") + logger.info("gcloud command executed successfully.") return process except subprocess.CalledProcessError as e: + logger.exception(f"Sending signed challenges failed: {e}") raise subprocess.CalledProcessError(e.returncode, e.cmd, e.output, e.stderr) diff --git a/kms/singletenanthsm/setup.py b/kms/singletenanthsm/setup.py index 25d75a8d957..3cd331b50ca 100644 --- a/kms/singletenanthsm/setup.py +++ b/kms/singletenanthsm/setup.py @@ -28,11 +28,15 @@ def validate_operation(operation: str, management_key: str, pin: str): elif operation == "generate_rsa_keys": try: if not management_key or not pin: - raise ValueError("--management_key and --pin need to be specified for the generate_rsa_keys operation") + raise ValueError( + "--management_key and --pin need to be specified for the generate_rsa_keys operation" + ) ykman_utils.generate_private_key(management_key=management_key, pin=pin) except Exception as e: if not management_key or not pin: - raise ValueError("--management_key and --pin need to be specified for the generate_rsa_keys operation") + raise ValueError( + "--management_key and --pin need to be specified for the generate_rsa_keys operation" + ) raise Exception(f"Generating private keys failed {e}") elif operation == "generate_gcloud_and_keys": generate_private_keys_build_gcloud() diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem index 721b6babb89..944c8353b41 100644 --- a/kms/singletenanthsm/signed_challenges/public_key_1.pem +++ b/kms/singletenanthsm/signed_challenges/public_key_1.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtD7qkuyTSxuV8OKoKWAG -Xjfq6CzT1sq/VdTfDUIW9A+3K6d56XIfTSmLjq5IBuF4lH2Rps+LKJHuf6LYAD8U -5JvI9ZtjtySalGKwyN9t7ZhLkbVXhFbNo+pucCJkZAUbBWcaY82DuXu97sbEHY46 -FFeCc2X5Xt5WcFmyKofMLnYtiORt50kTOYYaVW/jRBhE726jK2X3VYrhlK55Zcwh -akiB5K2azRbvwophZLkmEgkjxY/JiSGKZW1VIVdjmLFyWLJfuSacUQ/QQOtCirW+ -ks/o1wCNh+MJgd5dlhkmI6mV0EITT4hNn06XMMXfZ62PIQBXz6oInlVZnFT0R58M -zwIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxnfF4KVlr7gTbUFqpyeM +7NEZa/NtHb/CMOIJ5EbsofeYNRWyd+hreMu/8YGszNSpXbvSPv6abpcR7a7XOXg4 +XdhLnbzHQEIJJXDDpiiAIqXRWJ/YYvllKUg0MM9D/dNYdQlggRCACBax/iEPvjTh +pLAAfqTpTT9Ud2X1ByBQg3Nvg3bixapgABNv6vNZDpgw8zQBXZ0XaeOID4C94Xhd +OVoYEjcZrSHe19pWqM7G242npO/AEBX5GfQXPphd3J3mlEjWWZ5jkgAMaR622fjV +ySXbcIwpGTKwllvlshO5G6NMzaPFm4Eo+QWfHiXgYKjiECCioHhgckL1ASK+xtFp +6QIDAQAB -----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem index 6aa512f1638..c52d669951d 100644 --- a/kms/singletenanthsm/signed_challenges/public_key_2.pem +++ b/kms/singletenanthsm/signed_challenges/public_key_2.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApHhfnEUrlBaWacxltJ19 -1i+voxM+1IJIYTeQLQ/rZb6gwVABKDeoLP+vn//9rmmWTL7pjBubmIikWuROgbQ7 -y8Xvnyg/1q8wxKUbbduuAbS3s0mktV4nLMPBzvQ/GOBcZCfG0du+k7Vl/uvRgx9w -x8v5Zn1LIiei96k5EJr0YhAL9lNAh86e5HIjTqhRwa5b6S2oh2oslEXe9qCwdlVV -3sy+hpTSheiiUOcfHDOEiIWlR6r49MMqI0oav0NZ15eYa/oUB4MC9Zao+VqchItk -46o4cO+p85RXTAKt8rJub16Dl6C8V9oXRIoa3kRtv14QJE2OWV6Zp1nuDaEQwn5m -2wIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAybbLbtJyJYwQrxd79/A5 +I0ZmckiKCW7sq5L3d2rwyAX8CiOadoo3ndwTTrz4jHWvVKleKBLyhoOY71ab0Gj8 +dAIMYsl6tKnFATI9/PIlYMg1ay9n/15aj9rc/Zbx996GMhEkeeqradd3i5OAjIJw +8D/rF8JtOzNWLlNQCc1tEO8eIRlRxtaWMtiCpuuIKtMHsqJ0jj6S2iFnsazGTXjN +KFNHINv41t0zsK11Bxy7W1a/701pszHJB9FvqD3m/h6rTCi5jEVjcRb/U2agsrl9 +YSG7A65/3UqHYG/WDGlZ9uAk1ED73ipubRn9RIxkdzIXzMiIh83zYLApPYHy6uib +sQIDAQAB -----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem index fa44a9d6257..4d9e5093382 100644 --- a/kms/singletenanthsm/signed_challenges/public_key_3.pem +++ b/kms/singletenanthsm/signed_challenges/public_key_3.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx8SoBPVaK7b9fgDkbZPZ -94MxK8eDkoe4CJ/Hzi7PIHIVng5sA5ufdWqi1jGqoetc3LwaLlHVsduE3Iol4Ofy -vYgf61BaV8xMHTdgjLGTzPaxfgEH18W4epT6QOcD4m6Q1wCK93Q5UgWfDbdPVuSo -xcwffKOR3MnyO0M1WHWDy39rPyeXhISA9ZPHl3VwMkcixuGQtEsU1AidqNpj0POo -xdoJeXk7ZpA2MWudaldZdlcQVkeUr3NRGrc46CEKUbsFmPlx/p0aQyuLfxXHUwVZ -x8STwRf6ZEs2+tnlQpTYg/xv8+YJwFOB9+he4Qiz0ElAmrdHlt+AY3xGMRZgC9jw -RQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA39BaWHpiIFwqdN+yu0Ka +p52YDTa/IvWMxZhJ4GnL0UStPAT5le5N2cQF76cHyw+k3iO9kd2xP3seOnGn6NaV +dmF3LKMQRrPJBcf1/DQGGaNOP+coMxA9l+axaksb3NaYxmHe8ZVXihbUCRUlNJ7v +b0MfofdyyxYiCe80tEX4y58r8QxmWFWAr+TLMUyMH/ULvgr/YivHpwN49egMlCwz +A01V/3MyrHqjVudJWvwVNPe8NKpWQIJJZzfluPGOViCCzvxlYlxNaKIP5wg1NGeP +2LHPy6c6CMKUYmIcGdPSrJ4tE1E+PMzJwv4bMU1BwY+Ggi7YkLWv+96Jcjan7mCx +ZwIDAQAB -----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/signed_challenges/signed_challenge1.bin index 788b9e7eed91af757ada9d70478c594761d19fd9..0f7860d4f0567a84e9075ae91b1e784ef1221f4f 100644 GIT binary patch literal 256 zcmV+b0ssEMsfz1A*%ywgk=+y*0|4@o9*K?^$9c0}Q<`E|`)9W%BL?@kNEMxiY^Ec6c6gC$?7 z`@qYPoXij@rF_f^ehyQ&OtVJ4k1{SkCdRV>%PAusqjQV^ literal 256 zcmV+b0ssD3bmGnxjpV-C9g2;E6Ezr$8Zd(i7an&kIbk( z5IEFY9|bqM4;wKgpWZe-b`nTU1edo8k*0m^aEaK1$BVoPWt6l2q5B5~(zWEq+W&kJ zti@0d!}wSk6VA8U&Ylzaj5%I4|5a7fwa~S*@VEmT{7J#UV8WVET+QEbI}P@TK(dN_ zgee)zQ!fU1uC(ZphWe_C1V^v!9Rk7v`#brrB21{N1~$L^Z1M#vAx9fAjs89*L3~v}_H`<%l^4PR0b!27 z>Eb{`_9yR>rRNX&qwXo_C?7ff*l_2$Y-LOSRL-hBC?i0<%3Qu;@(t9)N-ASE;_mY8 zn>UhwpK`S^{WXB_JPxU-v9e3Lf3A;3he-Vy)_V~|xwn0kc;!+6$kFlJ0yso?)})wJ8t(gR7f4OpQY z^0^!$f;H=i!{w42GdB4bwd*-pL4BEEs|g_NxU(+ii$;z%z|f5O94O#oF-6RP2sI5q z{bYDG*Ch`$kH_2pZ?O~)IURzzX}dpq0U59X%OqK|!t-&e{>`8Kqd(9QY*e!E;GZzf zy@puRPK=?e1>#5E4jbD(?(DqhTfqRS(#3(qY=tS&_NegL)N#8%xyb5YFe_Rcs3w%k Gv-Wdzl!crC diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge3.bin b/kms/singletenanthsm/signed_challenges/signed_challenge3.bin index f1800f3b1d14768512e79a8f69c963f79a9db793..58799a127baf78edcd5ff9f9902098573abb36e3 100644 GIT binary patch literal 256 zcmV+b0ssEbrsiG@o}&;pvK2W&%=B~zl^YXT%L4Oz3SLpG^Y;$YDYXs4eagQ1CL>G= zdvAT>NYLEG%*^#d->u!sA`B$Aj#;jIXUQRG;+^B45ryeCf9)W$#k`G4hv7<Xr@zn07Y8qza{8aJ%8~8EiR33Q zCf>0ke|(Q(8Em*YHycL%j;D^Y6fs_HlHf&1)zc|8P$J(r@fo(YglC~xF+H=Gs=UB& G+2G^Fe|?Am literal 256 zcmV+b0ssD3k&^{>b5}AUA0CZ*HffUDyTrM}Gt&TLC%C!!rh7IfN}P0JUtyofZI&iH zH1+z|2dK;1f6fV;tDvx>bl2++-%NrF?Hpb!3=PIK%@g@kSq}oAzOV_k9#XB_8i`>} z_t+dfZzq2XiR+Damg$^zXAOd`(*?$XidN)}ZW^hFzp%nPQ>4e}CD~trk%#+8Kc%Sb zaGZ{l=e@X;35$vkR4gHv`uUv1wwl=0=S%)&JRTy)Nr-+(DqRkO3vV$&n0qc;+ZA5k z)nsc(uQa;K6;GOT`a=HFjKdDWPryDl=%EmEH=Mjrw2O_vah`u2PEaj61CTH|ly4n> GUiZ9l)qHaR diff --git a/kms/singletenanthsm/ykman_fake.py b/kms/singletenanthsm/ykman_fake.py index 38a7d75eeaf..c351aa39c1f 100644 --- a/kms/singletenanthsm/ykman_fake.py +++ b/kms/singletenanthsm/ykman_fake.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2020 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/kms/singletenanthsm/ykman_utils.py b/kms/singletenanthsm/ykman_utils.py index d7b3a810505..fd88cb8117d 100644 --- a/kms/singletenanthsm/ykman_utils.py +++ b/kms/singletenanthsm/ykman_utils.py @@ -149,8 +149,9 @@ def populate_challenges_from_files() -> list[Challenge]: return challenges -def sign_challenges(challenges: list[Challenge], management_key=DEFAULT_MANAGEMENT_KEY, - pin=DEFAULT_PIN) -> list[ChallengeReply]: +def sign_challenges( + challenges: list[Challenge], management_key=DEFAULT_MANAGEMENT_KEY, pin=DEFAULT_PIN +) -> list[ChallengeReply]: """Signs a proposal's challenges using a Yubikey.""" if not challenges: raise ValueError("Challenge list empty: No challenges to sign.") @@ -227,7 +228,7 @@ def urlsafe_base64_to_binary(urlsafe_string: str) -> bytes: if not isinstance(urlsafe_string, str): raise TypeError("Input must be a string") # Check if the input string contains only URL-safe base64 characters - if not re.match(r'^[a-zA-Z0-9_-]*$', urlsafe_string): + if not re.match(r"^[a-zA-Z0-9_-]*$", urlsafe_string): raise ValueError("Input string contains invalid characters") # Add padding if necessary. Base64 requires padding to be a multiple of 4 missing_padding = len(urlsafe_string) % 4 diff --git a/kms/singletenanthsm/ykman_utils_test.py b/kms/singletenanthsm/ykman_utils_test.py index 03f738cd2f0..bcff1c4320f 100644 --- a/kms/singletenanthsm/ykman_utils_test.py +++ b/kms/singletenanthsm/ykman_utils_test.py @@ -46,6 +46,7 @@ def generate_test_challenge_files(): print(challenge.public_key_pem) # Sign challenges signed_challenges = ykman_utils.sign_challenges(challenges) + # Use a sample challenge from the HSM ykman_utils.verify_challenge_signatures( signed_challenges, b"rddK-SCLvik55PPoxOxgjoZEnQ7kTttvtYg2-zYhpGsDjpsPEFw_2OKau1EFf3nN", From 56e23d29f8ef34ff5bf56c02e66423382885b633 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Mon, 5 May 2025 16:18:39 -0400 Subject: [PATCH 10/19] Removed generated challenge files --- kms/singletenanthsm/challenges/challenge1.txt | 1 - kms/singletenanthsm/challenges/challenge2.txt | 1 - kms/singletenanthsm/challenges/challenge3.txt | 1 - kms/singletenanthsm/challenges/public_key1.pem | 9 --------- kms/singletenanthsm/challenges/public_key2.pem | 9 --------- kms/singletenanthsm/challenges/public_key3.pem | 9 --------- .../signed_challenges/public_key_1.pem | 9 --------- .../signed_challenges/public_key_2.pem | 9 --------- .../signed_challenges/public_key_3.pem | 9 --------- .../signed_challenges/signed_challenge1.bin | Bin 256 -> 0 bytes .../signed_challenges/signed_challenge2.bin | Bin 256 -> 0 bytes .../signed_challenges/signed_challenge3.bin | 3 --- 12 files changed, 60 deletions(-) delete mode 100644 kms/singletenanthsm/challenges/challenge1.txt delete mode 100644 kms/singletenanthsm/challenges/challenge2.txt delete mode 100644 kms/singletenanthsm/challenges/challenge3.txt delete mode 100644 kms/singletenanthsm/challenges/public_key1.pem delete mode 100644 kms/singletenanthsm/challenges/public_key2.pem delete mode 100644 kms/singletenanthsm/challenges/public_key3.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_1.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_2.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_3.pem delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge1.bin delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge2.bin delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge3.bin diff --git a/kms/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/challenges/challenge1.txt deleted file mode 100644 index c891b41bd6d..00000000000 --- a/kms/singletenanthsm/challenges/challenge1.txt +++ /dev/null @@ -1 +0,0 @@ -#?xpAky7.m*\hkG.jo(T!5 \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/challenges/challenge2.txt deleted file mode 100644 index 93616f55ed6..00000000000 --- a/kms/singletenanthsm/challenges/challenge2.txt +++ /dev/null @@ -1 +0,0 @@ -:/~j;QQܿA3($̳N+;H'^7N;QX \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/challenges/challenge3.txt deleted file mode 100644 index 3f9fc362302..00000000000 --- a/kms/singletenanthsm/challenges/challenge3.txt +++ /dev/null @@ -1 +0,0 @@ -4>y)v2Ugn&3R"0g/熎X \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem deleted file mode 100644 index 944c8353b41..00000000000 --- a/kms/singletenanthsm/challenges/public_key1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxnfF4KVlr7gTbUFqpyeM -7NEZa/NtHb/CMOIJ5EbsofeYNRWyd+hreMu/8YGszNSpXbvSPv6abpcR7a7XOXg4 -XdhLnbzHQEIJJXDDpiiAIqXRWJ/YYvllKUg0MM9D/dNYdQlggRCACBax/iEPvjTh -pLAAfqTpTT9Ud2X1ByBQg3Nvg3bixapgABNv6vNZDpgw8zQBXZ0XaeOID4C94Xhd -OVoYEjcZrSHe19pWqM7G242npO/AEBX5GfQXPphd3J3mlEjWWZ5jkgAMaR622fjV -ySXbcIwpGTKwllvlshO5G6NMzaPFm4Eo+QWfHiXgYKjiECCioHhgckL1ASK+xtFp -6QIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem deleted file mode 100644 index c52d669951d..00000000000 --- a/kms/singletenanthsm/challenges/public_key2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAybbLbtJyJYwQrxd79/A5 -I0ZmckiKCW7sq5L3d2rwyAX8CiOadoo3ndwTTrz4jHWvVKleKBLyhoOY71ab0Gj8 -dAIMYsl6tKnFATI9/PIlYMg1ay9n/15aj9rc/Zbx996GMhEkeeqradd3i5OAjIJw -8D/rF8JtOzNWLlNQCc1tEO8eIRlRxtaWMtiCpuuIKtMHsqJ0jj6S2iFnsazGTXjN -KFNHINv41t0zsK11Bxy7W1a/701pszHJB9FvqD3m/h6rTCi5jEVjcRb/U2agsrl9 -YSG7A65/3UqHYG/WDGlZ9uAk1ED73ipubRn9RIxkdzIXzMiIh83zYLApPYHy6uib -sQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem deleted file mode 100644 index 4d9e5093382..00000000000 --- a/kms/singletenanthsm/challenges/public_key3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA39BaWHpiIFwqdN+yu0Ka -p52YDTa/IvWMxZhJ4GnL0UStPAT5le5N2cQF76cHyw+k3iO9kd2xP3seOnGn6NaV -dmF3LKMQRrPJBcf1/DQGGaNOP+coMxA9l+axaksb3NaYxmHe8ZVXihbUCRUlNJ7v -b0MfofdyyxYiCe80tEX4y58r8QxmWFWAr+TLMUyMH/ULvgr/YivHpwN49egMlCwz -A01V/3MyrHqjVudJWvwVNPe8NKpWQIJJZzfluPGOViCCzvxlYlxNaKIP5wg1NGeP -2LHPy6c6CMKUYmIcGdPSrJ4tE1E+PMzJwv4bMU1BwY+Ggi7YkLWv+96Jcjan7mCx -ZwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem deleted file mode 100644 index 944c8353b41..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxnfF4KVlr7gTbUFqpyeM -7NEZa/NtHb/CMOIJ5EbsofeYNRWyd+hreMu/8YGszNSpXbvSPv6abpcR7a7XOXg4 -XdhLnbzHQEIJJXDDpiiAIqXRWJ/YYvllKUg0MM9D/dNYdQlggRCACBax/iEPvjTh -pLAAfqTpTT9Ud2X1ByBQg3Nvg3bixapgABNv6vNZDpgw8zQBXZ0XaeOID4C94Xhd -OVoYEjcZrSHe19pWqM7G242npO/AEBX5GfQXPphd3J3mlEjWWZ5jkgAMaR622fjV -ySXbcIwpGTKwllvlshO5G6NMzaPFm4Eo+QWfHiXgYKjiECCioHhgckL1ASK+xtFp -6QIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem deleted file mode 100644 index c52d669951d..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAybbLbtJyJYwQrxd79/A5 -I0ZmckiKCW7sq5L3d2rwyAX8CiOadoo3ndwTTrz4jHWvVKleKBLyhoOY71ab0Gj8 -dAIMYsl6tKnFATI9/PIlYMg1ay9n/15aj9rc/Zbx996GMhEkeeqradd3i5OAjIJw -8D/rF8JtOzNWLlNQCc1tEO8eIRlRxtaWMtiCpuuIKtMHsqJ0jj6S2iFnsazGTXjN -KFNHINv41t0zsK11Bxy7W1a/701pszHJB9FvqD3m/h6rTCi5jEVjcRb/U2agsrl9 -YSG7A65/3UqHYG/WDGlZ9uAk1ED73ipubRn9RIxkdzIXzMiIh83zYLApPYHy6uib -sQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem deleted file mode 100644 index 4d9e5093382..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA39BaWHpiIFwqdN+yu0Ka -p52YDTa/IvWMxZhJ4GnL0UStPAT5le5N2cQF76cHyw+k3iO9kd2xP3seOnGn6NaV -dmF3LKMQRrPJBcf1/DQGGaNOP+coMxA9l+axaksb3NaYxmHe8ZVXihbUCRUlNJ7v -b0MfofdyyxYiCe80tEX4y58r8QxmWFWAr+TLMUyMH/ULvgr/YivHpwN49egMlCwz -A01V/3MyrHqjVudJWvwVNPe8NKpWQIJJZzfluPGOViCCzvxlYlxNaKIP5wg1NGeP -2LHPy6c6CMKUYmIcGdPSrJ4tE1E+PMzJwv4bMU1BwY+Ggi7YkLWv+96Jcjan7mCx -ZwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/signed_challenges/signed_challenge1.bin deleted file mode 100644 index 0f7860d4f0567a84e9075ae91b1e784ef1221f4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssEMsfz1A*%ywgk=+y*0|4@o9*K?^$9c0}Q<`E|`)9W%BL?@kNEMxiY^Ec6c6gC$?7 z`@qYPoXij@rF_f^ehyQ&OtVJ4k1{SkCdRV>%PAusqjQV^ diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge2.bin b/kms/singletenanthsm/signed_challenges/signed_challenge2.bin deleted file mode 100644 index 122984e58a906499ebf96dc3a0b8576af6eb8222..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssD7k5h7^`JH&(Q^%4zQQ_ZQr%o9fAjs89*L3~v}_H`<%l^4PR0b!27 z>Eb{`_9yR>rRNX&qwXo_C?7ff*l_2$Y-LOSRL-hBC?i0<%3Qu;@(t9)N-ASE;_mY8 zn>UhwpK`S^{WXB_JPxU-v9e3Lf3A;3he-Vy)_V~|xwn0kc;!+6$kFlJ0yso? Date: Mon, 5 May 2025 16:27:56 -0400 Subject: [PATCH 11/19] Add command to install libpcsclite-dev to ReadMe --- kms/singletenanthsm/README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kms/singletenanthsm/README.rst b/kms/singletenanthsm/README.rst index 959b45c507f..7a252415f92 100644 --- a/kms/singletenanthsm/README.rst +++ b/kms/singletenanthsm/README.rst @@ -34,6 +34,12 @@ Install Dependencies .. _Python Development Environment Setup Guide: https://cloud.google.com/python/setup +#. Install `libpcsclite-dev` if you do not already have it. + + .. code-block:: bash + + $ sudo apt install libpcsclite-dev + #. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. .. code-block:: bash From 618ebb7d7b4ac79d05373bf6fc01175a585de5d6 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Tue, 6 May 2025 11:47:38 -0400 Subject: [PATCH 12/19] Add command to install libpcsclite to run_test.sh startup script --- .kokoro/tests/run_tests.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 1715decdce7..569eb82a331 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -84,6 +84,9 @@ fi cd "${PROJECT_ROOT}" +# add libpcsclite +sudo apt install libpcsclite-dev + # add user's pip binary path to PATH export PATH="${HOME}/.local/bin:${PATH}" From 6fc6010f025aaa9236cd1071e5ce1468d9e72332 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Tue, 6 May 2025 11:55:52 -0400 Subject: [PATCH 13/19] Remove sudo from libpcsclite-dev startup script --- .kokoro/tests/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 569eb82a331..25d8a4b597d 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -85,7 +85,7 @@ fi cd "${PROJECT_ROOT}" # add libpcsclite -sudo apt install libpcsclite-dev +apt install libpcsclite-dev # add user's pip binary path to PATH export PATH="${HOME}/.local/bin:${PATH}" From fb969ec7bfd344ee5f1168698d00bb8e540169e1 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Tue, 6 May 2025 11:56:59 -0400 Subject: [PATCH 14/19] Remove extra spaces from run_tests.sh file --- .kokoro/tests/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 25d8a4b597d..acd132a936f 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -85,7 +85,7 @@ fi cd "${PROJECT_ROOT}" # add libpcsclite -apt install libpcsclite-dev +apt install libpcsclite-dev # add user's pip binary path to PATH export PATH="${HOME}/.local/bin:${PATH}" From 41fad1b16c3bbf394ebb3e8785f7cd4c6ea4fc44 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Tue, 6 May 2025 13:08:53 -0400 Subject: [PATCH 15/19] Add back sudo for install libpcsclite-dev command --- .kokoro/tests/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index acd132a936f..569eb82a331 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -85,7 +85,7 @@ fi cd "${PROJECT_ROOT}" # add libpcsclite -apt install libpcsclite-dev +sudo apt install libpcsclite-dev # add user's pip binary path to PATH export PATH="${HOME}/.local/bin:${PATH}" From 0bcc412173e1241f60c1599d655e2cbdf913c6e9 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Tue, 6 May 2025 14:07:44 -0400 Subject: [PATCH 16/19] =?UTF-8?q?=E2=80=9C=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .kokoro/docker/Dockerfile | 2 ++ .kokoro/tests/run_tests.sh | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.kokoro/docker/Dockerfile b/.kokoro/docker/Dockerfile index e2d74d172dc..edda87999b4 100644 --- a/.kokoro/docker/Dockerfile +++ b/.kokoro/docker/Dockerfile @@ -227,4 +227,6 @@ RUN mkdir -p /opt/selenium \ && curl http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip -o /opt/selenium/chromedriver_linux64.zip \ && cd /opt/selenium; unzip /opt/selenium/chromedriver_linux64.zip; rm -rf chromedriver_linux64.zip; ln -fs /opt/selenium/chromedriver /usr/local/bin/chromedriver; +RUN sudo apt install libpcsclite-dev + CMD ["python3"] diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 569eb82a331..75fb74d2626 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -84,8 +84,8 @@ fi cd "${PROJECT_ROOT}" -# add libpcsclite -sudo apt install libpcsclite-dev +# # add libpcsclite +# sudo apt install -q -y libpcsclite-dev # add user's pip binary path to PATH export PATH="${HOME}/.local/bin:${PATH}" From 5d3fccb277842b1d8ca2db574ddf611861eb4695 Mon Sep 17 00:00:00 2001 From: brandonluong-lgtm Date: Wed, 7 May 2025 16:07:25 -0400 Subject: [PATCH 17/19] Modified expected call in gcloud_commands --- .kokoro/docker/Dockerfile | 3 +-- .kokoro/tests/run_tests.sh | 2 +- kms/singletenanthsm/challenges/challenge1.txt | 1 + kms/singletenanthsm/challenges/challenge2.txt | 1 + kms/singletenanthsm/challenges/challenge3.txt | 1 + kms/singletenanthsm/challenges/public_key1.pem | 9 +++++++++ kms/singletenanthsm/challenges/public_key2.pem | 9 +++++++++ kms/singletenanthsm/challenges/public_key3.pem | 9 +++++++++ kms/singletenanthsm/gcloud_commands.py | 6 +++--- kms/singletenanthsm/gcloud_commands_test.py | 10 +++++++--- .../generated_public_keys/public_key_25167010.pem | 9 +++++++++ .../generated_public_keys/public_key_28787103.pem | 9 +++++++++ .../generated_public_keys/public_key_28787105.pem | 9 +++++++++ kms/singletenanthsm/requirements.txt | 2 +- .../signed_challenges/public_key_1.pem | 9 +++++++++ .../signed_challenges/public_key_2.pem | 9 +++++++++ .../signed_challenges/public_key_3.pem | 9 +++++++++ .../signed_challenges/signed_challenge1.bin | Bin 0 -> 256 bytes .../signed_challenges/signed_challenge2.bin | Bin 0 -> 256 bytes .../signed_challenges/signed_challenge3.bin | Bin 0 -> 256 bytes 20 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 kms/singletenanthsm/challenges/challenge1.txt create mode 100644 kms/singletenanthsm/challenges/challenge2.txt create mode 100644 kms/singletenanthsm/challenges/challenge3.txt create mode 100644 kms/singletenanthsm/challenges/public_key1.pem create mode 100644 kms/singletenanthsm/challenges/public_key2.pem create mode 100644 kms/singletenanthsm/challenges/public_key3.pem create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_25167010.pem create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787103.pem create mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787105.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_1.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_2.pem create mode 100644 kms/singletenanthsm/signed_challenges/public_key_3.pem create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge1.bin create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge2.bin create mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge3.bin diff --git a/.kokoro/docker/Dockerfile b/.kokoro/docker/Dockerfile index edda87999b4..6ba09ead867 100644 --- a/.kokoro/docker/Dockerfile +++ b/.kokoro/docker/Dockerfile @@ -46,6 +46,7 @@ RUN apt-get update \ liblzma-dev \ libmagickwand-dev \ libmemcached-dev \ + libpcsclite-dev \ libpython3-dev \ libreadline-dev \ libsnappy-dev \ @@ -227,6 +228,4 @@ RUN mkdir -p /opt/selenium \ && curl http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip -o /opt/selenium/chromedriver_linux64.zip \ && cd /opt/selenium; unzip /opt/selenium/chromedriver_linux64.zip; rm -rf chromedriver_linux64.zip; ln -fs /opt/selenium/chromedriver /usr/local/bin/chromedriver; -RUN sudo apt install libpcsclite-dev - CMD ["python3"] diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 75fb74d2626..6c7b20a08bb 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -85,7 +85,7 @@ fi cd "${PROJECT_ROOT}" # # add libpcsclite -# sudo apt install -q -y libpcsclite-dev +sudo apt install -q -y -s libpcsclite-dev # add user's pip binary path to PATH export PATH="${HOME}/.local/bin:${PATH}" diff --git a/kms/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/challenges/challenge1.txt new file mode 100644 index 00000000000..c891b41bd6d --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge1.txt @@ -0,0 +1 @@ +#?xpAky7.m*\hkG.jo(T!5 \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/challenges/challenge2.txt new file mode 100644 index 00000000000..93616f55ed6 --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge2.txt @@ -0,0 +1 @@ +:/~j;QQܿA3($̳N+;H'^7N;QX \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/challenges/challenge3.txt new file mode 100644 index 00000000000..3f9fc362302 --- /dev/null +++ b/kms/singletenanthsm/challenges/challenge3.txt @@ -0,0 +1 @@ +4>y)v2Ugn&3R"0g/熎X \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem new file mode 100644 index 00000000000..563ec0ae86c --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3QfTa1Ih4PX0cVt8DgES +JljsFAMmsCueNcCg2omng3BbW68PgDRJOIYBMN41iZ9C1k2D8XqcC1b0IRVCnC2p +sxdb1mSuQngBERIIUjyGjuu6/ad07KRxUGZ5FpUfS881eUFQUwB5caC4XGI9ZWIq +lE/ghGqDXCumvGEKh175/HQAlfBdYm0qfglZGHqj9qyj/LpqiZg5olP+YtPQLLJ0 +aJIpAKpAMsHuZ1xXuzv6xbsPkiLOx10WJ6DP9mZBvDbcAXEB/cbqbRQg91kuqGQl +K7bvZBATQFasnKKQCCG2nxk6L+JdqmBPvUhLM6LJmhDGTWo+E+YLUze2B6GTlaOH +iwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem new file mode 100644 index 00000000000..9bf091959fc --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlQpdePmUxR3fcHuAEMtx +DocciCBl3qJShrslieBUsVRJxxWPiWOS8eSBK5Y9eraOSPMtPL20g9E9rKByHsbs +qjxl1iT/S800Djpy6YUHK/lfByqA9KX9Ara5OrznPkJga60v09Wx6ZdXjFmTdQ6M +F80FHWchHk2ta93BuftCLQKongFzjo1XwYjFVIrgQmJn6bLIvSpe9KDccuFZRAnd +Fege2moJbhWdKLcIxpfZvbqV6MCRkdZd0sAUvOWejaFJY/rzk88W3ybs07ilw8tq +Dvgt6cHZw+6hZPLRmmRU6uAuHlobmjELTjJe6nfjVYqZBgPuDhpsuJlj0jNhO+Bf +WQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem new file mode 100644 index 00000000000..aec2d678ece --- /dev/null +++ b/kms/singletenanthsm/challenges/public_key3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjZ9aCKUc73dDIJ+ylvOA +Ubh+W/PcLZGGlJEVs+D5W7zrI/bEDa/dMZSyGKt38OsVKHsAT4G8uuomRfmTVsqR +fPfx6uU95eopy6vww5O0/V5uS+VLAK/jHcx2I/zUINFy4Z/HCdU4QpsOrapLiePf +ziykWSnrE24vbHvG1wPUOr7ivcymxdUrNtsAWJQC5TGXpRmLYatzNmcASyWBOHNr +ebeTbQIbN+mSconp9WMC+6LyrGrgxpdPYczh95CMjUyf6jcXHGALnBQ6tKRx09uR +2fDPKd3L0YWZ0MoDCjTlyc0arv77/ZoBpUw/SJvXI3rHYgPt0slFScmoiRn+zwZj +BwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/gcloud_commands.py b/kms/singletenanthsm/gcloud_commands.py index 6ff6fdf245b..f39e7b61a3e 100644 --- a/kms/singletenanthsm/gcloud_commands.py +++ b/kms/singletenanthsm/gcloud_commands.py @@ -60,10 +60,10 @@ def build_custom_gcloud(): print("\nAdding gcloud components") process = subprocess.run( command_add_components, - check=False, - capture_output=False, - text=True, + check=True, shell=True, + capture_output=True, + text=True, ) logger.info(f"Return Test: {process}") logger.info(f"Return Code: {process.returncode}") diff --git a/kms/singletenanthsm/gcloud_commands_test.py b/kms/singletenanthsm/gcloud_commands_test.py index bc1b6751ae0..5b5ca34b67c 100644 --- a/kms/singletenanthsm/gcloud_commands_test.py +++ b/kms/singletenanthsm/gcloud_commands_test.py @@ -75,13 +75,17 @@ def test_build_custom_gcloud_success(mock_subprocess_run): mock_subprocess_run.assert_has_calls( [ mock.call( - gcloud_commands.command_build_custom_gcloud, check=True, shell=True + gcloud_commands.command_build_custom_gcloud, + check=True, + shell=True, + capture_output=True, + text=True, ), mock.call( gcloud_commands.command_add_components, - check=False, + check=True, shell=True, - capture_output=False, + capture_output=True, text=True, ), ] diff --git a/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem b/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem new file mode 100644 index 00000000000..fce6bbe1b4d --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0r0zKnSu5mDm7IPraKQE +BFD3IAszvYo5krixQ0sq1Xo8UnP03cwYelLvvGWeKTzNL+dtRmv4zUcH/Aks/Z7O +gqRBPEjhIcS0pC+05wVrDShS5Y0x8E2wTyNwu24IQXkvlZNOUjyaLbXSyZIm9PmP +ux/Cq3FO3t7HSuW6mnUjWQYIfpdXvvr5Urtin4meiWFfYqcD0IwnYqgqRBtiRN/7 +kvdvxxZNw9PV4TeKFOKU2lKDPXldIEE7OLQVKQMtywYQ3lzUobtPvFh2Vr0uetcJ +UzF33yt0EUU8Vn5U1nwT9BzX7K8TiVlKFOXFc8tKkMMheyYc4qG9vadDAXejfYhN +OQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem b/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem new file mode 100644 index 00000000000..ad406c2f7ff --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6JVtst7rj/mhT6wCGweE +t2lIN8VnIlNu46zM2LD/woaNgKxUDTf3EXZNFck583r3piPDEbdQVDmBNfvWZc2C +65ZvuIlYIyRtFK2yJxALWzh1uz6m6V47aJGrxdlohHq/c/n4WzFvU5qgDz6etKTD +4ycQsyNzz5eo/JaXsCyTV0P+Q7GEdU/rgcJfZKpcr6xLF8qMjXstK/VASfwiCUOX +3K9ozRm8PcX6ZC783e7nej7UZh8vQfLw1QiPSPw/hVRZVf7UixepEUZOeh6244st +pwarM+8oFliFRDqEa7VqEr4+iy3cYSWhlCD19+1lyuL6BoE1wBcr6Vn0SuElMZtj +BQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem b/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem new file mode 100644 index 00000000000..7a9cbd40f73 --- /dev/null +++ b/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0dslXmtDZbYWfviEo3if +B9b0Oj9lD46mCPPyLYvfjHlGel8FPYN5VbWplfk/f/w+a8rwMG6XPpOUZANNsU+P +LAmh/lw2uS0UXkZb/EGB09LOdNi5eGXKE5FeMobS/spni2MlKFM1YNkKhAf9iJ/b +oNctWcTazEq/GrNZMrqDqNwXPVPnsXXNcr9gMQVP2gSOifBUngM1lX5qz+6Z8Y8e +Tj5G22FcuBM3YezjePpoPx3dsALmALIK8z3KdJ6SilzuzJy2okehgvYlO6tMWJdY +4bG/kkZhpXKBiYn5Gx2tSdhT0ecBGm9SIh7Aj5sy9UUtPWfo84AOaJHiBeysAsgp +bQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/requirements.txt b/kms/singletenanthsm/requirements.txt index 78114f03a63..12db531023d 100644 --- a/kms/singletenanthsm/requirements.txt +++ b/kms/singletenanthsm/requirements.txt @@ -4,7 +4,7 @@ backports.tarfile==1.2.0 cffi==1.17.1 click==8.1.8 colorlog==6.9.0 -cryptography==44.0.0 +cryptography==43.0.3 dependency-groups==1.3.0 distlib==0.3.9 fido2==1.2.0 diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem new file mode 100644 index 00000000000..563ec0ae86c --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_1.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3QfTa1Ih4PX0cVt8DgES +JljsFAMmsCueNcCg2omng3BbW68PgDRJOIYBMN41iZ9C1k2D8XqcC1b0IRVCnC2p +sxdb1mSuQngBERIIUjyGjuu6/ad07KRxUGZ5FpUfS881eUFQUwB5caC4XGI9ZWIq +lE/ghGqDXCumvGEKh175/HQAlfBdYm0qfglZGHqj9qyj/LpqiZg5olP+YtPQLLJ0 +aJIpAKpAMsHuZ1xXuzv6xbsPkiLOx10WJ6DP9mZBvDbcAXEB/cbqbRQg91kuqGQl +K7bvZBATQFasnKKQCCG2nxk6L+JdqmBPvUhLM6LJmhDGTWo+E+YLUze2B6GTlaOH +iwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem new file mode 100644 index 00000000000..9bf091959fc --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_2.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlQpdePmUxR3fcHuAEMtx +DocciCBl3qJShrslieBUsVRJxxWPiWOS8eSBK5Y9eraOSPMtPL20g9E9rKByHsbs +qjxl1iT/S800Djpy6YUHK/lfByqA9KX9Ara5OrznPkJga60v09Wx6ZdXjFmTdQ6M +F80FHWchHk2ta93BuftCLQKongFzjo1XwYjFVIrgQmJn6bLIvSpe9KDccuFZRAnd +Fege2moJbhWdKLcIxpfZvbqV6MCRkdZd0sAUvOWejaFJY/rzk88W3ybs07ilw8tq +Dvgt6cHZw+6hZPLRmmRU6uAuHlobmjELTjJe6nfjVYqZBgPuDhpsuJlj0jNhO+Bf +WQIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem new file mode 100644 index 00000000000..aec2d678ece --- /dev/null +++ b/kms/singletenanthsm/signed_challenges/public_key_3.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjZ9aCKUc73dDIJ+ylvOA +Ubh+W/PcLZGGlJEVs+D5W7zrI/bEDa/dMZSyGKt38OsVKHsAT4G8uuomRfmTVsqR +fPfx6uU95eopy6vww5O0/V5uS+VLAK/jHcx2I/zUINFy4Z/HCdU4QpsOrapLiePf +ziykWSnrE24vbHvG1wPUOr7ivcymxdUrNtsAWJQC5TGXpRmLYatzNmcASyWBOHNr +ebeTbQIbN+mSconp9WMC+6LyrGrgxpdPYczh95CMjUyf6jcXHGALnBQ6tKRx09uR +2fDPKd3L0YWZ0MoDCjTlyc0arv77/ZoBpUw/SJvXI3rHYgPt0slFScmoiRn+zwZj +BwIDAQAB +-----END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/signed_challenges/signed_challenge1.bin new file mode 100644 index 0000000000000000000000000000000000000000..b79ffd6fbf7da2d3bd03bada5cbfffea6c3743ff GIT binary patch literal 256 zcmV+b0ssCfm#Ym0BG54sH(qm{dtQ5eay;N&Oq`oM7jkLSv{6zS=Nc%Dqzys7{0 zcLJ7-#y{h_6F0$sif8|-!$4EH0bVFhg$;N90Yc39ZXSdPE{OAA)FCONz(8^!X%Kj@ z&6cozTA2=iCv4s+0qve#QI3ll^<;pC5Jr5e7Qy#W%xL%6i-?IK9v$(mJHswP&VZay}8a#CkIBjg1 z24)$jJcnZ>UHbGZOFNCg*HB6EAxah)12S0p^8UDlfpZqCx^Q?_IbpF{hCD$U{oDbyhus)%0%QSQf9B#eVCAMc z?E`0Sv|4WQs7`SNrm1`M-iCU}Dulv^Q%^ySq5c3&xq=*Dbaz)?%pp0tx~1{EqpB6i ztM9+VCsW$bo#SK>SRTTj$dGWjtC=~dB9=-WV|I%=@lTx&bE-ml-te&VLd4=+x%wFT z;Idm?C&iZdMQQ8bcuxhzQ3BMAxuDSQeBBRCv@#~&RX59=B`@pJa>Rij6H8UWu4u6) z&(Opb4DE1>uD0%BU#luGd*YeY$63e&*KRj1u`F`2ena3loyWJxtS%cXMdB3H)A99H GP2H06B84OX literal 0 HcmV?d00001 diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge3.bin b/kms/singletenanthsm/signed_challenges/signed_challenge3.bin new file mode 100644 index 0000000000000000000000000000000000000000..fa4f63048c21bbc0c17c54a43f8f19163637a7a0 GIT binary patch literal 256 zcmV+b0ssD6-Oxru!#^zTrBP3*o3#ub%v3c0`@IG>j{D!c2+pYV-%aJ07@QSNFM483 zfLe=1QW5hRf1ASVM!V=28uz`hV|n@vyM-Q1WQTcgTtA=3D~Yd}u|G|F;~Jv`XxIyB z!I{@Tr9~S<_U}%@UHWLT(wmr-M`2)jh5R9+HZVZvFQxSz0ORGVnNs0UH9(w(K13#T z>c(`cl)P1rspKhPeB%?kf+5w&srL-A(hwN|?RUL5oi4Ee9vPU$X*}~-$llRuM?)c8 zS!m)>jWDKMj6d>dd=pYYeP0=SuM@4YU-9Lq_v8SGJr++>CkR$=oD={_S=aP42m)1# GH8kQOpL Date: Wed, 7 May 2025 16:10:17 -0400 Subject: [PATCH 18/19] Modified expected call in gcloud_commands --- kms/singletenanthsm/challenges/challenge1.txt | 1 - kms/singletenanthsm/challenges/challenge2.txt | 1 - kms/singletenanthsm/challenges/challenge3.txt | 1 - kms/singletenanthsm/challenges/public_key1.pem | 9 --------- kms/singletenanthsm/challenges/public_key2.pem | 9 --------- kms/singletenanthsm/challenges/public_key3.pem | 9 --------- .../generated_public_keys/public_key_25167010.pem | 9 --------- .../generated_public_keys/public_key_28787103.pem | 9 --------- .../generated_public_keys/public_key_28787105.pem | 9 --------- .../signed_challenges/public_key_1.pem | 9 --------- .../signed_challenges/public_key_2.pem | 9 --------- .../signed_challenges/public_key_3.pem | 9 --------- .../signed_challenges/signed_challenge1.bin | Bin 256 -> 0 bytes .../signed_challenges/signed_challenge2.bin | Bin 256 -> 0 bytes .../signed_challenges/signed_challenge3.bin | Bin 256 -> 0 bytes 15 files changed, 84 deletions(-) delete mode 100644 kms/singletenanthsm/challenges/challenge1.txt delete mode 100644 kms/singletenanthsm/challenges/challenge2.txt delete mode 100644 kms/singletenanthsm/challenges/challenge3.txt delete mode 100644 kms/singletenanthsm/challenges/public_key1.pem delete mode 100644 kms/singletenanthsm/challenges/public_key2.pem delete mode 100644 kms/singletenanthsm/challenges/public_key3.pem delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_25167010.pem delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787103.pem delete mode 100644 kms/singletenanthsm/generated_public_keys/public_key_28787105.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_1.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_2.pem delete mode 100644 kms/singletenanthsm/signed_challenges/public_key_3.pem delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge1.bin delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge2.bin delete mode 100644 kms/singletenanthsm/signed_challenges/signed_challenge3.bin diff --git a/kms/singletenanthsm/challenges/challenge1.txt b/kms/singletenanthsm/challenges/challenge1.txt deleted file mode 100644 index c891b41bd6d..00000000000 --- a/kms/singletenanthsm/challenges/challenge1.txt +++ /dev/null @@ -1 +0,0 @@ -#?xpAky7.m*\hkG.jo(T!5 \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge2.txt b/kms/singletenanthsm/challenges/challenge2.txt deleted file mode 100644 index 93616f55ed6..00000000000 --- a/kms/singletenanthsm/challenges/challenge2.txt +++ /dev/null @@ -1 +0,0 @@ -:/~j;QQܿA3($̳N+;H'^7N;QX \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/challenge3.txt b/kms/singletenanthsm/challenges/challenge3.txt deleted file mode 100644 index 3f9fc362302..00000000000 --- a/kms/singletenanthsm/challenges/challenge3.txt +++ /dev/null @@ -1 +0,0 @@ -4>y)v2Ugn&3R"0g/熎X \ No newline at end of file diff --git a/kms/singletenanthsm/challenges/public_key1.pem b/kms/singletenanthsm/challenges/public_key1.pem deleted file mode 100644 index 563ec0ae86c..00000000000 --- a/kms/singletenanthsm/challenges/public_key1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3QfTa1Ih4PX0cVt8DgES -JljsFAMmsCueNcCg2omng3BbW68PgDRJOIYBMN41iZ9C1k2D8XqcC1b0IRVCnC2p -sxdb1mSuQngBERIIUjyGjuu6/ad07KRxUGZ5FpUfS881eUFQUwB5caC4XGI9ZWIq -lE/ghGqDXCumvGEKh175/HQAlfBdYm0qfglZGHqj9qyj/LpqiZg5olP+YtPQLLJ0 -aJIpAKpAMsHuZ1xXuzv6xbsPkiLOx10WJ6DP9mZBvDbcAXEB/cbqbRQg91kuqGQl -K7bvZBATQFasnKKQCCG2nxk6L+JdqmBPvUhLM6LJmhDGTWo+E+YLUze2B6GTlaOH -iwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key2.pem b/kms/singletenanthsm/challenges/public_key2.pem deleted file mode 100644 index 9bf091959fc..00000000000 --- a/kms/singletenanthsm/challenges/public_key2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlQpdePmUxR3fcHuAEMtx -DocciCBl3qJShrslieBUsVRJxxWPiWOS8eSBK5Y9eraOSPMtPL20g9E9rKByHsbs -qjxl1iT/S800Djpy6YUHK/lfByqA9KX9Ara5OrznPkJga60v09Wx6ZdXjFmTdQ6M -F80FHWchHk2ta93BuftCLQKongFzjo1XwYjFVIrgQmJn6bLIvSpe9KDccuFZRAnd -Fege2moJbhWdKLcIxpfZvbqV6MCRkdZd0sAUvOWejaFJY/rzk88W3ybs07ilw8tq -Dvgt6cHZw+6hZPLRmmRU6uAuHlobmjELTjJe6nfjVYqZBgPuDhpsuJlj0jNhO+Bf -WQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/challenges/public_key3.pem b/kms/singletenanthsm/challenges/public_key3.pem deleted file mode 100644 index aec2d678ece..00000000000 --- a/kms/singletenanthsm/challenges/public_key3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjZ9aCKUc73dDIJ+ylvOA -Ubh+W/PcLZGGlJEVs+D5W7zrI/bEDa/dMZSyGKt38OsVKHsAT4G8uuomRfmTVsqR -fPfx6uU95eopy6vww5O0/V5uS+VLAK/jHcx2I/zUINFy4Z/HCdU4QpsOrapLiePf -ziykWSnrE24vbHvG1wPUOr7ivcymxdUrNtsAWJQC5TGXpRmLYatzNmcASyWBOHNr -ebeTbQIbN+mSconp9WMC+6LyrGrgxpdPYczh95CMjUyf6jcXHGALnBQ6tKRx09uR -2fDPKd3L0YWZ0MoDCjTlyc0arv77/ZoBpUw/SJvXI3rHYgPt0slFScmoiRn+zwZj -BwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem b/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem deleted file mode 100644 index fce6bbe1b4d..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_25167010.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0r0zKnSu5mDm7IPraKQE -BFD3IAszvYo5krixQ0sq1Xo8UnP03cwYelLvvGWeKTzNL+dtRmv4zUcH/Aks/Z7O -gqRBPEjhIcS0pC+05wVrDShS5Y0x8E2wTyNwu24IQXkvlZNOUjyaLbXSyZIm9PmP -ux/Cq3FO3t7HSuW6mnUjWQYIfpdXvvr5Urtin4meiWFfYqcD0IwnYqgqRBtiRN/7 -kvdvxxZNw9PV4TeKFOKU2lKDPXldIEE7OLQVKQMtywYQ3lzUobtPvFh2Vr0uetcJ -UzF33yt0EUU8Vn5U1nwT9BzX7K8TiVlKFOXFc8tKkMMheyYc4qG9vadDAXejfYhN -OQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem b/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem deleted file mode 100644 index ad406c2f7ff..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_28787103.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6JVtst7rj/mhT6wCGweE -t2lIN8VnIlNu46zM2LD/woaNgKxUDTf3EXZNFck583r3piPDEbdQVDmBNfvWZc2C -65ZvuIlYIyRtFK2yJxALWzh1uz6m6V47aJGrxdlohHq/c/n4WzFvU5qgDz6etKTD -4ycQsyNzz5eo/JaXsCyTV0P+Q7GEdU/rgcJfZKpcr6xLF8qMjXstK/VASfwiCUOX -3K9ozRm8PcX6ZC783e7nej7UZh8vQfLw1QiPSPw/hVRZVf7UixepEUZOeh6244st -pwarM+8oFliFRDqEa7VqEr4+iy3cYSWhlCD19+1lyuL6BoE1wBcr6Vn0SuElMZtj -BQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem b/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem deleted file mode 100644 index 7a9cbd40f73..00000000000 --- a/kms/singletenanthsm/generated_public_keys/public_key_28787105.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0dslXmtDZbYWfviEo3if -B9b0Oj9lD46mCPPyLYvfjHlGel8FPYN5VbWplfk/f/w+a8rwMG6XPpOUZANNsU+P -LAmh/lw2uS0UXkZb/EGB09LOdNi5eGXKE5FeMobS/spni2MlKFM1YNkKhAf9iJ/b -oNctWcTazEq/GrNZMrqDqNwXPVPnsXXNcr9gMQVP2gSOifBUngM1lX5qz+6Z8Y8e -Tj5G22FcuBM3YezjePpoPx3dsALmALIK8z3KdJ6SilzuzJy2okehgvYlO6tMWJdY -4bG/kkZhpXKBiYn5Gx2tSdhT0ecBGm9SIh7Aj5sy9UUtPWfo84AOaJHiBeysAsgp -bQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_1.pem b/kms/singletenanthsm/signed_challenges/public_key_1.pem deleted file mode 100644 index 563ec0ae86c..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_1.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3QfTa1Ih4PX0cVt8DgES -JljsFAMmsCueNcCg2omng3BbW68PgDRJOIYBMN41iZ9C1k2D8XqcC1b0IRVCnC2p -sxdb1mSuQngBERIIUjyGjuu6/ad07KRxUGZ5FpUfS881eUFQUwB5caC4XGI9ZWIq -lE/ghGqDXCumvGEKh175/HQAlfBdYm0qfglZGHqj9qyj/LpqiZg5olP+YtPQLLJ0 -aJIpAKpAMsHuZ1xXuzv6xbsPkiLOx10WJ6DP9mZBvDbcAXEB/cbqbRQg91kuqGQl -K7bvZBATQFasnKKQCCG2nxk6L+JdqmBPvUhLM6LJmhDGTWo+E+YLUze2B6GTlaOH -iwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_2.pem b/kms/singletenanthsm/signed_challenges/public_key_2.pem deleted file mode 100644 index 9bf091959fc..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_2.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlQpdePmUxR3fcHuAEMtx -DocciCBl3qJShrslieBUsVRJxxWPiWOS8eSBK5Y9eraOSPMtPL20g9E9rKByHsbs -qjxl1iT/S800Djpy6YUHK/lfByqA9KX9Ara5OrznPkJga60v09Wx6ZdXjFmTdQ6M -F80FHWchHk2ta93BuftCLQKongFzjo1XwYjFVIrgQmJn6bLIvSpe9KDccuFZRAnd -Fege2moJbhWdKLcIxpfZvbqV6MCRkdZd0sAUvOWejaFJY/rzk88W3ybs07ilw8tq -Dvgt6cHZw+6hZPLRmmRU6uAuHlobmjELTjJe6nfjVYqZBgPuDhpsuJlj0jNhO+Bf -WQIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/public_key_3.pem b/kms/singletenanthsm/signed_challenges/public_key_3.pem deleted file mode 100644 index aec2d678ece..00000000000 --- a/kms/singletenanthsm/signed_challenges/public_key_3.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjZ9aCKUc73dDIJ+ylvOA -Ubh+W/PcLZGGlJEVs+D5W7zrI/bEDa/dMZSyGKt38OsVKHsAT4G8uuomRfmTVsqR -fPfx6uU95eopy6vww5O0/V5uS+VLAK/jHcx2I/zUINFy4Z/HCdU4QpsOrapLiePf -ziykWSnrE24vbHvG1wPUOr7ivcymxdUrNtsAWJQC5TGXpRmLYatzNmcASyWBOHNr -ebeTbQIbN+mSconp9WMC+6LyrGrgxpdPYczh95CMjUyf6jcXHGALnBQ6tKRx09uR -2fDPKd3L0YWZ0MoDCjTlyc0arv77/ZoBpUw/SJvXI3rHYgPt0slFScmoiRn+zwZj -BwIDAQAB ------END PUBLIC KEY----- diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge1.bin b/kms/singletenanthsm/signed_challenges/signed_challenge1.bin deleted file mode 100644 index b79ffd6fbf7da2d3bd03bada5cbfffea6c3743ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssCfm#Ym0BG54sH(qm{dtQ5eay;N&Oq`oM7jkLSv{6zS=Nc%Dqzys7{0 zcLJ7-#y{h_6F0$sif8|-!$4EH0bVFhg$;N90Yc39ZXSdPE{OAA)FCONz(8^!X%Kj@ z&6cozTA2=iCv4s+0qve#QI3ll^<;pC5Jr5e7Qy#W%xL%6i-?IK9v$(mJHswP&VZay}8a#CkIBjg1 z24)$jJcnZ>UHbGZOFNCg*HB6EAxah)12S0p^8UDlfpZqCx^Q?_IbpF{hCD$U{oDbyhus)%0%QSQf9B#eVCAMc z?E`0Sv|4WQs7`SNrm1`M-iCU}Dulv^Q%^ySq5c3&xq=*Dbaz)?%pp0tx~1{EqpB6i ztM9+VCsW$bo#SK>SRTTj$dGWjtC=~dB9=-WV|I%=@lTx&bE-ml-te&VLd4=+x%wFT z;Idm?C&iZdMQQ8bcuxhzQ3BMAxuDSQeBBRCv@#~&RX59=B`@pJa>Rij6H8UWu4u6) z&(Opb4DE1>uD0%BU#luGd*YeY$63e&*KRj1u`F`2ena3loyWJxtS%cXMdB3H)A99H GP2H06B84OX diff --git a/kms/singletenanthsm/signed_challenges/signed_challenge3.bin b/kms/singletenanthsm/signed_challenges/signed_challenge3.bin deleted file mode 100644 index fa4f63048c21bbc0c17c54a43f8f19163637a7a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssD6-Oxru!#^zTrBP3*o3#ub%v3c0`@IG>j{D!c2+pYV-%aJ07@QSNFM483 zfLe=1QW5hRf1ASVM!V=28uz`hV|n@vyM-Q1WQTcgTtA=3D~Yd}u|G|F;~Jv`XxIyB z!I{@Tr9~S<_U}%@UHWLT(wmr-M`2)jh5R9+HZVZvFQxSz0ORGVnNs0UH9(w(K13#T z>c(`cl)P1rspKhPeB%?kf+5w&srL-A(hwN|?RUL5oi4Ee9vPU$X*}~-$llRuM?)c8 zS!m)>jWDKMj6d>dd=pYYeP0=SuM@4YU-9Lq_v8SGJr++>CkR$=oD={_S=aP42m)1# GH8kQOpL Date: Wed, 7 May 2025 16:14:59 -0400 Subject: [PATCH 19/19] Remove install command for libpcsclite-dev from run_tests.sh --- .kokoro/tests/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index 6c7b20a08bb..dc01559a106 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -85,7 +85,7 @@ fi cd "${PROJECT_ROOT}" # # add libpcsclite -sudo apt install -q -y -s libpcsclite-dev +# sudo apt install -q -y -s libpcsclite-dev # add user's pip binary path to PATH export PATH="${HOME}/.local/bin:${PATH}"