diff --git a/letsencrypt/client/apache_configurator.py b/letsencrypt/client/apache_configurator.py index c8dd4df9a..f5ebf6be7 100644 --- a/letsencrypt/client/apache_configurator.py +++ b/letsencrypt/client/apache_configurator.py @@ -13,6 +13,7 @@ from Crypto import Random from letsencrypt.client import augeas_configurator from letsencrypt.client import CONFIG from letsencrypt.client import crypto_util +from letsencrypt.client import errors from letsencrypt.client import le_util from letsencrypt.client import logger @@ -1536,7 +1537,7 @@ LogLevel warn \n\ self.add_dir("/files" + mainConfig, "Include", CONFIG.APACHE_CHALLENGE_CONF) - def dvsni_create_chall_cert(self, name, ext, nonce, key): + def dvsni_create_chall_cert(self, name, ext, nonce, key_file): """Creates DVSNI challenge certifiate. Certificate created at dvsni_get_cert_file(nonce) @@ -1544,14 +1545,20 @@ LogLevel warn \n\ :param nonce: hex form of nonce :type nonce: str - :param key: file path to key + :param key_file: absolute path to key file :type key: str """ + try: + with open(key_file, 'r') as key_fd: + key_str = key_fd.read() + except IOError: + raise LetsEncryptDvsniError("Unable to load key file: %s" % key) + self.register_file_creation(True, self.dvsni_get_cert_file(nonce)) cert_pem = crypto_util.make_ss_cert( - key, [nonce + CONFIG.INVALID_EXT, name, ext]) + key_str, [nonce + CONFIG.INVALID_EXT, name, ext]) with open(self.dvsni_get_cert_file(nonce), 'w') as f: f.write(cert_pem) diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 437d3d815..6b625fc37 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -33,7 +33,7 @@ class Client(object): """ACME protocol client.""" def __init__(self, ca_server, cert_signing_request=None, - private_key=None, use_curses=True): + private_key=None, private_key_file=None, use_curses=True): """ :param ca_server: Certificate authority server @@ -45,6 +45,9 @@ class Client(object): :param private_key: Contents of the private key :type private_key: str + :param private_key_file: absolute path to private_key + :type private_key_file: str + :param use_curses: Use curses UI :type use_curses: bool @@ -61,6 +64,7 @@ class Client(object): self.server = ca_server self.csr = cert_signing_request self.privkey = private_key + self.privkey_file = private_key_file # TODO: Figure out all exceptions from this function try: @@ -396,7 +400,6 @@ class Client(object): else: self.choose_certs(certs) elif code == display.HELP: - print code, tag, cert display.more_info_cert(cert) self.choose_certs(certs) else: @@ -431,7 +434,7 @@ class Client(object): for host in vhost: self.config.deploy_cert(host, os.path.abspath(cert_file), - os.path.abspath(self.privkey), + os.path.abspath(self.privkey_file), cert_chain_abspath) # Enable any vhost that was issued to, but not enabled if not host.enabled: @@ -542,17 +545,17 @@ class Client(object): for row in csvreader: idx = int(row[0]) + 1 csvwriter = csv.writer(csvfile) - csvwriter.writerow([str(idx), cert_file, self.privkey]) + csvwriter.writerow([str(idx), cert_file, self.privkey_file]) else: with open(list_file, 'wb') as csvfile: csvwriter = csv.writer(csvfile) - csvwriter.writerow(["0", cert_file, self.privkey]) + csvwriter.writerow(["0", cert_file, self.privkey_file]) - shutil.copy2(self.privkey, + shutil.copy2(self.privkey_file, os.path.join( CONFIG.CERT_KEY_BACKUP, - os.path.basename(self.privkey) + "_" + str(idx))) + os.path.basename(self.privkey_file) + "_" + str(idx))) shutil.copy2(cert_file, os.path.join( CONFIG.CERT_KEY_BACKUP, @@ -628,7 +631,7 @@ class Client(object): challenge_objs.append({ "type": "dvsni", "listSNITuple": sni_todo, - "dvsni_key": os.path.abspath(self.privkey), + "dvsni_key": os.path.abspath(self.privkey_file), }) challenge_obj_indices.append(sni_satisfies) logger.debug(sni_todo) @@ -663,7 +666,9 @@ class Client(object): os.path.join(CONFIG.KEY_DIR, "key-letsencrypt.pem"), 0o600) key_f.write(key_pem) key_f.close() - logger.info("Generating key: %s" % key_filename) + + self.privkey_file = key_filename + logger.info("Generating key: %s" % self.privkey_file) else: key_pem = self.privkey diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index 0c46bea6d..f2bb4c6f7 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -22,7 +22,7 @@ def b64_cert_to_pem(b64_der_cert): le_util.jose_b64decode(b64_der_cert)).as_pem() -def create_sig(msg, key_file, nonce=None, nonce_len=CONFIG.NONCE_SIZE): +def create_sig(msg, key_str, nonce=None, nonce_len=CONFIG.NONCE_SIZE): """Create signature with nonce prepended to the message. TODO: Change this over to M2Crypto... PKey @@ -32,9 +32,9 @@ def create_sig(msg, key_file, nonce=None, nonce_len=CONFIG.NONCE_SIZE): :param msg: Message to be signed :type msg: Anything with __str__ method - :param key_file: Path to a file containing RSA key. Accepted formats + :param key_str: Key in string form. Accepted formats are the same as for `Crypto.PublicKey.RSA.importKey`. - :type key_file: str + :type key_str: str :param nonce: Nonce to be used. If None, nonce of `nonce_len` size will be randomly genereted. @@ -48,7 +48,7 @@ def create_sig(msg, key_file, nonce=None, nonce_len=CONFIG.NONCE_SIZE): """ msg = str(msg) - key = Crypto.PublicKey.RSA.importKey(open(key_file).read()) + key = Crypto.PublicKey.RSA.importKey(key_str) nonce = Random.get_random_bytes(nonce_len) if nonce is None else nonce msg_with_nonce = nonce + msg @@ -96,12 +96,12 @@ def make_key(bits=CONFIG.RSA_KEY_SIZE): return key.exportKey(format='PEM') -def make_csr(key_file, domains): +def make_csr(key_str, domains): """ Returns new CSR in PEM and DER form using key_file containing all domains """ assert domains, "Must provide one or more hostnames for the CSR." - rsa_key = M2Crypto.RSA.load_key(key_file) + rsa_key = M2Crypto.RSA.load_key_string(key_str) pubkey = M2Crypto.EVP.PKey() pubkey.assign_rsa(rsa_key) @@ -128,13 +128,14 @@ def make_csr(key_file, domains): return csr.as_pem(), csr.as_der() -def make_ss_cert(key_file, domains): +def make_ss_cert(key_str, domains): """Returns new self-signed cert in PEM form. - Uses key_file and contains all domains. + Uses key_str and contains all domains. """ assert domains, "Must provide one or more hostnames for the CSR." - rsa_key = M2Crypto.RSA.load_key(key_file) + + rsa_key = M2Crypto.RSA.load_key_string(key_str) pubkey = M2Crypto.EVP.PKey() pubkey.assign_rsa(rsa_key) diff --git a/letsencrypt/client/errors.py b/letsencrypt/client/errors.py index 288da35cf..b0f5b769b 100644 --- a/letsencrypt/client/errors.py +++ b/letsencrypt/client/errors.py @@ -3,3 +3,7 @@ class LetsEncryptClientError(Exception): """Generic Let's Encrypt client error.""" + + +class LetsEncryptDvsniError(Exception): + """Let's Encrypt DVSNI error.""" diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index 465e3a781..d38762925 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -28,10 +28,10 @@ def main(): nargs="+") parser.add_argument("-s", "--server", dest="server", help="The ACME CA server address.") - parser.add_argument("-p", "--privkey", dest="privkey", type=read_file, + parser.add_argument("-p", "--privkey", dest="privkey_tup", type=read_file, help="Path to the private key file for certificate " "generation.") - parser.add_argument("-c", "--csr", dest="csr", type=read_file, + parser.add_argument("-c", "--csr", dest="csr_tup", type=read_file, help="Path to the certificate signing request file " "corresponding to the private key file. The " "private key file argument is required if this " @@ -63,7 +63,7 @@ def main(): args = parser.parse_args() # Enforce '--privkey' is set along with '--csr'. - if args.csr and not args.privkey: + if args.csr_tup and not args.privkey_tup: parser.error("private key file (--privkey) must be specified along{0} " "with the certificate signing request file (--csr)" .format(os.linesep)) @@ -83,7 +83,14 @@ def main(): server = args.server is None and CONFIG.ACME_SERVER or args.server - acme = client.Client(server, args.csr, args.privkey, args.curses) + # Prepare for init of Client + if args.privkey_tup is None: + args.privkey_tup = (None, None) + if args.csr_tup is None: + args.csr_tup = (None, None) + + acme = client.Client(server, args.csr_tup[1], args.privkey_tup[1], + args.privkey_tup[0], args.curses) if args.revoke: acme.list_certs_keys() else: @@ -103,7 +110,7 @@ def read_file(filename): """ try: - return file(filename, 'rU').read() + return filename, file(filename, 'rU').read() except IOError as exc: raise argparse.ArgumentTypeError(exc.strerror)