Python将CA证书作为字符串请求

时间:2017-07-31 08:11:14

标签: python python-requests ca

目前,我们正在使用在服务器上放置CA证书以访问第三方API的方法。

certificate_path = os.path.join(CERT_PATH, 'cacert.pem')
certificate_key_path = os.path.join(CERT_PATH, 'cacert.key')
response = requests.get(url, cert=(certificate_path, certificate_key_path))

这样可行,但我们正在寻找而不是在服务器上存储CA证书,出于安全目的存储在数据库的Accounts表中(客户提出的安全原因)。

所以问题是:

  • 是否有任何方法可以直接将CA证书的字符串直接传递给requests(除了将内容写入临时文件)?

  • 是否有其他http python模块支持在http获取/发布请求中传递CA证书的字符串?

  • 我们应该使用其他方法,而不是将它们存储在数据库和服务器上吗?

3 个答案:

答案 0 :(得分:3)

有一种方法可以通过临时文件来实现,比如:

cert = tempfile.NamedTemporaryFile(delete=False)
cert.write(CERTIFICATE_AS_STRING)
cert.close()
requests.get(url, cert=cert.name, verify=True)
os.unlink(cert.name)

如果您想知道为什么这可能不安全,请在此处查看我的答案:https://stackoverflow.com/a/46570264/6445270

答案 1 :(得分:2)

您提供的示例是通过请求documentation中所示的客户端证书。

就目前而言,无法通过客户端证书和密钥(或作为字符串)传递内存。

将猴子修补到紧急情况-通过猴子修补requests,您可以添加从内存加载客户端证书和密钥的功能。以下修补程序可以在不破坏现有功能的情况下以各种格式传递客户端证书和密钥。

import requests
from OpenSSL.crypto import PKCS12, X509, PKey


def _is_key_file_encrypted(keyfile):
    '''In memory key is not encrypted'''
    if isinstance(keyfile, PKey):
        return False
    return _is_key_file_encrypted.original(keyfile)


class PyOpenSSLContext(requests.packages.urllib3.contrib.pyopenssl.PyOpenSSLContext):
    '''Support loading certs from memory'''
    def load_cert_chain(self, certfile, keyfile=None, password=None):
        if isinstance(certfile, X509) and isinstance(keyfile, PKey):
            self._ctx.use_certificate(certfile)
            self._ctx.use_privatekey(keyfile)
        else:
            super().load_cert_chain(certfile, keyfile=keyfile, password=password)


class HTTPAdapter(requests.adapters.HTTPAdapter):
    '''Handle a variety of cert types'''
    def cert_verify(self, conn, url, verify, cert):
        if cert:
            # PKCS12
            if isinstance(cert, PKCS12):
                conn.cert_file = cert.get_certificate()
                conn.key_file = cert.get_privatekey()
                cert = None
            elif isinstance(cert, tuple) and len(cert) == 2:
                # X509 and PKey
                if isinstance(cert[0], X509) and hasattr(cert[1], PKey):
                    conn.cert_file = cert[0]
                    conn.key_file = cert[1]
                    cert = None
                # cryptography objects
                elif hasattr(cert[0], 'public_bytes') and hasattr(cert[1], 'private_bytes'):
                    conn.cert_file = X509.from_cryptography(cert[0])
                    conn.key_file = PKey.from_cryptography_key(cert[1])
                    cert = None
        super().cert_verify(conn, url, verify, cert)


def patch_requests(adapter=True):
    '''You can perform a full patch and use requests as usual:

    >>> patch_requests()
    >>> requests.get('https://httpbin.org/get')

    or use the adapter explicitly:

    >>> patch_requests(adapter=False)
    >>> session = requests.Session()
    >>> session.mount('https', HTTPAdapter())
    >>> session.get('https://httpbin.org/get')
    '''
    if hasattr(requests.packages.urllib3.util.ssl_, '_is_key_file_encrypted'):
        _is_key_file_encrypted.original = requests.packages.urllib3.util.ssl_._is_key_file_encrypted
        requests.packages.urllib3.util.ssl_._is_key_file_encrypted = _is_key_file_encrypted
    requests.packages.urllib3.util.ssl_.SSLContext = PyOpenSSLContext
    if adapter:
        requests.sessions.HTTPAdapter = HTTPAdapter

要使用补丁,您可以执行以下操作(假设上面的代码在名为patch.py的文件中)

import os
import requests
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from patch import patch_requests


CLIENT_CERT = serialization.load_pem_x509_certificate(
    os.getenv('CLIENT_CERT'), default_backend())
CLIENT_KEY = serialization.load_pem_private_key(
    os.getenv('CLIENT_KEY'), None, default_backend())


# monkey patch load_cert_chain to allow loading
# cryptography certs and keys from memory
patch_requests()


response = requests.get(url, cert=(CLIENT_CERT, CLIENT_KEY))

您现在可以为pyopenssl对象或cryptography对象中的内存请求提供客户端证书。

答案 2 :(得分:1)

如果要在不使用临时文件的情况下执行此操作,则可以通过覆盖请求SSLContext来实现。可以在this answer中看到示例。