我正在尝试调用在以下 .PEM 验证的 zeep 客户端会话中启动的服务:
from requests import Session
from zeep import Client
from zeep.settings import Settings
from zeep.transports import Transport
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.hazmat.primitives import serialization
session = Session()
session.verify = True
private_key, certificate, additional_certificates = pkcs12.load_key_and_certificates(
open(pfx_cert_path, "rb").read(), cert_psw_b)
key_tmpfile = tempfile.NamedTemporaryFile(dir="C:/Users/USERA/Anaconda3/envs/env/"
key_tmpfile.write(
private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
)
key_tmpfile.flush()
cert_tmpfile=tempfile.NamedTemporaryFile(dir="C:/Users/USERA/Anaconda3/envs/env/")
cert_tmpfile.write(
certificate.public_bytes(serialization.Encoding.PEM),
)
cert_tmpfile.flush()
session.cert = cert_tmpfile.name, key_tmpfile.name
settings = Settings(raw_response=True)
soap_client = Client(wsdl_url, transport=Transport(session=session), settings=settings)
我发送的内容包含具有动态值的 SOAP 主体和 SOAP 标头。我这样做类似于:
soap_client.service.UploadMethod({"body_field":"body_value","_soapheaders":{"head_field":"head_value"}})
这会产生以下错误队列,最终导致 urllib3 以读取模式访问临时文件时权限被拒绝。
File "C:\Users\USERA\Anaconda3\envs\env\lib\site-packages\urllib3\connection.py", line 419, in connect
self.sock = ssl_wrap_socket(
File "C:\Users\USERA\Anaconda3\envs\env\lib\site-packages\urllib3\util\ssl_.py", line 413, in ssl_wrap_socket
if keyfile and key_password is None and _is_key_file_encrypted(keyfile):
File "C:\Users\USERA\Anaconda3\envs\env\lib\site-packages\urllib3\util\ssl_.py", line 472, in _is_key_file_encrypted
with open(key_file, "r") as f:
PermissionError: [Errno 13] Permission denied:
我尝试过不同的地点。我也尝试过使用 Suds 客户端执行此操作,或直接依赖请求进行肥皂调用,但我无法像上面那样加载 .pem 证书和密钥。目前我设法在 C# 中简单地做到这一点,如下所示:
CLIENT client = new CLIENT(); // Project -> Add Service Reference -> Advanced -> Add Web Reference -> https://service_domain/service.asmx -> Rename "CLIENT"
string cert_path = "C:/PATH/Certificates/PKCS12_Credential.pfx";
string cert_psw_path = "C:/PATH/Certificates/psw_pfx.txt";
X509Certificate2 certificate = new X509Certificate2(cert_path, File.OpenText(cert_psw_path).ReadLine());
client.ClientCertificates.Add(certificate);
client.Login("", ""); // Which loads the soap headers
string xml_body = "C:/PATH/Body.txt";
client.UploadMethod(File.ReadAllText(xml_body));
有人知道如何解决这个问题吗?也许通过更改 urllib 版本或使用另一种方式直接将证书作为字节字符串而不是通过路径文件传递?
提前致谢。
当您尝试在 Windows 上的 Python 中访问由
tempfile.NamedTemporaryFile
创建的临时文件时,您似乎遇到了权限问题。这是用户在 Windows 上使用此功能时面临的常见问题,因为操作系统不允许多个进程打开同一文件。 urllib3 库以读取模式打开文件,这会导致 PermissionError
,因为临时文件在 Python 代码中仍处于打开状态。
要解决此问题,您可以避免创建临时文件并直接从内存加载证书和密钥。这可以通过使用
load_cert_chain()
方法来完成,该方法接受证书和密钥的二进制数据,而不是文件路径。
您可以使用
private_bytes
和 public_bytes
方法将私钥和证书转换为字符串,将它们解码为 UTF-8,然后使用 ssl.SSLContext()
将字符串提供给 ssl 上下文。下面,请找到一个不使用临时文件的示例实现,使用 Zeep 的低级内置支持来提供自定义传输:
import ssl
from zeep import Client, Settings, Plugin
from zeep.transports import Transport
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.hazmat.primitives import serialization
private_key, certificate, additional_certificates = pkcs12.load_key_and_certificates(
open(pfx_cert_path, "rb").read(), cert_psw_b)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(
certfile=certificate.public_bytes(serialization.Encoding.PEM).decode('utf-8'),
keyfile=private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
).decode('utf-8'))
settings = Settings(strict=True, xml_huge_tree=True)
client = Client(wsdl_url,
plugins=[MyLoggingPlugin()],
transport=Transport(session=session, timeout=10, operation_timeout=10, ssl_context=context),
settings=settings)
response = client.service.MyMethod()
请注意,在创建 Transport 对象期间使用了
ssl_context
参数。这将允许您传递包含证书和密钥的 SSL 上下文,以便与 Web 服务进行通信。
此外,请记住将
wsdl_url
和 MyMethod()
替换为您自己的 Web 服务详细信息。请注意,session
也需要正确初始化和配置。
此方法应该避免您面临的
PermissionError
,因为它绕过了创建临时文件的需要,并将证书和密钥直接加载到 SSL 上下文中。请记住确保这些字符串的安全,因为它们将包含非常敏感的密钥和证书数据。