diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ee0d3..74bef51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.0] 2020-05-22 +### Added +- Added client cert auth using keychain identities to `SecureTransportAdapter`. + +### Changed +- Renamed `SecureTransportSession` to `Session` to indicate that it's the one you probably want to use. +- Updated documentation accordingly. + ## [0.2.1] 2020-04-28 ### Added - Reworked to simplify usage, and add alternate strategies. @@ -14,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial MacKeychainTransportAdapter source. -[Unreleased]: https://github.com/sheagcraig/MacKeychainTransportAdapter/compare/v0.2.1...HEAD -[0.2.0]: https://github.com/sheagcraig/MacKeychainTransportAdapter/compare/v0.1.0...v0.2.1 -[0.1.0]: https://github.com/sheagcraig/MacKeychainTransportAdapter/releases/tag/v0.1.0 \ No newline at end of file +[Unreleased]: https://github.com/sheagcraig/MacSesh/compare/v0.3.0...HEAD +[0.3.0]: https://github.com/sheagcraig/MacSesh/compare/v0.2.1...v0.3.0 +[0.2.1]: https://github.com/sheagcraig/MacSesh/compare/v0.1.0...v0.2.1 +[0.1.0]: https://github.com/sheagcraig/MacSesh/releases/tag/v0.1.0 \ No newline at end of file diff --git a/README.md b/README.md index a38548f..df8e526 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,69 @@ # MacSesh -This package allows requests to verify certs with the macOS keychain, -rather than using certifi. It also includes some tools for easily -hooking up a `SecureTransport` adapter (a la Pip) and then later -undoing all of the sneaky infiltrations required to set this up. - -## Which certs? -It uses any of the trusted certs from keychains included in the current -user's keychain search list, as well as the system roots. Typically, -this is the user's default at `~/Library/Keychains/login.keychain`, -the system keychain at `/Library/Keychains/System.keychain`, and the -System Roots keychain at -`/System/Library/Keychains/SystemRootCertificates.keychain`. - -To achieve this, one of three different strategies can be employed: -1. `KeychainSession` uses a custom SSLContext, requests Adapter, and - requests Session, and injects the SSLContext into urllib3. This - approach is the recommendation. -2. `SecureTransportSession` uses the urllib3 contrib module for injecting - SecureTransport equivalents into stock urllib3. While this approach - uses more of the native networking framework, it also seems to be - written primarily with the goal of solving the issues with macOS and - aging OpenSSL versions to ensure that Macs could still use pip. - Therefore, it's not entirely feature-complete in providing a full - requests Adapter. It's definitely worth experimenting with. -3. `SimpleKeychainSession` circumvents the normal flow of session - startup, and tells the SSLContext to load its trust information - early; in this case from certs dumped from the keychain. +This package allows the popular requests library to use the macOS +keychain for both validating a server, and for doing client cert auth. +Its original use-case was for Mac admins wanting to use python requests +and certs provided by an MDM for TLS, Specifically, SCEP certs client +cert auth and x509 payloads for server validation. ## Example Usage: +Validate using a trusted cert from the keychain: + ``` >>> import macsesh ->>> sesh = macsesh.KeychainSession() +>>> sesh = macsesh.Session() >>> response = sesh.get('https://nethack.org') ``` If you want to use the "basic" requests API without creating a session: + ``` >>> macsesh.inject_into_requests() >>> requests.get('https://en.wikipedia.org/wiki/Taco') # Uses keychain ``` +Client cert auth: + +``` +>>> import macsesh +>>> sesh = macsesh.Session() +>>> response = sesh.get('https://nethack.org', cert='My Identity Cert') +``` + +## Validating a server +macsesh uses any of the _trusted_ certs from keychains included in the +current user's keychain search list, as well as the system roots. +Typically, this search list consists of: +- The user's default keychain at `~/Library/Keychains/login.keychain` +- The system keychain at `/Library/Keychains/System.keychain` +- The System Roots keychain at + `/System/Library/Keychains/SystemRootCertificates.keychain`. + +Certs in the system roots are implicitly trusted. Certs from other +keychains must be marked as trusted for this purpose or they won't be +included. + +When using macsesh, just leave the requests `verify` at its default of +`True` and macsesh will do the rest. + +## Client cert auth +macsesh can also do client cert auth from the keychain, currently only +with the `SecureTransportAdapter`. The other adapter types can do client +cert auth as well of course, but they require the identity to be +available on the filesystem just like regular requests. + +To specify a certificate to use, provide the Common Name of the cert to +requests' normal `cert` argument as in the example above. As the keychain +may have multiple certs which match , macsesh performs some additional +filtering. +1. The query does a "subject contains" search with the CN provided, so + for exmaple `cert='taco'` could match a CN of `taco truck` or `taco + party`. macsesh will drop anything that is not an exact match. +2. The resulting certs may have been renewed, and the old certs are still + in the keychain. macsesh sorts them by "not valid after" date and + picks the one with the longest lifespan. + ## Advanced + +### Cleanup If for some reason you want to revert to "normal" requests (probably using certifi), in the same python process, you'll need to remove this module's injected stuff from urllib3 or requests. @@ -53,13 +77,34 @@ Any certs added to the keychains after starting a session will not be available. Digging down in and updating the SSLContext is rough; just make a new session if you have this need! -### What about mutual TLS certs? +### Choosing a session type +macsesh provides three different types of requests `Session` classes. +While we probably only need the `Session`, the other two are included for +posterity since they are interesting and potentially useful. + +1. If in doubt, use `Session` It uses the securetransport module + contributed to urllib3 as a base SSLContext. Pip, for example, uses + this on macOS. The securetransport module uses an entirely different + `SSLContext`, using ctypes to connect to the macOS `Security` + framework. macsesh then injects additional code into the urllib3 + code to use the keychain instead of conforming to the OpenSSL approach + of using paths to files. If you need to do client cert auth from + keychain identities, this is the one you want. +1. `KeychainSession` uses a custom SSLContext, requests Adapter, and + requests Session, and injects the SSLContext into urllib3. The certs + for validation are dumped from the keychain and held in memory. The + goal of this approach is to use the minimum amount of messing about to + achieve cert validation. +3. `SimpleKeychainSession` circumvents the normal flow of session + startup, and tells the SSLContext to load its trust information + up front rather than waiting for a bunch of internal checks to decide + the context needs to load the trust store. It uses the same method + as the `KeychainSession` and functionally shouldn't be any different. -I have MDM-provided SCEP certs or non-exportable certs: -This should be doable with the `SecureTransportSession`. Please let me -know if you have tried it and it works; I don't have a way to test this -at the moment. +### What about cert auth for `SimpleKeyhainSession` and +`KeychainSession`? -Also, I can implement looking up and retrieving certs by name for the -other "strategies", but again, I don't have anything to test against, -so I haven't written it yet. \ No newline at end of file +I can try implementing looking up and retrieving certs by name for the +other "strategies", but I'm not sure how much utility there is for that, +as the keys would have to be exportable. At that point, just export them +and use regular old requests. diff --git a/macsesh/__init__.py b/macsesh/__init__.py index bec8f5e..f0ee000 100644 --- a/macsesh/__init__.py +++ b/macsesh/__init__.py @@ -1,48 +1,40 @@ -"""This package allows requests to verify certs with the macOS keychain - -It uses any trusted certs from keychains included in the current -user's keychain search list, as well as the system roots. Typically, -this is the user's default at ~/Library/Keychains/login.keychain, -the system keychain at /Library/Keychains/System.keychain, and the -System Roots keychain at -/System/Library/Keychains/SystemRootCertificates.keychain. - -To achieve this, one of three different strategies can be employed: -1. KeychainSession uses a custom SSLContext, requests Adapter, and - requests Session, and injects the SSLContext into urllib3. This - approach is the recommendation. -2. SecureTransportSession uses the urllib3 contrib module for injecting - SecureTransport equivalents into stock urllib3. While this approach - uses more of the native networking framework, it also seems to be - written primarily with the goal of solving the issues with macOS and - aging OpenSSL versions to ensure that Macs could still use pip. - Therefore, it's not entirely feature-complete in providing a full - requests Adapter. It's definitely worth experimenting with. -3. SimpleKeychainSession circumvents the normal flow of session - startup, and tells the SSLContext to load its trust information - early; in this case from certs dumped from the keychain. - -Example Usage: +"""MacSesh + +This package allows the popular requests library to use the macOS +keychain for both validating a server, and for doing client cert auth. +Its original use-case was for Mac admins wanting to use python requests +and certs provided by an MDM for TLS, Specifically, SCEP certs client +cert auth and x509 payloads for server validation. + +## Example Usage: +Validate using a trusted cert from the keychain: + ``` >>> import macsesh ->>> sesh = macsesh.KeychainSession() +>>> sesh = macsesh.Session() >>> response = sesh.get('https://nethack.org') ``` -Note: if you want to revert to "normal" requests (probably using -certifi), in the same python process, you'll need to remove this -module's injected stuff from urllib3: +If you want to use the "basic" requests API without creating a session: + ``` ->>> macsesh.extract_from_urllib3() +>>> macsesh.inject_into_requests() +>>> requests.get('https://en.wikipedia.org/wiki/Taco') # Uses keychain ``` -Finally, any certs added to the keychains after starting a session will -not be available. The sessions and adapters all have an update_truststore -method for re-dumping the trust. +Client cert auth: + +``` +>>> import macsesh +>>> sesh = macsesh.Session() +>>> response = sesh.get('https://nethack.org', cert='My Identity Cert') +``` """ -from .keychain import get_trusted_certs, get_truststore_data, get_system_roots + + from .injected_adapter import KeychainAdapter, KeychainContext +from .keychain import get_trusted_certs, get_truststore_data, get_system_roots from .secure_transport_adapter import SecureTransportAdapter -from .session import KeychainSession, SecureTransportSession, SimpleKeychainSession +from .session import KeychainSession, Session, SimpleKeychainSession from .simple_adapter import SimpleKeychainAdapter from .util import extract_from_urllib3, inject_into_requests, extract_from_requests from .version import __version__ diff --git a/macsesh/session.py b/macsesh/session.py index 07211c4..d63e5ce 100644 --- a/macsesh/session.py +++ b/macsesh/session.py @@ -23,7 +23,7 @@ class KeychainSession(BaseKeychainSession): _adapter_class = KeychainAdapter -class SecureTransportSession(BaseKeychainSession): +class Session(BaseKeychainSession): """Requests session using the SecureTransport""" _adapter_class = SecureTransportAdapter diff --git a/macsesh/util.py b/macsesh/util.py index f827e92..653e81e 100644 --- a/macsesh/util.py +++ b/macsesh/util.py @@ -39,8 +39,8 @@ def inject_into_requests(session_class=None): # Handle the default class here by importing late to avoid circular # import issues. if not session_class: - from .session import KeychainSession - session_class = KeychainSession + from .session import Session + session_class = Session # Create a func that replicates requests.api.request, just subbing # in the passed session class as a kind of closure. def request(method, url, **kwargs): diff --git a/macsesh/version.py b/macsesh/version.py index fb13a35..290d7c6 100644 --- a/macsesh/version.py +++ b/macsesh/version.py @@ -1 +1 @@ -__version__ = '0.2.1' \ No newline at end of file +__version__ = '0.3.0' \ No newline at end of file