security_layer — Implementations to negotiate stream security

This module provides different implementations of the security layer (TLS+SASL).

These are coupled, as different SASL features might need different TLS features (such as channel binding or client cert authentication).

aioxmpp.security_layer.tls_with_password_based_authentication(password_provider[, ssl_context_factory][, max_auth_attempts=3])[source]

Produce a commonly used security layer, which uses TLS and password authentication. If ssl_context_factory is not provided, an SSL context with TLSv1+ is used.

password_provider must be a coroutine which is called with the jid as first and the number of attempt as second argument. It must return the password to us, or None to abort.

Return a security layer which can be passed to Client.

aioxmpp.security_layer.security_layer(tls_provider, sasl_providers)[source]

See also

Use this function only if you need more customization than provided by tls_with_password_based_authentication().

Return a partially applied negotiate_stream_security() function, where the tls_provider and sasl_providers arguments are already bound.

The return value can be passed to the constructor of Client.

Some very basic checking on the input is also performed.

coroutine aioxmpp.security_layer.negotiate_stream_security(tls_provider, sasl_providers, negotiation_timeout, jid, features, xmlstream)[source]

Negotiate stream security for the given xmlstream. For this to work, features must be the most recent stream_elements.StreamFeatures node.

First, transport layer security is negotiated using tls_provider. If that fails non-fatally, negotiation continues as normal. Exceptions propagate upwards.

After TLS has been tried, SASL is negotiated, by sequentially attempting SASL negotiation using the providers in the sasl_providers list. If a provider fails to negotiate SASL with an aiosasl.AuthenticationFailure or has no mechanisms in common with the peer server, the next provider can continue. Otherwise, the exception propagates upwards.

If no provider succeeds and there was an authentication failure, that error is re-raised. Otherwise, a dedicated aiosasl.SASLFailure exception is raised, which states that no common mechanisms were found.

On success, a pair of (tls_transport, features) is returned. If TLS has been negotiated, tls_transport is the SSL asyncio.Transport created by asyncio (as returned by the tls_provider). If no TLS has been negotiated, tls_transport is None. features is the latest StreamFeatures element received during negotiation.

On failure, an appropriate exception is raised. Authentication failures can be caught as aiosasl.AuthenticationFailure. Errors related to SASL or TLS negotiation itself can be caught using aiosasl.SASLFailure and TLSFailure respectively.

Certificate verifiers

To verify the peer certificate provided by the server, different :class:`CertificateVerifier`s are available:

class aioxmpp.security_layer.PKIXCertificateVerifier[source]

This verifier enables the default PKIX based verification of certificates as implemented by OpenSSL.

The verify_callback() checks that the certificate subject matches the domain name of the JID of the connection.

To implement your own verifiers, see the documentation at the base class for certificate verifiers:

class aioxmpp.security_layer.CertificateVerifier[source]

A certificate verifier hooks into the two mechanisms provided by ssl_transport.STARTTLSTransport for certificate verification.

On the one hand, the verify callback provided by OpenSSL.SSL.Context is used and forwarded to verify_callback(). On the other hand, the post handshake coroutine is set to post_handshake(). See the documentation of ssl_transport.STARTTLSTransport for the semantics of that coroutine.

In addition to these two hooks into the TLS handshake, a third coroutine which is called before STARTTLS is intiiated is provided.

This baseclass provides a bit of boilerplate.

Certificate and key pinning

Often in the XMPP world, we need certificate or public key pinning, as most XMPP servers do not have certificates trusted by the usual certificate stores. This module also provide certificate verifiers which can be used for that purpose, as well as stores for saving the pinned information.

class aioxmpp.security_layer.PinningPKIXCertificateVerifier(query_pin, post_handshake_deferred_failure, post_handshake_success=None)[source]

The PinningPKIXCertificateVerifier is a subclass of the HookablePKIXCertificateVerifier which uses the hooks to implement certificate or public key pinning.

It does not store the pins itself. Instead, the user must pass a callable to the query_pin argument. That callable will be called with two arguments: the servername and the x509. The x509 is a OpenSSL.crypto.X509 instance, which is the leaf certificate which attempts to identify the host. The servername is the name of the server we try to connect to (the identifying name, like the domain part of the JID). The callable must return True (to accept the certificate), False (to reject the certificate) or None (to defer the decision to the post_handshake_deferred_failure callback). query_pin must not block; if it needs to do blocking operations, it should defer.

The other two arguments are coroutines with semantics identical to those of the same-named arguments in HookablePKIXCertificateVerifier.

See also

AbstractPinStore.query() is a method which can be passed as query_pin callback.

class aioxmpp.security_layer.CertificatePinStore[source]

This pin store stores the whole certificates which are passed to its pin() method.

class aioxmpp.security_layer.PublicKeyPinStore[source]

This pin store stores the public keys of the X.509 objects which are passed to its pin() method.

Base classes

For future expansion or customization, the base classes of the above utilities can be subclassed and extended:

class aioxmpp.security_layer.HookablePKIXCertificateVerifier(quick_check, post_handshake_deferred_failure, post_handshake_success)[source]

This PKIX-based verifier has several hooks which allow overriding of the checking process, for example to implement key or certificate pinning.

It provides three callbacks:

  • quick_check is a synchronous callback (and must be a plain function) which is called from verify_callback(). It is only called if the certificate fails full PKIX verification, and only for certain cases. For example, expired certificates do not get a second chance and are rejected immediately.

    It is called with the leaf certificate as its only argument. It must return True if the certificate is known good and should pass the verification. If the certificate is known bad and should fail the verification immediately, it must return False.

    If the certificate is unknown and the check should be deferred to the post_handshake_deferred_failure callback, None must be returned.

    Passing None to quick_check is the same as if a callable passed to quick_check would return None always (i.e. the decision is deferred).

  • post_handshake_deferred_failure must be a coroutine. It is called after the handshake is done but before the STARTTLS negotiation has finished and allows the application to take more time to decide on a certificate and possibly request user input.

    The coroutine receives the verifier instance as its argument and can make use of all the verification attributes to present the user with a sensible choice.

    If post_handshake_deferred_failure is None, the result is identical to returning False from the callback.

  • post_handshake_success is only called if the certificate has passed the verification (either because it flawlessly passed by OpenSSL or the quick_check callback returned True).

    You may pass None to this argument to disable the callback without any further side effects.

The following attributes are available when the post handshake callbacks are called:

recorded_errors

This is a set with tuples consisting of a OpenSSL.crypto.X509 instance, an OpenSSL error number and the depth of the certificate in the verification chain (0 is the leaf certificate).

It is a collection of all errors which were passed into verify_callback() by OpenSSL.

hostname_matches

This is True if the host name in the leaf certificate matches the domain part of the JID for which we are connecting (i.e. the usual server name check).

leaf_x509

The OpenSSL.crypto.X509 object which represents the leaf certificate.

class aioxmpp.security_layer.AbstractPinStore[source]

This is the abstract base class for both PublicKeyPinStore and CerificatePinStore. The interface for both types of pinning is identical; the only difference is in which information is stored.

pin(hostname, x509)[source]

Pin an OpenSSL.crypto.X509 object x509 for use with the given hostname. Which information exactly is used to identify the certificate depends _x509_key().

query(hostname, x509)[source]

Return true if the given OpenSSL.crypto.X509 object x509 has previously been pinned for use with the given hostname and None otherwise.

Returning None allows this method to be used with PinningPKIXCertificateVerifier.

get_pinned_for_host(hostname)[source]

Return the set of hashable values which are used to identify the X.509 certificates which are accepted for the given hostname.

If no values have previously been pinned, this returns the empty set.

export_to_json()[source]

Return a JSON dictionary which contains all the pins stored in this store.

import_from_json(data, *, override=False)[source]

Import a JSON dictionary which must have the same format as exported by export().

If override is true, the existing data in the pin store will be overriden with the data from data. Otherwise, the data will be merged into the store.

For subclasses:

_encode_key(key)[source]

Encode the key (which has previously been obtained from _x509_key()) into a string which is both JSON compatible and can be used as XML text (which means that it must not contain control characters, for example).

The method is called by export_to_json(). The default implementation returns key.

_decode_key(obj)[source]

Decode the obj into a key which is compatible to the values returned by _x509_key().

The method is called by import_from_json(). The default implementation returns obj.

_x509_key(key)[source]

Return a hashable value which identifies the given x509 certificate for the purposes of the key store. See the implementations PublicKeyPinStore._x509_key() and CertificatePinStore._x509_key() for details on what is stored for the respective subclasses.

This method is abstract and must be implemented in subclasses.

Partial security providers

Partial security providers serve as arguments to pass to negotiate_stream_security().

Transport layer security provider

As an tls_provider argument to SecurityLayer, instances of the following classes can be used:

class aioxmpp.security_layer.STARTTLSProvider(ssl_context_factory, certificate_verifier_factory=<class 'aioxmpp.security_layer.PKIXCertificateVerifier'>, *, require_starttls=True, **kwargs)[source]

A TLS provider to negotiate STARTTLS on an existing XML stream. This requires that the stream uses ssl_wrapper.STARTTLSableTransportProtocol as a transport.

ssl_context_factory must be a callable returning a valid ssl.SSLContext object. It is called without arguments.

require_starttls can be set to False to allow stream negotiation to continue even if STARTTLS fails before it has been started (the stream is fatally broken if the STARTTLS command has been sent but SSL negotiation fails afterwards).

certificate_verifier_factory must be a callable providing a CertificateVerifer instance which will hooked up to the transport and the SSL context to perform certificate validation.

SASL providers

As elements of the sasl_providers argument to SecurityLayer, instances of the following classes can be used:

class aioxmpp.security_layer.PasswordSASLProvider(password_provider, *, max_auth_attempts=3, **kwargs)[source]

Perform password-based SASL authentication.

jid must be a JID object for the client. password_provider must be a coroutine which is called with the jid as first and the number of attempt as second argument. It must return the password to use, or None to abort. In that case, an errors.AuthenticationFailure error will be raised.

At most max_auth_attempts will be carried out. If all fail, the authentication error of the last attempt is raised.

The SASL mechanisms used depend on whether TLS has been negotiated successfully before. In any case, aiosasl.SCRAM is used. If TLS has been negotiated, aiosasl.PLAIN is also supported.

Abstract base classes

For implementation of custom SASL providers, the following base class can be used:

class aioxmpp.security_layer.SASLProvider[source]
coroutine execute(client_jid, features, xmlstream, tls_transport)[source]

Perform SASL negotiation. The implementation depends on the specific SASLProvider subclass in use.

This coroutine returns True if the negotiation was successful. If no common mechanisms could be found, False is returned. This is useful to chain several SASL providers (e.g. a provider supporting EXTERNAL in front of password-based providers).

Any other error case, such as no SASL support on the remote side or authentication failure results in an aiosasl.SASLFailure exception to be raised.