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 byNamemap[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("reflectFieldtype=[%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