예제 #1
0
        def new_opensslconf():
            """
            Create new openssl.conf configuration file.
            """
            #            print "new_opensslconf"
            _log.debug("__init__::new_opensslconf")
            for section in self.__class__.DEFAULT.keys():
                self.config.add_section(section)
                #            print "[{}]".format(section)
                hostname = socket.gethostname()
                for option in self.__class__.DEFAULT[section]:
                    if option == "0.organizationName":
                        value = self.domain
                    #TODO: use dynamic number of DNS entries instead of hardcoding the number
                    elif option == "DNS.1":
                        value = self.node_name
                    elif (option == "DNS.2") and len(self.hostnames) > 0:
                        value = self.hostnames[0]
                    elif (option == "DNS.3") and len(self.hostnames) > 1:
                        value = self.hostnames[1]
                    elif (option == "DNS.4") and len(self.hostnames) > 2:
                        value = self.hostnames[2]
                    elif option == "IP.1":
                        value = self.ip
                    elif option == "dir":
                        value = self.runtime_dir
                    elif section == 'req_distinguished_name' and option == 'commonName':
                        value = self.node_name
                    elif option == 'dnQualifier':
                        value = self.node_id
                    #The python cryptography and the pyOpensSSL packages does not support
                    #parsing the Attributes extension in a CSR, so instead it is stored
                    #outside of the CSR
    #                    elif option == 'challengePassword':
    #                        value = self.enrollment_password
                    else:
                        value = self.__class__.DEFAULT[section][option]

    #                    print "\t{}={}".format(option, value)
                    self.config.set(section, option, value)
            with open(self.configfile, 'wb') as configfd:
                self.config.write(configfd)
                configfd.close()
            confsort.reorder(self.configfile)
    def _new_opensslconf(self):
        """
        Create new openssl.conf configuration file.
        """
#            print "new_opensslconf"
        _log.debug("__init__::new_opensslconf")
        for section in self.__class__.DEFAULT.keys():
            self.config.add_section(section)
#            print "[{}]".format(section)
            hostname = socket.gethostname()
            for option in self.__class__.DEFAULT[section]:
                if option == "0.organizationName":
                    value = self.domain
                #TODO: use dynamic number of DNS entries instead of hardcoding the number
                elif option == "DNS.1":
                    value = self.node_name
                elif (option == "DNS.2") and len(self.hostnames)>0:
                    value = self.hostnames[0]
                elif (option == "DNS.3") and len(self.hostnames)>1:
                    value = self.hostnames[1]
                elif (option == "DNS.4") and len(self.hostnames)>2:
                    value = self.hostnames[2]
                elif option == "IP.1":
                    value = self.ip
                elif option == "dir":
                    value = self.runtime_dir
                elif section == 'req_distinguished_name' and option == 'commonName':
                    value = self.node_name
                elif option == 'dnQualifier':
                    value = self.node_id
                #The python cryptography and the pyOpensSSL packages does not support
                #parsing the Attributes extension in a CSR, so instead it is stored
                #outside of the CSR
#                    elif option == 'challengePassword':
#                        value = self.enrollment_password
                else:
                    value = self.__class__.DEFAULT[section][option]
#                    print "\t{}={}".format(option, value)
                self.config.set(section, option, value)
        with open(self.configfile, 'wb') as configfd:
            self.config.write(configfd)
            configfd.close()
        confsort.reorder(self.configfile)
class CA():
    """
    A openssl.conf configuration parser class.
    Create this object by pointing at the configuration file
    to be parsed.

    To access a previously known openssl configuration file.
    myconf = CA(configfile="/tmp/openssl.conf")

    or to create a new domain:
    myconf = CA(domain="mydomain")

    to access an existing known domain configuration use:
    myconf = CA(domain="myolddomain")
    """
    DEFAULT = {
        'v3_req': {
            'subjectAltName': 'email:move'
        },
        'req': {
            'distinguished_name': 'req_distinguished_name',
            'attributes': 'req_attributes',
            'prompt': 'no',
            'default_keyfile': 'privkey.pem',
            'default_bits': '2048'
        },
        'req_attributes': {},
        'req_distinguished_name': {
            '0.organizationName': 'domain',
            'commonName': 'runtime'
        },
        'ca': {
            'default_ca': 'CA_default'
        },
        'CA_default': {
            'dir': '~/.calvin/security/',
            'preserve': 'no',
            'crl_dir': '$dir/crl',
            'RANDFILE': '$dir/private/.rand',
            'certificate': '$dir/cacert.pem',
            'database': '$dir/index.txt',
            'private_dir': '$dir/private/',
            'new_certs_dir': '$dir/newcerts',
            'private_key': '$dir/private/ca.key',
            'runtimes_dir': '$dir/runtimes',
            'email_in_dn': 'no',
            'x509_extensions': 'usr_cert',
            'copy_extensions': 'copy',
            'certs': '$dir/certs',
            'default_days': '365',
            'policy': 'policy_any',
            'cert_opt': 'ca_default',
            'serial': '$dir/serial',
            'default_crl_days': '30',
            'name_opt': 'ca_default',
            'crl': '$dir/crl.pem',
            'default_md': 'sha256'
        },
        'v3_ca': {
            'basicConstraints': 'CA:true',
            'subjectKeyIdentifier': 'hash',
            'authorityKeyIdentifier': 'keyid:always,issuer:always'
        },
        'usr_cert': {
            'subjectKeyIdentifier': 'hash',
            'authorityKeyIdentifier': 'keyid,issuer',
            'basicConstraints': 'CA:false'
        },
        'policy_any': {
            'countryName': 'optional',
            'organizationalUnitName': 'optional',
            'organizationName': 'supplied',  # match
            'emailAddress': 'optional',
            'commonName': 'supplied',
            'dnQualifier': 'optional',
            'stateOrProvinceName': 'optional'
        }
    }

    # TODO Find out why the policy does not match equal org names.

    def __init__(self,
                 domain,
                 commonName=None,
                 security_dir=None,
                 force=False,
                 readonly=False):
        _log.debug("__init__")
        self.configfile = None
        self.commonName = commonName or 'runtime'
        self.config = ConfigParser.SafeConfigParser()
        self.config.optionxform = str
        self.enrollment_challenge_db = {}
        os.umask(0077)
        self.domain = domain
        _log.debug("CA init")
        security_path = _conf.get("security", "security_dir")
        if security_dir:
            self.configfile = os.path.join(security_dir, domain,
                                           "openssl.conf")
        elif security_path:
            self.configfile = os.path.join(security_path, domain,
                                           "openssl.conf")
        else:
            homefolder = get_home()
            self.configfile = os.path.join(homefolder, ".calvin", "security",
                                           domain, "openssl.conf")
        exist = os.path.isfile(self.configfile)
        if not exist and readonly:
            raise Exception(
                "Configuration file does not exist, create CA first")
        if exist and not force:
            self.configuration = self.parse_opensslconf()
            _log.debug("Configuration already exists")

#            print "Configuration already exists " \
#                  "using {}".format(self.configfile)
        else:
            _log.debug("Configuration does not exist, let's create CA")
            self.new_opensslconf()
            self.configuration = self.parse_opensslconf()
            #Generate keys and CA certiticate
            try:
                self.new_ca_credentials(security_dir=security_dir,
                                        force=False,
                                        readonly=False)
            except:
                _log.error("Creation of new CA credentials failed")
                raise
#            print "Made new configuration at " \
#                  "{}".format(self.configfile)
            self.cert_enrollment_update_db_file()

    def new_ca_credentials(self,
                           security_dir=None,
                           force=False,
                           readonly=False):
        """
        Generate keys, files and certificate
        for the new CA
        """
        from os import urandom
        _log.debug("new_ca_credentials")
        outpath = self.configuration["CA_default"]["new_certs_dir"]
        private = self.configuration["CA_default"]["private_dir"]
        crlpath = self.configuration["CA_default"]["crl_dir"]
        private_key = self.configuration["CA_default"]["private_key"]
        out = self.configuration["CA_default"]["certificate"]

        password_file = os.path.join(private, "ca_password")

        os.umask(0077)
        try:
            os.mkdir(crlpath, 0700)
        except OSError:
            pass

        try:
            os.mkdir(outpath, 0700)
        except OSError:
            pass

        try:
            os.mkdir(private, 0700)
        except OSError:
            pass

        certificate.touch(self.configuration["CA_default"]["database"])
        serialfd = open(self.configuration["CA_default"]["serial"], 'w')
        serialfd.write("1000")
        serialfd.close()

        organization = self.domain
        commonname = self.commonName
        subject = "/O={}/CN={}".format(organization, commonname)
        # Generate a password for protection of the private key,
        # store it in the password file
        password = self.generate_password(20)
        try:
            with open(password_file, 'w') as fd:
                fd.write(password)
        except Exception as err:
            _log.err("Failed to write CA password to file")
            raise
        # Generate keys and CA certificate
        log = subprocess.Popen([
            "openssl", "req", "-new", "-config", self.configfile, "-x509",
            "-days", "1825", "-utf8", "-subj", subject, "-passout",
            "file:{}".format(password_file), "-out", out, "-keyout",
            private_key
        ],
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)
        stdout, stderr = log.communicate()
        _log.debug("new_ca_credentials")
        if log.returncode != 0:
            _log.error("CA req failed")
            raise IOError(stderr)
        return

    def new_opensslconf(self):
        """
        Create new openssl.conf configuration file.
        """
        directory = os.path.dirname(self.configfile)

        for section in self.__class__.DEFAULT.keys():
            self.config.add_section(section)
            for option in self.__class__.DEFAULT[section]:
                if option == "0.organizationName":
                    value = self.domain
                elif option == "dir":
                    value = directory
                elif section == 'req_distinguished_name' and option == 'commonName':
                    value = self.commonName
                else:
                    value = self.__class__.DEFAULT[section][option]
                self.config.set(section, option, value)

        try:
            os.makedirs(directory, 0700)
        except OSError, e:
            _log.error("Failed to create directory, err={}".format(e))
        with open(self.configfile, 'wb') as configfd:
            self.config.write(configfd)
            configfd.close()
        confsort.reorder(self.configfile)