'''
Certificate Management Functions
'''
import os
import shutil
import logging
import tarfile
import datetime
import random
import re
import ConfigParser
import yaml
import OpenSSL
import pybash
import esg_functions
import esg_property_manager
import esg_keystore_manager
from plumbum.commands import ProcessExecutionError
logger = logging.getLogger("esgf_logger" + "." + __name__)
current_directory = os.path.join(os.path.dirname(__file__))
NO_LIST = ["n", "no", "N", "No", "NO"]
YES_LIST = ["y", "yes", "Y", "Yes", "YES"]
with open(os.path.join(os.path.dirname(__file__), os.pardir, 'esg_config.yaml'), 'r') as config_file:
config = yaml.load(config_file)
# ----------------------------------------------
# Certificate Creation functions (pyopenssl)
# ---------------------------------------------
[docs]def create_key_pair(key_type, bits=4096):
'''source: https://github.com/pyca/pyopenssl/blob/master/examples/certgen.py'''
"""
Create a public/private key pair.
Arguments: key_type - Key type, must be one of TYPE_RSA and TYPE_DSA
bits - Number of bits to use in the key
Returns: The public/private key pair in a PKey object
"""
pkey = OpenSSL.crypto.PKey()
pkey.generate_key(key_type, bits)
return pkey
[docs]def create_cert_request(pkey, digest="sha256", **name):
'''source: https://github.com/pyca/pyopenssl/blob/master/examples/certgen.py'''
"""
Create a certificate request.
Arguments: pkey - The key to associate with the request
digest - Digestion method to use for signing, default is sha256
**name - The name of the subject of the request, possible
arguments are:
C - Country name
ST - State or province name
L - Locality name
O - Organization name
OU - Organizational unit name
CN - Common name
emailAddress - E-mail address
Returns: The certificate request in an X509Req object
"""
req = OpenSSL.crypto.X509Req()
subj = req.get_subject()
setattr(subj, "O", name["O"])
setattr(subj, "OU", name["OU"])
setattr(subj, "CN", name["CN"])
req.set_pubkey(pkey)
req.sign(pkey, digest)
return req
[docs]def create_certificate(req, issuer_cert_key, serial, validity_period,
digest="sha256"):
"""
Generate a certificate given a certificate request.
Arguments: req - Certificate request to use
issuer_cert - The certificate of the issuer
issuer_key - The private key of the issuer
serial - Serial number for the certificate
not_before - Timestamp (relative to now) when the certificate
starts being valid
not_after - Timestamp (relative to now) when the certificate
stops being valid
digest - Digest method to use for signing, default is sha256
Returns: The signed certificate in an X509 object
"""
issuer_cert, issuer_key = issuer_cert_key
not_before, not_after = validity_period
cert = OpenSSL.crypto.X509()
cert.set_serial_number(serial)
cert.gmtime_adj_notBefore(not_before)
cert.gmtime_adj_notAfter(not_after)
cert.set_issuer(issuer_cert.get_subject())
cert.set_subject(req.get_subject())
cert.set_pubkey(req.get_pubkey())
cert.sign(issuer_key, digest)
return cert
# ------------------------------------
# Certificate functions
# ------------------------------------
[docs]def set_commercial_ca_paths():
'''Set locations of the commercial CA key and cert'''
try:
commercial_key_path = esg_property_manager.get_property("commercial.key.path")
except ConfigParser.NoOptionError:
commercial_key_path = raw_input("Enter the file path of the commercial key: ")
try:
commercial_cert_path = esg_property_manager.get_property("commercial.cert.path")
except ConfigParser.NoOptionError:
commercial_cert_path = raw_input("Enter the file path of the commercial cert: ")
try:
ca_chain_path = esg_property_manager.get_property("cachain.path")
except ConfigParser.NoOptionError:
ca_chain_path = raw_input("Enter the file path of the ca chain: ")
return (commercial_key_path, commercial_cert_path, ca_chain_path)
# ------------------------------------
# Install Cert functions
# ------------------------------------
[docs]def check_for_commercial_ca():
'''Checks if Commerical CA directory has been created; asks user if they would like proceed with
Commercial CA installation if directory is found'''
print "*******************************"
print "Checking for Commercial CA"
print "******************************* \n"
try:
commercial_ca_setup = esg_property_manager.get_property("install.signed.certs")
except ConfigParser.NoOptionError:
commercial_ca_setup = raw_input(
"Do you have a commercial CA that you want to install [Y/n]: ") or "yes"
if commercial_ca_setup in YES_LIST:
commercial_key_path, commercial_cert_path, ca_chain_path = set_commercial_ca_paths()
# Backup existing certs
backup_existing_certs()
esg_keystore_manager.install_keypair(commercial_key_path, commercial_cert_path)
print "Local installation of certs complete."
[docs]def install_local_certs(node_type_list, firstrun=None):
'''Installs local certs to /var/lib/globus-connect-server/myproxy-ca and /etc/grid-security'''
if firstrun:
file_list = ("cakey.pem", "cacert.pem", "hostcert.pem",
"hostkey.pem", "myproxy-server.config")
certdir = "/etc/tempcerts"
else:
if "IDP" in node_type_list:
file_list = ("cakey.pem", "cacert.pem", "hostcert.pem", "hostkey.pem")
else:
file_list = ("hostcert.pem", "hostkey.pem")
certdir = "/etc/esgfcerts"
with pybash.pushd(certdir):
for file_name in file_list:
if not os.path.exists(file_name):
raise OSError(
"File {} is not found in {}; Please place it there and reexecute esg_node.py --install-local-certs".format(file_name, certdir))
if "IDP" in node_type_list:
try:
cert_obj = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, open("cacert.pem").read())
except OpenSSL.crypto.Error:
logger.exception("Certificate is not correct.")
local_hash = esg_functions.convert_hash_to_hex(cert_obj.subject_name_hash())
globus_pack = "globus_simple_ca_{}_setup-0.tar.gz".format(local_hash)
if not os.path.exists(globus_pack):
raise OSError(
"File {} is not found in {}; Please place it there and reexecute esg_node.py --install-local-certs".format(globus_pack, certdir))
if "IDP" in node_type_list:
shutil.copyfile("cacert.pem", "/var/lib/globus-connect-server/myproxy-ca/cacert.pem")
shutil.copyfile(
"cakey.pem", "/var/lib/globus-connect-server/myproxy-ca/private/cakey.pem")
shutil.copyfile(
globus_pack, "/var/lib/globus-connect-server/myproxy-ca/{}".format(globus_pack))
shutil.copyfile(globus_pack, "/etc/grid-security/certificates/{}".format(globus_pack))
if os.path.exists("hostkey.pem"):
shutil.copyfile("hostkey.pem", "/etc/grid-security/hostkey.pem")
os.chmod("/etc/grid-security/hostkey.pem", 0600)
if os.path.exists("hostcert.pem"):
shutil.copyfile("hostcert.pem", "/etc/grid-security/hostcert.pem")
print "Local installation of certs complete."
# ----------------------------------------------
# CSR functions
# ---------------------------------------------
[docs]def generate_ssl_key_and_csr(private_key="/usr/local/tomcat/hostkey.pem", public_cert_req=None):
'''Create a SSL keypair and CSR'''
print "Generating private host key... "
key_pair = create_key_pair(OpenSSL.crypto.TYPE_RSA, 4096)
with open(private_key, "wt") as key_file_handle:
key_file_handle.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_pair))
os.chmod(private_key, 0400)
print "Generating Certificate Signing Request (csr)... "
if not public_cert_req:
public_cert_req = "/usr/local/tomcat/{}-esg-node.csr".format(esg_functions.get_esgf_host())
public_cert_dn = extract_keystore_dn()
if public_cert_dn:
careq = create_cert_request(private_key, public_cert_dn)
else:
careq = create_cert_request(private_key, O="ESGF", OU="ESGF.ORG",
CN=esg_functions.get_esgf_host())
print "Generating 30 day temporary self-signed certificate... "
newcert = create_certificate(careq, (careq, private_key), random.randint(1, pow(2,30)), (0, 30*60*24*365*5))
with open('/usr/local/tomcat/{}-esg-node.pem'.format(esg_functions.get_esgf_host()), 'w') as ca_file:
ca_file.write(
OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, newcert).decode('utf-8')
)
print "--------------------------------------------------------"
print "In Directory: /usr/local/tomcat"
print "Generated private key: {}".format(private_key)
print "Generated certificate: /usr/local/tomcat/{}-esg-node.pem".format(
esg_functions.get_esgf_host())
print "Please obtain and install appropriate certificates at the earliest. Execute esg_node.py --cert-howto for details."
#print "Then run %> esg-node --install-ssl-keypair <signed cert> <private key> (use --help for details)"
print "--------------------------------------------------------"
[docs]def generate_esgf_csrs(node_type_list):
'''Generates a CSR to be signed by a ESGF CA'''
if "IDP" in node_type_list:
key_pair = create_key_pair(OpenSSL.crypto.TYPE_RSA, 4096)
with open("/etc/esgfcerts/cakey.pem", "wt") as key_file_handle:
key_file_handle.write(OpenSSL.crypto.dump_privatekey(
OpenSSL.crypto.FILETYPE_PEM, key_pair))
key_string = open("/etc/esgfcerts/cakey.pem", 'rt').read()
key_object = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_string)
careq = create_cert_request(key_object, O="ESGF", OU="ESGF.ORG",
CN=esg_functions.get_esgf_host()+"-CA")
with open('/etc/esgfcerts/cacert_req.csr', 'w') as csr:
csr.write(
OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, careq).decode('utf-8')
)
print "Successfully generated request for a simpleCA CA certificate: /etc/esgfcerts/cacert_req.csr"
print "You are strongly advised to obtain and install commercial CA issued certificates for the web container."
key_pair = create_key_pair(OpenSSL.crypto.TYPE_RSA, 4096)
with open("/etc/esgfcerts/hostkey.pem", "wt") as key_file_handle:
key_file_handle.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_pair))
key_string = open("/etc/esgfcerts/hostkey.pem", 'rt').read()
key_object = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_string)
careq = create_cert_request(key_object, O="ESGF", OU="ESGF.ORG", CN=esg_functions.get_esgf_host())
with open('/etc/esgfcerts/hostcert_req.csr', 'w') as csr:
csr.write(
OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, careq).decode('utf-8')
)
print "Successfully generated request for a simpleCA CA certificate: /etc/esgfcerts/hostcert_req.csr"
print "Please mail the csr files for signing to Lukasz Lacinski <lukasz@uchicago.edu>, Prashanth Dwarakanath <pchengi@nsc.liu.se>, or Sebastien Denvil <sebastien.denvil@ipsl.jussieu.fr>"
print "When you receive the signed certificate pack, untar all files into /etc/esgfcerts and execute esg_node.py --install-local-certs"
print "If you also want to install the local certs for the tomcat web-container, execute esg_node.py --install-keypair /etc/esgfcerts/hostcert.pem /etc/esgfcerts/hostkey.pem"
print "When prompted for the cachain file, specify /etc/esgfcerts/cachain.pem"
[docs]def generate_esgf_csrs_ext(node_type):
'''Generates a CSR to be signed by a ESGF CA'''
print "Are you requesting certs for an index-node or a datanode? (index/data)?"
if node_type.lower() != "index" or node_type != "data":
print "Please specify index or data as node type"
return
req_node_hostname = raw_input("Enter FQDN of node you are requesting certificates for")
pybash.mkdir_p("/etc/extcsrs")
cert_files = ['hostkey.pem', 'hostcert_req.csr']
if node_type == "index":
cert_files.append('cacert_req.csr')
cert_files.append('cakey.pem')
key_pair = create_key_pair(OpenSSL.crypto.TYPE_RSA, 4096)
with open("/etc/extcsrs/cakey.pem", "wt") as key_file_handle:
key_file_handle.write(OpenSSL.crypto.dump_privatekey(
OpenSSL.crypto.FILETYPE_PEM, key_pair))
key_string = open("/etc/extcsrs/cakey.pem", 'rt').read()
key_object = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_string)
careq = create_cert_request(key_object, O="ESGF", OU="ESGF.ORG",
CN=esg_functions.get_esgf_host()+"-CA")
with open('/etc/extcsrs/cacert_req.csr', 'w') as csr:
csr.write(
OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, careq).decode('utf-8')
)
print "Successfully generated request for a simpleCA CA certificate: /etc/extcsrs/cacert_req.csr"
print "You are strongly advised to obtain and install commercial CA issued certificates for the web container."
key_pair = create_key_pair(OpenSSL.crypto.TYPE_RSA, 4096)
with open("/etc/extcsrs/hostkey.pem", "wt") as key_file_handle:
key_file_handle.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_pair))
key_string = open("/etc/extcsrs/hostkey.pem", 'rt').read()
key_object = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, key_string)
careq = create_cert_request(key_object, O="ESGF", OU="ESGF.ORG", CN=req_node_hostname)
with open('/etc/extcsrs/hostcert_req.csr', 'w') as csr:
csr.write(
OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, careq).decode('utf-8')
)
print "Successfully generated request for a simpleCA CA certificate: /etc/extcsrs/hostcert_req.csr"
with pybash.pushd("/etc/extcsrs"):
try:
with tarfile.open(req_node_hostname, "w:tgz") as tar:
for file_name in cert_files:
tar.add(file_name)
print "A copy of the generated keys and CSRs has been saved as /etc/extcsrs/{}.tgz".format(
req_node_hostname)
except Exception:
print "ERROR: Problem with creating backup archive: {}".format(req_node_hostname)
print "Please mail the csr files for signing to Lukasz Lacinski <lukasz@uchicago.edu>, Prashanth Dwarakanath <pchengi@nsc.liu.se>, or Sebastien Denvil <sebastien.denvil@ipsl.jussieu.fr>"
print "When you receive the signed certificate pack, untar all files into /etc/esgfcerts and execute esg_node.py --install-local-certs"
print "If you also want to install the local certs for the tomcat web-container, execute esg_node.py --install-keypair /etc/esgfcerts/hostcert.pem /etc/esgfcerts/hostkey.pem"
print "When prompted for the cachain file, specify /etc/esgfcerts/cachain.pem"
# ------------------------------------
# Utility functions
# ------------------------------------
[docs]def check_cert_expiry(cert_name):
'''Check if a certificate is valid or has expired'''
if not os.path.exists(cert_name):
print "Certficate file {} does not exists".format(cert_name)
return
cert_data = open(cert_name).read()
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_data)
expire_date = x509.get_notAfter()
if x509.has_expired():
print "{} is expired".format(cert_name)
print "{} will expire {}".format(cert_name, str(expire_date))
[docs]def check_certificates():
'''Check the validity of the ESGF and Globus certificates'''
print "check_certificates..."
tomcat_cert_file = "{}/{}-esg-node.pem".format(
config["tomcat_conf_dir"], esg_functions.get_esgf_host())
check_cert_expiry(tomcat_cert_file)
from idp_node import globus
globus.globus_check_certificates()
[docs]def backup_existing_certs():
'''Backup existing SSL certs on system'''
if os.path.isfile("/etc/certs/hostcert.pem"):
shutil.copyfile("/etc/certs/hostcert.pem",
"/etc/certs/hostcert.pem.{date}.bak".format(date=str(datetime.date.today())))
if os.path.isfile("/etc/certs/hostkey.pem"):
shutil.copyfile("/etc/certs/hostkey.pem",
"/etc/certs/hostkey.pem.{date}.bak".format(date=str(datetime.date.today())))