{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Certificate Editor\n", "\n", "This notebook uses [cryptography](https://cryptography.io/) to take an existing X.509 certificate and make edits to it, while preserving the other characteristics of the certificate. In particular, this preserves the currently existing certificate private key.\n", "\n", "By default, this code takes a certificate and changes its expiry date to be thirty years in the future from today, but in principle it can make other changes.\n", "\n", "The script has the following limitations:\n", "\n", "- It only works for self-signed certificates. This isn't a fundamental limitation, just done for simplicity's sake." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Imports and other basic setup.\n", "import datetime\n", "\n", "from cryptography import x509\n", "from cryptography.hazmat.backends import default_backend\n", "from cryptography.hazmat.primitives import serialization\n", "\n", "\n", "THIRTY_YEARS = datetime.timedelta(days=365*30)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# User-controlled settings. These settings adjust what certificate is used and what gets edited about it.\n", "CERT_PATH = \"client.pem\"\n", "KEY_PATH = \"client.key\"\n", "KEY_PASSWORD = None\n", "OUTPUT_PATH = \"newcert.pem\"\n", "\n", "NEW_PARAMETERS = {\n", " \"not_valid_after\": datetime.datetime.today() + THIRTY_YEARS\n", "}\n", "NEW_EXTENSIONS = {}" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# The actual logic of the code. First, define all the certificate attributes we'll try to set.\n", "# We deliberately exclude extensions here because they require more work.\n", "# The attributes are a dictionary of \"certificate builder name\" to \"certificate name\", to work\n", "# around the fact that cryptography has different names for most of these things,\n", "attrs = {\n", " 'subject_name': 'subject',\n", " 'issuer_name': 'issuer',\n", " 'not_valid_before': 'not_valid_before',\n", " 'not_valid_after': 'not_valid_after', \n", " 'serial_number': 'serial',\n", "}\n", "\n", "\n", "def build_certificate(current_cert, current_key):\n", " builder = x509.CertificateBuilder()\n", " \n", " # Apply the attributes. We have to do a weird getattr() dance here becuase the various\n", " # builder attributes are actually functions.\n", " for attr in attrs:\n", " if attr not in NEW_PARAMETERS:\n", " try:\n", " old_attr = getattr(current_cert, attrs[attr])\n", " except AttributeError:\n", " continue\n", " else:\n", " builder = getattr(builder, attr)(old_attr)\n", " else:\n", " builder = getattr(builder, attr)(NEW_PARAMETERS[attr])\n", " \n", " # Then the extensions. First copy across the ones that are there, editing if needed.\n", " for extension in current_cert.extensions:\n", " if extension.value.__class__ not in NEW_EXTENSIONS:\n", " builder = builder.add_extension(extension.value, critical=extension.critical)\n", " else:\n", " builder = builder.add_extension(NEW_EXTENSIONS[extension])\n", " del NEW_EXTENSIONS[extension]\n", " \n", " # Then set any extra new extensions.\n", " for extension, value in NEW_EXTENSIONS.items():\n", " builder = builder.add_extension(value)\n", " \n", " # Finally, set the key and sign it.\n", " builder = builder.public_key(current_key.public_key())\n", " \n", " signature_algorithm = current_cert.signature_hash_algorithm\n", " return builder.sign(\n", " private_key=current_key,\n", " algorithm=signature_algorithm,\n", " backend=default_backend(),\n", " )" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Finally, a code block that actually executes the transformation.\n", "with open(CERT_PATH, 'rb') as f:\n", " current_cert = x509.load_pem_x509_certificate(f.read(), default_backend())\n", " \n", "with open(KEY_PATH, 'rb') as f:\n", " current_key = serialization.load_pem_private_key(f.read(), KEY_PASSWORD, default_backend())\n", " \n", "new_cert = build_certificate(current_cert, current_key)\n", "with open(OUTPUT_PATH, 'wb') as f:\n", " f.write(new_cert.public_bytes(serialization.Encoding.PEM))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.11" } }, "nbformat": 4, "nbformat_minor": 0 }