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

时间:2016-04-18 22:29:59

标签: 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))

我知道有另一个常见的lisp库drakma可以做我需要的东西。但我只是好奇为什么我的方法失败了?

2 个答案:

答案 0 :(得分:2)

我不熟悉常见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标头中提取主体大小并只读取给定的字节数。在这种情况下使用Connection: keep-alive是一个坏主意,因为这样您就可以要求服务器在发送正文后保持连接打开。无论如何,使用Connection: keep-alive时隐含HTTP/1.1

答案 1 :(得分:0)

根据Steffen的重要解释,我找到了让它运作的方法:

(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)
                :hostname "beta.quicklisp.org")))
    (unwind-protect
         (progn
           (format https "GET /quicklisp.lisp HTTP/1.0~%Host:beta.quicklisp.org~%Accept: */*~2%")
           (force-output https)
           (loop for line = (read-line https nil)
              while line do (format t "HTTPS> ~a~%" line)))
      (close https))))

谢谢,Steffen。