[Cryptech-Commits] [sw/libhal] branch pkcs8 updated: First cut at HSM backup script.

git at cryptech.is git at cryptech.is
Mon Apr 10 02:40:44 UTC 2017


This is an automated email from the git hooks/post-receive script.

sra at hactrn.net pushed a commit to branch pkcs8
in repository sw/libhal.

The following commit(s) were added to refs/heads/pkcs8 by this push:
     new a90d24e  First cut at HSM backup script.
a90d24e is described below

commit a90d24ee670af4e605cbd95418b000f811265c59
Author: Rob Austein <sra at hactrn.net>
AuthorDate: Sun Apr 9 22:39:41 2017 -0400

    First cut at HSM backup script.
---
 cryptech_backup | 240 +++++++++++++++++++++++++++++++++++++++++++
 key-backup.py   | 308 --------------------------------------------------------
 2 files changed, 240 insertions(+), 308 deletions(-)

diff --git a/cryptech_backup b/cryptech_backup
new file mode 100755
index 0000000..7360a0d
--- /dev/null
+++ b/cryptech_backup
@@ -0,0 +1,240 @@
+#!/usr/bin/env python
+
+#    KEY SOURCE                            KEY BACKUP
+#
+#                                          Generate and export KEKEK:
+#                                               hal_rpc_pkey_generate_rsa()
+#                                               hal_rpc_pkey_get_public_key()
+#
+#   Load KEKEK public   <----------------  Export KEKEK public
+#
+#                       {
+#                               "kekek-uuid":   "[UUID]",
+#                               "kekek":        "[Base64]"
+#                       }
+#
+#       hal_rpc_pkey_load()
+#       hal_rpc_pkey_export()
+#
+#   Export PKCS #8 and KEK   ---------->   Load PKCS #8 and KEK, import key:
+#
+#                       {
+#                               "kekek-uuid":   "[UUID]",
+#                               "pkey":         "[Base64]",
+#                               "kek":          "[Base64]"
+#                       }
+#
+#
+#                                               hal_rpc_pkey_import()
+
+import sys
+import json
+import uuid
+import atexit
+import getpass
+import argparse
+
+from libhal import *
+
+def main():
+
+    parser = argparse.ArgumentParser(
+        formatter_class = argparse.ArgumentDefaultsHelpFormatter,
+        description = __doc__)
+    subparsers = parser.add_subparsers(
+        title = "Commands (use \"--help\" after command name for help with individual commands)",
+        metavar = "")
+    parser.add_argument(
+        "-p", "--pin",
+        help = "wheel PIN")
+
+    subparser = defcmd(subparsers, cmd_setup)
+    group = subparser.add_mutually_exclusive_group()
+    group.add_argument(
+        "-n", "--new", action = "store_true",
+        help = "force creation of new KEKEK")
+    group.add_argument(
+        "-u", "--uuid",
+        help = "UUID of existing KEKEK to use")
+    subparser.add_argument(
+        "-k", "--keylen", type = int, default = 2048,
+        help = "length of new KEKEK if we need to create one")
+    subparser.add_argument(
+        "-o", "--output", type = argparse.FileType("w"), default = "-",
+        help = "output file")
+
+    subparser = defcmd(subparsers, cmd_export)
+    subparser.add_argument(
+        "-i", "--input", type = argparse.FileType("r"), default = "-",
+        help = "input file")
+    subparser.add_argument(
+        "-o", "--output", type = argparse.FileType("w"), default = "-",
+        help = "output file")
+
+    subparser = defcmd(subparsers, cmd_import)
+    subparser.add_argument(
+        "-i", "--input", type = argparse.FileType("r"), default = "-",
+        help = "input file")
+
+    args = parser.parse_args()
+
+    hsm = HSM()
+
+    try:
+        hsm.login(HAL_USER_WHEEL, args.pin or getpass.getpass("Wheel PIN: "))
+
+    except HALError as e:
+        sys.exit("Couldn't log into HSM: {}".format(e))
+
+    try:
+        sys.exit(args.func(args, hsm))
+
+    finally:
+        hsm.logout()
+
+
+def defcmd(subparsers, func):
+    assert func.__name__.startswith("cmd_")
+    subparser = subparsers.add_parser(func.__name__[4:],
+                                      description = func.__doc__,
+                                      help = func.__doc__.strip().splitlines()[0])
+    subparser.set_defaults(func = func)
+    return subparser
+
+
+def b64(bytes):
+    return bytes.encode("base64").splitlines()
+
+def b64join(lines):
+    return "".join(lines).decode("base64")
+
+
+def cmd_setup(args, hsm):
+    """
+    Set up backup HSM for subsequent import.
+    Generates an RSA keypair with appropriate usage settings
+    to use as a key-encryption-key-encryption-key (KEKEK), and
+    writes the KEKEK to a JSON file for transfer to primary HSM.
+    """
+
+    result = {}
+
+    uuids = []
+    if args.uuid:
+        uuids.append(args.uuid)
+    elif not args.new:
+        uuids.extend(hsm.pkey_match(
+            type  = HAL_KEY_TYPE_RSA_PRIVATE,
+            flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT | HAL_KEY_FLAG_TOKEN))
+
+    for uuid in uuids:
+        with hsm.pkey_open(uuid, HAL_KEY_FLAG_TOKEN) as kekek:
+            if kekek.key_type != HAL_KEY_TYPE_RSA_PRIVATE:
+                sys.stderr.write("Key {} is not an RSA private key\n".format(uuid))
+            elif (kekek.key_flags & HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT) == 0:
+                sys.stderr.write("Key {} does not allow key encipherment\n".format(uuid))
+            else:
+                result.update(kekek_uuid   = str(kekek.uuid),
+                              kekek_pubkey = b64(kekek.public_key))
+                break
+
+    if not result and not args.uuid:
+        with hsm.pkey_generate_rsa(
+                keylen = args.keylen,
+                flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT | HAL_KEY_FLAG_TOKEN) as kekek:
+            result.update(kekek_uuid   = str(kekek.uuid),
+                          kekek_pubkey = b64(kekek.public_key))
+    if not result:
+        sys.exit("Could not find suitable KEKEK")
+
+    result.update(comment = "KEKEK public key")
+    json.dump(result, args.output, indent = 4, sort_keys = True)
+
+
+def key_flag_names(flags):
+    names = dict(digitalsignature = HAL_KEY_FLAG_USAGE_DIGITALSIGNATURE,
+                 keyencipherment  = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT,
+                 dataencipherment = HAL_KEY_FLAG_USAGE_DATAENCIPHERMENT,
+                 token            = HAL_KEY_FLAG_TOKEN,
+                 public           = HAL_KEY_FLAG_PUBLIC,
+                 exportable       = HAL_KEY_FLAG_EXPORTABLE)
+    return ", ".join(sorted(k for k, v in names.iteritems() if (flags & v) != 0))
+
+
+def cmd_export(args, hsm):
+    """
+    Export encrypted keys from primary HSM.
+    Takes a JSON file containing KEKEK (generated by running this
+    script's "setup" command against the backup HSM), installs that
+    key on the primary HSM, and backs up keys encrypted to the KEKEK
+    by writing them to another JSON file for transfer to the backup HSM.
+    """
+
+    db = json.load(args.input)
+
+    result = []
+
+    kekek = None
+    try:
+        kekek = hsm.pkey_load(der   = b64join(db["kekek_pubkey"]),
+                              flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT)
+
+        # What we *should* do here is a single .pkey_match() loop
+        # matching exactly the keys we want, but the current semantics
+        # of .pkey_match() are a bit confused.  While that yak is
+        # waiting for its shave, we do this the dumb way by iterating
+        # over all keys then skipping the ones we don't want.
+
+        for flags in (0, HAL_KEY_FLAG_TOKEN):
+            for uuid in hsm.pkey_match(flags = flags):
+                with hsm.pkey_open(uuid, flags) as pkey:
+                    if (pkey.key_flags & HAL_KEY_FLAG_EXPORTABLE) == 0:
+                        continue
+                    if pkey.key_type in (HAL_KEY_TYPE_RSA_PRIVATE, HAL_KEY_TYPE_EC_PRIVATE):
+                        pkcs8, kek = kekek.export_pkey(pkey)
+                        result.append(dict(
+                            comment = "Encrypted private key",
+                            pkcs8   = b64(pkcs8),
+                            kek     = b64(kek),
+                            uuid    = str(pkey.uuid),
+                            flags   = pkey.key_flags))
+                    elif pkey.key_type in (HAL_KEY_TYPE_RSA_PUBLIC, HAL_KEY_TYPE_EC_PUBLIC):
+                        result.append(dict(
+                            comment = "Public key",
+                            spki    = b64(pkey.public_key),
+                            uuid    = str(pkey.uuid),
+                            flags   = pkey.key_flags))
+    finally:
+        if kekek is not None:
+            kekek.delete()
+
+    db.update(comment = "Cryptech Alpha encrypted key backup",
+              keys    = result)
+    json.dump(db, args.output, indent = 4, sort_keys = True)
+
+
+def cmd_import(args, hsm):
+    """
+    Import encrypted keys into backup HSM.
+    Takes a JSON file containing a key backup (generated by running
+    this script's "export" command against the primary HSM) and imports
+    keys into the backup HSM.
+    """
+
+    db = json.load(args.input)
+    with hsm.pkey_open(uuid.UUID(db["kekek_uuid"]).bytes, HAL_KEY_FLAG_TOKEN) as kekek:
+        for k in db["keys"]:
+            pkcs8 = b64join(k.get("pkcs8", ""))
+            spki  = b64join(k.get("spki",  ""))
+            kek   = b64join(k.get("kek",   ""))
+            flags =         k.get("flags",  0)
+            if pkcs8 and kek:
+                with kekek.import_pkey(pkcs8 = pkcs8, kek = kek, flags = flags) as pkey:
+                    print "Imported {} as {}".format(k["uuid"], pkey.uuid)
+            elif spki:
+                with hsm.pkey_load(der = spki, flags = flags) as pkey:
+                    print "Loaded {} as {}".format(k["uuid"], pkey.uuid)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/key-backup.py b/key-backup.py
deleted file mode 100644
index 4cdd9e9..0000000
--- a/key-backup.py
+++ /dev/null
@@ -1,308 +0,0 @@
-#!/usr/bin/env python
-
-# Test of key backup code, will evolve into unit tests and a user
-# backup script after initial debugging.
-
-#    KEY SOURCE                            KEY BACKUP
-#
-#                                          Generate and export KEKEK:
-#                                               hal_rpc_pkey_generate_rsa()
-#                                               hal_rpc_pkey_get_public_key()
-#
-#   Load KEKEK public   <----------------  Export KEKEK public
-#
-#                       {
-#                               "kekek-uuid":   "[UUID]",
-#                               "kekek":        "[Base64]"
-#                       }
-#
-#       hal_rpc_pkey_load()
-#       hal_rpc_pkey_export()
-#
-#   Export PKCS #8 and KEK   ---------->   Load PKCS #8 and KEK, import key:
-#
-#                       {
-#                               "kekek-uuid":   "[UUID]",
-#                               "pkey":         "[Base64]",
-#                               "kek":          "[Base64]"
-#                       }
-#
-#
-#                                               hal_rpc_pkey_import()
-
-from libhal import *
-
-from Crypto.PublicKey   import RSA
-from Crypto.Cipher      import AES, PKCS1_v1_5
-from Crypto.Util.asn1   import DerObject, DerSequence, DerOctetString, DerObjectId, DerNull
-from Crypto.Random      import new as csprng
-from struct             import pack, unpack
-from atexit             import register as atexit
-
-def dumpasn1(der, flags = "-aop"):
-    from subprocess import call
-    from tempfile import NamedTemporaryFile
-    with NamedTemporaryFile() as f:
-        f.write(der)
-        f.flush()
-        call(("dumpasn1", flags, f.name))
-
-hal_asn1_oid_rsaEncryption = "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01"
-hal_asn1_oid_aesKeyWrap    = "\x60\x86\x48\x01\x65\x03\x04\x01\x30"
-
-kek_length = 256/8      # We can determine this from the keywrap OID, this is for AES-256
-
-hsm = None
-
-
-def main():
-    global hsm
-    hsm = HSM()
-    #hsm.debug_io = args.io_log
-    hsm.login(HAL_USER_WHEEL, "fnord")
-    atexit(hsm.logout)
-    test_export()
-    test_import()
-
-def test_export():
-    print "Testing hal_rpc_pkey_export()"
-
-    kekek = RSA.importKey(kekek_pem)
-
-    kekek_handle = hsm.pkey_load(
-        flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT,
-        der   = kekek.publickey().exportKey(format = "DER"))
-    atexit(kekek_handle.delete)
-
-    pkey1 = hsm.pkey_generate_ec(
-        curve = HAL_CURVE_P256,
-        flags = HAL_KEY_FLAG_USAGE_DIGITALSIGNATURE | HAL_KEY_FLAG_EXPORTABLE)
-    atexit(pkey1.delete)
-
-    pkey2 = hsm.pkey_generate_rsa(
-        keylen= 2048,
-        flags = HAL_KEY_FLAG_USAGE_DIGITALSIGNATURE | HAL_KEY_FLAG_EXPORTABLE)
-    atexit(pkey2.delete)
-
-    for pkey in (pkey1, pkey2):
-        pkcs8_der, kek_der = kekek_handle.export_pkey(pkey)
-        kek = PKCS1_v1_5.new(kekek).decrypt(
-            parse_EncryptedPrivateKeyInfo(kek_der, hal_asn1_oid_rsaEncryption),
-            csprng().read(kek_length))
-        der = AESKeyWrapWithPadding(kek).unwrap(
-            parse_EncryptedPrivateKeyInfo(pkcs8_der, hal_asn1_oid_aesKeyWrap))
-        dumpasn1(der)
-
-
-def test_import():
-    print "Testing hal_rpc_pkey_import()"
-
-    if False:
-        kekek = RSA.importKey(kekek_pem)
-        kekek_handle = hsm.pkey_load(
-            flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT,
-            der   = kekek.exportKey(format = "DER", pkcs = 8))
-        atexit(kekek_handle.delete)
-        kekek = kekek.publickey()
-
-    else:
-        kekek_handle = hsm.pkey_generate_rsa(
-            keylen= 2048,
-            flags = HAL_KEY_FLAG_USAGE_KEYENCIPHERMENT)
-        atexit(kekek_handle.delete)
-        kekek = RSA.importKey(kekek_handle.public_key)
-
-    for der in (rsa_2048_der, ecdsa_p384_der):
-
-        kek = csprng().read(kek_length)
-
-        pkey = kekek_handle.import_pkey(
-            pkcs8 = encode_EncryptedPrivateKeyInfo(AESKeyWrapWithPadding(kek).wrap(der),
-                                                   hal_asn1_oid_aesKeyWrap),
-            kek   = encode_EncryptedPrivateKeyInfo(PKCS1_v1_5.new(kekek).encrypt(kek),
-                                                   hal_asn1_oid_rsaEncryption),
-            flags = HAL_KEY_FLAG_USAGE_DIGITALSIGNATURE)
-
-        atexit(pkey.delete)
-
-        print "Imported", pkey.uuid
-        dumpasn1(pkey.public_key)
-        dumpasn1(der)
-
-
-def parse_EncryptedPrivateKeyInfo(der, oid):
-
-    encryptedPrivateKeyInfo = DerSequence()
-    encryptedPrivateKeyInfo.decode(der)
-
-    encryptionAlgorithm = DerSequence()
-    encryptionAlgorithm.decode(encryptedPrivateKeyInfo[0])
-
-    algorithm = DerObjectId()
-    algorithm.decode(encryptionAlgorithm[0])
-
-    encryptedData = DerOctetString()
-    encryptedData.decode(encryptedPrivateKeyInfo[1])
-
-    if algorithm.payload != oid:
-        raise ValueError
-
-    return encryptedData.payload
-
-
-def encode_EncryptedPrivateKeyInfo(der, oid):
-    return DerSequence([
-        DerSequence([chr(0x06) + chr(len(oid)) + oid]).encode(),
-        DerOctetString(der).encode()
-    ]).encode()
-
-
-class AESKeyWrapWithPadding(object):
-    """
-    Implementation of AES Key Wrap With Padding from RFC 5649.
-    """
-
-    class UnwrapError(Exception):
-        "Something went wrong during unwrap."
-
-    def __init__(self, key):
-        self.ctx = AES.new(key, AES.MODE_ECB)
-
-    def _encrypt(self, b1, b2):
-        aes_block = self.ctx.encrypt(b1 + b2)
-        return aes_block[:8], aes_block[8:]
-
-    def _decrypt(self, b1, b2):
-        aes_block = self.ctx.decrypt(b1 + b2)
-        return aes_block[:8], aes_block[8:]
-
-    @staticmethod
-    def _start_stop(start, stop):               # Syntactic sugar
-        step = -1 if start > stop else 1
-        return xrange(start, stop + step, step)
-
-    def wrap(self, Q):
-        "RFC 5649 section 4.1."
-        m = len(Q)                              # Plaintext length
-        if m % 8 != 0:                          # Pad Q if needed
-            Q += "\x00" * (8 - (m % 8))
-        R = [pack(">LL", 0xa65959a6, m)]        # Magic MSB(32,A), build LSB(32,A)
-        R.extend(Q[i : i + 8]                   # Append Q
-                 for i in xrange(0, len(Q), 8))
-        n = len(R) - 1
-        if n == 1:
-            R[0], R[1] = self._encrypt(R[0], R[1])
-        else:
-            # RFC 3394 section 2.2.1
-            for j in self._start_stop(0, 5):
-                for i in self._start_stop(1, n):
-                    R[0], R[i] = self._encrypt(R[0], R[i])
-                    W0, W1 = unpack(">LL", R[0])
-                    W1 ^= n * j + i
-                    R[0] = pack(">LL", W0, W1)
-        assert len(R) == (n + 1) and all(len(r) == 8 for r in R)
-        return "".join(R)
-
-    def unwrap(self, C):
-        "RFC 5649 section 4.2."
-        if len(C) % 8 != 0:
-            raise self.UnwrapError("Ciphertext length {} is not an integral number of blocks"
-                                   .format(len(C)))
-        n = (len(C) / 8) - 1
-        R = [C[i : i + 8] for i in xrange(0, len(C), 8)]
-        if n == 1:
-            R[0], R[1] = self._decrypt(R[0], R[1])
-        else:
-            # RFC 3394 section 2.2.2 steps (1), (2), and part of (3)
-            for j in self._start_stop(5, 0):
-                for i in self._start_stop(n, 1):
-                    W0, W1 = unpack(">LL", R[0])
-                    W1 ^= n * j + i
-                    R[0] = pack(">LL", W0, W1)
-                    R[0], R[i] = self._decrypt(R[0], R[i])
-        magic, m = unpack(">LL", R[0])
-        if magic != 0xa65959a6:
-            raise self.UnwrapError("Magic value in AIV should have been 0xa65959a6, was 0x{:02x}"
-                              .format(magic))
-        if m <= 8 * (n - 1) or m > 8 * n:
-            raise self.UnwrapError("Length encoded in AIV out of range: m {}, n {}".format(m, n))
-        R = "".join(R[1:])
-        assert len(R) ==  8 * n
-        if any(r != "\x00" for r in R[m:]):
-            raise self.UnwrapError("Nonzero trailing bytes {}".format(R[m:].encode("hex")))
-        return R[:m]
-
-
-# Static KEKEK for testing, this should come from the backup HSM.
-
-kekek_pem = '''\
------BEGIN PRIVATE KEY-----
-MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDTtBJvz+55FBHH
-0NhDZ6Xdp07kUPFxn9lYlNwg5BmSBPbXT/2JindI9NrfEx4xX0i0d3OxnbQoc8RJ
-WsF2ujALBAU92yO9bjbxUbgxvecy3by/UNulLo9pOhKD2hgCkH6FWdlE7wbIfex1
-pIFL1ms/h6qBme8qvXEGqTh79S5krVQG/tVZFyNyVanzrVCcAVGhZ/RqXK4Lb7pF
-QJg1tNuYaQkNieiVPpoxuqAX0jP0iot2OXwlMUj2aHl/cQfdmIYKCmC3IQfuCy1m
-grdW7Sb2u87tH6aFSEp4mCbScXYac7lBsi4AOQQcGR8816NslDqYU/0+cYcU4Ub/
-0D8W2Nr9AgMBAAECggEBAIIJ2klUL+evrDxQzIaa5AeC/bLBBY4F4jvHNG//rLVE
-11rqh5I0u5DU1pyv4ZvyK3au6SHw/PjcI3XriWqkc15Q2edk9E8npBgXWk0zmRBl
-o8rgoAqWzwCT60uSa60nlI/U4OC28jO1Jcodgk5TJw2fB90T8RUPyJ2O1GNP929e
-6autPcifNNBQGNAiVCMAboNHOunr0fBO28JAcEhgw5CqpjCNbWbv9YLPAaIB6Fr9
-mnidOB7UNQ8Uk+bybuSz7DtsmOpbktjBcbgQVpqJyzkjsA/2LjoTavUTq2UALtk2
-VeNVebfvQq7crMsfV09r0EdrAx3wawjrX/jyrbwf8AECgYEA+yfx0Fg5Kn+7ierT
-nLbJ1HgIra8KabmJB629cXjhllO1gBH7NdFU/13H9dPhcehA0zYkZuvQOWWRjh28
-VJhwb4fSdtlkxukqJfNNrppYhEmr3zs6RFJYb3qZKZSZE7Bo1S6WeM1cMQWY94le
-GylVC5f52a6H199hHiKQ9pIjKK0CgYEA18lVm6f1L/8wC3rXB7PW628ImQoIOFaT
-mAdBtfGgUfpVk8xsuipJ4bqve45l6B8s49xr3rY/j4t8wETE11h4kLaQGicRVXFq
-7xJUR4xZYYnDKMC2LwSHbd0JxYekKa4uaC9Sd6g5Pyg1f8QVmXdShI0z0Hpr3aYY
-hdXNfFDvNZECgYEAp1/wY9NXjX4AYiIPkiGykZjI186OFvUhX++mD2fqln8EtuvE
-yRHPHjvGVYo1dO69vMQZMEm4w3dvsBEbABly3LDcTn4EDhc3EoF5ZIHRuZ9LHgJf
-i0aBTxGZ3r774MYwptlcR/c7mCPN1DFEeL9rwMUwKaSJPRDNrQKGLvwm2CUCgYBK
-LBN4GKiH4gCiwYuuQxvp+1WKPU+MBf5fsIbewnpoE1NdJVRuPWD97UyqfMz8l9K3
-VCnj+OMqNTkhYcIDf46Zt5ca1jj4FK88FCHSIiULCO6DUJKO4NCoa+US98fu58dd
-2n5PUQy0b97L1xvRj5lWpK6dx6bSHmipgE9MnwlKcQKBgQCZR2Czs0O/fi1V0Ecl
-d1XDDCAS3sECclhqiJkcn9TaM/0chGR7E//0ChP82ca5ihkByVgsOfaYaWZg+Eci
-FUQep3DnjONc0kX9xeiSn3Z2jbUMcoub/uY0OWreE+3FL1ZgjYs1KKdUOWF2DL/X
-L7en4sepnWifRGs2gnPYKrn1Zg==
------END PRIVATE KEY-----
-'''
-
-# Static keys for import testing.
-
-rsa_2048_der = '''\
-MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwyEYARfw428GU
-6XwflyOMJt1U+SM+H1zVmguXDqOX9i/aAhe8dvmYTokcxiWJ14N8dfbwKh3pyaBB
-HenlaarQvINRYa812X7z/UeBBTNEmURWVCiGMq/ginxpyUSxjTxtOP/VRH8tYlwm
-9K/L/7BN78bdQLeA6atFuuJkZDjvrLn7UypzCa/3Mip9kpu/6nJDnDUYkngwE4G5
-4J6yWO+/BEqoRFhVkDdtyDcyBOvZsV3Vqi8tvpNrLTujzFHAQD8EIT9r7IxP/me7
-We3Tu2i8CEnqUNiGxhoi5hzKq36NIDYrYvzg3xQNorS9SC3rHz/F3I9+XDqNnfYV
-ok8CBFNFAgMBAAECggEBAIH0Z8kxqW1e1tqSHUXXxDD2LQSXNPoo8gSv/k8oWsiO
-GLUpjqtjxq3ZJeA6JUREYosu6L26KE1BhAX6aIPV/tT9j4dWyQdMAJB6I4NMAFkw
-VlUj/rpQLoxhIX5ej5n6Gm6sVR1BAkCpqtaUT1smdkOEvWrOdVdV7ysOa/ii2FwP
-IzYbHoBOWeI/aq8RdtZ/NeQenPZX++VihRT7b4prfjxTbeghvN1NNy7TmCquBZSM
-9aVMIB4Q4dXAdRePB6K6N0Gy60CQll62veppbFJHDPdjKjkxiOgIJo2XYIM5LziD
-ta0VxHRNpTULCGwAA94f9yNo/YzpxLNw94COu0StLa0CgYEA4k0UHHSRb0jhkB0f
-6jknEthhBWIFmJ8bDJ/ObN0wxLhluiajzelPS/YB2Qsdj8NGXSX5yIrrNyCc+tjk
-wniFD8X52h3z4nEzrx0Hn2jqL6w8k2jp1WMZCGW7Ure6o5ilhb5vMUqjvHyVDsG6
-aAl/82oWWXV0HWEHHzjWgeNWpPMCgYEAx/uJx07z0TztvyEaJrBy/rvPtdOnZ03F
-UiKAUFWS6C9vncmoH5m+fHaxKn7jPCCxHyoea3BKvqv5MIxkn3DJMmu9UVwLAGne
-JnElygsA1ogy9f9YN+Fp1jXikdywbd2T0RsuoaUm+iMFO/fSPGM5qTbOTzY4dw4R
-oSX/NmzjlOcCgYEAgss10nR1EjK3W8nZhlBeCwBQowHSZjGfOp6qejUlWK2S7hIj
-HoG4ORkIXF+WSF7+rhui0IuqAwSwdjMhlFx/22v7SluBd+EhlBZdL389yyvrHu/G
-JnTOJRJXQCm8j41MLY6xSXXwSKJgrFS/3h2PfCpWnIHMCKbprNv27r9sdo0CgYBg
-UBGUDr84N1rdIQkiNvq7GiK4FD5cb0UoAHvBtOTys93SpUs2JOprsRI0QDYaQDht
-pPBPmB43ZEW4DvVrIHuVr/PWmjimM1aNNxMXEmON7rx0Y0zOZN5/DyaWTy4dS4ik
-Pa4gpZR3BaTAs+LpuHQNvdpwpdFd7UWqUc1vHdQhYwKBgAbz0tbrr/QTl6/WvbBS
-6rALr2x7hueOmyCGzgk6o1gPkqbvuGZDJIkInzjYMxKly13pWQhFJlSZMlhpBosu
-u/L+h81Pj1Ks38yzxnoz1QJ/s3xWfE77xFvz8u319Gv25Hf+SFkd97+BxF1Fq8tV
-r/gYnWjq6Ay5HGptjovGzyYi
-'''.decode("base64")
-
-ecdsa_p384_der = '''\
-MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB4+4rLfjwI6g5bIk4H
-Glylc+ggvl7rBcFCxTY2K6Rd/ZuDto4ZOwxaQcvTyctOZaKhZANiAATaNzklDKdP
-QYe2NhCvkoxirFCw3AKy767BCmPjad4ZfVNCchSRY+fKTgatEDtCly8+G2914q1w
-/CdxWp+coDHxgG6zBV/y7KvtoO8cA5E3KE2jHZP8gwkzUe/SNx9Tx6U=
-'''.decode("base64")
-
-if __name__ == "__main__":
-    main()

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the Commits mailing list