Skip to content

Commit 19f9699

Browse files
committed
refactor(auth): replace pyOpenSSL with standard ssl and cryptography
Replace pyOpenSSL with standard library ssl for mTLS transport and update key decryption to use cryptography library. This change also enhances security for handling private keys by: - Using Linux memfd_create for RAM-backed in-memory files to avoid writing secrets to physical storage. - Encrypting plaintext keys on-the-fly before writing to fallback temporary files on disk. - Securely wiping temporary files with null bytes before deletion.
1 parent 384724c commit 19f9699

15 files changed

Lines changed: 377 additions & 181 deletions

File tree

packages/google-auth/google/auth/aio/transport/mtls.py

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,12 @@
2525
from typing import Optional
2626

2727
from google.auth import exceptions
28-
import google.auth.transport._mtls_helper
2928
import google.auth.transport.mtls
29+
from google.auth.transport._mtls_helper import secure_cert_key_paths
3030

3131
_LOGGER = logging.getLogger(__name__)
3232

3333

34-
@contextlib.contextmanager
35-
def _create_temp_file(content: bytes):
36-
"""Creates a temporary file with the given content.
37-
38-
Args:
39-
content (bytes): The content to write to the file.
40-
41-
Yields:
42-
str: The path to the temporary file.
43-
"""
44-
# Create a temporary file that is readable only by the owner.
45-
fd, file_path = tempfile.mkstemp()
46-
try:
47-
with os.fdopen(fd, "wb") as f:
48-
f.write(content)
49-
yield file_path
50-
finally:
51-
# Securely delete the file after use.
52-
if os.path.exists(file_path):
53-
os.remove(file_path)
54-
55-
5634
def make_client_cert_ssl_context(
5735
cert_bytes: bytes, key_bytes: bytes, passphrase: Optional[bytes] = None
5836
) -> ssl.SSLContext:
@@ -71,13 +49,15 @@ def make_client_cert_ssl_context(
7149
Raises:
7250
google.auth.exceptions.TransportError: If there is an error loading the certificate.
7351
"""
74-
with _create_temp_file(cert_bytes) as cert_path, _create_temp_file(
75-
key_bytes
76-
) as key_path:
52+
with secure_cert_key_paths(cert_bytes, key_bytes, passphrase=passphrase) as (
53+
cert_path,
54+
key_path,
55+
passphrase_val,
56+
):
7757
try:
7858
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
7959
context.load_cert_chain(
80-
certfile=cert_path, keyfile=key_path, password=passphrase
60+
certfile=cert_path, keyfile=key_path, password=passphrase_val
8161
)
8262
return context
8363
except (ssl.SSLError, OSError, IOError, ValueError, RuntimeError) as exc:

packages/google-auth/google/auth/identity_pool.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,9 @@ def __init__(self, trust_chain_path, leaf_cert_callback):
152152

153153
@_helpers.copy_docstring(SubjectTokenSupplier)
154154
def get_subject_token(self, context, request):
155-
# Import OpennSSL inline because it is an extra import only required by customers
156-
# using mTLS.
157-
from OpenSSL import crypto
155+
from cryptography import x509
158156

159-
leaf_cert = crypto.load_certificate(
160-
crypto.FILETYPE_PEM, self._leaf_cert_callback()
161-
)
157+
leaf_cert = x509.load_pem_x509_certificate(self._leaf_cert_callback())
162158
trust_chain = self._read_trust_chain()
163159
cert_chain = []
164160

@@ -184,9 +180,7 @@ def get_subject_token(self, context, request):
184180
return json.dumps(cert_chain)
185181

186182
def _read_trust_chain(self):
187-
# Import OpennSSL inline because it is an extra import only required by customers
188-
# using mTLS.
189-
from OpenSSL import crypto
183+
from cryptography import x509
190184

191185
certificate_trust_chain = []
192186
# If no trust chain path was provided, return an empty list.
@@ -204,9 +198,7 @@ def _read_trust_chain(self):
204198
cert_data = b"-----BEGIN CERTIFICATE-----" + cert_block
205199
try:
206200
# Load each certificate and add it to the trust chain.
207-
cert = crypto.load_certificate(
208-
crypto.FILETYPE_PEM, cert_data
209-
)
201+
cert = x509.load_pem_x509_certificate(cert_data)
210202
certificate_trust_chain.append(cert)
211203
except Exception as e:
212204
raise exceptions.RefreshError(
@@ -221,13 +213,11 @@ def _read_trust_chain(self):
221213
)
222214

223215
def _encode_cert(cert):
224-
# Import OpennSSL inline because it is an extra import only required by customers
225-
# using mTLS.
226-
from OpenSSL import crypto
216+
from cryptography.hazmat.primitives import serialization
227217

228-
return base64.b64encode(
229-
crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)
230-
).decode("utf-8")
218+
return base64.b64encode(cert.public_bytes(serialization.Encoding.DER)).decode(
219+
"utf-8"
220+
)
231221

232222

233223
def _parse_token_data(token_content, format_type="text", subject_token_field_name=None):

packages/google-auth/google/auth/transport/_custom_tls_signer.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
import os
2424
import sys
2525

26-
import cffi # type: ignore
27-
2826
from google.auth import exceptions
2927

3028
_LOGGER = logging.getLogger(__name__)
@@ -45,11 +43,6 @@
4543
)
4644

4745

48-
# Cast SSL_CTX* to void*
49-
def _cast_ssl_ctx_to_void_p_pyopenssl(ssl_ctx):
50-
return ctypes.cast(int(cffi.FFI().cast("intptr_t", ssl_ctx)), ctypes.c_void_p)
51-
52-
5346
# Cast SSL_CTX* to void*
5447
def _cast_ssl_ctx_to_void_p_stdlib(context):
5548
return ctypes.c_void_p.from_address(
@@ -274,7 +267,7 @@ def attach_to_ssl_context(self, ctx):
274267
if not self._offload_lib.ConfigureSslContext(
275268
self._sign_callback,
276269
ctypes.c_char_p(self._cert),
277-
_cast_ssl_ctx_to_void_p_pyopenssl(ctx._ctx._context),
270+
_cast_ssl_ctx_to_void_p_stdlib(ctx),
278271
):
279272
raise exceptions.MutualTLSChannelError(
280273
"failed to configure ECP Offload SSL context"

0 commit comments

Comments
 (0)