NiceLeeのBlog 用爱发电 bilibili~

使用CF Worker检测TLS证书的失败尝试(Python篇)

2024-09-16
nIceLee

阅读:


事情的起因是发现Cloudflare Worker支持Python了,虽然还是在Beta,但就是想用来尝试点什么。
于是就想简单的检测一下证书过期时间。

尝试1 - 不支持socket建立TCP连接

直接socket一把梭

import socket,ssl

def get_server_certificate(host, port, sni, **kwargs):
    context = ssl.SSLContext()
    context.verify_mode = ssl.CERT_NONE
    context.check_hostname = False
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(1)
    with context.wrap_socket(s, server_hostname=sni) as sslSocket:
        sslSocket.connect((host, port))
        dercert = sslSocket.getpeercert(True)
        return dercert
        
from js import Response
import json
def on_fetch(request):
    config = [
        {
            "host": "www.baidu.com",
            "port": 443,
            "sni": "www.baidu.com",
            "remarks": "百度"
        },
    ]
    cert = get_server_certificate(**config[0])
    return Response.new(cert.get("notAfter"))

报错

Error: PythonError: Traceback (most recent call last):
  File "/lib/python312.zip/pyodide/code.py", line 105, in relaxed_call
    return _do_call(func, sig, args, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python312.zip/pyodide/code.py", line 69, in _do_call
    return func(*bound.args, **bound.kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/session/metadata/index.py", line 25, in on_fetch
    cert = get_server_certificate(**config[0])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/session/metadata/index.py", line 9, in get_server_certificate
    with context.wrap_socket(s, server_hostname=sni) as sslSocket:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/session/lib/python3.12/site-packages/ssl.py", line 455, in wrap_socket
    return self.sslsocket_class._create(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/session/lib/python3.12/site-packages/ssl.py", line 960, in _create
    if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM:
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
OSError: [Errno 50] Protocol not available

看样子并不支持使用Socket发送TCP报文

尝试2 - 没有内嵌httpx模块

官方文档Built-in packages, including FastAPI ↗, Langchain ↗, httpx ↗ and more.
那么我们使用httpx

from httpx.backends.base import lookup_backend

async def _get_server_certificate(host, port, sni):
    backend = lookup_backend("auto") # auto asyncio trio
    ssl_context = SSLConfig(verify=True,http2=True).ssl_context
    ssl_context.check_hostname = False
    timeout = Timeout(5)
    
    socket = await backend.open_tcp_stream(host, port, ssl_context, timeout)
    ssl_object = socket.stream_writer.get_extra_info("ssl_object")
    peercert = ssl_object.getpeercert()
    # peercert = ssl.DER_cert_to_PEM_cert(peercert)
    await socket.close()
    return peercert

def get_server_certificate(host, port, sni, **kwargs):
    loop = asyncio.get_event_loop()
    peercert = loop.run_until_complete(_get_server_certificate(host, port, sni))
    return peercert

from js import Response
import json
def on_fetch(request):
    config = [
        {
            "host": "www.baidu.com",
            "port": 443,
            "sni": "www.baidu.com",
            "remarks": "百度"
        },
    ]
    cert = get_server_certificate(**config[0])
    return Response.new(cert.get("notAfter"))

报错

Error: PythonError: Traceback (most recent call last):
  File "/lib/python312.zip/_pyodide/_base.py", line 629, in pyimport_impl
    res = __import__(stem, fromlist=fromlist)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/session/metadata/index.py", line 4, in <module>
    from httpx.backends.base import lookup_backend
ModuleNotFoundError: No module named 'httpx'

好吧,说好的Built-in packages呢?

尝试3 - 不支持导入依赖

参考官方文档:

To import a Python package, add the package name to the requirements.txt file within the same directory as your wrangler.toml configuration file.`

所以,我加上了requirements.txt,内容为 httpx

[ERROR] Uncaught (in response) Error: internal error

      at async loadBundle (pyodide-internal:loadPackage:36:22)
      at async Promise.all (index 7)
      at async loadPackages (pyodide-internal:loadPackage:75:21)
      at async pyodide:python-entrypoint-helper:61:9
      at async setupPackages (pyodide:python-entrypoint-helper:59:12)
      at async pyodide:python-entrypoint-helper:86:13
      at async preparePython (pyodide:python-entrypoint-helper:100:24)
      at async pyodide:python-entrypoint-helper:107:81
      at async Object.fetch (pyodide:python-entrypoint-helper:107:32)

算了,这依赖不要也罢。
发现httpx异步依赖的是asyncio或者trio,尝试直接使用asyncio

尝试4 - 不需要异步转同步

尝试直接使用asyncio

import asyncio,ssl

async def _get_server_certificate(host, port, sni):
    context = ssl.create_default_context()
    # context = SSLConfig(verify=False,http2=True).ssl_context
    context.check_hostname = False
    reader, writer = await asyncio.open_connection(host, port, server_hostname=sni, ssl=context)
    ssl_object = writer.get_extra_info('ssl_object')
    peercert = ssl_object.getpeercert()
    # peercert = ssl.DER_cert_to_PEM_cert(peercert)
    await writer.close()
    # return pemcert
    return peercert

def get_server_certificate(host, port, sni, **kwargs):
    loop = asyncio.get_event_loop()
    peercert = loop.run_until_complete(_get_server_certificate(host, port, sni))
    return peercert

from js import Response
def on_fetch(request):
    config = [
        {
            "host": "www.baidu.com",
            "port": 443,
            "sni": "www.baidu.com",
            "remarks": "百度"
        },
    ]
    cert = get_server_certificate(**config[0])
    return Response.new(cert.get("notAfter"))

报错

Error: PythonError: Traceback (most recent call last):
  File "/lib/python312.zip/pyodide/code.py", line 105, in relaxed_call
    return _do_call(func, sig, args, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python312.zip/pyodide/code.py", line 69, in _do_call
    return func(*bound.args, **bound.kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/session/metadata/index.py", line 37, in on_fetch
    return Response.new(cert.get("notAfter"))
                        ^^^^^^^^
AttributeError: 'PyodideTask' object has no attribute 'get'

看了一下,直接异步就行了,我这个属于脱裤子放屁,多此一举

尝试5 - 不支持asyncio建立TCP连接

尝试直接使用asyncio,返回异步结果

import asyncio, ssl


async def get_server_certificate(host, port, sni, **kwargs):
    context = ssl.create_default_context()
    # context = SSLConfig(verify=False,http2=True).ssl_context
    context.check_hostname = False
    reader, writer = await asyncio.open_connection(host, port, server_hostname=sni, ssl=context)
    ssl_object = writer.get_extra_info('ssl_object')
    peercert = ssl_object.getpeercert()
    # peercert = ssl.DER_cert_to_PEM_cert(peercert)
    await writer.close()
    # return pemcert
    return peercert

from js import Response
async def on_fetch(request):
    config = [
        {
            "host": "www.baidu.com",
            "port": 443,
            "sni": "www.baidu.com",
            "remarks": "百度"
        },
    ]
    
    cert = await get_server_certificate(**config[0])
    return Response.new(cert.get("notAfter"))

报错

Error: PythonError: Traceback (most recent call last):
  File "/session/metadata/index.py", line 27, in on_fetch
    cert = await get_server_certificate(**config[0])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/session/metadata/index.py", line 8, in get_server_certificate
    reader, writer = await asyncio.open_connection(host, port, server_hostname=sni, ssl=context)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python312.zip/asyncio/streams.py", line 48, in open_connection
    transport, _ = await loop.create_connection(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python312.zip/asyncio/events.py", line 312, in create_connection
    raise NotImplementedError
NotImplementedError

底层asyncio根本没有实现

尝试6 - 当前场景 Pyodide 不支持 loadPackage

查看Pyodide的说明,尝试导入httpx或其它aiohttp模块,后者是PyodideCloudflare都声称是Built-in的

async def _get_server_certificate(host, port, sni, **kwargs):
    import pyodide_js
    await pyodide_js.loadPackage('httpx')
    # await pyodide_js.loadPackagesFromImports(pkg);
    httpx = pyodide_js.pyimport("httpx");
    from httpx.backends.base import lookup_backend
    
    backend = lookup_backend("auto") # auto asyncio trio
    ssl_context = SSLConfig(verify=True,http2=True).ssl_context
    ssl_context.check_hostname = False
    timeout = Timeout(5)
    
    socket = await backend.open_tcp_stream(host, port, ssl_context, timeout)
    ssl_object = socket.stream_writer.get_extra_info("ssl_object")
    peercert = ssl_object.getpeercert()
    # peercert = ssl.DER_cert_to_PEM_cert(peercert)
    await socket.close()
    return peercert

from js import Response
import json
async def on_fetch(request):
    config = [
        {
            "host": "www.baidu.com",
            "port": 443,
            "sni": "www.baidu.com",
            "remarks": "百度"
        },
    ]
    cert = await _get_server_certificate(**config[0])
    return Response.new(cert.get("notAfter"))

报错

Error: PythonError: Traceback (most recent call last):
  File "/session/metadata/index.py", line 34, in on_fetch
    cert = await _get_server_certificate(**config[0])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/session/metadata/index.py", line 5, in _get_server_certificate
    await pyodide_js.loadPackage('httpx')
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "pyodide-internal:generated/pyodide.asm", line 20, in JsvFunction_CallBound
  File "pyodide-internal:setupPackages", line 130, in Object.disabledLoadPackage [as loadPackage]
pyodide.ffi.JsException: Error: pyodide.loadPackage is disabled because packages are encoded in the binary

没救了,等死吧


相似文章

内容
隐藏