#23 koji-ssl-admin: write pkcs12 files for users
Merged 4 years ago by tkopecek. Opened 5 years ago by ktdreyer.
ktdreyer/koji-tools ssl-admin-pkcs12  into  master

file modified
+1
@@ -11,6 +11,7 @@ 

  

  BuildRequires:  python-devel

  Requires: koji

+ Requires: openssl

  

  %description

  provides a collection of tools/utilities that interacts

file modified
+66 -2
@@ -5,6 +5,7 @@ 

  from dateutil.relativedelta import relativedelta

  import errno

  import os

+ import subprocess

  from cryptography.hazmat.backends import default_backend

  from cryptography.hazmat.primitives import serialization

  from cryptography.hazmat.primitives.asymmetric import rsa
@@ -12,6 +13,11 @@ 

  from cryptography.x509.oid import NameOID

  from cryptography.x509.oid import ExtendedKeyUsageOID

  from cryptography.hazmat.primitives import hashes

+ try:

+     # pkcs12 support is only available in python-cryptography 2.5+

+     from cryptography.hazmat.primitives.serialization import pkcs12

+ except ImportError:

+     pkcs12 = None

  

  

  DESCRIPTION = """
@@ -38,8 +44,11 @@ 

     Like "master CA" above, you only need this if you don't have an external CA

     in your environment. (For example, in a testing environment.)

     You can use this to sign server certs or user certs with your CA.

+    For user certs, if you have the private key in the same directory, this

+    will also generate a "user_browser_cert.p12" bundle for your browser to log

+    into Kojiweb.

  

- Never share the .key files or post them in a public location.

+ Never share the .key or .p12 files or post them in a public location.

  """

  

  
@@ -294,6 +303,47 @@ 

      return certificate

  

  

+ def is_client_cert(certificate):

+     """

+     Determine if this is a client certificate (ie, not a server certificate).

+ 

+     User client certs will have CLIENT_AUTH in the ExtendedKeyUsage field.

+ 

+     :param certificate: an instance of cryptography.x509.Certificate

+     :returns: True if this is a client certificate.

+     """

+     eku = certificate.extensions.get_extension_for_class(x509.ExtendedKeyUsage)

+     client_auth = x509.ExtendedKeyUsage([ExtendedKeyUsageOID.CLIENT_AUTH])

+     return eku.value == client_auth

+ 

+ 

+ def write_pkcs12(crt_path, key_path, ca_crt_path, pkcs12_path, force):

+     """

+     Write out a private pkcs12 keypair with a password of "koji".

+ 

+     :param str crt_path: path to .crt file

+     :param str key_path: path to .key file

+     :param str ca_crt_path: path to CA .crt file

+     :param str pkcs12_path: path on disk to write the private p12 bundle file

+     """

+     if os.path.exists(pkcs12_path) and not force:

+         raise OSError(errno.EEXIST, os.strerror(errno.EEXIST), pkcs12_path)

+     # Unfortunately python-cryptography supports reading the pkcs12 format,

+     # but not writing it. We have to shell out to openssl, like:

+     # openssl pkcs12 -export -inkey kdreyer.key -in kdreyer.crt \

+     #                -CAfile koji-ca.crt -out kdreyer_browser_cert.p12

+     command = ('openssl', 'pkcs12', '-export',

+                '-in', crt_path, '-inkey', key_path,

+                '-CAfile', ca_crt_path, '-out', pkcs12_path,

+                '-passout', 'pass:koji')

+     subprocess.run(command)

+     # Sanity-check the new pkcs12 file exists and is valid.

+     with open(pkcs12_path, 'rb') as f:

+         if pkcs12:

+             backend = default_backend()

+             pkcs12.load_key_and_certificates(f.read(), 'koji', backend)

+ 

+ 

  def server_csr(args):

      force = args.force

      dnsnames = args.dnsnames
@@ -334,8 +384,22 @@ 

      ca_crt_path = args.ca_cert

      csr_path = args.csr

      crt_path = csr_path.replace('.csr', '.crt')  # todo: re.replace() here

-     sign_with_ca(csr_path, ca_key_path, ca_crt_path, crt_path, force)

+     cert = sign_with_ca(csr_path, ca_key_path, ca_crt_path, crt_path, force)

      print('wrote %s - publish this for users' % crt_path)

+     # If this is a user cert, and we have a matching .key file in the same

+     # directory, then generate a pkcs12 bundle file with the cert and key.

+     key_path = csr_path.replace('.csr', '.key')  # todo: re.replace() here

+     if is_client_cert(cert) and os.path.exists(key_path):

+         bundle_user_browser_cert(crt_path, key_path, ca_crt_path, force)

+ 

+ 

+ def bundle_user_browser_cert(crt_path, key_path, ca_crt_path, force):

+     pkcs12_path = key_path.replace('.key', '_browser_cert.p12')

+     # If this is a user cert, and we have a matching .key file in the same

+     # directory, then generate a pkcs12 bundle file with the cert and key.

+     write_pkcs12(crt_path, key_path, ca_crt_path, pkcs12_path, force)

+     print('wrote %s for kojiweb - protect this private file' % pkcs12_path)

+     print('to import %s into browser, the password is "koji"' % pkcs12_path)

  

  

  def parse_args():

When we sign a user certificate and we have a private key file available, we can also generate a pkcs12 bundle.

@tkopecek would you please merge this?

Commit 7bb7404 fixes this pull-request

Pull-Request has been merged by tkopecek

4 years ago

Pull-Request has been merged by tkopecek

4 years ago