Skip to content

Instantly share code, notes, and snippets.

@djgrasss
Forked from pudquick/secure_gurl.py
Last active August 29, 2015 14:07
Show Gist options
  • Select an option

  • Save djgrasss/7cdb55fa1c457ba6f669 to your computer and use it in GitHub Desktop.

Select an option

Save djgrasss/7cdb55fa1c457ba6f669 to your computer and use it in GitHub Desktop.
"""
First attempt at bringing security into play with the .pem files.
Only the security tool is used, no additional tools (openssl, etc.).
This code does the following:
- Creates the specified keychain if it doesn't exist
- Unlocks it with the specified password
- Configures it to not lock
- Adds it to the keychain search paths if it's not present already (necessary for 10.9)
- Import the client.pem cert / identity
- Import the CA.pem cert as a trusted cert (but only at the user level, stored in this specific keychain)
- Checks for an existing identity preference for the site:
- If one is found:
- If it's wrong, it's corrected
- If it's correct, it's left alone
- If one is not found, it's created
The end result should be:
- 'munki.keychain' created (if necessary) and unlocked
- CA from ca.pem trusted
- Client cert from client.pem imported
- Identity preference for specific site created and configured with client cert
This should work from OS X 10.5.4 - 10.9+
"""
import os.path, subprocess, re
def pref(something):
return None
def security(verb_name, *args):
cmd = ['/usr/bin/security', verb_name] + list(args)
proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = proc.communicate()
return (proc.returncode, output, err)
if True:
site_addr = 'https://trustedsite.dev/'
client_cert = '/Users/mike/Desktop/certs/client.pem'
ca_cert = '/Users/mike/Desktop/certs/ca.pem'
# Defined information for the keychain
default_chain = 'munki.keychain'
default_pass = 'munki'
keychain_name = (pref('keychainName') or default_chain).strip()
keychain_pass = (pref('keychainPass') or default_pass).strip()
# If we have an odd path that appears to be all directory and no file name, revert to default filename
if not os.path.basename(keychain_name):
keychain_name = default_chain
# Check to make sure it's just a simple file name, no directory information
if os.path.dirname(keychain_name):
# All keychains should be a relative filename, so we'll drop down to the base name
keychain_name = os.path.basename(keychain_name).strip() or default_chain
# Correct the filename to include '.keychain' if not already present
if not keychain_name.lower().endswith('.keychain'):
keychain_name += '.keychain'
# Check to see if the file already exists at ~/Library/Keychains/name
abs_keychain_name = os.path.realpath(os.path.join(os.path.expanduser('~/Library/Keychains'), keychain_name))
if not os.path.exists(abs_keychain_name):
# Keychain doesn't appear to be present, create it
(code, output, err) = security('create-keychain', '-p', keychain_pass, keychain_name)
# Check to make sure the keychain is in the search path
(code, output, err) = security('list-keychains', '-d', 'user')
# Split the output and strip it of whitespace and leading/trailing quotes, the result are absolute paths to keychains
# Preserve the order in case we need to append to them
search_keychains = [x.strip().strip('"') for x in output.split('\n') if x.strip()]
if not abs_keychain_name in search_keychains:
# Keychain is not in the search paths
search_keychains.append(abs_keychain_name)
(code, output, err) = security('list-keychains', '-d', 'user', '-s', *search_keychains)
# Configure the keychain as unlocked and non-locking
(code, output, err) = security('unlock-keychain', '-p', keychain_pass, keychain_name)
(code, output, err) = security('set-keychain-settings', keychain_name)
# Just blanket import the client cert - if it's already there, it'll return a warning we'll ignore
if os.path.exists(client_cert):
(code, output, err) = security('import', client_cert, '-k', keychain_name)
# Same goes for the CA cert
if os.path.exists(ca_cert):
(code, output, err) = security('add-trusted-cert', '-k', keychain_name, ca_cert)
# Set up an identity if it doesn't exist already for our site
# First we need to find the existing identity in our keychain
(code, output, err) = security('find-identity', keychain_name)
if ' 1 identities found' in output:
# We have a solitary match and can configure / verify the identity preference
id_hash = re.findall(r'\W+1\)\W+([0-9A-F]+)\W', output)[0]
# First, check to see if we have an identity already
(code, output, err) = security('get-identity-preference', '-s', site_addr, '-Z')
create_identity = False
if code == 0:
# No error, we found an identity
# Check if it matches the one we want
current_hash = re.match(r'SHA-1 hash:\W+([A-F0-9]+)\W', output).group(1)
if id_hash != current_hash:
# We only care if there's a different hash being used.
# Remove the incorrect one.
(code, output, err) = security('set-identity-preference', '-n', '-s', site_addr)
# Signal that we want to create a new identity preference
create_identity = True
elif id_hash not in output:
# Non-zero error code and hash not detected in output
# Signal that we want to create a new identity preference
create_identity = True
if create_identity:
# This code was moved into a common block that both routes could access as it's a little complicated.
# security will only create an identity preference in the default keychain - which means a default has to be defined/selected
# For normal users, this is login.keychain - but for root there's no login.keychain and no default keychain configured
# So we'll handle the case of no default keychain (just set one) as well as pre-existing default keychain
# (in which case we set it long enough to create the preference, then set it back)
(code, output, err) = security('default-keychain', '-d', 'user')
if code == 0:
# One is defined, remember the path
default_keychain = [x.strip().strip('"') for x in output.split('\n') if x.strip()][0]
else:
default_keychain = None
# Temporarily assign the default keychain to ours
(code, output, err) = security('default-keychain', '-d', 'user', '-s', abs_keychain_name)
# Create the identity preference
(code, output, err) = security('set-identity-preference', '-s', site_addr, '-Z', id_hash, keychain_name)
if default_keychain:
# We originally had a different one, set it back
(code, output, err) = security('default-keychain', '-d', 'user', '-s', default_keychain)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment