Go语言 从客户端获取TLS信息

ubby3x7f  于 12个月前  发布在  Go
关注(0)|答案(3)|浏览(111)

net/http go包有一个类型请求,它定义了一个包含ConnectionState的TLS字段。尽管如此,描述中的最后一条语句说它被HTTP客户端忽略了。我在调试时也检查了它,它是nil
我需要从该连接状态(或其他地方)获取值TLSUnique,以便在将其注册/发送到服务器之前将其包含在证书请求(也称为CSR)中。
服务器不在我的问题范围内,我关心的是客户端。
然后,服务器接收请求,并检查CSR的签名沿着TLS唯一值,证明与TLS连接建立的同一客户端与签署CSR的客户端相同。
这是来自RFC 7030 -第3.5节(EST协议)的内容

[我用的是什么]

我正在试验GlobalSign EST Go包,它们似乎不包括此功能。
他们的EST客户端似乎为每个EST操作创建了一个http客户端,我想我可以改变这种行为,让一个客户端发送所有请求。
然而,由于客户端接受RoundTripper接口,我不能在实现之外使用底层连接的信息。

igetnqfo

igetnqfo1#

注意事项:在这个答案下的评论线程中,很明显OP是在发出客户端请求时获取“TLS unique”值之后。对此,请参阅my other answer或OP的解决方案。
我决定保留这个答案作为参考,因为它展示了一个有用的技术。
示例化http.Server,然后将其字段ConnContext设置为您需要编写的某个函数。
该函数在客户端创建到服务器的每个新TCP连接时被调用一次(一个连接能够服务多个请求)。当调用时,它接收服务客户端请求的net.Conn,因此您可以将其类型Assert为tls.Conn,然后在其上调用ConnectionState并在返回值中检查TLSUnique
由于您可能需要使此值可用于通过该连接执行的HTTP请求,因此可以说最明智的解决方案是将该内容作为“值”隐藏在上下文中,该上下文将通过处理程序代码中的http.Request s提供。
要做到这一点,在同一个回调代码中,您可以“ Package ”与请求关联的原始context.Context,并将其传递给回调,并从该TLSUnique中提取一些值,以便您可以在服务于请求的HTTP处理程序中检查它。
类似这样(未经测试):

srv := &http.Server{
  ConnContext: func(ctx context.Context, c net.Conn) context.Context {
    tc := c.(*tls.Conn)

    return context.WithValue(ctx, mypkg.MyKey,
      tc.ConnectionState().TLSUnique)
  },
  // other fields, if needed
}

// ... then, in HTTP request handlers:

func MyHTTPReqHandler(rw http.ResponseWriter, req *http.Request) {
  uniq := req.Context().Value(mypkg.MyKey)
  // verify it's not nil and use
}

字符串
查看有关context.Context.Value的文档,了解如何声明mypkg.MyKey
您可能还需要在提取TLSUique时实际复制它的值-我不知道是否需要。

qpgpyjmq

qpgpyjmq2#

其中一种方法是对全局符号EST包进行一个小的更改。
就像我前面说的,当前的实现为每个EST操作(CACerts、CSRAttrs、Enroll等)创建一个新的http客户端。
1-让我们为所有EST操作创建一个http客户端。
2-无论哪种方式,我们都需要在注册CSR之前打电话获取CA证书。
3-对任何客户端请求的http响应都会公开字段http.Response.TLS.TLSUnique
4-让客户端在证书请求中包括它,签名并注册它。
当我想到全局签名EST包以及为什么他们每次都选择创建一个新的http客户端时,我不确定这是否有任何安全问题。
(除非它只是根据用户的意愿等待使用的示例)

5m1hhzi4

5m1hhzi43#

好吧,根据我的另一个答案下的评论线程,下面是我如何实现客户端的:

package main

import (
    "bytes"
    "context"
    "crypto/tls"
    "errors"
    "io"
    "log"
    "net/http"
    "net/http/httptrace"
)

type csrParams struct{}

type csrLazyBody struct {
    ctx    context.Context
    params csrParams

    constructed bool
    data        bytes.Buffer
}

func newCSRLazyBody(ctx context.Context, params csrParams) *csrLazyBody {
    return &csrLazyBody{
        ctx:    ctx,
        params: params,
    }
}

func (b *csrLazyBody) Read(p []byte) (int, error) {
    if !b.constructed {
        if err := b.construct(); err != nil {
            return 0, err
        }

        b.constructed = true
    }

    return b.data.Read(p)
}

func (b *csrLazyBody) Close() error {
    return nil
}

func (b *csrLazyBody) construct() error {
    switch uniqPtr := b.ctx.Value(tlsUniqKey).(type) {
    case nil:
        return errors.New("missing TLS unique value")
    case *[]byte:
        _, err := b.data.Write(*uniqPtr)
        return err
    default:
        panic("cannot happen")
    }
}

var _ io.ReadCloser = &csrLazyBody{}

type tlsUniqKeyType int

const tlsUniqKey = tlsUniqKeyType(0)

func newCSR(method, url string, params csrParams) (*http.Request, error) {
    uniqPtr := new([]byte)

    ctx := context.WithValue(context.Background(),
        tlsUniqKey, uniqPtr)

    trace := &httptrace.ClientTrace{
        GotConn: func(connInfo httptrace.GotConnInfo) {
            conn := connInfo.Conn

            tc, ok := conn.(*tls.Conn)
            if !ok {
                return
            }

            uniq := tc.ConnectionState().TLSUnique

            *uniqPtr = uniq
        },
    }

    return http.NewRequestWithContext(
        httptrace.WithClientTrace(ctx, trace),
        method, url, newCSRLazyBody(ctx, params))
}

func main() {
    log.SetFlags(0)

    req, err := newCSR("GET", "https://csa.acme.com/api/enroll", csrParams{})
    if err != nil {
        log.Fatal(err)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()
}

字符串
关键点:

  • 用于创建将CSR传输到注册服务的HTTP请求的函数newCSR(我假设这是所需的)创建了一个新的http.Request,它:
  • 配备了context.Context,它:
  • 有一个特殊的“跟踪”设施附加到它(见下文)。
  • 携带一个“value”,它是指向一个切片的指针,该切片包含要附加的“TLS unique”值。
  • 以特殊类型的变量的形式包含请求主体,一旦Read方法第一次调用它,它就会自动返回。

此变量与HTTP跟踪程序共享context.Context

  • 当使用中的http.Transport通过任何方式 * 获得 * 一个连接来执行被跟踪的请求 * 时,HTTP跟踪器的“got conn”回调会被调用。* 这包括新连接或从空闲(重用)连接池中拉取的连接。

如果连接是tls.Conn,回调的代码将从中提取“TLS unique”值,并将其保存到变量中,该变量是隐藏在上下文中的指针。

  • 一段时间后,往返请求的http.Transport开始读取请求的主体,这就是“懒惰主体构造”的开始:它读取包含跟踪器获得的“TLS unique”数据的变量的指针,并使用它来构造实际的CSR数据,然后以“正常方式”读取。

代码是复杂的,但在另一方面,它可以与底层http.Transport的任何设置一起工作-连接池是主要的兴趣点。
当然,如果你确切地知道你的设置的要求,这种方法很容易被过度使用。例如,如果你知道 * 或要求 * 不应该使用池,那么可以走一条更简单的路线,比如:

  • 直接使用tls包创建一个tls.Conn,保存到某个地方,同时保存从中提取的“TLS unique”数据。
  • 使用自定义http.Transport,它:
  • 将其DialTLSWithContext字段设置为仅返回已保存连接的回调。
  • 已禁用HTTP keepalive和空闲连接。
  • Maybe被 Package 在http.RoundTripper中,以确保在任何给定时间只有一个RoundTrip调用处于活动状态。

相关问题