ChainMaker 兼容第三方ca

ChainMaker v2版本新添加了一個(gè)兼容第三方ca的功能几苍,現(xiàn)有如下問(wèn)題

  1. 第三方ca兼容是怎么實(shí)現(xiàn)的
  2. 第三方ca是如何驗(yàn)證的
  3. 第三方ca有沒(méi)有使用限制

帶著問(wèn)題看源碼颇玷,在SDK中 當(dāng)設(shè)置好公私鑰等參數(shù)污桦,創(chuàng)建客戶端時(shí)有一個(gè)壓縮證書的方法func (cc *ChainClient) EnableCertHash() 同時(shí)也提供證書驗(yàn)證
EnableCertHash里包含一個(gè)獲取鏈配置的RPC方法

func (cc *ChainClient) GetChainConfig() (*config.ChainConfig, error) {
//省略
    ...
    // 這里看出這是一個(gè)調(diào)用invoke rpc的方法同時(shí) 交易類型還是TxType_QUERY_CONTRACT
    payload := cc.createPayload("", common.TxType_QUERY_CONTRACT, syscontract.SystemContract_CHAIN_CONFIG.String(),
        syscontract.ChainConfigFunction_GET_CHAIN_CONFIG.String(), nil, defaultSeq)

    resp, err := cc.proposalRequest(payload, nil)
    if err != nil {
        return nil, fmt.Errorf("send %s failed, %s", payload.TxType.String(), err.Error())
    }
//省略
...
}

看一下服務(wù)端的invoke方法實(shí)現(xiàn)

### module/rpcserver/api_service.go
// invoke contract according to TxType
func (s *ApiService) invoke(tx *commonPb.Transaction, source protocol.TxSource) *commonPb.TxResponse {
...
    if tx.Payload.ChainId != SYSTEM_CHAIN {
           // 這里 如果不是系統(tǒng)鏈 我們就需要驗(yàn)證一下交易
        errCode, errMsg = s.validate(tx)
        if errCode != commonErr.ERR_CODE_OK {
            resp.Code = commonPb.TxStatusCode_INTERNAL_ERROR
            resp.Message = errMsg
            resp.TxId = tx.Payload.TxId
            return resp
        }
    }
...
}


// validate tx
func (s *ApiService) validate(tx *commonPb.Transaction) (errCode commonErr.ErrCode, errMsg string) {
    ...
    ///  獲取區(qū)塊配置
    bc, err = s.chainMakerServer.GetBlockchain(tx.Payload.ChainId)
    if err != nil {
        errCode = commonErr.ERR_CODE_GET_BLOCKCHAIN
        errMsg = s.getErrMsg(errCode, err)
        s.log.Error(errMsg)
        return
    }
      // 驗(yàn)證交易 GetAccessControl()訪問(wèn)控制權(quán)限等
    if err = utils.VerifyTxWithoutPayload(tx, tx.Payload.ChainId, bc.GetAccessControl()); err != nil {
        errCode = commonErr.ERR_CODE_TX_VERIFY_FAILED
        errMsg = fmt.Sprintf("%s, %s, txId:%s, sender:%s", errCode.String(), err.Error(), tx.Payload.TxId,
            hex.EncodeToString(tx.Sender.Signer.MemberInfo))
        s.log.Error(errMsg)
        return
    }

    return commonErr.ERR_CODE_OK, ""
}

下一步 就到交易驗(yàn)證的工具模塊utils

### module/utils/transaction.go
// VerifyTxWithoutPayload verify a transaction with access control provider. The payload of the transaction will not be verified.
func VerifyTxWithoutPayload(tx *commonPb.Transaction, chainId string, ac protocol.AccessControlProvider) error {
    if tx == nil {
        return errors.New("tx is nil")
    }
   // 驗(yàn)證交易頭部  
    if err := verifyTxHeader(tx.Payload, chainId); err != nil {
        return fmt.Errorf("verify tx header failed, %s", err)
    }
   // 驗(yàn)證發(fā)送者身份
    if err := verifyTxAuth(tx, ac); err != nil {
        return fmt.Errorf("verify tx authentation failed, %s", err)
    }
    return nil
}

// verify transaction sender's authentication (include signature verification, cert-chain verification, access verification)
func verifyTxAuth(t *commonPb.Transaction, ac protocol.AccessControlProvider) error {
    ...
    endorsements := []*commonPb.EndorsementEntry{t.Sender}
    txType := t.Payload.TxType
    principal, err := ac.CreatePrincipal(txType.String(), endorsements, txBytes)
    if err != nil {
        return fmt.Errorf("fail to construct authentication principal: %s", err)
    }
    // 這里套了一層
    ok, err := ac.VerifyPrincipal(principal)
    if err != nil {
        return fmt.Errorf("authentication error, %s", err)
    }
    if !ok {
        return fmt.Errorf("authentication failed")
    }
    ...
}

轉(zhuǎn)到權(quán)限控制模塊accesscontrol

###module/accesscontrol/cert_ac.go

// VerifyPrincipal verifies if the principal for the resource is met
func (cp *certACProvider) VerifyPrincipal(principal protocol.Principal) (bool, error) {
...
       // 包裝一層
    refinedPrincipal, err := cp.refinePrincipal(principal)
    if err != nil {
        return false, fmt.Errorf("authentication failed, [%s]", err.Error())
    }
    ...
}

// all-in-one validation for signing members: certificate chain/whitelist, signature, policies
func (cp *certACProvider) refinePrincipal(principal protocol.Principal) (protocol.Principal, error) {
    // 獲取背書
    endorsements := principal.GetEndorsement()
    // 獲取消息
    msg := principal.GetMessage()
    // 完善 背書信息
    refinedEndorsement := cp.refineEndorsements(endorsements, msg)
    if len(refinedEndorsement) <= 0 {
        return nil, fmt.Errorf("refine endorsements failed, all endorsers have failed verification")
    }

    refinedPrincipal, err := cp.CreatePrincipal(principal.GetResourceName(), refinedEndorsement, msg)
    if err != nil {
        return nil, fmt.Errorf("create principal failed: [%s]", err.Error())
    }

    return refinedPrincipal, nil
}

接著看一下refineEndorsements方法,這里分為2種情況,證書已經(jīng)存儲(chǔ)在緩存中误算,證書沒(méi)有存儲(chǔ)在緩存中

func (cp *certACProvider) refineEndorsements(endorsements []*common.EndorsementEntry,
    msg []byte) []*common.EndorsementEntry {

    refinedSigners := map[string]bool{}
// 組裝后返回新的背書數(shù)據(jù)
    var refinedEndorsement []*common.EndorsementEntry
    var memInfo string

    for _, endorsementEntry := range endorsements {
    // 開(kāi)始創(chuàng)建新數(shù)據(jù)
        endorsement := &common.EndorsementEntry{
            Signer: &pbac.Member{
                OrgId:      endorsementEntry.Signer.OrgId,
                MemberInfo: endorsementEntry.Signer.MemberInfo,
                MemberType: endorsementEntry.Signer.MemberType,
            },
            Signature: endorsementEntry.Signature,
        }
      // 鏈上證書存儲(chǔ) 分為MemberType_CERT 全證書 和 MemberType_CERT_HASH 證書哈希值兩種
        if endorsement.Signer.MemberType == pbac.MemberType_CERT {
            cp.log.Debugf("target endorser uses full certificate")
            memInfo = string(endorsement.Signer.MemberInfo)
        }
        if endorsement.Signer.MemberType == pbac.MemberType_CERT_HASH {
            cp.log.Debugf("target endorser uses compressed certificate")
            memInfoBytes, ok := cp.lookUpCertCache(endorsement.Signer.MemberInfo)
            if !ok {
                cp.log.Infof("authentication failed, unknown signer, the provided certificate ID is not registered")
                continue
            }
            memInfo = string(memInfoBytes)
            endorsement.Signer.MemberInfo = memInfoBytes
        }
         // 從緩存中查找證書
        signerInfo, ok := cp.acService.lookUpMemberInCache(memInfo)
        if !ok {
         // 沒(méi)有在緩存中束倍,表示SDK第一次使用
            remoteMember, certChain, ok, err := cp.verifyPrincipalSignerNotInCache(endorsement, msg, memInfo)
            ...
            signerInfo = &cachedMember{
                member:    remoteMember,
                certChain: certChain,
            }
            //加入緩存
            cp.acService.addMemberToCache(memInfo, signerInfo)
        } else {
// 如果證書已經(jīng)存在,則驗(yàn)證證書
            flat, err := cp.verifyPrincipalSignerInCache(signerInfo, endorsement, msg, memInfo)
             ...
        }
        ...
    }
    return refinedEndorsement
}
先說(shuō)簡(jiǎn)單一點(diǎn)的 已經(jīng)加入緩存了的證書 驗(yàn)證
func (cp *certACProvider) verifyPrincipalSignerInCache(signerInfo *cachedMember, endorsement *common.EndorsementEntry,
    msg []byte, memInfo string) (bool, error) {
    // check CRL and certificate frozen list
     // 先判斷當(dāng)前證書的內(nèi)容是否存在于第三方列表中trust_members中
    isTrustMember := false
    for _, v := range cp.acService.trustMembers {
        if v.MemberInfo == memInfo {
            isTrustMember = true
            break
        }
    }
   // 如果不存在則使用ChainMaker 自己的證書驗(yàn)證邏輯
    if !isTrustMember {
        ...
    }
  // 如果存在則用第三方的證書公鑰來(lái)驗(yàn)證 SDK上傳上來(lái)的 由改私鑰簽名的數(shù)據(jù)
    if err := signerInfo.member.Verify(cp.hashType, msg, endorsement.Signature); err != nil {
        ...
        return false, err
    }
    return true, nil
}

最終使用公鑰來(lái)驗(yàn)簽

### module/accesscontrol/cert_member.go
func (cm *certMember) Verify(hashType string, msg []byte, sig []byte) error {
    hashAlgo, err := bcx509.GetHashFromSignatureAlgorithm(cm.cert.SignatureAlgorithm)
    if err != nil {
        return fmt.Errorf("cert member verify failed: get hash from signature algorithm failed: [%s]", err.Error())
    }
  // 公鑰驗(yàn)簽 
    ok, err := cm.cert.PublicKey.VerifyWithOpts(msg, sig, &bccrypto.SignOpts{
        Hash: hashAlgo,
        UID:  bccrypto.CRYPTO_DEFAULT_UID,
    })
    if err != nil {
        return fmt.Errorf("cert member verify signature failed: [%s]", err.Error())
    }
    if !ok {
        return fmt.Errorf("cert member verify signature failed: invalid signature")
    }
    return nil
}

此時(shí)驗(yàn)證完畢

未加入緩存的證書 驗(yàn)證
### module/accesscontrol/cert_ac.go
func (cp *certACProvider) verifyPrincipalSignerNotInCache(endorsement *common.EndorsementEntry, msg []byte,
    memInfo string) (remoteMember protocol.Member, certChain []*bcx509.Certificate, ok bool, err error) {
  // 根據(jù)發(fā)送者創(chuàng)建新Member
    remoteMember, err = cp.acService.newMember(endorsement.Signer)
    if err != nil {
        err = fmt.Errorf("new member failed: [%s]", err.Error())
        ok = false
        return
    }
   // 驗(yàn)證certMember 是否符合x(chóng)509協(xié)議 是否具有相同的算法
    certChain, err = cp.verifyMember(remoteMember)
    if err != nil {
        err = fmt.Errorf("verify member failed: [%s]", err.Error())
        ok = false
        return
    }
  // 驗(yàn)證交易簽名 也就是sdk 發(fā)出的交易簽名 能否使用remoteMember的公鑰驗(yàn)簽
    if err = remoteMember.Verify(cp.hashType, msg, endorsement.Signature); err != nil {
        ...
        ok = false
        return
    }
    ok = true
    return
}
### module/accesscontrol/ac_server.go
// 創(chuàng)建新Member
func (acs *accessControlService) newMember(member *pbac.Member) (protocol.Member, error) {
// 從Cache中獲取
    memberCached, ok := acs.lookUpMemberInCache(m(member.MemberInfo))
    if ok && memberCached.member.GetOrgId() == member.OrgId {
        acs.log.Debugf("member found in local cache")
        return memberCached.member, nil
    }
// 沒(méi)有則新增
    memberFactory := MemberFactory()
    return memberFactory.NewMember(member, acs)
}

### module/accesscontrol/member_factory.go
func (mf *memberFactory) NewMember(pbMember *pbac.Member, acs *accessControlService) (protocol.Member, error) {
    switch pbMember.MemberType {
    case pbac.MemberType_CERT, pbac.MemberType_CERT_HASH:
         // 創(chuàng)建Member
        return newCertMemberFromPb(pbMember, acs)
    }
    return nil, fmt.Errorf("new member failed: the member type is not supported")
}

### module/accesscontrol/cert_member.go
func newCertMemberFromPb(member *pbac.Member, acs *accessControlService) (*certMember, error) {
      // 先判斷是否在外部兼容trust_members
    for _, v := range acs.trustMembers {
        certBlock, _ := pem.Decode([]byte(v.MemberInfo))
        if certBlock == nil {
            return nil, fmt.Errorf("new member failed, the trsut member cert is not PEM")
        }
     // 注意采用的是證書內(nèi)容來(lái)比對(duì)是否相等
        if v.MemberInfo == string(member.MemberInfo) {
            var isCompressed bool
            if member.MemberType == pbac.MemberType_CERT {
                isCompressed = false
            }
      //生成 certMember
            return newCertMember(v.OrgId, v.Role, acs.hashType, isCompressed, []byte(v.MemberInfo))
        }
    }
 // 如果不在 則按照ChainMaker 規(guī)則生成 certMember
    if member.MemberType == pbac.MemberType_CERT {
        certBlock, rest := pem.Decode(member.MemberInfo)
        if certBlock == nil {
            return newMemberFromCertPem(member.OrgId, acs.hashType, rest, false)
        }
        return newMemberFromCertPem(member.OrgId, acs.hashType, certBlock.Bytes, false)
    }

    if member.MemberType == pbac.MemberType_CERT_HASH {
        return newMemberFromCertPem(member.OrgId, acs.hashType, member.MemberInfo, true)
    }

    return nil, fmt.Errorf("setup member failed, unsupport cert member type")
}

至此整個(gè)證書驗(yàn)簽 流程都簡(jiǎn)要看了一遍

1.第三方ca兼容是怎么實(shí)現(xiàn)的
答:通過(guò)代碼梳理基本了解chainmaker 證書驗(yàn)證流程乱顾,如果是啟用了trust_members板祝,則優(yōu)先使用第三方證書

2.第三方ca是如何驗(yàn)證的
答:sdk創(chuàng)建客戶端時(shí) 用私鑰簽名,服務(wù)端用第三方證書公鑰 驗(yàn)簽

3.第三方ca有沒(méi)有使用限制
答:沒(méi)有任何限制走净,只要trust_members 上傳的證書和SDK使用的私鑰配對(duì)即可

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末券时,一起剝皮案震驚了整個(gè)濱河市孤里,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌橘洞,老刑警劉巖捌袜,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異炸枣,居然都是意外死亡虏等,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門适肠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)霍衫,“玉大人,你說(shuō)我怎么就攤上這事侯养《氐” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵逛揩,是天一觀的道長(zhǎng)柠傍。 經(jīng)常有香客問(wèn)我,道長(zhǎng)辩稽,這世上最難降的妖魔是什么惧笛? 我笑而不...
    開(kāi)封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮搂誉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘静檬。我一直安慰自己炭懊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布拂檩。 她就那樣靜靜地躺著侮腹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稻励。 梳的紋絲不亂的頭發(fā)上父阻,一...
    開(kāi)封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音望抽,去河邊找鬼加矛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛煤篙,可吹牛的內(nèi)容都是我干的斟览。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼辑奈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼苛茂!你這毒婦竟也來(lái)了已烤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妓羊,失蹤者是張志新(化名)和其女友劉穎胯究,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體躁绸,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裕循,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涨颜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片费韭。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖庭瑰,靈堂內(nèi)的尸體忽然破棺而出星持,到底是詐尸還是另有隱情,我是刑警寧澤弹灭,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布督暂,位于F島的核電站,受9級(jí)特大地震影響穷吮,放射性物質(zhì)發(fā)生泄漏逻翁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一捡鱼、第九天 我趴在偏房一處隱蔽的房頂上張望八回。 院中可真熱鬧,春花似錦驾诈、人聲如沸缠诅。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)管引。三九已至,卻和暖如春闯两,著一層夾襖步出監(jiān)牢的瞬間褥伴,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工漾狼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留重慢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓逊躁,卻偏偏與公主長(zhǎng)得像伤锚,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容