diff --git a/linter_exclusions.yml b/linter_exclusions.yml index 81937685407..d52d538627f 100644 --- a/linter_exclusions.yml +++ b/linter_exclusions.yml @@ -3442,3 +3442,15 @@ neon postgres organization: neon postgres project: rule_exclusions: - require_wait_command_if_no_wait + +confcom fragment push: + parameters: + signed_fragment: + rule_exclusions: + - no_positional_parameters + +confcom fragment attach: + parameters: + signed_fragment: + rule_exclusions: + - no_positional_parameters diff --git a/src/confcom/HISTORY.rst b/src/confcom/HISTORY.rst index f45806e4666..29ccb170edb 100644 --- a/src/confcom/HISTORY.rst +++ b/src/confcom/HISTORY.rst @@ -3,6 +3,12 @@ Release History =============== +1.4.0 +++++++ +* restored the behaviour of --upload-fragment in acifragmentgen to attach to first image in input +* added `fragment push` command to allow explicit uploading of standalone fragments +* added `fragment attach` command to allow explicit uploading of image attached fragments + 1.3.0 ++++++ * Add a new --enable-stdio flag, with a warning if neither this or --disable-stdio is set diff --git a/src/confcom/azext_confcom/_help.py b/src/confcom/azext_confcom/_help.py index 15368cc61db..9817bef723e 100644 --- a/src/confcom/azext_confcom/_help.py +++ b/src/confcom/azext_confcom/_help.py @@ -278,3 +278,46 @@ - name: Input a Kubernetes YAML file with a custom containerd socket path text: az confcom katapolicygen --yaml "./pod.json" --containerd-pull --containerd-socket-path "/my/custom/containerd.sock" """ + +helps[ + "confcom fragment" +] = """ + type: group + short-summary: Commands to handle Confidential Container Policy Fragments. +""" + +helps[ + "confcom fragment push" +] = """ + type: command + short-summary: Push a Confidential Container Policy Fragment to an ORAS registry + + parameters: + - name: --manifest-tag + type: string + short-summary: 'The reference to push the signed fragment to' + + examples: + - name: Push a signed fragment to a registry + text: az confcom fragment push ./fragment.reg.cose --manifest-tag myregistry.azurecr.io/fragment:latest + - name: Push the output of acifragmentgen to a registry + text: az confcom acifragmentgen --chain my.cert.pem --key my_key.pem --svn "1" --namespace contoso --feed "test-feed" --input ./fragment_spec.json | az confcom fragment push --manifest-tag myregistry.azurecr.io/fragment:latest +""" + +helps[ + "confcom fragment attach" +] = """ + type: command + short-summary: Attach a Confidential Container Policy Fragment to an image in an ORAS registry. + + parameters: + - name: --manifest-tag + type: string + short-summary: 'The reference to attach the signed fragment to' + + examples: + - name: Attach a signed fragment to a registry + text: az confcom fragment attach ./fragment.reg.cose --manifest-tag myregistry.azurecr.io/image:latest + - name: Attach the output of acifragmentgen to a registry + text: az confcom acifragmentgen --chain my.cert.pem --key my_key.pem --svn "1" --namespace contoso --feed "test-feed" --input ./fragment_spec.json | az confcom fragment attach --manifest-tag myregistry.azurecr.io/image:latest +""" diff --git a/src/confcom/azext_confcom/_params.py b/src/confcom/azext_confcom/_params.py index 08bf87e28fc..78bab28be73 100644 --- a/src/confcom/azext_confcom/_params.py +++ b/src/confcom/azext_confcom/_params.py @@ -4,6 +4,8 @@ # -------------------------------------------------------------------------------------------- # pylint: disable=line-too-long +import argparse +import sys from knack.arguments import CLIArgumentType from azext_confcom._validators import ( validate_params_file, @@ -43,6 +45,32 @@ def load_arguments(self, _): c.argument("tags", tags_type) c.argument("confcom_name", confcom_name_type, options_list=["--name", "-n"]) + with self.argument_context("confcom fragment attach") as c: + c.positional( + "signed_fragment", + nargs='?', + type=argparse.FileType('rb'), + default=sys.stdin.buffer, + help="Signed fragment to attach", + ) + c.argument( + "manifest_tag", + help="Manifest tag for the fragment", + ) + + with self.argument_context("confcom fragment push") as c: + c.positional( + "signed_fragment", + nargs='?', + type=argparse.FileType('rb'), + default=sys.stdin.buffer, + help="Signed fragment to push", + ) + c.argument( + "manifest_tag", + help="Manifest tag for the fragment", + ) + with self.argument_context("confcom acipolicygen") as c: c.argument( "input_path", @@ -345,6 +373,13 @@ def load_arguments(self, _): help="Path to JSON file to write fragment import information. This is used with --generate-import. If not specified, the import statement will print to the console", validator=validate_fragment_json, ) + c.argument( + "out_signed_fragment", + action="store_true", + default=False, + required=False, + help="Emit only the signed fragment bytes", + ) with self.argument_context("confcom katapolicygen") as c: c.argument( diff --git a/src/confcom/azext_confcom/command/fragment_attach.py b/src/confcom/azext_confcom/command/fragment_attach.py new file mode 100644 index 00000000000..39f29ae48da --- /dev/null +++ b/src/confcom/azext_confcom/command/fragment_attach.py @@ -0,0 +1,46 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import subprocess +import tempfile +from typing import BinaryIO + + +def oras_attach( + signed_fragment: BinaryIO, + manifest_tag: str, +) -> None: + subprocess.run( + [ + "oras", + "attach", + "--artifact-type", "application/x-ms-ccepolicy-frag", + manifest_tag, + os.path.relpath(signed_fragment.name, start=os.getcwd()), + ], + check=True, + timeout=120, + ) + + +def fragment_attach( + signed_fragment: BinaryIO, + manifest_tag: str, +) -> None: + + if signed_fragment.name == "": + with tempfile.NamedTemporaryFile(delete=True) as temp_signed_fragment: + temp_signed_fragment.write(signed_fragment.read()) + temp_signed_fragment.flush() + oras_attach( + signed_fragment=temp_signed_fragment, + manifest_tag=manifest_tag, + ) + else: + oras_attach( + signed_fragment=signed_fragment, + manifest_tag=manifest_tag, + ) diff --git a/src/confcom/azext_confcom/command/fragment_push.py b/src/confcom/azext_confcom/command/fragment_push.py new file mode 100644 index 00000000000..89912c87637 --- /dev/null +++ b/src/confcom/azext_confcom/command/fragment_push.py @@ -0,0 +1,46 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import subprocess +import tempfile +from typing import BinaryIO + + +def oras_push( + signed_fragment: BinaryIO, + manifest_tag: str, +) -> None: + subprocess.run( + [ + "oras", + "push", + "--artifact-type", "application/x-ms-ccepolicy-frag", + manifest_tag, + os.path.relpath(signed_fragment.name, start=os.getcwd()), + ], + check=True, + timeout=120, + ) + + +def fragment_push( + signed_fragment: BinaryIO, + manifest_tag: str, +) -> None: + + if signed_fragment.name == "": + with tempfile.NamedTemporaryFile(delete=True) as temp_signed_fragment: + temp_signed_fragment.write(signed_fragment.read()) + temp_signed_fragment.flush() + oras_push( + signed_fragment=temp_signed_fragment, + manifest_tag=manifest_tag, + ) + else: + oras_push( + signed_fragment=signed_fragment, + manifest_tag=manifest_tag, + ) diff --git a/src/confcom/azext_confcom/commands.py b/src/confcom/azext_confcom/commands.py index 1d2bb45f724..7e1e93eabca 100644 --- a/src/confcom/azext_confcom/commands.py +++ b/src/confcom/azext_confcom/commands.py @@ -11,5 +11,9 @@ def load_command_table(self, _): g.custom_command("acifragmentgen", "acifragmentgen_confcom") g.custom_command("katapolicygen", "katapolicygen_confcom") + with self.command_group("confcom fragment") as g: + g.custom_command("attach", "fragment_attach", is_preview=True) + g.custom_command("push", "fragment_push", is_preview=True) + with self.command_group("confcom"): pass diff --git a/src/confcom/azext_confcom/custom.py b/src/confcom/azext_confcom/custom.py index acd513f2038..87ee93388d1 100644 --- a/src/confcom/azext_confcom/custom.py +++ b/src/confcom/azext_confcom/custom.py @@ -5,7 +5,7 @@ import os import sys -from typing import Optional +from typing import BinaryIO, Optional from azext_confcom import oras_proxy, os_util, security_policy from azext_confcom._validators import resolve_stdio @@ -22,6 +22,8 @@ get_image_name, inject_policy_into_template, inject_policy_into_yaml, pretty_print_func, print_existing_policy_from_arm_template, print_existing_policy_from_yaml, print_func, str_to_sha256) +from azext_confcom.command.fragment_attach import fragment_attach as _fragment_attach +from azext_confcom.command.fragment_push import fragment_push as _fragment_push from knack.log import get_logger from pkg_resources import parse_version @@ -240,6 +242,7 @@ def acifragmentgen_confcom( upload_fragment: bool = False, no_print: bool = False, fragments_json: str = "", + out_signed_fragment: bool = False, ): stdio_enabled = resolve_stdio(enable_stdio, disable_stdio) @@ -330,12 +333,16 @@ def acifragmentgen_confcom( fragment_text = policy.generate_fragment(namespace, svn, output_type, omit_id=omit_id) - if output_type != security_policy.OutputType.DEFAULT and not no_print: + if output_type != security_policy.OutputType.DEFAULT and not no_print and not out_signed_fragment: print(fragment_text) # take ".rego" off the end of the filename if it's there, it'll get added back later output_filename = output_filename.replace(".rego", "") filename = f"{output_filename or namespace}.rego" + + if out_signed_fragment: + filename = os.path.join("/tmp", filename) + os_util.write_str_to_file(filename, fragment_text) if key: @@ -343,11 +350,22 @@ def acifragmentgen_confcom( iss = cose_proxy.create_issuer(chain) out_path = filename + ".cose" + if out_signed_fragment: + out_path = os.path.join("/tmp", os.path.basename(out_path)) + cose_proxy.cose_sign(filename, key, chain, feed, iss, algo, out_path) - if upload_fragment and image_target: - oras_proxy.attach_fragment_to_image(image_target, out_path) - elif upload_fragment: - oras_proxy.push_fragment_to_registry(feed, out_path) + + # Preserve default behaviour established since version 1.1.0 of attaching + # the fragment to the first image specified in input + # (or --image-target if specified) + if upload_fragment: + oras_proxy.attach_fragment_to_image( + image_name=image_target or policy_images[0].containerImage, + filename=out_path, + ) + + if out_signed_fragment: + sys.stdout.buffer.write(open(out_path, "rb").read()) def katapolicygen_confcom( @@ -481,3 +499,23 @@ def get_fragment_output_type(outraw): if outraw: output_type = security_policy.OutputType.RAW return output_type + + +def fragment_attach( + signed_fragment: BinaryIO, + manifest_tag: str, +) -> None: + _fragment_attach( + signed_fragment=signed_fragment, + manifest_tag=manifest_tag + ) + + +def fragment_push( + signed_fragment: BinaryIO, + manifest_tag: str, +) -> None: + _fragment_push( + signed_fragment=signed_fragment, + manifest_tag=manifest_tag + ) diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_acifragmentgen.py b/src/confcom/azext_confcom/tests/latest/test_confcom_acifragmentgen.py new file mode 100644 index 00000000000..5426e8ec707 --- /dev/null +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_acifragmentgen.py @@ -0,0 +1,252 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import contextlib +import io +import json +import os +import subprocess +import tempfile +import pytest + +from azext_confcom.custom import acifragmentgen_confcom, fragment_push, fragment_attach + +TEST_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), "..")) +SAMPLES_DIR = os.path.abspath(os.path.join(TEST_DIR, "..", "..", "..", "samples")) + + +@pytest.fixture() +def docker_image(): + + registry_id = subprocess.run( + ["docker", "run", "-d", "-p", "0:5000", "registry:2"], + stdout=subprocess.PIPE, + text=True, + ).stdout + + registry_port = subprocess.run( + ["docker", "port", registry_id], + stdout=subprocess.PIPE, + text=True, + ).stdout.split(":")[-1].strip() + + test_container_ref = f"localhost:{registry_port}/hello-world:latest" + subprocess.run(["docker", "pull", "hello-world"]) + subprocess.run(["docker", "tag", "hello-world", test_container_ref]) + subprocess.run(["docker", "push", test_container_ref]) + + with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8", delete=True) as temp_file: + json.dump({ + "version": "1.0.0", + "containers": [ + { + "name": "hello-world", + "properties": { + "image": test_container_ref, + }, + } + ] + }, temp_file) + temp_file.flush() + + yield test_container_ref, temp_file.name + + subprocess.run(["docker", "stop", registry_id]) + + +@pytest.fixture(scope="session") +def cert_chain(): + with tempfile.TemporaryDirectory() as temp_dir: + subprocess.run( + [ + os.path.join(SAMPLES_DIR, "certs", "create_certchain.sh"), + temp_dir + ], + check=True, + ) + yield temp_dir + + +def test_acifragmentgen_fragment_gen(docker_image): + + image_ref, spec_file_path = docker_image + + with tempfile.TemporaryDirectory() as temp_dir: # Prevent test writing files to repo + acifragmentgen_confcom( + image_name=None, + tar_mapping_location=None, + key=None, + chain=None, + minimum_svn=None, + input_path=spec_file_path, + svn="1", + namespace="contoso", + feed="test-feed", + outraw=True, + output_filename=os.path.join(temp_dir, "fragment.rego"), + out_signed_fragment=False, + ) + + # TODO: Implement a proper validation for the fragment, this is hard + # because each test run will have a unique image to have unique local + # registries on different ports + + +def test_acifragmentgen_fragment_sign(docker_image, cert_chain): + + image_ref, spec_file_path = docker_image + + with tempfile.TemporaryDirectory() as temp_dir: # Prevent test writing files to repo + acifragmentgen_confcom( + image_name=None, + tar_mapping_location=None, + key=os.path.join(cert_chain, "intermediateCA", "private", "ec_p384_private.pem"), + chain=os.path.join(cert_chain, "intermediateCA", "certs", "www.contoso.com.chain.cert.pem"), + minimum_svn=None, + input_path=spec_file_path, + svn="1", + namespace="contoso", + feed="test-feed", + outraw=True, + output_filename=os.path.join(temp_dir, "fragment.rego"), + out_signed_fragment=False, + ) + + # TODO: Implement a proper validation for the cose document + + +def test_acifragmentgen_fragment_upload_fragment(docker_image, cert_chain): + + image_ref, spec_file_path = docker_image + + with tempfile.TemporaryDirectory() as temp_dir: # Prevent test writing files to repo + acifragmentgen_confcom( + image_name=None, + tar_mapping_location=None, + key=os.path.join(cert_chain, "intermediateCA", "private", "ec_p384_private.pem"), + chain=os.path.join(cert_chain, "intermediateCA", "certs", "www.contoso.com.chain.cert.pem"), + minimum_svn=None, + input_path=spec_file_path, + svn="1", + namespace="contoso", + feed="test-feed", + outraw=True, + upload_fragment=True, + output_filename=os.path.relpath(os.path.join(temp_dir, "fragment.rego"), os.getcwd()), # Must be relative for oras + out_signed_fragment=False, + ) + + # Confirm the fragment exists and is attached in the registry + oras_result = json.loads(subprocess.run( + ["oras", "discover", image_ref, "--format", "json"], + stdout=subprocess.PIPE, + check=True, + ).stdout) + + if "referrers" in oras_result: + fragment_ref = oras_result["referrers"][0]["reference"] + elif oras_result.get("manifests")[0].get("artifactType") == "application/x-ms-ccepolicy-frag": + fragment_ref = oras_result["manifests"][0]["reference"] + else: + raise AssertionError(f"{oras_result=}") + + fragment_path = json.loads(subprocess.run( + ["oras", "pull", fragment_ref, "--format", "json", "-o", "/tmp"], + check=True, + stdout=subprocess.PIPE, + ).stdout)["files"][0]["path"] + + + with open(fragment_path, "rb") as actual_fragment_file: + with open(os.path.join(temp_dir, "fragment.rego.cose"), "rb") as expected_fragment_file: + assert actual_fragment_file.read() == expected_fragment_file.read() + + +def test_acifragmentgen_fragment_push(docker_image, cert_chain, capsysbinary): + + image_ref, spec_file_path = docker_image + fragment_ref = image_ref.replace("hello-world", "fragment") + + acifragmentgen_confcom( + image_name=None, + tar_mapping_location=None, + key=os.path.join(cert_chain, "intermediateCA", "private", "ec_p384_private.pem"), + chain=os.path.join(cert_chain, "intermediateCA", "certs", "www.contoso.com.chain.cert.pem"), + minimum_svn=None, + input_path=spec_file_path, + svn="1", + namespace="contoso", + feed="test-feed", + out_signed_fragment=True, + ) + + signed_fragment = capsysbinary.readouterr()[0] + signed_fragment_io = io.BytesIO(signed_fragment) + signed_fragment_io.name = "" + + fragment_push( + signed_fragment=signed_fragment_io, + manifest_tag=fragment_ref, + ) + + # Confirm the fragment exists in the registry + fragment_path = json.loads(subprocess.run( + ["oras", "pull", fragment_ref, "--format", "json", "-o", "/tmp"], + check=True, + stdout=subprocess.PIPE, + ).stdout)["files"][0]["path"] + + with open(fragment_path, "rb") as f: + assert f.read() == signed_fragment + + +def test_acifragmentgen_fragment_attach(docker_image, cert_chain, capsysbinary): + + image_ref, spec_file_path = docker_image + + acifragmentgen_confcom( + image_name=None, + tar_mapping_location=None, + key=os.path.join(cert_chain, "intermediateCA", "private", "ec_p384_private.pem"), + chain=os.path.join(cert_chain, "intermediateCA", "certs", "www.contoso.com.chain.cert.pem"), + minimum_svn=None, + input_path=spec_file_path, + svn="1", + namespace="contoso", + feed="test-feed", + out_signed_fragment=True, + ) + + signed_fragment = capsysbinary.readouterr()[0] + signed_fragment_io = io.BytesIO(signed_fragment) + signed_fragment_io.name = "" + + fragment_attach( + signed_fragment=signed_fragment_io, + manifest_tag=image_ref, + ) + + # Confirm the fragment exists and is attached in the registry + oras_result = json.loads(subprocess.run( + ["oras", "discover", image_ref, "--format", "json"], + stdout=subprocess.PIPE, + check=True, + ).stdout) + + if "referrers" in oras_result: + fragment_ref = oras_result["referrers"][0]["reference"] + elif oras_result["manifests"][0].get("artifactType") == "application/x-ms-ccepolicy-frag": + fragment_ref = oras_result["manifests"][0]["reference"] + else: + raise AssertionError(f"{oras_result=}") + + fragment_path = json.loads(subprocess.run( + ["oras", "pull", fragment_ref, "--format", "json", "-o", "/tmp"], + check=True, + stdout=subprocess.PIPE, + ).stdout)["files"][0]["path"] + + with open(fragment_path, "rb") as f: + assert f.read() == signed_fragment diff --git a/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py b/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py index 2725ede31c0..24402abf7f2 100644 --- a/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py +++ b/src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py @@ -493,7 +493,7 @@ def test_tar_file_fragment(self): try: with tempfile.TemporaryDirectory() as folder: filename = os.path.join(folder, "oci.tar") - filename2 = os.path.join(self.path, "oci2.tar") + filename2 = os.path.join(folder, "oci2.tar") tar_mapping_file = {"mcr.microsoft.com/aks/e2e/library-busybox:master.220314.1-linux-amd64": filename2} create_tar_file(filename) diff --git a/src/confcom/samples/certs/create_certchain.sh b/src/confcom/samples/certs/create_certchain.sh index 5e94f4c6f4e..48575efef3f 100755 --- a/src/confcom/samples/certs/create_certchain.sh +++ b/src/confcom/samples/certs/create_certchain.sh @@ -3,87 +3,91 @@ OriginalPath=`pwd` RootPath=`realpath $(dirname $0)` -cd $RootPath +OutPath=${1:-$RootPath} + +mkdir -p $OutPath + +cd $OutPath # create dirs for root CA -mkdir -p $RootPath/rootCA/{certs,crl,newcerts,private,csr} -mkdir -p $RootPath/intermediateCA/{certs,crl,newcerts,private,csr} +mkdir -p $OutPath/rootCA/{certs,crl,newcerts,private,csr} +mkdir -p $OutPath/intermediateCA/{certs,crl,newcerts,private,csr} # create index files -echo 1000 > $RootPath/rootCA/serial -echo 1000 > $RootPath/intermediateCA/serial +echo 1000 > $OutPath/rootCA/serial +echo 1000 > $OutPath/intermediateCA/serial # create crlnumbers -echo 0100 > $RootPath/rootCA/crlnumber -echo 0100 > $RootPath/intermediateCA/crlnumber +echo 0100 > $OutPath/rootCA/crlnumber +echo 0100 > $OutPath/intermediateCA/crlnumber # create index files -touch $RootPath/rootCA/index.txt -touch $RootPath/intermediateCA/index.txt +touch $OutPath/rootCA/index.txt +touch $OutPath/intermediateCA/index.txt # NOTE: needed for testing -echo "unique_subject = no" >> $RootPath/rootCA/index.txt.attr -echo "unique_subject = no" >> $RootPath/intermediateCA/index.txt.attr +echo "unique_subject = no" >> $OutPath/rootCA/index.txt.attr +echo "unique_subject = no" >> $OutPath/intermediateCA/index.txt.attr # generate root key -openssl genrsa -out $RootPath/rootCA/private/ca.key.pem 4096 -chmod 400 $RootPath/rootCA/private/ca.key.pem +openssl genrsa -out $OutPath/rootCA/private/ca.key.pem 4096 +chmod 400 $OutPath/rootCA/private/ca.key.pem # view the key -# openssl rsa -noout -text -in $RootPath/rootCA/private/ca.key.pem +# openssl rsa -noout -text -in $OutPath/rootCA/private/ca.key.pem # generate root cert -openssl req -config openssl_root.cnf -key $RootPath/rootCA/private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out $RootPath/rootCA/certs/ca.cert.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Microsoft/OU=ACCCT/CN=Root CA" +openssl req -config $RootPath/openssl_root.cnf -key $OutPath/rootCA/private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out $OutPath/rootCA/certs/ca.cert.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Microsoft/OU=ACCCT/CN=Root CA" # change permissions on root key so it's not globally readable -chmod 644 $RootPath/rootCA/certs/ca.cert.pem +chmod 644 $OutPath/rootCA/certs/ca.cert.pem # verify root cert -openssl x509 -noout -text -in $RootPath/rootCA/certs/ca.cert.pem +openssl x509 -noout -text -in $OutPath/rootCA/certs/ca.cert.pem # generate intermediate key -openssl genrsa -out $RootPath/intermediateCA/private/intermediate.key.pem 4096 -chmod 600 $RootPath/intermediateCA/private/intermediate.key.pem +openssl genrsa -out $OutPath/intermediateCA/private/intermediate.key.pem 4096 +chmod 600 $OutPath/intermediateCA/private/intermediate.key.pem # make CSR for intermediate -openssl req -config openssl_intermediate.cnf -key $RootPath/intermediateCA/private/intermediate.key.pem -new -sha256 -out $RootPath/intermediateCA/certs/intermediate.csr.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Microsoft/OU=ACCCT/CN=Intermediate CA" +openssl req -config $RootPath/openssl_intermediate.cnf -key $OutPath/intermediateCA/private/intermediate.key.pem -new -sha256 -out $OutPath/intermediateCA/certs/intermediate.csr.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Microsoft/OU=ACCCT/CN=Intermediate CA" # sign intermediate cert with root -openssl ca -config openssl_root.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in $RootPath/intermediateCA/certs/intermediate.csr.pem -out $RootPath/intermediateCA/certs/intermediate.cert.pem -batch +openssl ca -config $RootPath/openssl_root.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in $OutPath/intermediateCA/certs/intermediate.csr.pem -out $OutPath/intermediateCA/certs/intermediate.cert.pem -batch # make it readable by everyone -chmod 644 $RootPath/intermediateCA/certs/intermediate.cert.pem +chmod 644 $OutPath/intermediateCA/certs/intermediate.cert.pem # print the cert -# openssl x509 -noout -text -in $RootPath/intermediateCA/certs/intermediate.cert.pem +# openssl x509 -noout -text -in $OutPath/intermediateCA/certs/intermediate.cert.pem # verify intermediate cert -openssl verify -CAfile $RootPath/rootCA/certs/ca.cert.pem $RootPath/intermediateCA/certs/intermediate.cert.pem +openssl verify -CAfile $OutPath/rootCA/certs/ca.cert.pem $OutPath/intermediateCA/certs/intermediate.cert.pem # create chain file -cat $RootPath/intermediateCA/certs/intermediate.cert.pem $RootPath/rootCA/certs/ca.cert.pem > $RootPath/intermediateCA/certs/ca-chain.cert.pem +cat $OutPath/intermediateCA/certs/intermediate.cert.pem $OutPath/rootCA/certs/ca.cert.pem > $OutPath/intermediateCA/certs/ca-chain.cert.pem # verify chain -openssl verify -CAfile $RootPath/intermediateCA/certs/ca-chain.cert.pem $RootPath/intermediateCA/certs/intermediate.cert.pem +openssl verify -CAfile $OutPath/intermediateCA/certs/ca-chain.cert.pem $OutPath/intermediateCA/certs/intermediate.cert.pem # create server key -openssl ecparam -out $RootPath/intermediateCA/private/www.contoso.com.key.pem -name secp384r1 -genkey -openssl pkcs8 -topk8 -nocrypt -in $RootPath/intermediateCA/private/www.contoso.com.key.pem -out $RootPath/intermediateCA/private/ec_p384_private.pem +openssl ecparam -out $OutPath/intermediateCA/private/www.contoso.com.key.pem -name secp384r1 -genkey +openssl pkcs8 -topk8 -nocrypt -in $OutPath/intermediateCA/private/www.contoso.com.key.pem -out $OutPath/intermediateCA/private/ec_p384_private.pem -chmod 600 $RootPath/intermediateCA/private/www.contoso.com.key.pem +chmod 600 $OutPath/intermediateCA/private/www.contoso.com.key.pem # create csr for server -openssl req -config openssl_intermediate.cnf -key $RootPath/intermediateCA/private/www.contoso.com.key.pem -new -sha384 -out $RootPath/intermediateCA/csr/www.contoso.com.csr.pem -batch +openssl req -config $RootPath/openssl_intermediate.cnf -key $OutPath/intermediateCA/private/www.contoso.com.key.pem -new -sha384 -out $OutPath/intermediateCA/csr/www.contoso.com.csr.pem -batch # sign server cert with intermediate key -openssl ca -config openssl_intermediate.cnf -extensions server_cert -days 375 -notext -md sha384 -in $RootPath/intermediateCA/csr/www.contoso.com.csr.pem -out $RootPath/intermediateCA/certs/www.contoso.com.cert.pem -batch +openssl ca -config $RootPath/openssl_intermediate.cnf -extensions server_cert -days 375 -notext -md sha384 -in $OutPath/intermediateCA/csr/www.contoso.com.csr.pem -out $OutPath/intermediateCA/certs/www.contoso.com.cert.pem -batch # print the cert -# openssl x509 -noout -text -in $RootPath/intermediateCA/certs/www.contoso.com.cert.pem +# openssl x509 -noout -text -in $OutPath/intermediateCA/certs/www.contoso.com.cert.pem # make a public key -# openssl x509 -pubkey -noout -in $RootPath/intermediateCA/certs/www.contoso.com.cert.pem -out $RootPath/intermediateCA/certs/pubkey.pem +# openssl x509 -pubkey -noout -in $OutPath/intermediateCA/certs/www.contoso.com.cert.pem -out $OutPath/intermediateCA/certs/pubkey.pem # create chain file -cat $RootPath/intermediateCA/certs/www.contoso.com.cert.pem $RootPath/intermediateCA/certs/intermediate.cert.pem $RootPath/rootCA/certs/ca.cert.pem > $RootPath/intermediateCA/certs/www.contoso.com.chain.cert.pem +cat $OutPath/intermediateCA/certs/www.contoso.com.cert.pem $OutPath/intermediateCA/certs/intermediate.cert.pem $OutPath/rootCA/certs/ca.cert.pem > $OutPath/intermediateCA/certs/www.contoso.com.chain.cert.pem cd $OriginalPath \ No newline at end of file