通过 https 下载文件时出现 SSL 错误

标签 ssl https common-lisp

我需要从https://beta.quicklisp.org/quicklisp.lisp下载最新版本的quicklisp.lisp

首先,我在代码中使用common-lisp库usocket和cl+ssl尝试下载html页面https://www.quicklisp.org/beta/ ,并且有效

(usocket:with-client-socket (sock stream "quicklisp.org" 443)
  (let ((https (cl+ssl:make-ssl-client-stream
                stream :unwrap-stream-p t
                :external-format '(:iso-8859-1 :eol-style :lf))))
    (unwind-protect
         (progn
           (format https "GET /beta/ HTTP/1.0~%Host:www.quicklisp.org~%Connection:keep-alive~2%")
           (force-output https)
           (loop for line = (read-line https nil)
              while line do (format t "HTTPS> ~a~%" line)))
      (close https))))

然后我稍微修改一下上面的 Quicklisp.lisp 文件的代码:

(usocket:with-client-socket (sock stream "beta.quicklisp.org" 443)
  (let ((https (cl+ssl:make-ssl-client-stream
                stream :unwrap-stream-p t
                :external-format '(:iso-8859-1 :eol-style :lf))))
    (unwind-protect
         (progn
           (format https "GET /quicklisp.lisp HTTP/1.1~%Host:beta.quicklisp.org~%Connection:keep-alive~%Accept: */*~2%")
           (force-output https)
           (loop for line = (read-line https nil)
              while line do (format t "HTTPS> ~a~%" line)))
      (close https))))

这一次,它失败了,运气不好。错误消息显示:

A failure in the SSL library occurred on handle #.(SB-SYS:INT-SAP #X7FFFDC019A80) (return code: 1).
SSL error queue:
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
   [Condition of type CL+SSL::SSL-ERROR-SSL]

Restarts:
 0: [RETRY] Retry SLIME interactive evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [ABORT] abort thread (#<THREAD "worker" RUNNING {10088E6223}>)

Backtrace:
  0: (CL+SSL::SSL-SIGNAL-ERROR #.(SB-SYS:INT-SAP #X7FFFDC019A80) #<FUNCTION CL+SSL::SSL-CONNECT> 1 -1)
      Locals:
        ERROR-CODE = 1
        HANDLE = #.(SB-SYS:INT-SAP #X7FFFDC019A80)
        ORIGINAL-ERROR = -1
        SYSCALL = #<FUNCTION CL+SSL::SSL-CONNECT>
  1: (CL+SSL:MAKE-SSL-CLIENT-STREAM #<unavailable lambda list>)
      [No Locals]
  2: ((LAMBDA ()))
  3: (SB-INT:SIMPLE-EVAL-IN-LEXENV (USOCKET:WITH-CLIENT-SOCKET (SOCK STREAM "beta.quicklisp.org" 443) (LET (#) (UNWIND-PROTECT # #))) #<NULL-LEXENV>)
  4: (EVAL (USOCKET:WITH-CLIENT-SOCKET (SOCK STREAM "beta.quicklisp.org" 443) (LET (#) (UNWIND-PROTECT # #))))
  5: ((LAMBDA NIL :IN SWANK:INTERACTIVE-EVAL))

我知道还有另一个 common-lisp 库 drakma 可以满足我的需要。但我只是好奇为什么我的方法失败了?

最佳答案

我不熟悉 Common Lisp 中的 SSL 工作原理,但来自 SSLLabs report可以看出,beta.quicklistp.org仅在客户端使用Server Name Indication (SNI)时才起作用。而您的工作示例中的 quicklistp.org 不需要 SNI。

如果我正确地解释了您的代码,您首先会创建到目标主机的 TCP 连接,然后在该连接周围封装 TLS。由于在进行此包装时您没有提供有关目标主机名的任何信息,因此我假设 TLS 包装器不会知道主机名,因此无法在 TLS 握手中告诉服务器所请求的主机名,即它无法使用 SNI。由于服务器需要 SNI,因此连接将失败。

根据to the documentation有一种方法可以指定主机名。来自文档:

Function CL+SSL:MAKE-SSL-CLIENT-STREAM 
(fd-or-stream &key 
   external-format certificate key password close-callback 
   (unwrap-streams-p t) 
   hostname
 )
 ...
 hostname if specified, will be sent by client during TLS negotiation, 
 according to the Server Name Indication (SNI) extension to the TLS. 

除此之外:
您正在执行 HTTP/1.1 请求,但无法正确处理 HTTP/1.1 响应(例如分块编码) 。对于像您这样的简单请求,我建议仅使用 HTTP/1.0 。此外,您的代码期望服务器在数据传输完成后关闭连接,即您不从 HTTP header 中提取正文大小并仅读取给定的字节数。在这种情况下使用 Connection: keep-alive 是一个坏主意,因为这样您会要求服务器在发送正文后保持连接打开。此外,无论如何使用 HTTP/1.1 时,Connection: keep-alive 都是隐式的。

关于通过 https 下载文件时出现 SSL 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36705427/

相关文章:

common-lisp - 使用波浪号将文件名扩展到其完整路径(Common Lisp)

ssl - 未找到 BizTalk WCF-WebHttp 适配器客户端证书

database - 如何在 Neo4j 3.0.3 中使用 SSL 证书

java - 如何用公共(public) CA 机构签署的服务器公共(public)证书解决 "unable to find valid certification path to requested target"?

apache - 未加载 SSL 证书

lisp - ecl_init_module 中的第一个参数是做什么的?

ssl - 端口 8883 上的 MQTT TLS 并在 1883 中打开连接

java - SOAP 连接握手期间远程主机关闭连接

security - 使用 HTTPS 传输解密存储的 secret

lisp - 用于 eql 的 Common Lisp 通配符