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