{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Multi-Region IoT - Create the private CA in ACM\n", "\n", "This notebook let's you create a private CA which will later on being registered in the master and slave region." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Libraries" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from OpenSSL import crypto, SSL\n", "from os.path import exists, join\n", "from os import makedirs\n", "from shutil import copy\n", "from time import time, gmtime, localtime, strftime\n", "import boto3\n", "import json\n", "import time" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Some Handy Functions\n", "Functions to create key, certificate signing requests and certificates." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def createCertRequest(pkey, subject, digest=\"sha256\"):\n", " print(\"subject: {}\".format(subject))\n", " req = crypto.X509Req()\n", " subj = req.get_subject()\n", " \n", " for i in ['C', 'ST', 'L', 'O', 'OU', 'CN']:\n", " if i in subject:\n", " setattr(subj, i, subject[i])\n", "\n", " req.set_pubkey(pkey)\n", " req.sign(pkey, digest)\n", " return req\n", "\n", "\n", "def createCertificate(req, issuerCertKey, serial, validityPeriod,\n", " digest=\"sha256\", createca=False, intermediate=False):\n", "\n", " issuerCert, issuerKey = issuerCertKey\n", " notBefore, notAfter = validityPeriod\n", " cert = crypto.X509()\n", " cert.set_version(2)\n", " cert.set_serial_number(serial)\n", " cert.gmtime_adj_notBefore(notBefore)\n", " cert.gmtime_adj_notAfter(notAfter)\n", " cert.set_issuer(issuerCert.get_subject())\n", " cert.set_subject(req.get_subject())\n", " cert.set_pubkey(req.get_pubkey())\n", "\n", " if createca:\n", " if intermediate:\n", " cert.add_extensions([\n", " crypto.X509Extension(b\"basicConstraints\", False, b\"critical,CA:TRUE,pathlen:0\"),\n", " crypto.X509Extension(b\"subjectKeyIdentifier\", False, b\"hash\", subject=cert),\n", " crypto.X509Extension(b\"authorityKeyIdentifier\", False, b\"keyid:always,issuer\", issuer=issuerCert)\n", " ]) \n", " else:\n", " cert.add_extensions([\n", " crypto.X509Extension(b\"basicConstraints\", False, b\"CA:TRUE\"),\n", " crypto.X509Extension(b\"subjectKeyIdentifier\", False, b\"hash\", subject=cert),\n", " ])\n", " cert.add_extensions([\n", " crypto.X509Extension(b\"authorityKeyIdentifier\", False, b\"keyid:always,issuer:always\", issuer=cert)\n", " ])\n", " else:\n", " cert.add_extensions([\n", " crypto.X509Extension(b\"subjectKeyIdentifier\", False, b\"hash\", subject=cert),\n", " crypto.X509Extension(b\"authorityKeyIdentifier\", False, b\"keyid:always,issuer:always\", issuer=issuerCert)\n", " ])\n", "\n", " cert.sign(issuerKey, digest)\n", " return cert\n", "\n", "\n", "def load_ca(cert_dir, cert_file, key_file):\n", " cacert = crypto.load_certificate(crypto.FILETYPE_PEM,\n", " open(join(cert_dir, cert_file), \"r\").read())\n", " cakey = crypto.load_privatekey(crypto.FILETYPE_PEM,\n", " open(join(cert_dir, key_file), \"r\").read())\n", " return cacert, cakey\n", "\n", "\n", "def create_ca(cert_dir, cert_file, key_file, subject, duration):\n", "\n", " if exists(join(cert_dir, cert_file)) and exists(join(cert_dir, key_file)):\n", " print(\"CA {} exists\".format(subject['CN']))\n", " return load_ca(cert_dir, cert_file, key_file)\n", "\n", " if not exists(cert_dir):\n", " print(\"creating directory: {}\".format(cert_dir))\n", " makedirs(cert_dir)\n", "\n", " print(\"Create CA {}\".format(subject['CN']))\n", " cakey = crypto.PKey()\n", " cakey.generate_key(crypto.TYPE_RSA, 2048)\n", " careq = createCertRequest(cakey, subject)\n", "\n", "\n", " serial = int(time.strftime(\"%y%m%d%H%M%S\", localtime()))\n", " cacert = createCertificate(careq, (careq, cakey), serial, (0, 60*60*24*365*duration), createca=True)\n", " open(join(cert_dir, cert_file), \"wt\").write(\n", " crypto.dump_certificate(crypto.FILETYPE_PEM, cacert).decode('utf-8'))\n", " open(join(cert_dir, key_file), \"wt\").write(\n", " crypto.dump_privatekey(crypto.FILETYPE_PEM, cakey).decode('utf-8'))\n", " print('created CA with private key \"'+key_file+'\" and certificate \"'+cert_file+'\"')\n", "\n", " return cacert, cakey\n", "\n", "\n", "def create_priv_key_and_csr(cert_dir, csr_file, key_file, subject):\n", " if not exists(cert_dir):\n", " print(\"creating directory: {}\".format(cert_dir))\n", " makedirs(cert_dir)\n", " \n", " priv_key = crypto.PKey()\n", " priv_key.generate_key(crypto.TYPE_RSA, 2048)\n", " #print(crypto.dump_privatekey(crypto.FILETYPE_PEM, priv_key))\n", "\n", " key_file = join(cert_dir, key_file)\n", " f = open(key_file,\"w\")\n", " f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, priv_key))\n", " f.close()\n", " \n", " csr = createCertRequest(priv_key, subject)\n", "\n", " csr_file = join(cert_dir, csr_file)\n", " f= open(csr_file,\"w\")\n", " f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr))\n", " f.close()\n", " \n", " return crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Global Vars" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%store -r config\n", "\n", "print(\"config: {}\".format(json.dumps(config, indent=4, default=str)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Top Level CA\n", "\n", "ACM PCA supports subordinated CAs. Therefore we need to create a CA outside of ACM which is being used to chain the subordinated CA.\n", "\n", "Create a CA locally which will be used to chain with the subordinate CA from ACM. The PCA will be created later. CA key and certificate will be stored in the directory that is defined by the variable *CA_directory*. A duration of 20 years is used for the top level CA." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ca_cert_crypto, ca_key_crypto = create_ca(\n", " config['CA_directory'], \n", " config['CA_cert'], \n", " config['CA_key'], \n", " config['CA_subject'], 20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Boto3 Client\n", "Create a boto3 client for the acm-pca service endpoint." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c_acm_pca = boto3.client('acm-pca', region_name = config['aws_region_pca'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Subordinate CA\n", "Create a private subordinated CA in ACM." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = c_acm_pca.create_certificate_authority(\n", " CertificateAuthorityConfiguration={\n", " 'KeyAlgorithm': 'RSA_2048',\n", " 'SigningAlgorithm': 'SHA256WITHRSA',\n", " 'Subject': {\n", " 'Country': 'DE',\n", " 'Organization': 'AWS',\n", " 'OrganizationalUnit': 'IoT',\n", " 'State': 'Berlin',\n", " 'CommonName': 'Subordinated IoT Device CA for multi region',\n", " 'Locality': 'Berlin'\n", " }\n", " },\n", " RevocationConfiguration={\n", " 'CrlConfiguration': {\n", " 'Enabled': False\n", " }\n", " },\n", " CertificateAuthorityType='SUBORDINATE',\n", " IdempotencyToken='MySubOrdinateCA'\n", ")\n", "\n", "print(\"response: {}\".format(json.dumps(response, indent=4, default=str)))\n", "\n", "pca_arn = response['CertificateAuthorityArn']\n", "print(\"pca_arn: {}\".format(pca_arn))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Retrieve CSR for PCA\n", "After the PCA has been created you need to get the CSR for your private CA. This CSR must be signed by the top level CA that has been created in the first step. After creating a cert from the CSR, the certificate must be imported into ACM to activate the private CA. \n", "CSR is written to file." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = c_acm_pca.get_certificate_authority_csr(\n", " CertificateAuthorityArn = pca_arn\n", ")\n", "print(\"response: {}\".format(json.dumps(response, indent=4, default=str)))\n", "subca_csr = response['Csr']\n", "print(\"subca_csr: {}\".format(subca_csr))\n", "\n", "file_subca_csr = join(config['CA_directory'], 'subca_csr.pem')\n", "f = open(file_subca_csr,\"w\")\n", "f.write(subca_csr)\n", "f.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Certificate for Subordinated CA\n", "Based on the CSR from the subordinate CA we need to create a certificate signed by the top level CA. \n", "\n", "### Note: Subject of the CSR and CRT must be the same. Otherwise ACM PCA will not accept the cert.\n", "\n", "Duration of the certificate for the PCA cert will be set to 10 years." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "subca_req = crypto.load_certificate_request(crypto.FILETYPE_PEM, subca_csr)\n", "serial = int(time.strftime(\"%y%m%d%H%M%S\", localtime()))\n", "subca_cert_crypto = createCertificate(subca_req, (ca_cert_crypto, ca_key_crypto), serial, (0, 60*60*24*365*10), createca=True, intermediate=True)\n", "\n", "subca_cert = crypto.dump_certificate(crypto.FILETYPE_PEM, subca_cert_crypto).decode()\n", "print(subca_cert)\n", "\n", "file_subca_crt = join(config['CA_directory'], 'subca_crt.pem')\n", "f = open(file_subca_crt,\"w\")\n", "f.write(subca_cert)\n", "f.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Import CA into ACM\n", "To import the certificate that has been created from the CSR from the subordinated CA into ACM the following parameters are required:\n", "\n", "* ARN of the private CA in ACM\n", "* Certificate based on the CSR from the private CA\n", "* Certificate chain of all certificates used to sign the CSR from the private CA. In this case the certificate from the top level CA is the one that is needed" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Last Check that all parameters are in place" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"pca_arn: {}\".format(pca_arn))\n", "print(subca_cert)\n", "ca_cert = crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert_crypto).decode()\n", "print(ca_cert)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Import the Certificate" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = c_acm_pca.import_certificate_authority_certificate(\n", " CertificateAuthorityArn = pca_arn,\n", " Certificate = subca_cert,\n", " CertificateChain = ca_cert\n", ")\n", "\n", "print(\"response: {}\".format(json.dumps(response, indent=4, default=str)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Verify \n", "Verify that the certificate has been imported correctly by describing the private CA in ACM." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = c_acm_pca.get_certificate_authority_certificate(\n", " CertificateAuthorityArn = pca_arn\n", ")\n", "print(\"response: {}\\n\".format(json.dumps(response, indent=4, default=str)))\n", "pca_certificate = response['Certificate']\n", "print(\"pca_certificate:\\n{}\".format(pca_certificate))" ] } ], "metadata": { "kernelspec": { "display_name": "conda_python3", "language": "python", "name": "conda_python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 4 }