MSP是Membership Service Provider的缩写,个人习惯直译为成员关系服务提供者。作用类似于,在一个运行的fabric系统网络中有众多的参与者,MSP就是为了管理这些参与者,辨识验证哪些人有资格,哪些人没资格,既维护某一个参与者的权限,也维护参与者之间的关系。关于MSP更专业的概念,请参阅Fabric文档。
这里说句题外话,最近这几篇文章其实感觉挺别扭的,因为有些模块是相互牵连的,放在一起说吧文章过于复杂,主题不明,分开说又感觉讲不深,形不成体系,比如MSP和BCCSP之间就是这样。之后在start.go完结之后,会有chaincode安装之类操作性较强的文章,在这些文章中,以具体的示例,通过原始数据在系统中被各个服务之间接收、加工、传送,以此来形成比较系统认识。
MSP的核心代码在/fabric/msp中,相关代码分布在/fabric/common/config/msp、/fabric/protos/msp、/fabric/sampleconfig/msp。主要目录结构如下:
结构图:
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)
GetIdentityDeserializer在mgmt.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。
A和B都将接收的creatorBytes参数Unmarshal成SerializedIdentity结构对象,该对象定义在fabric/protos/msp/identities.pb.go中,且其成员Mspid和IdBytes将用于A和B的后续验证。
A验证Mspid与自身存储的name是否一致后,将IdBytes传入内调函数deserializeIdentityInternal,以进行进一步的验证;B验证以Mspid为key值的映射是否存在于自身的mspsMap中后,用switch判断映射的MSP对象的类型,若是系统实现的bccspmsp,则与A一样去调用deserializeIdentityInternal,若是用户自己实现的MSP,则再去调用用户实现的DeserializeIdentity方法,我们撇下用户自己实现的MSP的这种情况。
deserializeIdentityInternal函数的操作涉及到了mspObj对象中的
bccsp bccsp.BCCSP
成员,该成员是一个bccsp服务对象,而bccsp是fabric整个系统的加密服务的提供者,将在主题文章中详述。deserializeIdentityInternal函数接收IdBytes,并根据IdBytes调用一系列bccsp所提供的函数,如GetHashOpt
、Hash
、KeyImport
,生成identity对象所需的数据,最终生成identity对象并返回。identity对象实现了Identity接口,在msp/identities.go中定义,代表一个身份对象,也提供了验证能力(函数)。这里的身份是用来表明一个会员的身份的,包含了这个会员的名称、使用的证书、加密算法、公匙和MSP对象自身等信息,验证能力指的是其提供的Validate和Verify两个函数。使用identity对象,也就是creator接收到的值,进行验证和确认。追溯Validate和Verify两个函数,都在/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接口
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,有以下三种类型的身份:
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想象成一个部门或一个个体职员,当为一个部门时,其为组织类型,当为一个个体时,又有管理者和普通职员之分。
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——文档翻译之MSP的MSP配置章节所讲的一个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:验证身份有效,主要验证(满足)三点:
func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err error)
,这里使用到的参数opts就是bccsmsp成员opts,这个成员相当与一个证书地图,即指示函数去哪些证书池(CertPool)中与c这个证书进行对比。opts在bccsmsp的Setup实现中被初始化为包含rootCerts和intermediateCerts两个证书池。SatisfiesPrincipal:验证给定的身份id是否与所给的principal中所描述的类型相匹配。过程就是根据身份的类型所进行的一个switch-case分支判断的过程,这个判断就是分别抽取id和principal中对应的字段信息进行对比。
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接口表示。
//本地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数据存储的主体就是localMsp和mspMap。
可以把mgmt想象成一个集团化公司的总部,管理各个子集团(MSPManager)并统一向外界提供自身所管理的成员数据和信息,这也就是所谓的MSP服务。