fabric-sdk-go调用Fabric-CA详细流程

网上资料甚少,所以自己研究了一下,核心是使用配置文件的registrar来进行注册和发行身份。

配置文件可以参考fabric-sdk-go在github里的示例配置文件config_e2e.yaml

一、注册大致流程

  1. 初始化sdk sdk, err = fabsdk.New(config.FromFile(configFile))

  2. 初始化mspClient

    ctx := sdk.Context()
    mspClient, err := msp.New(ctx)
    

    这里msp.New时可以选择组织或者选择CA,如果都不选,则使用配置文件默认客户端组织

  3. 构建请求对象:

    request := &msp.RegistrationRequest{
          
          
      Name:   username,
      Type:   "client",
      CAName: "ca-org1",
      Secret: password,
    }
    

    注:可以不设置密码,此时会返回一个随机密码

  4. 发送注册请求secret, err := mspClient.Register(request)

二、注册详解

  1. ctrl + click点击Register方法,跳到type *CAClient* interface就无法进行了。也就是肯定有个对象实现了该方法。

  2. 搜索) Register(,区分大小写,会出现9个文件,仔细查看后会发现有三个文件可能符合,分别为:

    1. fabric-ca/lib/identity.go
    2. pkg/msp/caclient.go
    3. pkg/msp/fabcaadapter.go

    通过在相应文件里打log的方式,确定调用顺序为:

    caclient.go => fabcaadapter.go => identity.go

  3. 在caclient.go的Register 函数中,有这么一句代码:registrar, err := c.getRegistrar(c.registrar.EnrollID, c.registrar.EnrollSecret),从配置文件中给的registrar获取相应身份。

  4. 继续看getRegistrar函数,会有这么一句,registrar, err := c.identityManager.GetSigningIdentity(enrollID),代表先获取相应身份如果获取不到,试着先发行它,然后再获取。

  5. 直接跳转到该函数定义不可得,但是我们通过搜索可以锁定pkg/msp/getsigid.go中的GetSigningIdentity函数。可以看到,它调用了GetUser。我们接着看GetUser(就在同一个文件里)定义,它首先从store里读取用户,这个store在哪里呢,就是指配置文件中的credentialStore.例如我们的例子是/tmp/examplestore,那以该目录下可以看到一个文件叫着[email protected]。这个文件名称是固定的,来源等会再讲。我们先删除它,看有什么效果。

  6. 我们接着看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正是该文件里的。从文件名也能看出是越来越底层的。

  7. 接着看最新的Load函数。可以看到,如果它在该目录下找不到,便会返回一个nil,然后上一个Load也会返回nil,再向上loadUserFromStore也会返回nil,然后返回到GetUser函数。此时,u为nil,会进入下一个流程.

  8. 接着上面u == nil ,此时会首先查找getEmbeddedCertBytes。该函数是什么意思呢?在配置文件的credentialStore定义时有注释提到:Not needed if all credentials are embedded in configuration and enrollments are performed elswhere。大致意思为所有证书内嵌在配置中并且在别处执行。所以这里的getEmbeddedCertBytes应该是查找内嵌证书的意思(个人猜测)。当然,一般go-sdk使用的配置文件里没有这个内嵌证书,所以肯定会返回nil,接着向下。

  9. 接着从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]格式是固定的,可以在相关源码可查看到是写死了的(以前被坑过一次)。

  10. 重点来了,实际应用中,是不区分大小写的,而在fabric-sample示例中,一般Admin是组织管理员身份,并不是组织CA的bootstrap账号,所以此时会用组织管理员去发行注册新用户,自然会认证失败。查看CA的日志可以清楚的看到这一点。所以如果注册提示认证失败,可以查看一下CA日志,看到底在请求头中添加的是什么身份。

  11. 让我们作个测试,删除cryptoPath目录下的Admin账号。然后接着测试。(注意保持/tmp/examplestore/[email protected]一直不存在,小提示:可以在适当的位置panic,更好的看出函数调用关系和防止新生成的账号改写[email protected])。这样,GetUser也会返回nil,同文件的GetSigningIdentity也会返回nil

  12. 接着会回退到caClient.go中的getRegistrar函数,此时因为err不为nil,所以接着向下会来到err = c.Enroll(&api.EnrollmentRequest{Name: enrollID, Secret: enrollSecret})。也就是bootstrap身份不存在会先发行。发行之后会再次获取registrar身份并返回。

  13. registrar身份返回给Register函数(同文件caclient.go内)。这里它会接着向下走,调用fabcaadapter.goRegister函数中。然后点击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.goCreateToken函数里,它用到了x509相关的东西。

  14. 打开[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目录下了。

  15. 载入bootstrap身份并创建用户的逻辑在pkg/msg/getsigid.go中的newUser函数中。该函数先是从证书中读取了公钥,然后又根据公钥的SKI来读取私钥,最后返回一个User。具体读取私钥的逻辑在bccsp/sw/fileks.go中的loadPrivateKey函数中。

三、发行详解

注册之后就可以发行身份了。

  1. 发行之前先判断有无身份,使用info, err := mspClient.GetSigningIdentity(username)来判断。
  2. 接着调用mspClient的Enroll方法:err = mspClient.Enroll(username, msp.WithSecret(password))
  3. 点击该方法跳到定义,在pkg/client/msp/client.go中。它又调用了adapter的Enroll方法:cert, err := c.adapter.Enroll(request)
  4. 接着点击,跳到pkg/msp/fabcaadapter.go中的Enroll方法。然后它会调用caClient的Enroll方法
  5. 接着点击,跳到fabric-ca/lib/client.go中的Enroll函数,函数最后会调用handleX509Enroll
  6. 在该函数里,首先会调用GenCSR生成csrPEM。然后生成一个post请求,post, err := c.newPost("enroll", body)。接着设置发行时的用户名密码:post.SetBasicAuth(req.Name, req.Secret),然后调用SendReq方法发行。然后依次返回发行结果
  7. 在caclient.go里,发行完成之后会保存用户数据,但是这里只会保存公钥,目录在/tmp/examplestore/。那么私钥保存在哪里呢?我们回到GenCSR这个函数,点击进去,有这么一句:key, cspSigner, err := util.BCCSPKeyRequestGenerate(cr, c.csp)
  8. 然后再点击进去,会进入fabric-ca/util/csp.go中的BCCSPKeyRequestGenerate函数。这其中有这么一句:key, err := myCSP.KeyGen(keyOpts)。但是这里再无法点击进去了。
  9. 然而我们在注册时提到了读取私钥的文件fabric/bccsp/sw/fileks.go,然后仔细查看,果然有storePrivateKey函数,它是一个包内函数,调用它的函数为StoreKey,于是在这里打一个panic,可以很清楚的看到调用流程了。
  10. 接着8,我们转到pkg/core/cryptosuite/bccsp/wrapper/cryptosuiteimpl.go,它有一个KeyGen函数,正是步骤8里无法点击跳转的那个函数。然而此时key, err := c.BCCSP.KeyGen(opts)又无法点击进入了。
  11. 我们来到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。所以这里无法给出一些结果。

本文主要是自己记录使用,因此在阅读上可能有些困难。欢迎大家留言指正错误。

猜你喜欢

转载自blog.csdn.net/weixin_39430411/article/details/109527482