fabric源码解析12——peer的MSP服务

fabric源码解析12——peer的MSP服务

MSPMembership Service Provider的缩写,个人习惯直译为成员关系服务提供者。作用类似于,在一个运行的fabric系统网络中有众多的参与者,MSP就是为了管理这些参与者,辨识验证哪些人有资格,哪些人没资格,既维护某一个参与者的权限,也维护参与者之间的关系。关于MSP更专业的概念,请参阅Fabric文档。

这里说句题外话,最近这几篇文章其实感觉挺别扭的,因为有些模块是相互牵连的,放在一起说吧文章过于复杂,主题不明,分开说又感觉讲不深,形不成体系,比如MSP和BCCSP之间就是这样。之后在start.go完结之后,会有chaincode安装之类操作性较强的文章,在这些文章中,以具体的示例,通过原始数据在系统中被各个服务之间接收、加工、传送,以此来形成比较系统认识。

MSP的核心代码在/fabric/msp中,相关代码分布在/fabric/common/config/msp、/fabric/protos/msp、/fabric/sampleconfig/msp。主要目录结构如下:

  • msp
    • msp.go - 定义MSP,MSPManager,Identity,SigningIdentity等主要接口
    • mspimpl.go - 实现MSP接口,结构为bccspmsp
    • mspmgrimpl.go - 实现MSPManager接口,结构为mspManagerImpl
    • identities.go - 实现Identity,SigningIdentity接口
    • configbuilder.go - 提供读取证书文件并将其组装成MSP等接口需要的数据结构,转换配置结构体(由FactoryOpts->MSPConfig)等工具函数
    • mgmt - msp的管理代码目录
      • mgmt.go - 主要文件,localMsp和mspMap都在这个文件,还有多个管理函数
  • common
    • config
      • msp/config.go - 实现了MSPManager接口,结构为MSPConfigHandler,是用来配置MSP的,用于configtx工具
  • protos
    • msp - 原始的msp配置(MSPConfig)、定义,和对应生成的.go文件
  • sampleconfig
    • msp - msp所需的证书文件等的一个实例

结构图

MSP.png

背书检验

fabirc源码解析11所涉及到了Endorser服务使用到了MSP对接收的SignedProposal消息进行检验,在此将详解检验过程。

函数追溯过程:ProcessProposal -> validation.ValidateProposalMessage -> checkSignatureFromCreator。在checkSignatureFromCreator函数中,进行了如下验证:

//接收的参数有:
  creatorBytes - SignatureHeader中的Creator
  sig          - SignedProposal中的Signature
  msg          - SignedProposal中的ProposalBytes
  ChainID      - ChannelHeader中的ChannelId

//根据ChainID获取实现IdentityDeserializer对象
mspObj := mspmgmt.GetIdentityDeserializer(ChainID)
//利用获取的对象,根据二进制的creatorBytes数据获取creator对象
creator, err := mspObj.DeserializeIdentity(creatorBytes)
//使用creator的方法进行验证
err = creator.Validate()
err = creator.Verify(msg, sig)

GetIdentityDeserializermgmt.go中定义,返回的是一个IdentityDeserializer接口对象,实际上最终返回的是bccspmsp对象或mspManagerImpl/MSPConfigHandler对象,因为IdentityDeserializer接口从定义处的注释就可以看出其作用注意在粘合MSP和MSPManager这两个接口,这两个接口都要求实现了IdentityDeserializer接口。对应的,当返回的是bccspmsp对象时,是通过GetLocalMSP函数,其实质返回的是localMsp变量;当返回的是mspManagerImpl/MSPConfigHandler对象时,是通过GetManagerForChain函数,其实质返回的是mspMap映射的ChainID的值。

这里要另外说的是MSPConfigHandler对象,在/fabric/common/config/msp/config.go中定义,该对象将MSPManager接口作为了成员,自然也应实现了IdentityDeserializer接口,但是冲突的是,搜索源码,并没有发现其实现MSPManager接口的代码,想必是让用户自己实现或在具体使用的时候再赋予具体的MSPManager实现对象。GetManagerForChain函数中,若MSPConfigHandler的成员MSPManager为空,则直接返回nil,进而checkSignatureFromCreator函数也会直接返回。在一些test代码中,如mgmt_test.go中,实例化MSPConfigHandler对象时,其MSPManager成员直接初始化为系统实现的mspManagerImpl或直接给nil。在此将此对象先搁置,等之后的代码中遇到可解明之处了再补。

因此,mspObj是bccspmsp对象或mspManagerImpl对象,在此分别用A和B表示。两种对象实现IdentityDeserializer接口的DeserializeIdentity方法略有所不同,原因在于其自身结构的差异,但殊途同归。A是一个MSP,而B实现MSPManager,自然肩负这管理MSP的任务,因此其成员中有一个MSP的映射mspsMap,算是一堆MSP。

  1. A和B都将接收的creatorBytes参数Unmarshal成SerializedIdentity结构对象,该对象定义在fabric/protos/msp/identities.pb.go中,且其成员MspidIdBytes将用于A和B的后续验证。

  2. A验证Mspid与自身存储的name是否一致后,将IdBytes传入内调函数deserializeIdentityInternal,以进行进一步的验证;B验证以Mspid为key值的映射是否存在于自身的mspsMap中后,用switch判断映射的MSP对象的类型,若是系统实现的bccspmsp,则与A一样去调用deserializeIdentityInternal,若是用户自己实现的MSP,则再去调用用户实现的DeserializeIdentity方法,我们撇下用户自己实现的MSP的这种情况。

  3. deserializeIdentityInternal函数的操作涉及到了mspObj对象中的bccsp bccsp.BCCSP成员,该成员是一个bccsp服务对象,而bccsp是fabric整个系统的加密服务的提供者,将在主题文章中详述。deserializeIdentityInternal函数接收IdBytes,并根据IdBytes调用一系列bccsp所提供的函数,如GetHashOptHashKeyImport,生成identity对象所需的数据,最终生成identity对象并返回。identity对象实现了Identity接口,在msp/identities.go中定义,代表一个身份对象,也提供了验证能力(函数)。这里的身份是用来表明一个会员的身份的,包含了这个会员的名称、使用的证书、加密算法、公匙和MSP对象自身等信息,验证能力指的是其提供的ValidateVerify两个函数。

  4. 使用identity对象,也就是creator接收到的值,进行验证和确认。追溯ValidateVerify两个函数,都在/fabric/msp/identities.go中定义,我们会发现最终使用的是其所包含的MSP对象的Validate和MSP对象所包含的bccsp对象的Verify函数。这里和第3步一样,使用了bccsp服务,在此不再赘述。identity validation(与certificate validation是一个意思,即证书和身份的概念是重叠的,证书就代表着身份)和signature verification这两个短语在fabric文档中是专用的,分别对应两个函数Validate和Verify,也就是说,前者验证的是身份,后者确认的是签名。

了解一个服务模块,基本上问题还是集中在两点:

  • 服务模块自身能做什么
  • 系统如何使用这个模块。这里的如何包含了何时使用和如何使用两个意思。

我们接下来从这个思路入手,了解MSP服务。这里先进行一个打比方的描述和证书的解释:

打个比方:

一个集团化的大型公司,可能有一个集团总部,集团下分若干个子集团,分别涉及不同商业领域(如建设集团,商业发展集团,投资集团等子集团),每个集团下在全国各地有若干个分公司,分公司下有部门,部分中有职员,职员有管理者和普通职员之分。**

关于证书(fabric里面所用的都是x509证书):

  • 证书本身是承载公匙的容器,里面最主要的就是公匙,和一些认证信息。比较证书的时候,自然比较公匙。
  • SKI是当前证书的标识符,所谓标识符,一般是对公匙进行hash,得到的一段字符标识。
  • SKI是当前证书的标识符,AKI是签署方的SKI,也就是签署方的公匙标识符。
  • 证书是一级级信任的,比如某个CA的密匙签署了你的密匙,生成了你的证书,也就是该CA认证了你的证书,也就是你的证书就是该CA证书的下一级证书。原来的那个CA证书自己的标识符也是SKI,而对于你这个证书而言,CA那个证书的标识符是你的签署方标识符,也就是AKI。即上一级的SKI变为下一级的AKI。
  • x509是证书的一种类型,具体可参看/fabric/sampleconfig下的admincerts,cacerts中的文件。## Identity和SigningIdentity接口

Identity

type identity struct {
    //实例的身份标识,如MSPID等
    id *IdentityIdentifier
    //实例所对应的x509证书,代表着身份
    cert *x509.Certificate
    //实例的身份公匙
    pk bccsp.Key
    //“拥有”此实例的MSP实例
    msp *bccspmsp
}

//调用msp的SatisfiesPrincipal接口检查身份实例是否与principal中所描述的那种类型匹配
//如果匹配,则返回nil
func (id *identity) SatisfiesPrincipal(principal *msp.MSPPrincipal)error{
    return id.msp.SatisfiesPrincipal(id, principal)
}
//调用msp的Validate接口验证这个身份实例
func (id *identity) Validate() error {
    return id.msp.Validate(id)
}
//调用msp的成员bccsp的Verify接口确认签名sig与消息msg是否匹配,也即验证签名的有效性
func (id *identity) Verify(msg []byte, sig []byte) error {
    ...
    digest, err := id.msp.bccsp.Hash(msg, hashOpt)
    ...
    valid, err := id.msp.bccsp.Verify(id.pk, sig, digest, nil)
    ...
}
//调用msp的SerializedIdentity接口把此身份实例转为byte形式
func (id *identity) Serialize() ([]byte, error) {
    ...
    sId := &msp.SerializedIdentity{Mspid: id.id.Mspid, IdBytes: pemBytes}
    idBytes, err := proto.Marshal(sId)
    ...
}
//未列出的接口实现要么没用(不实现,只是直接返回错误或打印一句话),要么是GetXXX系列
...

在此省略Identity接口中GetXXX相关的实现,这些都是获取身份信息的,算不上什么功能性接口,而相较于获取身份信息这种比较简单的实现,我们更想知道身份中的数据是如何形成的和这些数据可以做什么。Identity是系统成员身份的代表,而其所包含的x509证书就可以在系统中代表着这个身份,由MSP“拥有”和管理的。Identity其实不真正实现什么功能,只是比较本质的起到代表一个成员的作用(就足够了),这点可以从SatisfiesPrincipal,Validate,Verify,Serialize这样的功能性接口的实现看出:这些实现都是调用管理这个身份实例的msp的接口实现的,而身份实例自身只提供身份数据。身份在系统中,可以代表一个peer结点的身份,可以代表一个组织的身份,等身份性的对象。还有一点,如果一个身份中包含所属的组织信息(同时也就是说身份可能没有组织信息),该组织信息是被包含在x509证书中的。

从SatisfiesPrincipal这个接口实现,可以引出身份的类型之分。类型之分的原始定义在/fabric/protos/common中的msp_principal.proto和对应生成msp_principal.pb.go。结构体为MSPPrincipal,有以下三种类型的身份:

  • ROLE,表示一个角色,有成员MEMBER管理者ADMIN之分。类似于一个公司中,普通职员和经理。
  • ORGANIZATION_UNIT,表示组织单位。类似于一个公司中的,一个部门。
  • IDENTITY,就表示一个普通的身份。是与ORGANIZATION_UNIT相对的,类似于一个公司中,一个个体。

SigningIdentity

type signingidentity struct {
    //签名者身份身份实例
    identity
    //crypto库中的“专用签名笔”
    signer crypto.Signer
}

//在msg上进行签名
func (id *signingidentity) Sign(msg []byte) ([]byte, error) {
    //获取用于签名的hash选项(用哪种hash技术进行签名)
    hashOpt, err := id.getHashOpt(id.msp.cryptoConfig.SignatureHashFamily)
    ...
    //获取哈希值
    digest, err := id.msp.bccsp.Hash(msg, hashOpt)
    ...
    //使用专用签名笔进行签名
    return id.signer.Sign(rand.Reader, digest, nil)
}
//其他实现都是未实质实现(只是返回一个错误)
...

签名者身份是一个拿有“专用签名笔”的身份。自然,也用来进行签名。在MSP管理的所有身份对象中,可以指定一些成员用来对消息进行签名。在Sign实现中,使用的是“专用签名笔”crypto标准库里的Signer对象,同时用了MSP的一些功能作为签名的辅助。

可以把Identity想象成一个部门或一个个体职员,当为一个部门时,其为组织类型,当为一个个体时,又有管理者和普通职员之分。

MSP和MSPManager接口

MSP

type bccspmsp struct {
    //root CAs
    rootCerts []Identity
    //intermediate CAs
    intermediateCerts []Identity
    //签名身份
    signer SigningIdentity
    //管理员身份列表
    admins []Identity
    //加密算法
    bccsp bccsp.BCCSP
    //MSP的名字
    name string
    //验证选项
    opts *x509.VerifyOptions
    //废除CAs
    CRL []*pkix.CertificateList
    //组织列表
    ouIdentifiers map[string][][]byte
    //加密选项
    cryptoConfig *m.FabricCryptoConfig
}

//根据配置信息conf1建立MSP对象,即利用conf1填充这个bccspmsp实例中的字段
func (msp *bccspmsp) Setup(conf1 *m.MSPConfig) error { ... }
//验证给定的身份id是否有效
func (msp *bccspmsp) Validate(id Identity) error { ... }
//验证给定的身份id是否与所给的principal中所描述的类型相匹配
func (msp *bccspmsp) SatisfiesPrincipal(id Identity, principal *m.MSPPrincipal) error{ ... }
//其余都为GetXXX系列和辅助性内调函数

关于bccspmsp的结构体中的字段,可以对照fabirc源码解析9——文档翻译之MSPMSP配置章节所讲的一个MSP需要指定哪些数据,就很容易理解了。这里起名为bccspmsp,指的是该实现使用了bccsp作为其加密技术的提供者,正如其有一个成员bccsp一样。由对Identity接口所讲的那样,MSP实质上承载着身份验证和管理的任务。可以把MSP想象成一个子公司,每个其所管理的成员要么是公司的一个个体,要么是公司的一个部门,个体要么是经理,要么是普通职员,而且这些主体都用一个通用的身份接口Identity表示

Setup:Setup的过程会如你想象的那样,就是对参数conf1中所携带的数据进行择选,然后填充bccspmsp的各个字段,这其中用到了bccsp的加密技术,如果某些所需的数据conf1中没有,则置默认值。这也就是说,conf1中有我们需要的数据,那么这个conf1是怎么形成的呢,它的数据是从哪儿来的?如果不想搜索源代码以查看这个接口在何时何处被调用(因为既然要调用,肯定会准备好数据,依此可以寻找conf1是怎么形成的),那么可以直接看test或mock文件,这里,比较简单直接的且能说明问题的就是/fabric/msp/mspwithintermediatecas_test.go这个测试文件,里面简单查看Setup调用之前的conf数据组装,就可以知道conf是怎么来的了。

Validate:验证身份有效,主要验证(满足)三点:

  • 身份中所携带的x509证书存在于bccsmsp的rootCerts或intermediateCerts之中。这需要你比较熟悉x509证书Certificate的方法Verify的用法(追溯Validate函数可以找到),原型为func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error),这里使用到的参数opts就是bccsmsp成员opts,这个成员相当与一个证书地图,即指示函数去哪些证书池(CertPool)中与c这个证书进行对比。opts在bccsmsp的Setup实现中被初始化为包含rootCerts和intermediateCerts两个证书池。
  • 身份中所携带的x509证书不在CRL中。这里的对比不是直接用证书本身对比,因为CRL所对应的结构体是pkix.CertificateList。
  • 身份中所携带的组织信息有效。

SatisfiesPrincipal:验证给定的身份id是否与所给的principal中所描述的类型相匹配。过程就是根据身份的类型所进行的一个switch-case分支判断的过程,这个判断就是分别抽取id和principal中对应的字段信息进行对比。

MSPManager

type mspManagerImpl struct {
    //一个MSP的映射,包含所有建立的MSP,添加的MSP
    mspsMap map[string]MSP
    //是否正常启用的标识
    up bool
}

//根据所给的msps填充该实例中的mspsMap
func (mgr *mspManagerImpl) Setup(msps []MSP) error { ... }
//根据所给的byte格式的身份,返回其对应的Identity结构体
func (mgr *mspManagerImpl) DeserializeIdentity(serializedID []byte) (Identity, error) { ... }
//GetXXX系列
func (mgr *mspManagerImpl) GetMSPs() (map[string]MSP, error) {
    return mgr.mspsMap, nil
}

可以把MSPManager想象成一个子集团,其管理集团旗下的各个子公司,而且这些子公司主体都用一个MSP接口表示

mgmt

//本地MSP实例对象
var localMsp msp.MSP
//本地存储的按chaincodeID为key的MSPManager映射
var mspMap map[string]msp.MSPManager = make(map[string]msp.MSPManager)

//从目录dir中加载本地MSP
func LoadLocalMsp(dir string, bccspConfig *factory.FactoryOpts, mspID string) error {
    ...
    //调用configbuilder.go中的GetLocalMspConfig将FactoryOpts转为MSPConfig
    conf, err := msp.GetLocalMspConfig(dir, bccspConfig, mspID)
    ...
    //根据conf调用localMsp的Setup填充localMsp
    return GetLocalMSP().Setup(conf)
}
//获取指定chaincodeID的MSPManager,如果没有,则创建
func GetManagerForChain(chainID string) msp.MSPManager { ... }
//其他公共函数,GetXXX系列和临时替代者XXXSetMSPManager
...

mgmt就是management的缩写,也就是管理的意思。这里的本地指代的是peer结点,而mgmt也不是一个具体的对象。可以说真正经常供peer其他模块调用和使用的函数是这里提供的函数,也即mgmt才是对外提供MSP服务的窗口。MSP数据存储的主体就是localMspmspMap

  • localMsp,本地MSP,管理了所有本地的身份。LoadLocalMsp是给localMsp读入MSP数据的,按部就班,攒成配置选项,然后调用Setup填充localMsp。GetXXX系列中与localMsp相关的则是向外界提供这个本地的MSP。在fabric源码分析5–kvledger的初始化中开篇所遗留的关于InitCrypto的问题,在此就可以解释一下,InitCrypto在/fabric/peer/main.go中main函数中被调用,在/fabric/peer/common/common.go中定义。而InitCrypto中就调用了LoadLocalMsp,也就初始化了peer结点本地MSP。在/fabric/orderer/main.go中,也调用了LoadLocalMsp,也就初始化了orderer结点的本地MSP。
  • mspMap是一个以chaincodeID为key的映射,可以把每个chaincode当作是一个不同的商业领域,而把mspMap看作是按chaincode分的每个chaincode上的子集团的集合,这些子集团在各自的商业领域管理这自己的子公司(MSP)。GetManagerForChain函数既是数据赋予者也是数据给予者,当所给定的chaincodeID不存在时,会自动创建一个MSPManager对象,当所给的chaincodeID存在时,则返回对应的MSPManager。如在/fabric/core/scc/lscc/lscc.go中的checkInstantiationPolicy函数中调用了GetManagerForChain,获取了指定chainName的chaincode的MSPManager以供使用。

可以把mgmt想象成一个集团化公司的总部,管理各个子集团(MSPManager)并统一向外界提供自身所管理的成员数据和信息,这也就是所谓的MSP服务。

猜你喜欢

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