一、介紹
分析MSP其實應該先對權限控制源碼進行分析最好,但是權限控制的主要源碼在CA相關代碼中比較多,是以先把這塊放到後面再分析。
MSP(membership service provider),成員管理服務提供者,它是從1.0引進的一個元件,目的是抽象各個成員間的管理結構關系。它包含:證書的管理、使用者認證、加密和協定等。
二、MSP内容
MSP主要的内容有什麼呢?其實主要就是身份認證。有沒有權利在這個“組織”内工作,此處的組織不是Fabric内的組織。
MSP是基于x.509證書,利用PKI體系為每個成員分發數字證書。并通過MSP進行身份認證和權限控制。說得挺高大上,其實就是通過根證書,簽發中間CA憑證,再利用中間CA簽發用戶端證書。它主要檢查三個方面:
證書的有效性,是否過期或吊銷
證書的路徑的檢查,用戶端證書-根證書
MSP的辨別認證,就是身份檢查,因為MSP中證書和身份是綁定的。
針對實際的MSP管理,有三種方式:
組織和MSP建立1:1關系:推薦這種方式
組織和MSP建立1:N關系:也就說大公司有N個部門進行不同的管理方式。
組織和MSP建立N:1關系:跨組織同步友善,多個公司使用同一套MSP。
在進行MSP設計管理時,要注意将不同的CA憑證存儲到不同的路徑,區分管理者和不同的根證書(如CA根證書和TLS根證書),嚴格管理證書的吊銷和更新。
三、源碼
源碼主要位于msp目錄下,其它一些相關的分散在不同的應用目錄下,比如common/config/locamsp,proto/msp,在sampleconfig/msp實作了一個簡單的例子。更具體的目錄結構請看源碼。
1、相關的資料結構
type mspSigner struct {
}
//反序列化ID的接口
type IdentityDeserializer interface {
// 反序列化身份關聯到相關的MSP。如果不是,将傳回錯誤
DeserializeIdentity(serializedIdentity []byte) (Identity, error)
// 反序列化的檢查
IsWellFormed(identity *msp.SerializedIdentity) error
}
type MSPManager interface {
// 見上面的結構體
IdentityDeserializer
//根據配置設定MSPManager 執行個體
Setup(msps []MSP) error
//獲得成員服務提供者清單
GetMSPs() (map[string]MSP, error)
}
// MSP is the minimal Membership Service Provider Interface to be implemented
// to accommodate peer functionality
type MSP interface {
// 見上面的結構體
IdentityDeserializer
//根據配置資訊設定MSP執行個體
Setup(config *msp.MSPConfig) error
// GetVersion returns the version of this MSP
GetVersion() MSPVersion
// GetType returns the provider type
GetType() ProviderType
// 傳回提供者ID
GetIdentifier() (string, error)
// 根據提供ID傳回簽名身份ID
GetSigningIdentity(identifier *IdentityIdentifier) (SigningIdentity, error)
//傳回預設簽名身份ID
GetDefaultSigningIdentity() (SigningIdentity, error)
// 傳回根證書
GetTLSRootCerts() [][]byte
// 傳回TLS中間根證書
GetTLSIntermediateCerts() [][]byte
// 提供的ID是否有效
Validate(id Identity) error
// 身份比對,逐位元組或需要MSP驗證
SatisfiesPrincipal(id Identity, principal *msp.MSPPrincipal) error
}
type Identity interface {
// 傳回ID過期時間
ExpiresAt() time.Time
// 傳回身份ID
GetIdentifier() *IdentityIdentifier
// 獲得MSP的相關執行個體的ID
GetMSPIdentifier() string
// MSP規則驗證
Validate() error
// 獲得組織機關如證書頒發機構等
GetOrganizationalUnits() []*OUIdentifier
//匿名判定
Anonymous() bool
// 驗證消息簽名
Verify(msg []byte, sig []byte) error
// 序列化
Serialize() ([]byte, error)
// MSPPrincipal檢查驗證,有可能逐位元組比對
SatisfiesPrincipal(principal *msp.MSPPrincipal) error
}
//擴充的ID,包含簽名功能
type SigningIdentity interface {
// Extends Identity
Identity
// Sign the message
Sign(msg []byte) ([]byte, error)
// GetPublicVersion returns the public parts of this identity
GetPublicVersion() Identity
}
// 特殊ID
type IdentityIdentifier struct {
// MSP提供的辨別ID
Mspid string
// ID
Id string
}
另外還有些小的資料結構就不貼上來了。從上面可以看到,其實資料結構也是從小到大(或者說從大到小),從ID到MSP再到MSP管理者。然後再具體到相關資料的反序列化。
2、MSP的初始化代碼
無論是在前面的Peer還是Orderer啟動都在分析時看到了相關的MSP的代碼的初始化部分,下面看一下相關代碼:
//Peer
func serve(args []string) error {
//
mspType := mgmt.GetLocalMSP().GetType()
......
identityDeserializerFactory := func(chainID string) msp.IdentityDeserializer {
return mgmt.GetManagerForChain(chainID)
}
......
membershipInfoProvider := privdata.NewMembershipInfoProvider(createSelfSignedData(), identityDeserializerFactory)
......
signingIdentity := mgmt.GetLocalSigningIdentityOrPanic()
serializedIdentity, err := signingIdentity.Serialize()
......
}
//Orderer
func Main() {
......
initializeLocalMsp(conf)
......
}
找到的相關的部分,下面看一下具體實作的代碼有什麼異同。
Orderer部分:
func initializeLocalMsp(conf *localconfig.TopLevel) {
// Load local MSP
err := mspmgmt.LoadLocalMsp(conf.General.LocalMSPDir, conf.General.BCCSP, conf.General.LocalMSPID)
if err != nil { // Handle errors reading the config file
logger.Fatal("Failed to initialize local MSP:", err)
}
}
func LoadLocalMsp(dir string, bccspConfig *factory.FactoryOpts, mspID string) error {
if mspID == "" {
return errors.New("the local MSP must have an ID")
}
//獲得本地配置,就是提供的.yaml檔案
conf, err := msp.GetLocalMspConfig(dir, bccspConfig, mspID)
if err != nil {
return err
}
//這個在前面有,取得目前MSP并配置
return GetLocalMSP().Setup(conf)
}
func GetLocalMspConfig(dir string, bccspConfig *factory.FactoryOpts, ID string) (*msp.MSPConfig, error) {
signcertDir := filepath.Join(dir, signcerts)
keystoreDir := filepath.Join(dir, keystore)
bccspConfig = SetupBCCSPKeystoreConfig(bccspConfig, keystoreDir)
err := factory.InitFactories(bccspConfig)
if err != nil {
return nil, errors.WithMessage(err, "could not initialize BCCSP Factories")
}
//從指定路徑獲得PEM證書
signcert, err := getPemMaterialFromDir(signcertDir)
if err != nil || len(signcert) == 0 {
return nil, errors.Wrapf(err, "could not load a valid signer certificate from directory %s", signcertDir)
}
//正面會和BCCSP有關系了
/* FIXME: for now we're making the following assumptions
1) there is exactly one signing cert
2) BCCSP's KeyStore has the private key that matches SKI of
signing cert
*/
sigid := &msp.SigningIdentityInfo{PublicSigner: signcert[0], PrivateSigner: nil}
return getMspConfig(dir, ID, sigid)
}
func loadLocaMSP() msp.MSP {
// determine the type of MSP (by default, we'll use bccspMSP)
mspType := viper.GetString("peer.localMspType")
if mspType == "" {
mspType = msp.ProviderTypeToString(msp.FABRIC)
}
var mspOpts = map[string]msp.NewOpts{
msp.ProviderTypeToString(msp.FABRIC): &msp.BCCSPNewOpts{NewBaseOpts: msp.NewBaseOpts{Version: msp.MSPv1_4_3}},
msp.ProviderTypeToString(msp.IDEMIX): &msp.IdemixNewOpts{NewBaseOpts: msp.NewBaseOpts{Version: msp.MSPv1_1}},
}
newOpts, found := mspOpts[mspType]
if !found {
mspLogger.Panicf("msp type " + mspType + " unknown")
}
mspInst, err := msp.New(newOpts)
if err != nil {
mspLogger.Fatalf("Failed to initialize local MSP, received err %+v", err)
}
switch mspType {
case msp.ProviderTypeToString(msp.FABRIC):
mspInst, err = cache.New(mspInst)
if err != nil {
mspLogger.Fatalf("Failed to initialize local MSP, received err %+v", err)
}
case msp.ProviderTypeToString(msp.IDEMIX):
// Do nothing
default:
panic("msp type " + mspType + " unknown")
}
mspLogger.Debugf("Created new local MSP")
return mspInst
}
//在New函數裡會調用生成MSP的相關函數,不同的版本人會有不同的建立方法,這裡取了其中一種
func newBccspMsp(version MSPVersion) (MSP, error) {
mspLogger.Debugf("Creating BCCSP-based MSP instance")
bccsp := factory.GetDefault()
theMsp := &bccspmsp{}
theMsp.version = version
theMsp.bccsp = bccsp
switch version {
case MSPv1_0:
theMsp.internalSetupFunc = theMsp.setupV1
theMsp.internalValidateIdentityOusFunc = theMsp.validateIdentityOUsV1
theMsp.internalSatisfiesPrincipalInternalFunc = theMsp.satisfiesPrincipalInternalPreV13
theMsp.internalSetupAdmin = theMsp.setupAdminsPreV143
case MSPv1_1:
theMsp.internalSetupFunc = theMsp.setupV11
theMsp.internalValidateIdentityOusFunc = theMsp.validateIdentityOUsV11
theMsp.internalSatisfiesPrincipalInternalFunc = theMsp.satisfiesPrincipalInternalPreV13
theMsp.internalSetupAdmin = theMsp.setupAdminsPreV143
case MSPv1_3:
theMsp.internalSetupFunc = theMsp.setupV11
theMsp.internalValidateIdentityOusFunc = theMsp.validateIdentityOUsV11
theMsp.internalSatisfiesPrincipalInternalFunc = theMsp.satisfiesPrincipalInternalV13
theMsp.internalSetupAdmin = theMsp.setupAdminsPreV143
case MSPv1_4_3:
theMsp.internalSetupFunc = theMsp.setupV143
theMsp.internalValidateIdentityOusFunc = theMsp.validateIdentityOUsV143
theMsp.internalSatisfiesPrincipalInternalFunc = theMsp.satisfiesPrincipalInternalV143
theMsp.internalSetupAdmin = theMsp.setupAdminsV143
default:
return nil, errors.Errorf("Invalid MSP version [%v]", version)
}
return theMsp, nil
}
//最後Setup
func (msp *bccspmsp) Setup(conf1 *m.MSPConfig) error {
if conf1 == nil {
return errors.New("Setup error: nil conf reference")
}
// given that it's an msp of type fabric, extract the MSPConfig instance
conf := &m.FabricMSPConfig{}
err := proto.Unmarshal(conf1.Config, conf)
if err != nil {
return errors.Wrap(err, "failed unmarshalling fabric msp config")
}
// set the name for this msp
msp.name = conf.Name
mspLogger.Debugf("Setting up MSP instance %s", msp.name)
// setup
return msp.internalSetupFunc(conf)
}
這一系列的動作,有點小複雜。
Peer部分:
GetLoclaMSP和Orderer完全一樣的代碼,看一下下面的反序列化注冊函數:
func GetManagerForChain(chainID string) msp.MSPManager {
m.Lock()
defer m.Unlock()
mspMgr, ok := mspMap[chainID]
if !ok {
mspLogger.Debugf("Created new msp manager for channel `%s`", chainID)
mspMgmtMgr := &mspMgmtMgr{msp.NewMSPManager(), false}
mspMap[chainID] = mspMgmtMgr
mspMgr = mspMgmtMgr
} else {
// 類型比對檢查
if !(reflect.TypeOf(mspMgr).Elem().Name() == "mspManagerImpl" || reflect.TypeOf(mspMgr).Elem().Name() == "mspMgmtMgr") {
panic("Found unexpected MSPManager type.")
}
mspLogger.Debugf("Returning existing manager for channel '%s'", chainID)
}
return mspMgr
}
//直到Setup方法後才會初始化,這裡隻生成一個空執行個體
func NewMSPManager() MSPManager {
return &mspManagerImpl{}
}
//再看membershipInfoProvider :=
//privdata.NewMembershipInfoProvider(createSelfSignedData(), identityDeserializerFactory)
func createSelfSignedData() common2.SignedData {
sId := mgmt.GetLocalSigningIdentityOrPanic()
msg := make([]byte, 32)
sig, err := sId.Sign(msg)
if err != nil {
logger.Panicf("Failed creating self signed data because message signing failed: %v", err)
}
peerIdentity, err := sId.Serialize()
if err != nil {
logger.Panicf("Failed creating self signed data because peer identity couldn't be serialized: %v", err)
}
return common2.SignedData{
Data: msg,
Signature: sig,
Identity: peerIdentity,
}
}
//這個函數生成一個新的相關MSP執行個體交将上面得到相關序列化函數指針代入
func NewMembershipInfoProvider(selfSignedData common.SignedData, identityDeserializerFunc func(chainID string) msp.IdentityDeserializer) *MembershipProvider {
return &MembershipProvider{selfSignedData: selfSignedData, IdentityDeserializerFactory: identityDeserializerFunc}
}
func GetLocalSigningIdentityOrPanic() msp.SigningIdentity {
id, err := GetLocalMSP().GetDefaultSigningIdentity()
if err != nil {
mspLogger.Panicf("Failed getting local signing identity [%+v]", err)
}
return id
}
func (msp *bccspmsp) GetDefaultSigningIdentity() (SigningIdentity, error) {
mspLogger.Debugf("Obtaining default signing identity")
if msp.signer == nil {
return nil, errors.New("this MSP does not possess a valid default signing identity")
}
return msp.signer, nil
}
3、交易驗證
交易的驗證在背書中首先會被用到,看一下代碼:
背書的除了Proto部分外,主要在core/endorser這個檔案夾下,重點看一下MSP相關的驗證:
func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
// start time for computing elapsed time metric for successfully endorsed proposals
startTime := time.Now()
......
// 0 -- check and validate
vr, err := e.preProcess(signedProp)
......
}
這裡可以看看這個簽名的定義:
// This structure is necessary to sign the proposal which contains the header
// and the payload. Without this structure, we would have to concatenate the
// header and the payload to verify the signature, which could be expensive
// with large payload
//
// When an endorser receives a SignedProposal message, it should verify the
// signature over the proposal bytes. This verification requires the following
// steps:
// 1. Verification of the validity of the certificate that was used to produce
// the signature. The certificate will be available once proposalBytes has
// been unmarshalled to a Proposal message, and Proposal.header has been
// unmarshalled to a Header message. While this unmarshalling-before-verifying
// might not be ideal, it is unavoidable because i) the signature needs to also
// protect the signing certificate; ii) it is desirable that Header is created
// once by the client and never changed (for the sake of accountability and
// non-repudiation). Note also that it is actually impossible to conclusively
// verify the validity of the certificate included in a Proposal, because the
// proposal needs to first be endorsed and ordered with respect to certificate
// expiration transactions. Still, it is useful to pre-filter expired
// certificates at this stage.
// 2. Verification that the certificate is trusted (signed by a trusted CA) and
// that it is allowed to transact with us (with respect to some ACLs);
// 3. Verification that the signature on proposalBytes is valid;
// 4. Detect replay attacks;
type SignedProposal struct {
// The bytes of Proposal
ProposalBytes []byte `protobuf:"bytes,1,opt,name=proposal_bytes,json=proposalBytes,proto3" json:"proposal_bytes,omitempty"`
// Signaure over proposalBytes; this signature is to be verified against
// the creator identity contained in the header of the Proposal message
// marshaled as proposalBytes
Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
注釋寫的很清楚,接着往下看:
func (e *Endorser) preProcess(signedProp *pb.SignedProposal) (*validateResult, error) {
vr := &validateResult{}
// at first, we check whether the message is valid
prop, hdr, hdrExt, err := validation.ValidateProposalMessage(signedProp)
if err != nil {
e.Metrics.ProposalValidationFailed.Add(1)
vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}
return vr, err
}
......
}
func ValidateProposalMessage(signedProp *pb.SignedProposal) (*pb.Proposal, *common.Header, *pb.ChaincodeHeaderExtension, error) {
......
//得到Proposal的比特流
prop, err := utils.GetProposal(signedProp.ProposalBytes)
if err != nil {
return nil, nil, nil, err
}
// 1) look at the ProposalHeader
hdr, err := utils.GetHeader(prop.Header)
if err != nil {
return nil, nil, nil, err
}
// validate the header
chdr, shdr, err := validateCommonHeader(hdr)
if err != nil {
return nil, nil, nil, err
}
// validate the signature
err = checkSignatureFromCreator(shdr.Creator, signedProp.Signature, signedProp.ProposalBytes, chdr.ChannelId)
......
}
//把相關的資料結構取出用來傳遞給下面的驗證
func validateCommonHeader(hdr *common.Header) (*common.ChannelHeader, *common.SignatureHeader, error) {
if hdr == nil {
return nil, nil, errors.New("nil header")
}
chdr, err := utils.UnmarshalChannelHeader(hdr.ChannelHeader)
if err != nil {
return nil, nil, err
}
shdr, err := utils.GetSignatureHeader(hdr.SignatureHeader)
if err != nil {
return nil, nil, err
}
err = validateChannelHeader(chdr)
if err != nil {
return nil, nil, err
}
err = validateSignatureHeader(shdr)
if err != nil {
return nil, nil, err
}
return chdr, shdr, nil
}
//下面的驗證就順理成章--基本可以看到相關的基礎接口的實作的函數定義
func checkSignatureFromCreator(creatorBytes []byte, sig []byte, msg []byte, ChainID string) error {
putilsLogger.Debugf("begin")
// check for nil argument
if creatorBytes == nil || sig == nil || msg == nil {
return errors.New("nil arguments")
}
mspObj := mspmgmt.GetIdentityDeserializer(ChainID)
if mspObj == nil {
return errors.Errorf("could not get msp for channel [%s]", ChainID)
}
// get the identity of the creator
creator, err := mspObj.DeserializeIdentity(creatorBytes)
if err != nil {
return errors.WithMessage(err, "MSP error")
}
putilsLogger.Debugf("creator is %s", creator.GetIdentifier())
// ensure that creator is a valid certificate
err = creator.Validate()
if err != nil {
return errors.WithMessage(err, "creator certificate is not valid")
}
putilsLogger.Debugf("creator is valid")
// validate the signature
err = creator.Verify(msg, sig)
if err != nil {
return errors.WithMessage(err, "creator's signature over the proposal is not valid")
}
putilsLogger.Debugf("exits successfully")
return nil
}
其它的地方同樣還有這種驗證,基本類似就不再一一分析,以後遇到逐一說明。
四、總結
MSP隻是Fabric中的一個基本的子產品,其實他的重要性更在于聯盟鍊的許可性。也正是通過它來完成了Channel的多鍊形态的保障。是以說,一個系統中,每個子產品都不是孤立的,是有機的和其它子產品或者系統融合在一起的。在分析代碼時,要注意這一點,要放到更廣泛的系統中去看代碼,這樣才會有更大的收獲。