Skip to content

Platform specific TLS/SSL configuration context #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .ci/certs/ca.p12
Binary file not shown.
Binary file modified .ci/certs/client.p12
Binary file not shown.
24 changes: 11 additions & 13 deletions .ci/certs/client_certificate.pem
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBAjANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH
MIIDvDCCAqSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH
ZW5TZWxmU2lnbmVkdFJvb3RDQSAyMDIzLTA5LTExVDIwOjUxOjM5LjYwMzAxMzEN
MAsGA1UEBwwEJCQkJDAeFw0yMzA5MTExODUxNDBaFw0zMzA5MDgxODUxNDBaMDYx
IzAhBgNVBAMMGmdzYW50b21hZ2c2TFZETS52bXdhcmUuY29tMQ8wDQYDVQQKDAZj
Expand All @@ -9,16 +9,14 @@ p8kuK93PxM0qsYxvCj5fywaGI2mL9sNibrs6CFtvPL+Rj57LSt5UJHSaH3LmY0CE
bV2OdBEuYEBR7eGtzmpupmA+PptHF/U0hTmfIaet6sVLjvJTmD2/3LcztNm/8ksH
iqeHgJDUE+ERWUVl7AEcBo1rHDJw+z/jsKEtKbmqoNxsfcdb2UdZw9cJkB5ojKMr
l73m35s9uIWZxf2iNd3/tqos7cXMLJcTpwr4x6n6F+PsMhBK5sVTw+kFkq+iyxsu
nSVpVT6nAgMBAAGjgfEwge4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0l
BAwwCgYIKwYBBQUHAwIwTAYDVR0RBEUwQ4IaZ3NhbnRvbWFnZzZMVkRNLnZtd2Fy
ZS5jb22CGmdzYW50b21hZ2c2TFZETS52bXdhcmUuY29tgglsb2NhbGhvc3QwMQYD
VR0fBCowKDAmoCSgIoYgaHR0cDovL2NybC1zZXJ2ZXI6ODAwMC9iYXNpYy5jcmww
HQYDVR0OBBYEFF+biSCzxAazbay1NaTfGDWawU6dMB8GA1UdIwQYMBaAFCjyUnXF
pZI5+zz7PJxHxDuulqqZMA0GCSqGSIb3DQEBCwUAA4IBAQCREnq62BDzp61MRlzL
lsheI/13hkLutFl+OJAoNGcSgprys7d0zwQJGakCO5o05Csi1pQmP0MCKSyPN2Xb
CTEb1qeDBt3FQkgSzXUCAjVL2wvWoL1nIZaAkD5XDjDvGr5Yd4Eczc7WYwujlT5B
JausVa/ShyYatuiTfgPI7UKASW625fkdi+h30OxQ6vnP+X3FUjOV5NO5/GSrlyFN
Fk0M1YqcypUa9meFooDo2aSMTF8zUuZKsOhFLO9B1z7Io/iAiACdPvjdZWjcpJmI
m+gUWeyMH/R4ql6VlPaitUus+CUWkWtdNuQIZEH8HKR1CIOeCW3xwmIJCK9rnbvI
oGb4
nSVpVT6nAgMBAAGjgb4wgbswTAYDVR0RBEUwQ4IaZ3NhbnRvbWFnZzZMVkRNLnZt
d2FyZS5jb22CGmdzYW50b21hZ2c2TFZETS52bXdhcmUuY29tgglsb2NhbGhvc3Qw
CQYDVR0TBAIwADAfBgNVHSMEGDAWgBQo8lJ1xaWSOfs8+zycR8Q7rpaqmTAdBgNV
HQ4EFgQUX5uJILPEBrNtrLU1pN8YNZrBTp0wCwYDVR0PBAQDAgWgMBMGA1UdJQQM
MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQAbfphg4JZuCmxoAzXhbDdB
48K98aPVv4ABa6L8BrJcWcXo9H/q0puC2+4LtLNAX7M/hri+UcIWDt2nG+9qkUYN
HXZCfpgZo99FbpfmPlk/m7mBqB6YBFYx94VzcHGTipsOVEfSmOMybf+XW8KPJrXd
ZhsbrAu1ev3k85P8jkv7psc+TKG+HjCG3Rqiy73gMpNvWRQriEO2yOa0sx5Pct+l
x0KBlXNLQc37gddV08BgcTn6fsS72FbjMh0GXbG1mZMCA4DxpRlhwkWqaHCbcfFq
3LKBCvuz/ysAEeJoWug8LKHly8p1Gpelot+tj/lFEYryOr6whJI5xoN6VxNHp67S
-----END CERTIFICATE-----
Binary file modified .ci/certs/server.p12
Binary file not shown.
43 changes: 21 additions & 22 deletions .ci/certs/server_certificate.pem
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBATANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH
ZW5TZWxmU2lnbmVkdFJvb3RDQSAyMDIzLTA5LTExVDIwOjUxOjM5LjYwMzAxMzEN
MAsGA1UEBwwEJCQkJDAeFw0yMzA5MTExODUxNDBaFw0zMzA5MDgxODUxNDBaMDYx
IzAhBgNVBAMMGmdzYW50b21hZ2c2TFZETS52bXdhcmUuY29tMQ8wDQYDVQQKDAZz
ZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6H9gnMoGCmgDN
GXpqgLiIJBmDvbo64P+FsPOvKEYFNKj/Poz2UVVY96kOJRDTBXW3p42C0GCll/2z
/4RcOwN4Jcf4TIU+IsytOyQ39FYNVMDJpMzH4dQPYlvx9euyIqxUccTYCiXtHkrd
xw5cV3gs7HPQLcklQtBgoVNnlf1fPQcPgYPa5x95+oEki2yWhScXa9EP3W6G+KXE
guCi1enoIZ3+MfxbEkfdm+C9Yo47vh6LXcokyKpiuOYk2TGrfaw5JQb1tRwb4BOQ
ORriMCHi6+TkQf58yQ5GRZvJ5sjBeJgLtmCvRJXbdZXcw25jKXPwz74qS1Q728kD
c2k7lgKvAgMBAAGjgfEwge4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0l
BAwwCgYIKwYBBQUHAwEwTAYDVR0RBEUwQ4IaZ3NhbnRvbWFnZzZMVkRNLnZtd2Fy
ZS5jb22CGmdzYW50b21hZ2c2TFZETS52bXdhcmUuY29tgglsb2NhbGhvc3QwHQYD
VR0OBBYEFG5VGCQucC7FqyOJOTzIYtclS9/SMB8GA1UdIwQYMBaAFCjyUnXFpZI5
+zz7PJxHxDuulqqZMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwtc2VydmVy
OjgwMDAvYmFzaWMuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQB3nWIIa+9Oo29gU0us
fvryYJo92A/mEGIBpixX2i4eQoPhgTSJvFWN3QCHDexbnccM6tRksQmKwn5Rrf+P
DdM8BiTLP/jOQWJXChZro8xpHLmNjlOGletsQ7wo7/p5hvD6Y7pB6FK6LdLcbwbI
Rmvy8olsfOMewEyyWLbKB7e7+iwDIO5lxxgNWXKspO+Kx7wgVeS3j2OhLaOBj1N4
a+YAXVVaN3IkkdHwUHBTPfuvguXCD8fZxVW5RkYDiweeHAMuwpu3o2rd7y2dGzG7
u5mLzNazq4Ki/FTSZMkMAloN4/vfXQfGUO4UJcGXB/c3XO9XURsF2N1k0T9ThIUh
bhmL
MIIDzzCCAregAwIBAgIUeRECMRSdARujrzjF1SEBlULbo4IwDQYJKoZIhvcNAQEL
BQAwTDE7MDkGA1UEAwwyVExTR2VuU2VsZlNpZ25lZHRSb290Q0EgMjAyMy0wOS0x
MVQyMDo1MTozOS42MDMwMTMxDTALBgNVBAcMBCQkJCQwHhcNMjUwMjI2MTUyNDA1
WhcNMjYwMjI2MTUyNDA1WjA2MSMwIQYDVQQDDBpnc2FudG9tYWdnNkxWRE0udm13
YXJlLmNvbTEPMA0GA1UECgwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAuh/YJzKBgpoAzRl6aoC4iCQZg726OuD/hbDzryhGBTSo/z6M9lFV
WPepDiUQ0wV1t6eNgtBgpZf9s/+EXDsDeCXH+EyFPiLMrTskN/RWDVTAyaTMx+HU
D2Jb8fXrsiKsVHHE2Aol7R5K3ccOXFd4LOxz0C3JJULQYKFTZ5X9Xz0HD4GD2ucf
efqBJItsloUnF2vRD91uhvilxILgotXp6CGd/jH8WxJH3ZvgvWKOO74ei13KJMiq
YrjmJNkxq32sOSUG9bUcG+ATkDka4jAh4uvk5EH+fMkORkWbyebIwXiYC7Zgr0SV
23WV3MNuYylz8M++KktUO9vJA3NpO5YCrwIDAQABo4G+MIG7MEwGA1UdEQRFMEOC
GmdzYW50b21hZ2c2TFZETS52bXdhcmUuY29tghpnc2FudG9tYWdnNkxWRE0udm13
YXJlLmNvbYIJbG9jYWxob3N0MAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUKPJSdcWl
kjn7PPs8nEfEO66WqpkwHQYDVR0OBBYEFG5VGCQucC7FqyOJOTzIYtclS9/SMAsG
A1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOC
AQEAemxaoori7vi3+drMpOSSmeqG/lmtupuoX7ysbYlhZenRPGAOiGTEMaafwp7E
bsBFG+vHajY9bLMesVCbKXPJS5KzvAHq4nIQeI7ravUAKUb4wGin6Hsqma6ac7vc
0U8jVGKStr+Ax9Xvu1YbZLIPyrWXL9d8pDsVogGySXKYU9fBfiohcwvdZTnXnHaY
FkpMcYORJywNulz53jHspIjKTf+jCG/LC4pWsyidmNsDUZlDePVB5wR/ycFUGzdi
OjGRZUvQ2+UNmbLdmzfqIHursON8I/7t22ogeCbY/hX1S29zMpNjzZ4XagWDtLUf
BDnD2qWuKVvdPm9YiRckNlWxNQ==
-----END CERTIFICATE-----
4 changes: 2 additions & 2 deletions examples/getting_started/getting_started.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# type: ignore


from rabbitmq_amqp_python_client import ( # SSlConfigurationContext,; SslConfigurationContext,; ClientCert,
from rabbitmq_amqp_python_client import ( # PosixSSlConfigurationContext,; PosixClientCert,
AddressHelper,
AMQPMessagingHandler,
Connection,
Expand Down Expand Up @@ -68,7 +68,7 @@ def create_connection(environment: Environment) -> Connection:
# client_key = ".ci/certs/client_key.pem"
# connection = Connection(
# "amqps://guest:guest@localhost:5671/",
# ssl_context=SslConfigurationContext(
# ssl_context=PosixSslConfigurationContext(
# ca_cert=ca_cert_file,
# client_cert=ClientCert(client_cert=client_cert, client_key=client_key),
# ),
Expand Down
6 changes: 3 additions & 3 deletions examples/streams/example_with_streams.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# type: ignore

from rabbitmq_amqp_python_client import ( # SSlConfigurationContext,; SslConfigurationContext,; ClientCert,
from rabbitmq_amqp_python_client import ( # PosixSSlConfigurationContext,; PosixClientCert,
AddressHelper,
AMQPMessagingHandler,
Connection,
Expand Down Expand Up @@ -72,9 +72,9 @@ def create_connection(environment: Environment) -> Connection:
# client_key = ".ci/certs/client_key.pem"
# connection = Connection(
# "amqps://guest:guest@localhost:5671/",
# ssl_context=SslConfigurationContext(
# ssl_context=PosixSslConfigurationContext(
# ca_cert=ca_cert_file,
# client_cert=ClientCert(client_cert=client_cert, client_key=client_key),
# client_cert=PosixClientCert(client_cert=client_cert, client_key=client_key),
# ),
# )
connection.dial()
Expand Down
92 changes: 78 additions & 14 deletions examples/tls/tls_example.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
# type: ignore
import sys
from traceback import print_exception


from rabbitmq_amqp_python_client import ( # SSlConfigurationContext,; SslConfigurationContext,; ClientCert,
from rabbitmq_amqp_python_client import (
AddressHelper,
AMQPMessagingHandler,
ClientCert,
Connection,
CurrentUserStore,
Environment,
Event,
ExchangeSpecification,
ExchangeToQueueBindingSpecification,
LocalMachineStore,
Message,
PKCS12Store,
PosixClientCert,
PosixSslConfigurationContext,
QuorumQueueSpecification,
SslConfigurationContext,
WinClientCert,
WinSslConfigurationContext,
)
from rabbitmq_amqp_python_client.ssl_configuration import (
FriendlyName,
)

messages_to_publish = 100
Expand Down Expand Up @@ -74,20 +83,75 @@ def main() -> None:
exchange_name = "test-exchange"
queue_name = "example-queue"
routing_key = "routing-key"
ca_p12_store = ".ci/certs/ca.p12"
ca_cert_file = ".ci/certs/ca_certificate.pem"
client_cert = ".ci/certs/client_certificate.pem"
client_key = ".ci/certs/client_key.pem"
client_p12_store = ".ci/certs/client.p12"
uri = "amqps://guest:guest@localhost:5671/"

if sys.platform == "win32":
ca_stores = [
# names for the current user and local machine are not
# case-sensitive
CurrentUserStore(name="Root"),
LocalMachineStore(name="Root"),
PKCS12Store(path=ca_p12_store),
]
client_stores = [
# `personal` is treated as an alias for `my` by qpid proton
# Recommended read:
# https://github.com/apache/qpid-proton/blob/2847000fbb3732e80537e3c3ff5e097bb95bfae0/c/src/ssl/PLATFORM_NOTES.md
CurrentUserStore(name="Personal"),
LocalMachineStore(name="my"),
PKCS12Store(path=client_p12_store),
]

for ca_store, client_store in zip(ca_stores, client_stores):
ssl_context = WinSslConfigurationContext(
ca_store=ca_store,
client_cert=WinClientCert(
store=client_store,
# qpid proton uses Windows constant CERT_NAME_FRIENDLY_DISPLAY_TYPE
# to retrieve the value which is compare to the one we provide
# If certificates have no friendly name Windows falls back to
# CERT_NAME_SIMPLE_DISPLAY_TYPE which has further fallbacks
# https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetnamestringa
disambiguation_method=FriendlyName("1"),
password=None,
),
)
environment = Environment(
uri,
ssl_context=ssl_context,
)

try:
print("connection to amqp server")
connection = create_connection(environment)
break
except Exception as e:
print_exception(e)
continue
else:
raise RuntimeError(
"connection failed. working directory should be project root"
)
else:
environment = Environment(
uri,
ssl_context=PosixSslConfigurationContext(
ca_cert=ca_cert_file,
client_cert=PosixClientCert(
client_cert=client_cert,
client_key=client_key,
password=None,
),
),
)

environment = Environment(
"amqps://guest:guest@localhost:5671/",
ssl_context=SslConfigurationContext(
ca_cert=ca_cert_file,
client_cert=ClientCert(client_cert=client_cert, client_key=client_key),
),
)

print("connection to amqp server")
connection = create_connection(environment)
print("connection to amqp server")
connection = create_connection(environment)

management = connection.management()

Expand Down
18 changes: 14 additions & 4 deletions rabbitmq_amqp_python_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@
StreamSpecification,
)
from .ssl_configuration import (
ClientCert,
SslConfigurationContext,
CurrentUserStore,
LocalMachineStore,
PKCS12Store,
PosixClientCert,
PosixSslConfigurationContext,
WinClientCert,
WinSslConfigurationContext,
)

try:
Expand Down Expand Up @@ -69,8 +74,13 @@
"AMQPMessagingHandler",
"ArgumentOutOfRangeException",
"ValidationCodeException",
"SslConfigurationContext",
"ClientCert",
"PosixSslConfigurationContext",
"WinSslConfigurationContext",
"PosixClientCert",
"WinClientCert",
"LocalMachineStore",
"CurrentUserStore",
"PKCS12Store",
"ConnectionClosed",
"StreamOptions",
"OffsetSpecification",
Expand Down
85 changes: 74 additions & 11 deletions rabbitmq_amqp_python_client/connection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import logging
from typing import Annotated, Callable, Optional, TypeVar
from typing import (
Annotated,
Callable,
Optional,
TypeVar,
Union,
)

import typing_extensions

from .address_helper import validate_address
from .consumer import Consumer
Expand All @@ -10,7 +18,15 @@
from .qpid.proton._handlers import MessagingHandler
from .qpid.proton._transport import SSLDomain
from .qpid.proton.utils import BlockingConnection
from .ssl_configuration import SslConfigurationContext
from .ssl_configuration import (
CurrentUserStore,
FriendlyName,
LocalMachineStore,
PKCS12Store,
PosixSslConfigurationContext,
Unambiguous,
WinSslConfigurationContext,
)

logger = logging.getLogger(__name__)

Expand All @@ -34,7 +50,9 @@ def __init__(
uri: Optional[str] = None,
# multi-node mode
uris: Optional[list[str]] = None,
ssl_context: Optional[SslConfigurationContext] = None,
ssl_context: Union[
PosixSslConfigurationContext, WinSslConfigurationContext, None
] = None,
on_disconnection_handler: Optional[CB] = None, # type: ignore
):
"""
Expand All @@ -60,7 +78,9 @@ def __init__(
self._conn: BlockingConnection
self._management: Management
self._on_disconnection_handler = on_disconnection_handler
self._conf_ssl_context: Optional[SslConfigurationContext] = ssl_context
self._conf_ssl_context: Union[
PosixSslConfigurationContext, WinSslConfigurationContext, None
] = ssl_context
self._ssl_domain = None
self._connections = [] # type: ignore
self._index: int = -1
Expand All @@ -80,17 +100,47 @@ def dial(self) -> None:
logger.debug("Enabling SSL")

self._ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT)
if self._ssl_domain is not None:
self._ssl_domain.set_trusted_ca_db(self._conf_ssl_context.ca_cert)
assert self._ssl_domain
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why mypy needs this assert in order to assume it can't be None given that it is initialized to an instance of SSLDomain above, or never reaching this line due to the constructor raising an exception. But is does :(


if isinstance(self._conf_ssl_context, PosixSslConfigurationContext):
ca_cert = self._conf_ssl_context.ca_cert
elif isinstance(self._conf_ssl_context, WinSslConfigurationContext):
ca_cert = self._win_store_to_cert(self._conf_ssl_context.ca_store)
else:
typing_extensions.assert_never(self._conf_ssl_context)
self._ssl_domain.set_trusted_ca_db(ca_cert)

# for mutual authentication
if self._conf_ssl_context.client_cert is not None:
logger.debug("Enabling mutual authentication as well")
if self._ssl_domain is not None:
self._ssl_domain.set_credentials(
self._conf_ssl_context.client_cert.client_cert,
self._conf_ssl_context.client_cert.client_key,
self._conf_ssl_context.client_cert.password,

if isinstance(self._conf_ssl_context, PosixSslConfigurationContext):
client_cert = self._conf_ssl_context.client_cert.client_cert
client_key = self._conf_ssl_context.client_cert.client_key
password = self._conf_ssl_context.client_cert.password
elif isinstance(self._conf_ssl_context, WinSslConfigurationContext):
client_cert = self._win_store_to_cert(
self._conf_ssl_context.client_cert.store
)
disambiguation_method = (
self._conf_ssl_context.client_cert.disambiguation_method
)
if isinstance(disambiguation_method, Unambiguous):
client_key = None
elif isinstance(disambiguation_method, FriendlyName):
client_key = disambiguation_method.name
else:
typing_extensions.assert_never(disambiguation_method)

password = self._conf_ssl_context.client_cert.password
else:
typing_extensions.assert_never(self._conf_ssl_context)

self._ssl_domain.set_credentials(
client_cert,
client_key,
password,
)
self._conn = BlockingConnection(
url=self._addr,
urls=self._addrs,
Expand All @@ -100,6 +150,19 @@ def dial(self) -> None:
self._open()
logger.debug("Connection to the server established")

def _win_store_to_cert(
self, store: Union[LocalMachineStore, CurrentUserStore, PKCS12Store]
) -> str:
if isinstance(store, LocalMachineStore):
ca_cert = f"lmss:{store.name}"
elif isinstance(store, CurrentUserStore):
ca_cert = f"ss:{store.name}"
elif isinstance(store, PKCS12Store):
ca_cert = store.path
else:
typing_extensions.assert_never(store)
return ca_cert

def _open(self) -> None:
self._management = Management(self._conn)
self._management.open()
Expand Down
Loading