From 5580f754bf65c532a40cc8c33aa413cf8048f7c0 Mon Sep 17 00:00:00 2001 From: Sayali Gaikawad Date: Wed, 16 Aug 2023 16:30:32 -0700 Subject: [PATCH] Add support for signing jar and taco files using jar_signer Signed-off-by: Sayali Gaikawad --- src/sign_workflow/sign_args.py | 2 +- src/sign_workflow/signer_jar.py | 53 +++++++++++++++++ src/sign_workflow/signers.py | 2 + tests/tests_sign_workflow/test_signer_jar.py | 61 ++++++++++++++++++++ tests/tests_sign_workflow/test_signers.py | 6 ++ 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/sign_workflow/signer_jar.py create mode 100644 tests/tests_sign_workflow/test_signer_jar.py diff --git a/src/sign_workflow/sign_args.py b/src/sign_workflow/sign_args.py index 22980aa66e..778beed4c8 100644 --- a/src/sign_workflow/sign_args.py +++ b/src/sign_workflow/sign_args.py @@ -13,7 +13,7 @@ class SignArgs: ACCEPTED_SIGNATURE_FILE_TYPES = [".sig", ".asc"] - ACCEPTED_PLATFORM = ["linux", "windows", "mac"] + ACCEPTED_PLATFORM = ["linux", "windows", "mac", "jar_signer"] target: Path components: List[str] diff --git a/src/sign_workflow/signer_jar.py b/src/sign_workflow/signer_jar.py new file mode 100644 index 0000000000..4ca80b8a1a --- /dev/null +++ b/src/sign_workflow/signer_jar.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. + +import os +from pathlib import Path + +from sign_workflow.signer import Signer + +""" +This class is responsible for signing jar and taco files using the OpenSearch-signer-client and verifying its signature. +""" + + +class SignerJar(Signer): + ACCEPTED_FILE_TYPES = [".jar", ".taco"] + + def generate_signature_and_verify(self, artifact: str, basepath: Path, signature_type: str) -> None: + filename = os.path.join(basepath, artifact) + signed_filename = filename if self.overwrite else os.path.join(basepath, "signed_" + artifact) + self.sign(artifact, basepath, signature_type) + self.verify(signed_filename) + + def is_valid_file_type(self, file_name: str) -> bool: + return any( + file_name.endswith(x) for x in SignerJar.ACCEPTED_FILE_TYPES + ) + + def sign(self, artifact: str, basepath: Path, signature_type: str) -> None: + filename = os.path.join(basepath, artifact) + signed_filename = filename if self.overwrite else os.path.join(basepath, "signed_" + artifact) + signing_cmd = [ + "./opensearch-signer-client", + "-i", + filename, + "-o", + signed_filename, + "-p", + "jar_signer", + "-r", + str(self.overwrite) + ] + self.git_repo.execute(" ".join(signing_cmd)) + + def verify(self, filename: str) -> None: + verify_cmd = ["jarsigner", "-verify", filename, "-verbose", "-certs", "-strict"] + signature = self.git_repo.output(" ".join(verify_cmd)) + if signature.find('jar verified') == -1: + raise ValueError(f"Cannot verify the signature for {filename}") diff --git a/src/sign_workflow/signers.py b/src/sign_workflow/signers.py index 4b566b87a6..88216e6f22 100644 --- a/src/sign_workflow/signers.py +++ b/src/sign_workflow/signers.py @@ -8,6 +8,7 @@ from sign_workflow.signer import Signer +from sign_workflow.signer_jar import SignerJar from sign_workflow.signer_mac import SignerMac from sign_workflow.signer_pgp import SignerPGP from sign_workflow.signer_windows import SignerWindows @@ -18,6 +19,7 @@ class Signers: "windows": SignerWindows, "linux": SignerPGP, "mac": SignerMac, + "jar_signer": SignerJar } @classmethod diff --git a/tests/tests_sign_workflow/test_signer_jar.py b/tests/tests_sign_workflow/test_signer_jar.py new file mode 100644 index 0000000000..7eba00e8dc --- /dev/null +++ b/tests/tests_sign_workflow/test_signer_jar.py @@ -0,0 +1,61 @@ +# Copyright OpenSearch Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. + + +import os +import unittest +from pathlib import Path +from unittest.mock import MagicMock, Mock, call, patch + +from sign_workflow.signer_jar import SignerJar + + +class TestSignerJar(unittest.TestCase): + + @patch("sign_workflow.signer.GitRepository") + def test_accepted_file_types(self, git_repo: Mock) -> None: + artifacts = [ + "the-msi.msi", + "the-zip.zip", + "the-jar.jar", + "the-taco.taco", + "the-sys.sys", + "something-1.0.0.0.jar", + ] + expected = [ + call("the-jar.jar", Path("path"), 'null'), + call("the-taco.taco", Path("path"), 'null'), + call("something-1.0.0.0.jar", Path("path"), 'null') + ] + signer = SignerJar(True) + signer.sign = MagicMock() # type: ignore + signer.sign_artifacts(artifacts, Path("path"), 'null') + self.assertEqual(signer.sign.call_args_list, expected) + + @patch("sign_workflow.signer.GitRepository") + @patch('os.rename') + @patch('os.mkdir') + def test_signer_sign(self, mock_os_mkdir: Mock, mock_os_rename: Mock, mock_repo: Mock) -> None: + signer = SignerJar(False) + signer.sign("the-jar.jar", Path("/path/"), "null") + command = "./opensearch-signer-client -i " + os.path.join(Path("/path/"), 'the-jar.jar') + " -o " + os.path.join(Path("/path/"), 'signed_the-jar.jar') + " -p jar_signer -r False" + mock_repo.assert_has_calls( + [call().execute(command)]) + + @patch("sign_workflow.signer.GitRepository") + def test_sign_command_for_overwrite(self, mock_repo: Mock) -> None: + signer = SignerJar(True) + signer.sign("the-taco.taco", Path("/path/"), 'null') + command = "./opensearch-signer-client -i " + os.path.join(Path("/path/"), 'the-taco.taco') + " -o " + os.path.join(Path("/path/"), 'the-taco.taco') + " -p jar_signer" + " -r True" + mock_repo.assert_has_calls( + [call().execute(command)]) + + @patch("sign_workflow.signer.GitRepository") + def test_signer_verify(self, mock_repo: Mock) -> None: + signer = SignerJar(True) + signer.verify('/path/the-jar.jar') + mock_repo.assert_has_calls([call().output("jarsigner -verify /path/the-jar.jar -verbose -certs -strict")]) diff --git a/tests/tests_sign_workflow/test_signers.py b/tests/tests_sign_workflow/test_signers.py index 1c28f4d807..492d106c0d 100644 --- a/tests/tests_sign_workflow/test_signers.py +++ b/tests/tests_sign_workflow/test_signers.py @@ -8,6 +8,7 @@ import unittest from unittest.mock import Mock, patch +from sign_workflow.signer_jar import SignerJar from sign_workflow.signer_mac import SignerMac from sign_workflow.signer_pgp import SignerPGP from sign_workflow.signer_windows import SignerWindows @@ -31,6 +32,11 @@ def test_signer_macos(self, mock_repo: Mock) -> None: signer = Signers.create("mac", True) self.assertIs(type(signer), SignerMac) + @patch("sign_workflow.signer.GitRepository") + def test_signer_jar(self, mock_repo: Mock) -> None: + signer = Signers.create("jar_signer", True) + self.assertIs(type(signer), SignerJar) + def test_signer_invalid(self) -> None: with self.assertRaises(ValueError) as ctx: Signers.create("java", False)