网上资料甚少,所以自己研究了一下,核心是使用配置文件的registrar来进行注册和发行身份。
配置文件可以参考fabric-sdk-go在github里的示例配置文件config_e2e.yaml。
-
初始化sdk
sdk, err = fabsdk.New(config.FromFile(configFile)) -
初始化mspClient
ctx := sdk.Context() mspClient, err := msp.New(ctx)这里msp.New时可以选择组织或者选择CA,如果都不选,则使用配置文件默认客户端组织
-
构建请求对象:
request := &msp.RegistrationRequest{ Name: username, Type: "client", CAName: "ca-org1", Secret: password, }注:可以不设置密码,此时会返回一个随机密码
-
发送注册请求
secret, err := mspClient.Register(request)
-
ctrl + click点击Register方法,跳到type *CAClient* interface就无法进行了。也就是肯定有个对象实现了该方法。 -
搜索
) Register(,区分大小写,会出现9个文件,仔细查看后会发现有三个文件可能符合,分别为:fabric-ca/lib/identity.gopkg/msp/caclient.gopkg/msp/fabcaadapter.go
通过在相应文件里打log的方式,确定调用顺序为:
caclient.go => fabcaadapter.go => identity.go
-
在caclient.go的Register 函数中,有这么一句代码:
registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret),从配置文件中给的registrar获取相应身份。 -
继续看
getRegistrar函数,会有这么一句,registrar, err := c.identityManager.GetSigningIdentity(enrollID),代表先获取相应身份如果获取不到,试着先发行它,然后再获取。 -
直接跳转到该函数定义不可得,但是我们通过搜索可以锁定
pkg/msp/getsigid.go中的GetSigningIdentity函数。可以看到,它调用了GetUser。我们接着看GetUser(就在同一个文件里)定义,它首先从store里读取用户,这个store在哪里呢,就是指配置文件中的credentialStore.例如我们的例子是/tmp/examplestore,那以该目录下可以看到一个文件叫着admin@Org1MSP-cert.pem。这个文件名称是固定的,来源等会再讲。我们先删除它,看有什么效果。 -
我们接着看
loadUserFromStore函数定义,然而到函数中的Load我们就无法自动跳转了。采用前面类似的搜索的方法,我们锁定了pkg/msp/certfileuserstore.go文件中的Load函数。查询该函数中的storeKeyFromUserIdentifier定义,为:func storeKeyFromUserIdentifier(key msp.IdentityIdentifier) string { return key.ID + "@" + key.MSPID + "-cert.pem" }看到了没有,它正是
admin@Org1MSP-cert.pem的全名来源。然而这个函数里还有一个Load,跳转不了,那么它在哪呢。刚才搜索Load时我们还会看到一个文件:pkg/fab/keyvaluestore/filekeyvaluestore.go。这个Load正是该文件里的。从文件名也能看出是越来越底层的。 -
接着看最新的
Load函数。可以看到,如果它在该目录下找不到,便会返回一个nil,然后上一个Load也会返回nil,再向上loadUserFromStore也会返回nil,然后返回到GetUser函数。此时,u为nil,会进入下一个流程. -
接着上面u == nil ,此时会首先查找
getEmbeddedCertBytes。该函数是什么意思呢?在配置文件的credentialStore定义时有注释提到:Not needed if all credentials are embedded in configuration and enrollments are performed elswhere。大致意思为所有证书内嵌在配置中并且在别处执行。所以这里的getEmbeddedCertBytes应该是查找内嵌证书的意思(个人猜测)。当然,一般go-sdk使用的配置文件里没有这个内嵌证书,所以肯定会返回nil,接着向下。 -
接着从
getCertBytesFromCertStore来获取证书,记得前面是userstore,这里是certstore。然而这里又有一个Load跳转不了,然而Load定义就前面提到的两个。这里是filekeyvaluestore.go,在这里面有file, err := fkvs.keySerializer(key)这句代码,我们打印出file,可以看到它第一次为/tmp/examplestore/admin@Org1MSP-cert.pem(前面我们刚刚删除了,不存在了,所以有第二次),第二次为.../organizations/peerOrganizations/org1.example.com/users/admin@org1.example.com/msp/signcerts/admin@org1.example.com-cert.pem。看到了没有,这正是我们配置文件中org1中的cryptoPath定义。注意:这里admin@org1.example.com-cert.pem格式是固定的,可以在相关源码可查看到是写死了的(以前被坑过一次)。 -
重点来了,实际应用中,是不区分大小写的,而在
fabric-sample示例中,一般Admin是组织管理员身份,并不是组织CA的bootstrap账号,所以此时会用组织管理员去发行注册新用户,自然会认证失败。查看CA的日志可以清楚的看到这一点。所以如果注册提示认证失败,可以查看一下CA日志,看到底在请求头中添加的是什么身份。 -
让我们作个测试,删除
cryptoPath目录下的Admin账号。然后接着测试。(注意保持/tmp/examplestore/admin@Org1MSP-cert.pem一直不存在,小提示:可以在适当的位置panic,更好的看出函数调用关系和防止新生成的账号改写admin@Org1MSP-cert.pem)。这样,GetUser也会返回nil,同文件的GetSigningIdentity也会返回nil -
接着会回退到
caClient.go中的getRegistrar函数,此时因为err不为nil,所以接着向下会来到err = c.Enroll(&api.EnrollmentRequest{Name: enrollID, Secret: enrollSecret})。也就是bootstrap身份不存在会先发行。发行之后会再次获取registrar身份并返回。 -
registrar身份返回给Register函数(同文件caclient.go内)。这里它会接着向下走,调用fabcaadapter.go的Register函数中。然后点击Register函数中的registrar.Register(&req),会跳转到identity.go,然后我们接着点击Post函数,会看到有这么一行:err = i.addTokenAuthHdr(req, reqBody)。下面就是它的定义,可见它生成了一个token并添加到header中,见:req.Header.Set("authorization", token)。可以接着点击CreateToken查看定义,最终token的生成是在fabric-ca/utils/util.go的CreateToken函数里,它用到了x509相关的东西。 -
打开
admin@Org1MSP-cert.pem可以看到它只是一个证书,它在pkg/msp/certfileuserstore.go文件中的Load函数转化为useData.EnrollmentCertificate,然而它的私钥呢,在msp/store.go里注释里提到,PrivateKey is stored separately, in the crypto store。但我测试时实质上它在运行程序的根目录,我想这里肯定是某种设置没有设置正确。仔细查看github上的
config_e2e.yaml配置文件,果然发现问题了。在credentialStore下的cryptoStore设置里,就按示例那样设置为path: /tmp/msp。删除/tmp/examplestore/admin@Org1MSP-cert.pem,重新注册一次同时生成新的bootstrap账号,该bootstrap账号的key就在/tmp/msp/keystore目录下了。 -
载入bootstrap身份并创建用户的逻辑在
pkg/msg/getsigid.go中的newUser函数中。该函数先是从证书中读取了公钥,然后又根据公钥的SKI来读取私钥,最后返回一个User。具体读取私钥的逻辑在bccsp/sw/fileks.go中的loadPrivateKey函数中。
注册之后就可以发行身份了。
- 发行之前先判断有无身份,使用
info, err := mspClient.GetSigningIdentity(username)来判断。 - 接着调用mspClient的Enroll方法:
err = mspClient.Enroll(username, msp.WithSecret(password)) - 点击该方法跳到定义,在
pkg/client/msp/client.go中。它又调用了adapter的Enroll方法:cert, err := c.adapter.Enroll(request)。 - 接着点击,跳到
pkg/msp/fabcaadapter.go中的Enroll方法。然后它会调用caClient的Enroll方法 - 接着点击,跳到
fabric-ca/lib/client.go中的Enroll函数,函数最后会调用handleX509Enroll - 在该函数里,首先会调用
GenCSR生成csrPEM。然后生成一个post请求,post, err := c.newPost("enroll", body)。接着设置发行时的用户名密码:post.SetBasicAuth(req.Name, req.Secret),然后调用SendReq方法发行。然后依次返回发行结果 - 在caclient.go里,发行完成之后会保存用户数据,但是这里只会保存公钥,目录在
/tmp/examplestore/。那么私钥保存在哪里呢?我们回到GenCSR这个函数,点击进去,有这么一句:key, cspSigner, err := util.BCCSPKeyRequestGenerate(cr, c.csp)。 - 然后再点击进去,会进入
fabric-ca/util/csp.go中的BCCSPKeyRequestGenerate函数。这其中有这么一句:key, err := myCSP.KeyGen(keyOpts)。但是这里再无法点击进去了。 - 然而我们在注册时提到了读取私钥的文件
fabric/bccsp/sw/fileks.go,然后仔细查看,果然有storePrivateKey函数,它是一个包内函数,调用它的函数为StoreKey,于是在这里打一个panic,可以很清楚的看到调用流程了。 - 接着8,我们转到
pkg/core/cryptosuite/bccsp/wrapper/cryptosuiteimpl.go,它有一个KeyGen函数,正是步骤8里无法点击跳转的那个函数。然而此时key, err := c.BCCSP.KeyGen(opts)又无法点击进入了。 - 我们来到
fabric/bccsp/sw/impl.go,这里有个KeyGen函数,正好是上一步无法点击的函数。函数里面有提到,如果不是临时性的,就存储它,err = csp.ks.StoreKey(k)。这里的StoreKey也是无法点击跳转的,但是它就是步骤9里的函数。
我们可以在配置文件中将credentialStore这一项全部注释掉,那么它的行为又是什么样呢?
这时由于没有配置文件存储,所以它的保存和读取都是在内存中,通过全局查询) Store(后发现,pkg/msp/memory_user_store.go中操作用户的证书。按道理这里memory_key_store.go应该是操作的用户的私钥,但实际上仍然保存在项目根目录下的keystore目录中。这里未详细研究。
从上面分析可以看到,直接使用时go-sdk调用Fabric CA时,它生成的身份仅有证书和私钥,和Fabric使用时的目录位置及格式大不相同,生成好的身份估计是无法直接使用的。也许使用GateWay来生成新的身份会好一些,但是仅使用过node.js版本的gateway,未使用过go-sdk版本的gateway。所以这里无法给出一些结果。
本文主要是自己记录使用,因此在阅读上可能有些困难。欢迎大家留言指正错误。
