网上资料甚少,所以自己研究了一下,核心是使用配置文件的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.go
pkg/msp/caclient.go
pkg/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
,那以该目录下可以看到一个文件叫着[email protected]
。这个文件名称是固定的,来源等会再讲。我们先删除它,看有什么效果。 -
我们接着看
loadUserFromStore
函数定义,然而到函数中的Load
我们就无法自动跳转了。采用前面类似的搜索的方法,我们锁定了pkg/msp/certfileuserstore.go
文件中的Load
函数。查询该函数中的storeKeyFromUserIdentifier
定义,为:func storeKeyFromUserIdentifier(key msp.IdentityIdentifier) string { return key.ID + "@" + key.MSPID + "-cert.pem" }
看到了没有,它正是
[email protected]
的全名来源。然而这个函数里还有一个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/[email protected]
(前面我们刚刚删除了,不存在了,所以有第二次),第二次为.../organizations/peerOrganizations/org1.example.com/users/[email protected]/msp/signcerts/[email protected]
。看到了没有,这正是我们配置文件中org1
中的cryptoPath
定义。注意:这里[email protected]
格式是固定的,可以在相关源码可查看到是写死了的(以前被坑过一次)。 -
重点来了,实际应用中,是不区分大小写的,而在
fabric-sample
示例中,一般Admin
是组织管理员身份,并不是组织CA的bootstrap
账号,所以此时会用组织管理员去发行注册新用户,自然会认证失败。查看CA的日志可以清楚的看到这一点。所以如果注册提示认证失败,可以查看一下CA日志,看到底在请求头中添加的是什么身份。 -
让我们作个测试,删除
cryptoPath
目录下的Admin
账号。然后接着测试。(注意保持/tmp/examplestore/[email protected]
一直不存在,小提示:可以在适当的位置panic,更好的看出函数调用关系和防止新生成的账号改写[email protected]
)。这样,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相关的东西。 -
打开
[email protected]
可以看到它只是一个证书,它在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/[email protected]
,重新注册一次同时生成新的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
我们可以在配置文件中将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。所以这里无法给出一些结果。
本文主要是自己记录使用,因此在阅读上可能有些困难。欢迎大家留言指正错误。