golang如何訪問一個結構體(struct)的未導出域(field)

golang如何訪問一個結構體(struct)的未導出域(field)

問題的引出

在做fabric調試的時候,經常碰到證書驗證失敗的問題,看到orderer日志類似:

<orderer>  | <timestampe> [cauthdsl] deduplicate -> ERRO 177 Principal deserialization failure (the supplied
 identity is not valid: x509: certificate signed by unknown authority (possibly because of "x509: ECDSA
 verification failure" while trying to verify candidate authority certificate "...")) for identity <identify content>

在這個過程中打出來的是被驗證的證書的內容(通過前面文章我們知道可以從identity導出證書的PEM格式),但是沒有打印出驗證所需要的CA證書信息,怎么辦呢。我們需要得到驗證所用到的CA證書信息。

溯源CA證書信息

msp對象(實際是bccspmsp struct)包含一個成員opts:

# fabric/msp/mspimpl.go
type bccspmsp struct {
  ...
  // verification options for MSP members
  opts *x509.VerifyOptions
  ...
}

opts是一個x509.VerifyOptions對象。

# go/src/crypto/x509/verify.go
type VerifyOptions struct {
  ...
  Intermediates *CertPool
  Roots         *CertPool // if nil, the system roots are used
  ...
}

VerifyOptions包含一個Roots和Intermediates,這兩個pool里面的證書是用來驗證證書的根證書。正好這兩個變量都是導出變量(首字母大寫),可以直接使用。
下面看CertPool的定義。

# go/src/crypto/x509/cert_pool.go
type CertPool struct {
        bySubjectKeyId map[string][]int
        byName         map[string][]int
        certs          []*Certificate
}

我們看到所有的證書都存在certs這個域里面,他是一個slice類型變量,只是遺憾的是certs不是一個導出變量(首字母小寫),導致在包(package)外面不能訪問這個域;另外CertPool也沒有定義函數用來返回certs域,所有CertPool定義的導出函數只有如下:

func (s *CertPool) AddCert(cert *Certificate)
func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool)
func (s *CertPool) Subjects() [][]byte

這不抓瞎了嗎,明明certs就定義在那里已經看到了,但是就是訪問不了;庫的設計者為什么要這樣折磨人呢,為什么不暴露一個API導出這個slice呢,基于什么原因呢?

當然有一個辦法,我們手動添加一個導出函數,在cert_pool.go文件內,例如:

func (s *CertPool) Certs() []*Certificate {
  return c.certs
}

不過要提醒你的是,這可是golang的系統庫啊,不是你的用戶代碼。請你不要隨便改。

另一辦法,就是本文要說的如何訪問一個未導出域。
CertPool的certs是一個未導出域,我們已經得到了CertPool對象,并且知道他有一個域certs包含的就是CA證書列表,下面我們就是想辦法訪問這個未導出域。

訪問未導出域

使用reflect包訪問struct的未導出域。

package main

import (
    "fmt"
    "reflect"
    "unsafe"
    "crypto/x509"
)

func main() {
    var certPool *x509.CertPool
    var err error
    if certPool, err = x509.SystemCertPool(); err != nil {
        panic(err)
    }

    var reflectStruct reflect.Value = reflect.ValueOf(certPool).Elem()
    var reflectField reflect.Value = reflectStruct.FieldByName("certs")

    fmt.Printf("reflectStruct type=[%v]\n", reflectStruct.Type())
    fmt.Printf("reflectField  type=[%v]\n", reflectField.Type())

    reflectField = reflect.NewAt(reflectField.Type(), unsafe.Pointer(reflectField.UnsafeAddr()))
    reflectField = reflectField.Elem()

    var certs []*x509.Certificate = reflectField.Interface().([]*x509.Certificate)
    fmt.Printf("Total Certificate: %d\n", len(certs))
    for i, cert := range certs {
        if content, err := certToPEM(cert); err != nil {
            panic(err)
        } else {
            fmt.Printf("Certificate[%d]=[%s]\n", i, content)
        }
    }
}

幾個API:

  • func ValueOf(i interface{}) Value
    ValueOf returns a new Value initialized to the concrete value stored in the interface i
  • func (v Value) Elem() Value
    Elem returns the value that the interface v contains or that the pointer v points to
  • func (v Value) Interface() (i interface{})
    Interface returns v's current value as an interface{}
  • func NewAt(typ Type, p unsafe.Pointer) Value
    NewAt returns a Value representing a pointer to a value of the specified type, using p as that pointer
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容