mirror of
https://github.com/certbot/certbot.git
synced 2026-01-21 19:01:07 +03:00
232 lines
7.3 KiB
Python
232 lines
7.3 KiB
Python
"""Creates ACME accounts for server."""
|
|
import logging
|
|
import os
|
|
import re
|
|
|
|
import configobj
|
|
import zope.component
|
|
|
|
from acme import messages2
|
|
|
|
from letsencrypt import crypto_util
|
|
from letsencrypt import errors
|
|
from letsencrypt import interfaces
|
|
from letsencrypt import le_util
|
|
|
|
from letsencrypt.display import util as display_util
|
|
|
|
|
|
class Account(object):
|
|
"""ACME protocol registration.
|
|
|
|
:ivar config: Client configuration object
|
|
:type config: :class:`~letsencrypt.interfaces.IConfig`
|
|
:ivar key: Account/Authorized Key
|
|
:type key: :class:`~letsencrypt.le_util.Key`
|
|
|
|
:ivar str email: Client's email address
|
|
:ivar str phone: Client's phone number
|
|
|
|
:ivar regr: Registration Resource
|
|
:type regr: :class:`~acme.messages2.RegistrationResource`
|
|
|
|
"""
|
|
|
|
# Just make sure we don't get pwned
|
|
# Make sure that it also doesn't start with a period or have two consecutive
|
|
# periods <- this needs to be done in addition to the regex
|
|
EMAIL_REGEX = re.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+$")
|
|
|
|
def __init__(self, config, key, email=None, phone=None, regr=None):
|
|
le_util.make_or_verify_dir(
|
|
config.accounts_dir, 0o700, os.geteuid())
|
|
self.key = key
|
|
self.config = config
|
|
if email is not None and self.safe_email(email):
|
|
self.email = email
|
|
else:
|
|
self.email = None
|
|
self.phone = phone
|
|
|
|
self.regr = regr
|
|
|
|
@property
|
|
def uri(self):
|
|
"""URI link for new registrations."""
|
|
if self.regr is not None:
|
|
return self.regr.uri
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def new_authzr_uri(self): # pylint: disable=missing-docstring
|
|
if self.regr is not None:
|
|
return self.regr.new_authzr_uri
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def terms_of_service(self): # pylint: disable=missing-docstring
|
|
if self.regr is not None:
|
|
return self.regr.terms_of_service
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def recovery_token(self): # pylint: disable=missing-docstring
|
|
if self.regr is not None and self.regr.body is not None:
|
|
return self.regr.body.recovery_token
|
|
else:
|
|
return None
|
|
|
|
def save(self):
|
|
"""Save account to disk."""
|
|
le_util.make_or_verify_dir(
|
|
self.config.accounts_dir, 0o700, os.geteuid())
|
|
|
|
acc_config = configobj.ConfigObj()
|
|
acc_config.filename = os.path.join(
|
|
self.config.accounts_dir, self._get_config_filename(self.email))
|
|
|
|
acc_config.initial_comment = [
|
|
"DO NOT EDIT THIS FILE",
|
|
"Account information for %s under %s" % (
|
|
self._get_config_filename(self.email), self.config.server),
|
|
]
|
|
|
|
acc_config["key"] = self.key.file
|
|
acc_config["phone"] = self.phone
|
|
|
|
if self.regr is not None:
|
|
acc_config["RegistrationResource"] = {}
|
|
acc_config["RegistrationResource"]["uri"] = self.uri
|
|
acc_config["RegistrationResource"]["new_authzr_uri"] = (
|
|
self.new_authzr_uri)
|
|
acc_config["RegistrationResource"]["terms_of_service"] = (
|
|
self.terms_of_service)
|
|
|
|
regr_dict = self.regr.body.to_json()
|
|
acc_config["RegistrationResource"]["body"] = regr_dict
|
|
|
|
acc_config.write()
|
|
|
|
@classmethod
|
|
def _get_config_filename(cls, email):
|
|
return email if email is not None and email else "default"
|
|
|
|
@classmethod
|
|
def from_existing_account(cls, config, email=None):
|
|
"""Populate an account from an existing email."""
|
|
config_fp = os.path.join(
|
|
config.accounts_dir, cls._get_config_filename(email))
|
|
return cls._from_config_fp(config, config_fp)
|
|
|
|
@classmethod
|
|
def _from_config_fp(cls, config, config_fp):
|
|
try:
|
|
acc_config = configobj.ConfigObj(
|
|
infile=config_fp, file_error=True, create_empty=False)
|
|
except IOError:
|
|
raise errors.LetsEncryptClientError(
|
|
"Account for %s does not exist" % os.path.basename(config_fp))
|
|
|
|
if os.path.basename(config_fp) != "default":
|
|
email = os.path.basename(config_fp)
|
|
else:
|
|
email = None
|
|
phone = acc_config["phone"] if acc_config["phone"] != "None" else None
|
|
|
|
with open(acc_config["key"]) as key_file:
|
|
key = le_util.Key(acc_config["key"], key_file.read())
|
|
|
|
if "RegistrationResource" in acc_config:
|
|
acc_config_rr = acc_config["RegistrationResource"]
|
|
regr = messages2.RegistrationResource(
|
|
uri=acc_config_rr["uri"],
|
|
new_authzr_uri=acc_config_rr["new_authzr_uri"],
|
|
terms_of_service=acc_config_rr["terms_of_service"],
|
|
body=messages2.Registration.from_json(acc_config_rr["body"]))
|
|
else:
|
|
regr = None
|
|
|
|
return cls(config, key, email, phone, regr)
|
|
|
|
@classmethod
|
|
def get_accounts(cls, config):
|
|
"""Return all current accounts.
|
|
|
|
:param config: Configuration
|
|
:type config: :class:`letsencrypt.interfaces.IConfig`
|
|
|
|
"""
|
|
try:
|
|
filenames = os.listdir(config.accounts_dir)
|
|
except OSError:
|
|
return []
|
|
|
|
accounts = []
|
|
for name in filenames:
|
|
# Not some directory ie. keys
|
|
config_fp = os.path.join(config.accounts_dir, name)
|
|
if os.path.isfile(config_fp):
|
|
accounts.append(cls._from_config_fp(config, config_fp))
|
|
|
|
return accounts
|
|
|
|
@classmethod
|
|
def from_prompts(cls, config):
|
|
"""Generate an account from prompted user input.
|
|
|
|
:param config: Configuration
|
|
:type config: :class:`letsencrypt.interfaces.IConfig`
|
|
|
|
:returns: Account or None
|
|
:rtype: :class:`letsencrypt.account.Account`
|
|
|
|
"""
|
|
while True:
|
|
code, email = zope.component.getUtility(interfaces.IDisplay).input(
|
|
"Enter email address (optional, press Enter to skip)")
|
|
|
|
if code == display_util.OK:
|
|
try:
|
|
return cls.from_email(config, email)
|
|
except errors.LetsEncryptClientError:
|
|
continue
|
|
else:
|
|
return None
|
|
|
|
@classmethod
|
|
def from_email(cls, config, email):
|
|
"""Generate a new account from an email address.
|
|
|
|
:param config: Configuration
|
|
:type config: :class:`letsencrypt.interfaces.IConfig`
|
|
|
|
:param str email: Email address
|
|
|
|
:raises letsencrypt.errors.LetsEncryptClientError: If invalid
|
|
email address is given.
|
|
|
|
"""
|
|
if not email or cls.safe_email(email):
|
|
email = email if email else None
|
|
|
|
le_util.make_or_verify_dir(
|
|
config.account_keys_dir, 0o700, os.geteuid())
|
|
key = crypto_util.init_save_key(
|
|
config.rsa_key_size, config.account_keys_dir,
|
|
cls._get_config_filename(email))
|
|
return cls(config, key, email)
|
|
|
|
raise errors.LetsEncryptClientError("Invalid email address.")
|
|
|
|
@classmethod
|
|
def safe_email(cls, email):
|
|
"""Scrub email address before using it."""
|
|
if cls.EMAIL_REGEX.match(email):
|
|
return not email.startswith(".") and ".." not in email
|
|
else:
|
|
logging.warn("Invalid email address.")
|
|
return False
|