Internal Documentation¶
See also: Module Index
- serles.create_app()¶
initialize web app
This function should be passed to the WSGI server.
- class serles.backends.ejbca.EjbcaBackend(config)¶
Serles Backend for EJBCA (Community edition compatible)
Uses the EJBCA SOAP API to request certificates. Recommended setup on the EJBCA side:
A Certificate Profile for ACME-issued certificates, e.g. “ACMEServerProfile”. Must have an Extended Key Usage of “Server Authentication”. Should have relatively short Validity duration.
An End Entity Profile, e.g. “ACMEEndIdentityProfile”. Uses the Server Certificate Profile mentioned above and should allow for a number of DNS Name Subject Alternative Names.
To connect to the API, the following setup is used:
A Certificate Profile for authenticating EjbcaBackend with EJBCA’s SOAP API, e.g. “APIClientProfile”. Must have an Extended Key Usage of “Client Authentication”.
An End Entity Profile for client authentication, e.g. “APIClientIdentityProfile”. Uses the Client Certificate Profile mentioned above.
A concrete End Entity for EjbcaBackend, e.g. “client01”. Uses the Client Entity Profile mentioned above. Should have its common name same as its user name.
A certificate must be issued for this entity and its location stored in config.ini.
An Administrator Role for API clients, e.g. “ACMEUser”. From advanced mode, requires access to the Rules (<> denote variables)
/administrator/ca_functionality/create_certificate/ra_functionality/create_end_entity/ra_functionality/edit_end_entity/ca/<NAME_OF_CA>(e.g./ca/ACMECA)/endentityprofilesrules/<END_ENTITY_PROFILE>/create_end_entity(e.g./endentityprofilesrules/ACMEEndIdentityProfile/create_end_entity/)/endentityprofilesrules/<END_ENTITY_PROFILE>/edit_end_entity(e.g./endentityprofilesrules/ACMEEndIdentityProfile/edit_end_entity/)
Ensure the client entity is in the correct Administrator Role (e.g. via CN).
- serles.backends.ejbca.pkcs7_to_pem_chain(pkcs7_input)¶
Converts a PKCS#7 cert chain to PEM format.
- Parameters:
pkcs7_input (bytes) – the PKCS#7 chain as stored in the database.
- Returns:
PEM encoded certificate chain as expected by ACME clients.
- Return type:
str
- class serles.backends.base.Backend(config)¶
Abstract Base Backend
Inherit from this and implement
sign(), and optionally__init__()to use this.
- class serles.backends.certbot.Backend(config)¶
Serles Backend for certbot
Backend will pass the certificate signing request to an already configured certbot installation.
- Example use cases:
Want actual trusted certs without using an internal CA (you want it to work in browsers/etc. out of the box)
Don’t want the services externally exposed (internal services/development only)
Don’t want the internal services having control over your external DNS (random developers/users)
Have an intermediate server running serles that you are willing to grant external DNS update rights to.
This allows any internal entity to transparently use serles ACME CA with http-01 validation, but the actual signing requests are delegated to external ACME CA.
Contributed by Nathan Neulinger.
- class serles.models.Account(**kwargs)¶
To avoid having to send the large public key for each request, a client registers an Account and identifies itself using the key id. With Let’s Encrypt, accounts persist over a long time, and some clients will try to keep using it. Certbot is especially bad at this, as it fails when the account it expects doesn’t exist. This object also stores an optional contact email address, which we pass to the backend (e.g. for notifications regarding certificate expiry).
- query: t.ClassVar[Query]¶
A SQLAlchemy query for a model. Equivalent to
db.session.query(Model). Can be customized per-model by overridingquery_class.Warning
The query interface is considered legacy in SQLAlchemy. Prefer using
session.execute(select())instead.
- class serles.models.AccountStatus(*values)¶
- class serles.models.Authorization(**kwargs)¶
For each Identifier (domain name) the client requested in an Order, there is a Authorization. To obtain a certificate, the client must satisfy all of them. To satisfy an Authorization, the client can solve any one of the Challenges within (i.e., only 1 Challenge per Authorization is required).
- query: t.ClassVar[Query]¶
A SQLAlchemy query for a model. Equivalent to
db.session.query(Model). Can be customized per-model by overridingquery_class.Warning
The query interface is considered legacy in SQLAlchemy. Prefer using
session.execute(select())instead.
- class serles.models.AuthzStatus(*values)¶
- class serles.models.Certificate(**kwargs)¶
Pretty much what it says on the tin. Stored in the database for the short time between the client requesting finalization of their order and them fetching the cert from us.
- query: t.ClassVar[Query]¶
A SQLAlchemy query for a model. Equivalent to
db.session.query(Model). Can be customized per-model by overridingquery_class.Warning
The query interface is considered legacy in SQLAlchemy. Prefer using
session.execute(select())instead.
- class serles.models.Challenge(**kwargs)¶
A completed Challenge satisfies an Authorization. We support the HTTP-01 challenge type, which requires the client to temporarily serve a short text string on a location we decide.
- query: t.ClassVar[Query]¶
A SQLAlchemy query for a model. Equivalent to
db.session.query(Model). Can be customized per-model by overridingquery_class.Warning
The query interface is considered legacy in SQLAlchemy. Prefer using
session.execute(select())instead.
- class serles.models.ChallengeStatus(*values)¶
- class serles.models.ChallengeTypes(*values)¶
- class serles.models.Identifier(**kwargs)¶
An Identifier is essentially a domain name and some metadata.
- query: t.ClassVar[Query]¶
A SQLAlchemy query for a model. Equivalent to
db.session.query(Model). Can be customized per-model by overridingquery_class.Warning
The query interface is considered legacy in SQLAlchemy. Prefer using
session.execute(select())instead.
- class serles.models.IdentifierTypes(*values)¶
- class serles.models.Nonces(**kwargs)¶
To avoid replay attacks, each HTTP POST request must come with a nonce we issued.
- classmethod check(value)¶
returns True iff the nonce is valid (not yet used)
- query: t.ClassVar[Query]¶
A SQLAlchemy query for a model. Equivalent to
db.session.query(Model). Can be customized per-model by overridingquery_class.Warning
The query interface is considered legacy in SQLAlchemy. Prefer using
session.execute(select())instead.
- class serles.models.Order(**kwargs)¶
In ACME lingo, an Order identifies a client’s request for a certificate. It keeps a list of Identifiers (domain names) requested from the client, a list of Authorizations (see below), and (once we decided to issue one) the certificate.
- query: t.ClassVar[Query]¶
A SQLAlchemy query for a model. Equivalent to
db.session.query(Model). Can be customized per-model by overridingquery_class.Warning
The query interface is considered legacy in SQLAlchemy. Prefer using
session.execute(select())instead.
- class serles.models.OrderStatus(*values)¶
- class serles.models.UTCDateTime(*args: Any, **kwargs: Any)¶
SQLite stores datetimes without TZ info, and SQLAlchemy then returns TZ-less datetimes. This breaks calculating timedeltas. So this wrapper converts incoming timestamps to UTC before storing and adds the TZ (utc) back on retrieval.
- cache_ok = True¶
Indicate if statements using this
ExternalTypeare “safe to cache”.The default value
Nonewill emit a warning and then not allow caching of a statement which includes this type. Set toFalseto disable statements using this type from being cached at all without a warning. When set toTrue, the object’s class and selected elements from its state will be used as part of the cache key. For example, using aTypeDecorator:class MyType(TypeDecorator): impl = String cache_ok = True def __init__(self, choices): self.choices = tuple(choices) self.internal_only = True
The cache key for the above type would be equivalent to:
>>> MyType(["a", "b", "c"])._static_cache_key (<class '__main__.MyType'>, ('choices', ('a', 'b', 'c')))
The caching scheme will extract attributes from the type that correspond to the names of parameters in the
__init__()method. Above, the “choices” attribute becomes part of the cache key but “internal_only” does not, because there is no parameter named “internal_only”.The requirements for cacheable elements is that they are hashable and also that they indicate the same SQL rendered for expressions using this type every time for a given cache value.
To accommodate for datatypes that refer to unhashable structures such as dictionaries, sets and lists, these objects can be made “cacheable” by assigning hashable structures to the attributes whose names correspond with the names of the arguments. For example, a datatype which accepts a dictionary of lookup values may publish this as a sorted series of tuples. Given a previously un-cacheable type as:
class LookupType(UserDefinedType): """a custom type that accepts a dictionary as a parameter. this is the non-cacheable version, as "self.lookup" is not hashable. """ def __init__(self, lookup): self.lookup = lookup def get_col_spec(self, **kw): return "VARCHAR(255)" def bind_processor(self, dialect): ... # works with "self.lookup" ...
Where “lookup” is a dictionary. The type will not be able to generate a cache key:
>>> type_ = LookupType({"a": 10, "b": 20}) >>> type_._static_cache_key <stdin>:1: SAWarning: UserDefinedType LookupType({'a': 10, 'b': 20}) will not produce a cache key because the ``cache_ok`` flag is not set to True. Set this flag to True if this type object's state is safe to use in a cache key, or False to disable this warning. symbol('no_cache')
If we did set up such a cache key, it wouldn’t be usable. We would get a tuple structure that contains a dictionary inside of it, which cannot itself be used as a key in a “cache dictionary” such as SQLAlchemy’s statement cache, since Python dictionaries aren’t hashable:
>>> # set cache_ok = True >>> type_.cache_ok = True >>> # this is the cache key it would generate >>> key = type_._static_cache_key >>> key (<class '__main__.LookupType'>, ('lookup', {'a': 10, 'b': 20})) >>> # however this key is not hashable, will fail when used with >>> # SQLAlchemy statement cache >>> some_cache = {key: "some sql value"} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'dict'
The type may be made cacheable by assigning a sorted tuple of tuples to the “.lookup” attribute:
class LookupType(UserDefinedType): """a custom type that accepts a dictionary as a parameter. The dictionary is stored both as itself in a private variable, and published in a public variable as a sorted tuple of tuples, which is hashable and will also return the same value for any two equivalent dictionaries. Note it assumes the keys and values of the dictionary are themselves hashable. """ cache_ok = True def __init__(self, lookup): self._lookup = lookup # assume keys/values of "lookup" are hashable; otherwise # they would also need to be converted in some way here self.lookup = tuple((key, lookup[key]) for key in sorted(lookup)) def get_col_spec(self, **kw): return "VARCHAR(255)" def bind_processor(self, dialect): ... # works with "self._lookup" ...
Where above, the cache key for
LookupType({"a": 10, "b": 20})will be:>>> LookupType({"a": 10, "b": 20})._static_cache_key (<class '__main__.LookupType'>, ('lookup', (('a', 10), ('b', 20))))
Added in version 1.4.14: - added the
cache_okflag to allow some configurability of caching forTypeDecoratorclasses.Added in version 1.4.28: - added the
ExternalTypemixin which generalizes thecache_okflag to both theTypeDecoratorandUserDefinedTypeclasses.See also
sql_caching
- impl¶
alias of
DateTime
- process_bind_param(val, _)¶
Receive a bound parameter value to be converted.
Custom subclasses of
_types.TypeDecoratorshould override this method to provide custom behaviors for incoming data values. This method is called at statement execution time and is passed the literal Python data value which is to be associated with a bound parameter in the statement.The operation could be anything desired to perform custom behavior, such as transforming or serializing data. This could also be used as a hook for validating logic.
- Parameters:
value – Data to operate upon, of any type expected by this method in the subclass. Can be
None.dialect – the
Dialectin use.
See also
types_typedecorator
_types.TypeDecorator.process_result_value()
- process_result_value(val, _)¶
Receive a result-row column value to be converted.
Custom subclasses of
_types.TypeDecoratorshould override this method to provide custom behaviors for data values being received in result rows coming from the database. This method is called at result fetching time and is passed the literal Python data value that’s extracted from a database result row.The operation could be anything desired to perform custom behavior, such as transforming or deserializing data.
- Parameters:
value – Data to operate upon, of any type expected by this method in the subclass. Can be
None.dialect – the
Dialectin use.
See also
types_typedecorator
_types.TypeDecorator.process_bind_param()
- serles.challenge.additional_ip_address_checks(config, remote_ip, host, is_ipaddress=False)¶
perform additional checks on the remote IP address
These are useful in an enterprise setting, but not required by spec.
- Parameters:
remote_ip (str) – the IP address which we connected to for challenge verification
host (str) – dNSname which we resolved to get remote_ip
is_ipaddress (bool) – whether host is an IP address (skips verifyPTR)
- Returns:
An error, if one occured, or None.
- Return type:
Optional[str]
- serles.challenge.alpn_challenge(challenge)¶
verify a TLS-ALPN-01 Challenge
- Parameters:
challenge (Challenge) – The TLS-ALPN-01 challenge to verify.
- Returns:
problem detail type of the error and textual description, or (None,None).
- Return type:
tuple(str,str)
- serles.challenge.dns_challenge(challenge)¶
verify a DNS-01 Challenge
- Parameters:
challenge (Challenge) – The DNS-01 challenge to verify.
- Returns:
problem detail type of the error and textual description, or (None,None).
- Return type:
tuple(str,str)
- serles.challenge.http_challenge(challenge)¶
verify a HTTP Challenge
- Parameters:
challenge (Challenge) – The HTTP challenge to verify.
- Returns:
problem detail type of the error and textual description, or (None,None).
- Return type:
tuple(str,str)
- serles.challenge.key_authorization(challenge)¶
build key authorization string from challenge
- Parameters:
challenge (models.Challenge) – a challenge object
- Returns:
key authorization string
- Return type:
str
- serles.challenge.verify_challenge(challenge)¶
verify a challenge
- exception serles.configloader.ConfigError¶
This exception is raised when an error occurred while reading the config.
- serles.configloader.get_config()¶
Reads the configuration from the environment variable or the default path.
- Returns:
A tuple of
config,backend.- Return type:
(dict, object)
- serles.configloader.load_config_and_backend(filename)¶
Parses the config file given, or raises an exception. This is called directly on startup (as opposed to when a certificate request comes in) to alert the administrator to erros immediately.
- Parameters:
filename – config file to load.
- Returns:
A tuple containing the Backend class and the parsed config (dict-like)
- Return type:
(object, configparser.ConfigParser)
- Raises:
ConfigError – The config could not be loaded, is missing a required key or the specified Backend could not be loaded.
- exception serles.exceptions.ACMEError(message, status, error_type)¶
Raise this exception on invalid API usage. The exception handler will return an Error Document to the client.
- Parameters:
message (str) – returned to client as the “detail” field.
status (int) – HTTP status code for the response.
error_type (str) – type token from the ACME namespace.
- serles.flask_handlers.exception_handler(error)¶
This function is called by Flask when an exception is raised. We use the our ACMEError to return errors for failed API requests. Other exceptions are caught and transformed into an Internal Server Error and we log the exception for later review. Responses are in Problem Details format (RFC7807), albeit only with minimal content.
- serles.flask_handlers.parse_jws()¶
Verify that the signature is as specified by the RFC, and make the payload available to all POST views using Flask’s
gobject. Note that we aren’t fully checking every detail, just the security relevant ones. Note also that we don’t support key rollover (as required by spec).This function is registered as a before_request handler and augments the
gobject with the following attributes:g.payload: the actually interesting request data, JSON-decodedg.kid: the JWK key id (in our case a uuid) of the user, if knowng.jwk: the JWK public key, when a user tries to register
- Raises:
ACMEError – The request was not understood or not authorized.
- serles.utils.background_job(interval)¶
executes the decorated function in an interval
A very simple scheduler: decorate a function to call it in its own thread in a given interval.
- Parameters:
interval (int) – number of seconds between executions.
- serles.utils.base64d(s)¶
padding-ignoring base64-decoder
ACME uses url-safe base64, but does not add padding. Python’s base64 lib throws an exception on wrong padding, so we add it for it.
- Parameters:
s (str) – input to decode.
- Returns:
decoded input.
- Return type:
bytes
- serles.utils.ber_parse(b)¶
parse X.690 type/length/value tuple to string
python-cryptography for some reason does not trim type info and the length off the value, so we do it by hand.
- Parameters:
b (bytes) – byte sequence
- Returns:
decoded string
- Return type:
str
- serles.utils.get_ptr(ipaddr)¶
resolve an IP address to a domain name.
- Parameters:
ipaddr (str) – query.
- Returns:
FQDN, or None.
- Return type:
str
- serles.utils.ip_in_ranges(ipaddr, ranges)¶
check whether the given IP is in any of the given subnets.
- Parameters:
ipaddr (str) – IP to check.
ranges (list) – list of ipaddress.IPv4Network or ipaddress.IPv6Network.
- Returns:
True if it is, False if it isn’t.
- Return type:
bool
- serles.utils.normalize(domain)¶
don’t differentiate between FQDN and PartiallyQDN, ignore case
- Parameters:
domain (str) – domain name to normalize.
- Returns:
normalized domain name.
- Return type:
str
- serles.utils.query(qname, rdtype)¶
Query DNS records without raising an exception.
- Parameters:
qname (str) – query name.
rdtype (str) – query type.
- Returns:
results, or the empty list on error.
- Return type:
list
- class serles.views.AccountMain¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- post(kid)¶
View or update the specified Account object.
- Parameters:
kid – JSON Web Key ID that identifies the account
- Returns:
JSON-serialized Account object (post-update).
- class serles.views.AccountOrders¶
see AccountMain.
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- class serles.views.AuthorizationMain¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- post(authid)¶
View the specified Authorization object that contains challenges.
- Parameters:
authid
- Returns:
JSON-serialized Authorization object.
- class serles.views.CertificateMain¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- post(certid)¶
Download the specified certificate. Only the client who requested the order may access it.
- Parameters:
certid
- Returns:
PEM encoded certificate chain.
- class serles.views.ChallengeMain¶
Once the client calls this endpoint, we can start verifying the challenge.
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- class serles.views.Directory¶
- get()¶
Displays the URLs for accessing certain functions, and some metadata.
- methods: ClassVar[Collection[str] | None] = {'GET'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- class serles.views.KeyChange¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- class serles.views.LandingPage¶
- get()¶
return a 200 OK message on / so Users know what this is.
- methods: ClassVar[Collection[str] | None] = {'GET'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- class serles.views.NewAccount¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- post()¶
Request a new Account or get the Key ID associated with a JSON Web Key.
- class serles.views.NewAuthz¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- class serles.views.NewNonce¶
Lets the client fetch a nonce, if they ran out of them.
- methods: ClassVar[Collection[str] | None] = {'GET', 'HEAD'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- class serles.views.NewOrder¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- post()¶
Submit a new Order. The request will include a list of Identifers (domain names) the client wants on the certificate.
- class serles.views.OrderFinalize¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- post(orderid)¶
Upload CSR and (if order is ready) start issuance process.
- Returns:
JSON-serialized Order object, now including a certificate id.
- class serles.views.OrderMain¶
- methods: ClassVar[Collection[str] | None] = {'POST'}¶
The methods this view is registered for. Uses the same default (
["GET", "HEAD", "OPTIONS"]) asrouteandadd_url_ruleby default.
- post(orderid)¶
View the specified Order object. :param orderid:
- Returns:
JSON-serialized Order object.