fabric源码解析13——peer的BCCSP服务

fabric源码解析13——peer的BCCSP服务

加密的一般话题

加密涉及到了内容挺复杂的,是一门专业性很强的学科。笔者没有专门学过,在此只是略讲一些bccsp服务所涉及到的皮毛:

  • RSA - 一种非对称的加密算法,用于加密。有几种族簇,如RSA1024,RSA2048等。
  • AES - 一种块加密算法,用于加密成块的大量数据。有几种族簇,如AES128,AES192等。
  • ECDSA - 一种椭圆曲线签名,用于签名。有几种族簇,如ECDSAP256,ECDSAP384等。
  • Hash - 哈希,有几种族簇,如SHA256,SHA3_256等。
  • HMAC - 密匙相关的哈希运算消息认证码。
  • x509 - 证书的一种,可参看文章12中对证书的解释。
  • PKCS#11 - 一套标准安全接口,可与安全硬件相关,以上的这些东西,可以找它来建立,读取,写入,修改,删除等操作进行管理。

fabric所用到的这些技术的常量名称在/fabric/bccsp/opts.go中开始的部分定义,如ECDSA支持ECDSAP256,ECDSAP384等几种类型。

BCCSP服务结构

BCCSP,是blockchain cryptographic service provider的缩写,个人译作区域链加密服务提供者,为fabric项目提供各种加密技术,签名技术,工具的性质很强,MSP服务模块中就使用到了BCCSP。这里需要说明的一点是,工具性强,也就说明了,如果不是想专门学这一领域,其实不用太在乎其实现的细节,只要用就行了。BCCSP服务的代码集中在/fabric/bccsp中,目录结构如下:

  • mocks - 模拟代码文件夹,可以参看之帮助理解bccsp服务
  • signer - 实现的是crypto标准库的Signer接口,可参看文章12中MSP服务实现中带“专用签名笔”的身份signingidentity,该目录的签名接口是专用于向外界提供签名对象的功能的。
  • factory - bccsp服务工厂
  • pkcs11 - bccsp服务实现之一:HSM基础的bccsp(the hsm-based BCCSP implementation)
  • sw - bccsp服务实现之二:软件基础的bccsp(the software-based implementation of the BCCSP)
  • utils - bccsp服务工具函数
  • bccsp.go - 定义了BCCSP,Key接口,众多BCCSP接口所使用到的选项接口,如Key,KeyGenOpts,KeyDerivOpts等
  • keystore.go - 定义了key的管理存储接口,如果生成的key不是暂时的,则存储在该接口的实现对象中,如果是暂时性的,则不存储
  • XXXopts.go - XXX表示该目录下的各种值,bccsp服务实现所使用到的各种技术的选项实现

从以上可以看出bccsp服务有两种实现:pkcs11和sw。简单明了的解释的话(虽不太精准),就是pckcs11是硬件基础的加密服务实现,sw是软件基础的加密服务实现。这个硬件基础的实现以 https://github.com/miekg/pkcs11 这个库为基础,而HSM是Hardware Security Modules,即硬件安全模块的缩写。相对应的两种bccsp服务实现,这里有两种工厂,两种工厂为其他使用bccsp服务的模块提供了窗口函数(就是给其他模块提供窗口的函数,这些函数一般统一管理自己服务的功能模块,供外界调用,如后文所讲的InitFactories函数)。所有产生的bccsp实例存储在factory/factory.go中所定义的全局变量中。

bccsp中的接口和选项

接口

//在fabric/bccsp/bccsp.go中定义
type BCCSP interface {
    //根据key生成选项opts生成一个key
    //与key有关的选项opts选项要适合原始的key(与“证书是一级一级的认证”对看)
    KeyGen(opts KeyGenOpts) (k Key, err error)
    //根据key获取选项opts从k中重新获取一个key
    KeyDeriv(k Key, opts KeyDerivOpts) (dk Key, err error)
    //根据key导入选项opts从一个key原始的数据中导入一个key
    KeyImport(raw interface{}, opts KeyImportOpts) (k Key, err error)
    //根据SKI返回与该接口实例有联系的key
    GetKey(ski []byte) (k Key, err error)
    //根据哈希选项opts哈希一个消息msg,如果opts为空,则使用默认选项
    Hash(msg []byte, opts HashOpts) (hash []byte, err error)
    //根据哈希选项opts获取hash.Hash实例,如果opts为空,则使用默认选项
    GetHash(opts HashOpts) (h hash.Hash, err error)
    //根据签名者选项opts,使用k对digest进行签名,注意如果需要对一个特别大的消息的hash值
    //进行签名,调用者则负责对该特别大的消息进行hash后将其作为digest传入
    Sign(k Key, digest []byte, opts SignerOpts) (signature []byte, err error)
    //根据鉴定者选项opts,通过对比k和digest,鉴定签名
    Verify(k Key, signature, digest []byte, opts SignerOpts) (valid bool, err error)
    //根据加密者选项opts,使用k加密plaintext
    Encrypt(k Key, plaintext []byte, opts EncrypterOpts) (ciphertext []byte, err error)
    //根据解密者选项opts,使用k对ciphertext进行解密
    Decrypt(k Key, ciphertext []byte, opts DecrypterOpts) (plaintext []byte, err error)
}

选项

bccsp文件夹中任何带opt字眼的文件,都是和选项有关的源码。关于对象的配套选项,我们在讲MSP服务的时候就见识过。根据一个选项的不同配置,对象主体可以得到不同的数据或进行不同的操作,这也是一种比较值得学习的语言上的组织技巧。尤其是在bccsp这种涉及的技术比较多,而每个对象自身又分为好多类的情况。在此以哈希选项HashOptskey导入选项KeyImportOpts作为例子进行说明:

//在/fabric/bccsp/bccsp.go中定义
//哈希选项接口
type HashOpts interface {
    Algorithm() string  //获取hash算法字符串标识,如"SHA256","SHA3_256"
}
//在/fabric/bccsp/hashopts.go中定义
//哈希选项实现之一,SHA256选项
type SHA256Opts struct {
}
func (opts *SHA256Opts) Algorithm() string {
    return SHA256
}
//哈希选项实现之二,SHA384选项
type SHA384Opts struct {
}
func (opts *SHA384Opts) Algorithm() string {
    return SHA384
}
--------------------------------------------------
//在/fabric/bccsp/bccsp.go中定义
//key导入选项接口
type KeyImportOpts interface {
    Algorithm() string //返回key导入算法字符串标识
    Ephemeral() bool   //如果生成的key是短暂的(ephemeral),返回true,否则返回false
}
//在/fabric/bccsp/opts.go中定义
//key导入选项接口实现之一,ECDSA公匙的导入选项
type ECDSAPKIXPublicKeyImportOpts struct {
    Temporary bool
}
func (opts *ECDSAPKIXPublicKeyImportOpts) Algorithm() string {
    return ECDSA
}
func (opts *ECDSAPKIXPublicKeyImportOpts) Ephemeral() bool {
    return opts.Temporary
}
//key导入选项接口实现之二,ECDSA私匙的导入选项
type ECDSAPrivateKeyImportOpts struct {
    Temporary bool
}
func (opts *ECDSAPrivateKeyImportOpts) Algorithm() string {
    return ECDSA
}
func (opts *ECDSAPrivateKeyImportOpts) Ephemeral() bool {
    return opts.Temporary
}
//比较特殊的,比如签名选项接口SignerOpts
//由于因为使用的标准库,因此使用到此选项时多赋值为nil,bccsp源码中未实现

SW实现方式

BCCSP的SoftWare(SW)实现形式是默认的形式,这点仅从/fabric/bccsp/factory/opts.go中工厂的默认选项DefaultOpts和核心配置文档中关于bccsp的配置就可以看出来。主要使用的包是标准库hashcrypto(包括其中的各种包,如aesrsaecdsasha256ellipticx509等)。

目录结构

  • /fabric/bccsp/sw
    • impl.go - bccsp的sw实现impl
    • internals.go - 签名者、鉴定者、加密者、解密者接口定义
    • conf.go - bccsp的sw实现的配置定义
    • ———————————————————————–
    • aes.go - aes类型的生成key函数、加密者/解密者实现
    • ecdsa.go - ecdsa类型的签名者、公匙/私匙鉴定者实现
    • rsa.go - rsa类型的签名者、公匙/私匙鉴定者实现
    • ———————————————————————–
    • aeskey.go - aes类型的Key接口实现
    • ecdsakey.go - ecdsa类型的Key接口实现
    • rsakey.go - rsa类型的Key接口实现
    • ———————————————————————–
    • dummyks.go - dummy类型的KeyStore接口实现dummyKeyStore,当生成的key是短暂的,则说明这些key不会保存到文件中,而是保存到内存中,系统一关闭,这些key就消失了
    • fileks.go - file类型的KeyStore接口实现fileBasedKeyStore,当生成的key不是短暂的,则说明这些key在导入时,会存储在文件中,即便系统关闭,这些key也不会消失

BCCSP接口实现

//在/fabric/bccsp/sw/impl.go中定义
//SW bccsp的实例结构体
type impl struct {
    conf *config                          //bccsp实例的配置
    ks   bccsp.KeyStore                   //key存储系统对象,存储和获取Key对象
    encryptors map[reflect.Type]Encryptor //加密者映射
    decryptors map[reflect.Type]Decryptor //解密者映射
    signers    map[reflect.Type]Signer    //签名者映射,Key实现的类型作为映射的键
    verifiers  map[reflect.Type]Verifier  //鉴定者映射,Key实现的类型作为映射的键
}
//专用生成函数
func New(...) (bccsp.BCCSP, error) { ... }
//接口实现
func (csp *impl) KeyGen(opts bccsp.KeyGenOpts) (k bccsp.Key, ...) { ... }
...

粗线条上看,由于impl对象和各种操作选项的存在,绝大部分接口的实现都是以switch-case为主干,根据选项的类型或配置,分情况完成功能,且每个分支的操作基本都很类似,如KeyGen。接下来一一介绍:

  1. KeyGen - 根据key生成选项不同,生成三种系列的key:ECDSA,AES,RSA。ECDSA使用库ecdsa的GenerateKey函数,AES使用自定义的GetRandomBytes函数(在aes.go中实现,包装了rand.Read),RSA使用库rsa的GenerateKey函数。每个系列又根据具体参数的不同生成不同“尺寸”的key,具体的细节略过。最后返回不同的Key实现对象,如ecdsaPrivateKey,aesPrivateKey等(分别定义在同目录中的XXXkey.go中)。这里要注意的是,当前版本中用于签名的key,只支持ECDSA系列的key,这是官方文档中所说的,但是从实现上看签名的支持不止一种,又但是,bccsp本质上无论实现多上中key,也只有被调用者决定使用哪一种。而MSP模块是bccsp的使用者之一,应该是在它这个地方只认ECDSA的key。
  2. KeyDeriv - 根据key获取选项opts从k中重新获取一个key,这里的重新获取可以理解为把k中的内容重新打乱再生成一个key。处理两种key类型:ecdsaXXXKey(XXX代表Public和Private),aesPrivateKey。最后返回打乱后重新生成的两种类型的key:reRandomizedKeyhmacedKey。对于重新生成的key,如果选项中指定的不是暂时性的key,则会在ks中存储。
  3. KeyImport - 从原始的数据raw中取出选项opts指定的key,如果不是临时性的,则在ks中存储,最后返回key。这里的原始,指的是[]byte或者含有key的数据(如证书数据里的key)。raw是一个空接口,也就是说可以接收任何形式的原始数据。为了得到key,对原始数据raw的转化或抽取,一部分使用了utils下的工具函数utils.Cloneutils.DERToPublicKey,如AES256,ECDSAPrivateKey等类型的key;一部分直接用go语言的断言raw.(*Key类型),如ECDSAGoPublicKey,RSAGoPublicKey等类型的key。
  4. Hash - 根据哈希选项opts对一个消息msg进行哈希,返回该msg的哈希值。如果opts为空,则使用默认选项。这个比较好理解,种类支持SHA2,SHA3哈希家族。
  5. Sign - 根据签名者选项,使用k对digest进行签名,这里的签名者选项在当前版本里没什么用处,调用者都给的是nil。签名涉及到了Key接口签名者Signer在SW bccsp中的实现。Key接口有三种实现:ecdsakey.go中的ecdsaPublicKey/ecdsaPrivateKey,rsakey.go中的rsaPublicKey/rsaPrivateKey,aeskey.go中的aesPrivateKey,这里签名(自然)使用的都是私匙。签名者接口Signer定义在同目录下的internals.go中,有两种类型的实现:ecdsa.go中的ecdsaSigner和rsa.go中的rsaSigner。参看SW bccsp专用生成函数New的signers赋值部分,可知用到了两种对应类型的Key和Signer:ecdsaPrivateKey - ecdsaSignerrsaPrivateKey - rsaSigner。这里签名的实现是,从该bccsp实例的签名者集合成员signers获取类型为reflect.TypeOf(k)的签名者signer,然后直接调用signer的接口Sign,追溯,ecdsaSigner使用ecdsa库的Sign函数,rsaSigner使用rsa库PrivateKey结构体的Sign函数。
  6. Verify - 根据鉴定者选项opts,通过对比k和digest,鉴定签名。鉴定涉及到了Key接口鉴定者接口Verifier在SW bccsp中的实现。Key接口实现如上描述。鉴定者接口Verifier定义在同目录下的internals.go中,有两种类型的实现:ecdsa.go中的ecdsaPublicKeyKeyVerifier/ecdsaPrivateKeyVerifier和rsa.go中的rsaPublicKeyKeyVerifier/rsaPrivateKeyVerifier。参看SW bccsp专用生成函数New的verifiers赋值部分,可知所有鉴定者(与对应的Key接口实现)都有用到。这里鉴定的实现是,从该bccsp实例的鉴定者集合成员verifiers获取类型为reflect.TypeOf(k)的鉴定者verifier,然后直接调用verifier的接口Verify,追溯,ecdsaXXXKeyVerifier使用ecdsa库的Verify函数,rsaXXXKeyVerifier使用rsa库的VerifyPSS函数(这里XXX表示PublicKey或Private)。
  7. Encrypt - 根据加密者选项opts,使用k**加密plaintext。加密涉及到了**Key接口加密者接口Encryptor在SW bccsp中的实现。Key接口实现如上描述。加密者接口Encryptor定义在同目录下的internals.go中,在aes.go中实现(只能是aes,因为只有aes是用来加密的):aescbcpkcs7Encryptor。参看SW bccsp专用生成函数New的encryptors赋值部分,只有aesPrivateKey - aescbcpkcs7Encryptor被使用。这里加密的实现是,从该bccsp实例的加密者集合成员encryptors获取类型为reflect.TypeOf(k)的加密者encryptor,然后直接调用encryptor的接口Encrypt,追溯,aescbcpkcs7Encryptor使用了aes库的加密流程进行加密。
  8. Decrypt - 解密与加密类似,过程类似,也是只有aes配套实现,最终使用aes库解密流程进行解密。
  9. GetXXX - GetXXX系列接口,获取实例对象中的数据,略。

在fabirc源码解析12——peer的MSP服务文中背书检验部分的第3、4点,涉及到了包含在msp中的bccsp对象成员,使用了bccsp对象的GetHashOptHashKeyImportVerify等接口用以生成identities对象或identities自身一些接口实现。在此可以对看。

pkcs11实现方式

BCCSP的pkcs11实现形式主要使用到的库与sw实现如出一辙,但外加一个github.com/miekg/pkcs11库,最好参看其文档以熟悉pkcs11的简要操作。pkcs11(PKCS,Public-Key Cryptography Standards)是一套非常通用的接口标准,可以说这里是用pkcs11实现了bccsp的功能,也为fabric支持热插拔和个人安全硬件模块提供了服务。这点可以从bccsp的pkcs11的实现实例的专用生成函数New(参看下文)中所调用的loadLib函数可以看出来:loadLib加载了一个系统中的动态库,能加载系统的动态库,就可以和驱动、热插拔、连接电脑的字符设备联系在一起。比如将来,开发出了一款在区域链上类似于现在网上银行所用的U盾之类的个人身份或安全交易硬件模块或芯片,这些硬件模块或芯片只需要也遵循pkcs11,fabric即可对此进行支持和扩展。

对于pkcs11的所提供的接口,在此提供两个文档地址,读者可以稍作了解: https://www.ibm.com/developerworks/cn/security/s-pkcs/http://docs.oracle.com/cd/E19253-01/819-7056/6n91eac56/index.html#chapter2-9 。这些文档与fabric和区域链无关,但是因为pkcs11是通用的接口,所以有一定参考价值。pkcs11库中的解释相对过于简单。

目录结构

  • /fabric/bccsp/pkcs11
    • impl.go - bccsp的pkcs11实现impl
    • conf.go - 定义了bccsp服务的配置和PKCS11Opts、FileKeystoreOpts、DummyKeystoreOpts选项
    • pkcs11.go - 以pkcs11库为基础,包装各种pkcs11功能,实现了impl基于pkcs11的内调函数,和一些bccsp服务使用到的独立的内调函数
    • ———————————————————————–
    • aes.go - 实现aes类型的生成key、加密、解密函数
    • ecdsa.go - 实现impl在ecdsa技术下的签名函数signECDSA和鉴定函数verifyECDSA
    • ———————————————————————–
    • aeskey.go - 实现aes类型的Key接口,只实现私匙aesPrivateKey
    • ecdsakey.go - 实现ecdsa类型的Key接口,实现了公匙ecdsaPublicKey,私匙ecdsaPrivateKey
    • rsakey.go - 实现了rsa类型的Key接口,实现了公匙rsaPublicKey,私匙rsaPrivateKey
    • ———————————————————————–
    • dummyks.go - dummy类型的KeyStore接口实现DummyKeyStore
    • fileks.go - file类型的KeyStore接口实现FileBasedKeyStore

BCCSP接口实现

type impl struct {
    conf *config                       //配置
    ks   bccsp.KeyStore                //key存储系统对象,存储和获取Key对象
    ctx      *pkcs11.Ctx               //标准库的pkcs11上下文
    sessions chan pkcs11.SessionHandle //实质是uint,会话标识符频道,默认10个缓存
    slot     uint                      //安全硬件外设连接插槽标识号
    lib          string                //加载库所在路径
    noPrivImport bool                  //禁止导入私匙标识
    softVerify   bool                  //使用软件方式鉴定签名标识
}
//专用生成函数
func New(opts PKCS11Opts, keyStore bccsp.KeyStore) (bccsp.BCCSP, error) { ... }
//接口实现
func (csp *impl) KeyGen(opts bccsp.KeyGenOpts) (k bccsp.Key, err error) { ... }
...

BCCSP的pkcs11实现的骨架在impl.go中与sw的实现基本一致,只是追溯到最终实现的语句时,sw实现是使用crypto库下的各个包进行签名,加密,密匙导入等,而pkcsll则用pkcs11包对数据进行了多一层的处理,使用pkcs11提供的上下文(pkcs11.Ctx)和会话(SessionHandle)之上对签名,密匙,加密等进行管理,这也是pkcs11.go文件的作用。两者最大的不同是一个面向软件,一个面向硬件,pkcs11自身又非常的冗杂,因此在此只讲pkcs11中与安全硬件模块建立连接的loadLib函数。

loadLib函数在pkcs11.go中定义,供专用生成函数New使用。为建立与安全硬件模块的通信,进行了如下步骤:

  1. 根据所给的系统动态库路径lib加载动态库(如openCryptoki的动态库),调用pkcs11.New(lib)建立pkcs11实例ctx。ctx相当于fabric与安全硬件模块通信的桥梁:bccsp<–>ctx<–>驱动lib<–>安全硬件模块,只要驱动lib是按照pkcs11标准开发。
  2. ctx.Initialize()进行初始化。
  3. ctx.GetSlotList(true)返回的列表中获取由label指定的插槽标识slot(这里的槽可以简单的理解为电脑主机上供安全硬件模块插入的槽,如USB插口,可能不止一个,每一个在系统内核中都有名字和标识号)。
  4. 尝试10次调用ctx.OpenSession打开一个会话session(会话就是通过通信路径与安全硬件模块建立连接,可以简单的理解为pkcs11的chan)。
  5. 登陆会话ctx.Login
  6. 返回ctx,slot,会话对象session,用于赋值给impl实例成员ctx,slot,把session发送到sessions里。

关于pkcs11,还有一点可说的,就是SoftHSM库,它是一个模拟硬件实现的pkcsll,对应到的系统动态库可参看impl.go中FindPKCS11Lib测试函数中所涉及的,如Linux下的libsofthsm2.so。现阶段是没有安全硬件模块可以配合测试的,所以只有使用SoftHSM模拟测试,将libsofthsm2.so导入pkcs11对象。

BCCSP工厂

对应两种bccsp实现,这里也有两种bccsp工厂:pkcs11factory.goswfactory.go。fabric中某一模块一旦涉及工厂factory,则说明该模块基本就是由工厂提供“窗口函数”,供其他模块调用。这里以swfactory为例进行讲解。

目录结构

  • /fabric/bccsp/factory/
    • factory.go - 声明了默认bccsp实例defaultBCCSP,bccsp实例存储映射bccspMap等全局变量和这些变量的获取函数GetXXX,定义bccsp工厂接口。
    • nopkcs11.go/pkcs11.go - 定义了两种版本的工厂选项FactoryOpts,初始化工厂函数InitFactories和获取指定bccsp实例函数GetBCCSPFromOpts。nopkcs11是默认版本,可条件编译指定使用哪种版本(编译时加入nopkcs11或!nopkcs11选项)。两种版本的差异集中在是否使用pkcs11上。
    • opts.go - 定义了默认的工厂选项DefaultOpts
    • pkcs11factory.go - pkcs11类型的bccsp工厂实现PKCS11Factory
    • swfactory.go - sw类型的bccsp工厂实现SWFactory。还定义了sw版本的bccsp选项。

swfactory接口和实现

//在factory.go中定义
//接口
type BCCSPFactory interface {

    //返回工厂的名字
    Name() string
    //返回符合工厂选项opts的bccsp实例
    Get(opts *FactoryOpts) (bccsp.BCCSP, error)
}

//在swfactory.go中定义
//实现
type SWFactory struct{}
func (f *SWFactory) Name() string {
    return SoftwareBasedFactoryName
}
func (f *SWFactory) Get(config *FactoryOpts) (bccsp.BCCSP, error) { ... }

实现的代码本身比较简单,Get最终是调用的sw的专用生成函数New来生成符合opts的bccsp实例。Name则是直接返回一个”SW”常量。

在每个chaincode例子中,如/fabric/examples/e2e_cli/examples/chaincode/go/chaincode_example02/chaincode_example02.go,都使用了chaincode垫片shim中的Start函数。不知道在fabirc源码解析7——peer的ChaincodeSupport服务文中是否说过,在此说一下,chaincode的“垫片”shim核心代码集中在/fabric/core/chaincode/shim中,该垫片所“承垫”的是与各个结点通信的任务,也即ChaincodeSupport服务。chaincode形成的通信的信息,通过shim分发到各个结点,然后shim负责从各个结点收集信息,汇总返回给chaincode,完成chaincode的功能。其中shim的Start函数就是用来启动一个chaincode,定义在/fabric/core/chaincode/shim/chaincode.go中。在Start的函数中,就调用了err := factory.InitFactories(&factory.DefaultOpts)来初始化一个默认的bccsp工厂,在此可以知道,这里使用的默认工厂选项(参看opts.go),也就是使用的swfactory。

猜你喜欢

转载自blog.csdn.net/idsuf698987/article/details/77200287