超级账本中 cryptogen和Fabric CA证书互通问题

本文以Fabric 1.4.6为例,简单介绍一下cryptogen和Fabric CA证书互通问题。在Fabric 中 启用一个测试网络最快捷的办法就是运行first-network中的./byfn.sh up(2.0.0版本以上使用test-network替代了first-network,这里使用方法类似,不讲)。然而使用脚本时默认使用cryptogen工具生成身份,但使用该工具生成的账号(证书)数量是固定的。虽然可以更改crypto-config.yaml中的设置,将额外的用户数量改成大于1的数字。但这个更改是发生在网络启动前,一旦网络启动后就没法更改了。因此我们还是需要一种可以动态添加用户的机制,这里就轮到CA 出场了。

准备工作,根据官方文档Install Samples, Binaries, and Docker Images安装好官方原版 Fabric 1.4.6 、Fabric Samples 1.4.6 和Fabric CA 1.4.2。

小提示:也可以自己编写脚本,直接使用CA而不是cryptogen生成身份后再启动网络(参见2.0.0以上版本的fabric-samples),这样就不会涉及到互通问题。但是本文主要是为了讲互通的问题,所以我们还是使用Fabric 1.4.6版本。

一、启动测试网络和CA

使用CA发行身份需要启动CA服务器,可以手动使用命令启动,也可以使用byfn.sh脚本来启动一个docker container,我们采用脚本的方式,这样工作最小化。

切换到fabric-samples下的first-network目录,运行下面的脚本:

./byfn.sh down
./byfn.sh up -a

这里的-a选项是启动网络时同时启动一个CA,具体看byfn.sh脚本最开始的介绍。

网络部署完成后会有提示,这时我们查看一下运行的容器:

➜  fabric-samples git:(337f82c) ✗ docker ps -a
CONTAINER ID        IMAGE                                                                                                  COMMAND                  CREATED             STATUS                      PORTS                              NAMES
63334fdbdd15        dev-peer0.org2.example.com-mycc-1.0-15b571b3ce849066b7ec74497da3b27e54e0df1345daff3951b94245ce09c42b   "chaincode -peer.add…"   43 minutes ago      Up 43 minutes                                                  dev-peer0.org2.example.com-mycc-1.0
7e2df103a97c        dev-peer0.org1.example.com-mycc-1.0-384f11f484b9302df90b453200cfb25174305fce8f53f4e94d45ee3b6cab0ce9   "chaincode -peer.add…"   46 minutes ago      Up 46 minutes                                                  dev-peer0.org1.example.com-mycc-1.0
27c9e9e18e16        dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab   "chaincode -peer.add…"   15 hours ago        Exited (0) 55 minutes ago                                      dev-peer1.org2.example.com-mycc-1.0
3f4f0dbc9c9e        hyperledger/fabric-tools:latest                                                                        "/bin/bash"              15 hours ago        Up 55 minutes                                                  cli
fb080f3a877c        hyperledger/fabric-ca:latest                                                                           "sh -c 'fabric-ca-se…"   15 hours ago        Up 55 minutes               0.0.0.0:7054->7054/tcp             ca_peerOrg1
98be2c8c32b7        hyperledger/fabric-peer:latest                                                                         "peer node start"        15 hours ago        Up 55 minutes               0.0.0.0:7051->7051/tcp             peer0.org1.example.com
3f36dbf02ed1        hyperledger/fabric-peer:latest                                                                         "peer node start"        15 hours ago        Up 55 minutes               0.0.0.0:8051->8051/tcp             peer1.org1.example.com
789e6006049a        hyperledger/fabric-orderer:latest                                                                      "orderer"                15 hours ago        Up 55 minutes               0.0.0.0:7050->7050/tcp             orderer.example.com
1a4e27bc675f        hyperledger/fabric-peer:latest                                                                         "peer node start"        15 hours ago        Up 55 minutes               0.0.0.0:10051->10051/tcp           peer1.org2.example.com
7f4adb26061e        hyperledger/fabric-ca:latest                                                                           "sh -c 'fabric-ca-se…"   15 hours ago        Up 55 minutes               7054/tcp, 0.0.0.0:8054->8054/tcp   ca_peerOrg2
87c6b79f7292        hyperledger/fabric-peer:latest                                                                         "peer node start"        15 hours ago        Up 55 minutes               0.0.0.0:9051->9051/tcp             peer0.org2.example.com
➜  fabric-samples git:(337f82c)

NAMES中我们可以看到,我们启动了两个组织的CA服务器容器,端口分别为70548054,对应org1和org2。本文的工作就是使用org1的CA来增加一个User2。

让我们来看一下生成身份后的对应部分的目录结构,主要看User1的:
超级账本中 cryptogen和Fabric CA证书互通问题_第1张图片
可以看到我们生成了一个组织管理员Admin和一个用户User1,现在让我们在不重新启动网络的情况下生成一个用户User2。

二、建立Fabric CA客户端

为了方便,我们就在fabric-samples目录进行操作。运行下面的命令切换到fabric-samples目录并且建立一个工作目录:

cd ..
mkdir fabric_ca_test
cd fabric_ca_test
cp ../bin/fabric-ca-client ./

可以看到我们将fabric-ca-client这个Fabric CA的客户端工具复制到了本目录。因为脚本启动CA时默认开启了TLS,所以我们需要CA Server的TLS 证书。在 first-network目录下的docker-compose-ca.yaml中可以看到该证书路径:
- FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.example.com-cert.pem。注意,这是一个虚拟目录,真实目录是在volumes中映射的:

volumes:
  - ./crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/fabric-ca-server-config

这里根据证书的格式还可以看出该CA的域名为: ca.org1.example.com。

这里还需要注意的一个环境变量设置为- FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-config/${BYFN_CA1_PRIVATE_KEY} 。它将Fabric CA Server的私钥设置成了cryptogen工具生成的私钥。私钥相同,生成的证书才是一致的,这是不同工具生成的证书互通的前提。

运行下面的命令来复制这个TLS证书:

mkdir tls-root-cert
cp ../first-network/crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem tls-root-cert/tls-ca-cert.pem

可以看到为方便使用,我们同时将该文件改名了。

现在我们已经有客户端和TLS证书了,我们可以向CA Server请求来发行身份了。

三、发行CA引导用户

发行身份的第一步是发行引导用户(相当于CA的管理员,不是组织管理员),引导用户的账号密码在启动CA Server时就确定了,参见docker-compose-ca.yaml中的启动命令:
command: sh -c 'fabric-ca-server start --ca.certfile /etc/hyperledger/fabric-ca-server-config/ca.org1.example.com-cert.pem --ca.keyfile /etc/hyperledger/fabric-ca-server-config/${BYFN_CA1_PRIVATE_KEY} -b admin:adminpw -d'

具体CA的使用可以阅读官方文档 ,这里不解释了。从上面的命令可以看到,引导用户的用户名为admin,密码为adminpw

前面提到了,CA的域名为: ca.org1.example.com,因为我们这里无法没有服务器端配置文件 ,所以无法使用localhost,还需要在/etc/hosts中添加一个127.0.0.1ca.org1.example.com的解析。

运行下面的命令来发行引导用户:

export FABRIC_CA_CLIENT_HOME=$PWD
./fabric-ca-client enroll -u https://admin:[email protected]:7054 --tls.certfiles tls-root-cert/tls-ca-cert.pem --csr.hosts localhost --mspdir admin/msp

你会得到类似的输出结果:

2020/08/22 12:05:43 [INFO] Created a default configuration file at /Users/likai/fabric/fabric-samples/fabric_ca_test/fabric-ca-client-config.yaml
2020/08/22 12:05:43 [INFO] TLS Enabled
2020/08/22 12:05:43 [INFO] generating key: &{A:ecdsa S:256}
2020/08/22 12:05:43 [INFO] encoded CSR
2020/08/22 12:05:43 [INFO] Stored client certificate at /Users/likai/fabric/fabric-samples/fabric_ca_test/admin/msp/signcerts/cert.pem
2020/08/22 12:05:43 [INFO] Stored root CA certificate at /Users/likai/fabric/fabric-samples/fabric_ca_test/admin/msp/cacerts/ca-org1-example-com-7054.pem
2020/08/22 12:05:43 [INFO] Stored Issuer public key at /Users/likai/fabric/fabric-samples/fabric_ca_test/admin/msp/IssuerPublicKey
2020/08/22 12:05:43 [INFO] Stored Issuer revocation public key at /Users/likai/fabric/fabric-samples/fabric_ca_test/admin/msp/IssuerRevocationPublicKey

表明引导用户发行成功了,相关信息位于admin/msp目录下。

四、使用引导用户注册和发行User2

1、注册

发行身份前必须先注册,运行下面的命令注册:

./fabric-ca-client register --id.name User2 --id.secret User2pw --tls.certfiles tls-root-cert/tls-ca-cert.pem --mspdir admin/msp/

会有类似如下输出:

2020/08/22 12:09:39 [INFO] Configuration file location: /Users/likai/fabric/fabric-samples/fabric_ca_test/fabric-ca-client-config.yaml
2020/08/22 12:09:39 [INFO] TLS Enabled
2020/08/22 12:09:39 [INFO] TLS Enabled
Password: User2pw

2、发行

使用刚才注册的用户名和密码来发行身份:

./fabric-ca-client enroll -u https://User2:[email protected]:7054 --tls.certfiles tls-root-cert/tls-ca-cert.pem --csr.hosts localhost --mspdir users/User2/msp

会有类似如下输出 :

2020/08/22 12:13:13 [INFO] TLS Enabled
2020/08/22 12:13:13 [INFO] generating key: &{A:ecdsa S:256}
2020/08/22 12:13:13 [INFO] encoded CSR
2020/08/22 12:13:13 [INFO] Stored client certificate at /Users/likai/fabric/fabric-samples/fabric_ca_test/users/User2/msp/signcerts/cert.pem
2020/08/22 12:13:13 [INFO] Stored root CA certificate at /Users/likai/fabric/fabric-samples/fabric_ca_test/users/User2/msp/cacerts/ca-org1-example-com-7054.pem
2020/08/22 12:13:13 [INFO] Stored Issuer public key at /Users/likai/fabric/fabric-samples/fabric_ca_test/users/User2/msp/IssuerPublicKey
2020/08/22 12:13:13 [INFO] Stored Issuer revocation public key at /Users/likai/fabric/fabric-samples/fabric_ca_test/users/User2/msp/IssuerRevocationPublicKey

我们可以使用tree命令来查看刚才生成的目录结构:

 tree users/User2/

生成的结构如下:

users/User2/
└── msp
    ├── IssuerPublicKey
    ├── IssuerRevocationPublicKey
    ├── cacerts
    │   └── ca-org1-example-com-7054.pem
    ├── keystore
    │   └── 6f097b265f17e3bcab411a9ea3e328c62e851f9948ad4af7298d4e57535cb471_sk
    ├── signcerts
    │   └── cert.pem
    └── user

5 directories, 5 files

我们的User2已经生成了,此时可以对比一下vscode中User1的目录结构,发现还是有些区别的。我们下一步就是将新发行的User2的证书和密钥复制到first-network中对应的目录结构中。

五、添加User2到first-network

运行下面命令,将User1的目录结构复制到User2中去:

cd ../first-network/crypto-config/peerOrganizations/org1.example.com/users/
mkdir [email protected]
cd [email protected]
cp -r ../[email protected]/tls ./
cp -r ../[email protected]/msp ./
rm -f msp/keystore/*

这里选择y,删除User1的密钥,然后接着运行:

rm -f msp/signcerts/*

同样选择y,删除User1的证书。

我们将User2的密钥和证书复制过来:

cp ../../../../../../fabric_ca_test/users/User2/msp/keystore/* msp/keystore/
cp ../../../../../../fabric_ca_test/users/User2/msp/signcerts/cert.pem  msp/signcerts/[email protected]

最后我们复制User2的CA根证书(可以省略)

cp ../../../../../../fabric_ca_test/users/User2/msp/cacerts/ca-org1-example-com-7054.pem msp/cacerts/ca.org1.example.com-cert.pem

这里为什么可以省略呢,因为目标文件已经存在了,它是从User1中复制的。因为我们两种方式cryptogenFabric CA使用了同一个私钥,所以证书的信任根肯定是相同的,不然没有办法互通。有兴趣的读者可以检查一下,最后复制的源文件和目标文件内容是否完全一致,如果不一致就说明哪里出错了。

到这里我们的新用户User2就发行成功了。

这里有一个笔者遇到的坑,就是注册User2时,不要指定--id.type,让它默认为client就好。

六、测试

测试这里稍微复杂一点,涉及到fabric-sdk-go的应用。但这里有一篇文章可以参考一下:使用fabric-sdk-go操作链码 。可以将源码中(不是配置文件中)使用Admin的地方替换为User1User2进行测试。这里笔者写了一个简单的测试文件main.go,但是go mod init 及配置文件需要读者自己去弄了。

package main

import (
	"fmt"
	"log"

	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
)

const (
	org1CfgPath = "./config.yaml"
	ChannelID   = "mychannel"

	peer0Org1 = "peer0.org1.example.com"
	peer0Org2 = "peer0.org2.example.com"
)

func main() {
	sdk, err := fabsdk.New(config.FromFile(org1CfgPath))
	if err != nil {
		log.Panicf("failed to create fabric sdk: %s", err)
	}
	ccp := sdk.ChannelContext(ChannelID, fabsdk.WithUser("User2"))
	cc, err := channel.New(ccp)
	if err != nil {
		log.Panicf("failed to create channel client: %s", err)
	}
	fmt.Println("channel init over")
	query(cc)
	// execute(cc)
	// time.Sleep(5 * time.Second)
	// query(cc)
}

func query(cc *channel.Client) {
	// new channel request for query
	req := channel.Request{
		ChaincodeID: "mycc",
		Fcn:         "query",
		Args:        packArgs([]string{"a"}),
	}
	// send request and handle response
	reqPeers := channel.WithTargetEndpoints(peer0Org1)

	response, err := cc.Query(req, reqPeers)
	if err != nil {
		fmt.Printf("failed to query chaincode: %s\n", err)
	}

	if len(response.Payload) > 0 {
		fmt.Printf("chaincode query success,the value is %s\n", string(response.Payload))
	}
}

func execute(cc *channel.Client) {
	args := packArgs([]string{"a", "b", "10"})
	req := channel.Request{
		ChaincodeID: "mycc",
		Fcn:         "invoke",
		Args:        args,
	}
	peers := []string{peer0Org1, peer0Org2}
	reqPeers := channel.WithTargetEndpoints(peers...)
	response, err := cc.Execute(req, reqPeers)
	if err != nil {
		fmt.Printf("failed to Execute chaincode: %s\n", err)
	}
	fmt.Printf("Execute chaincode success,txId:%s\n", response.TransactionID)
}

func packArgs(paras []string) [][]byte {
	var args [][]byte
	for _, k := range paras {
		args = append(args, []byte(k))
	}
	return args
}

我们可以将ccp := sdk.ChannelContext(ChannelID, fabsdk.WithUser("User2"))中间的"User2"换成"User1"或者"Admin",它都是可以运行的。
运行:

go run main.go

将会得到类似下面的结果

channel init over
chaincode query success,the value is 90

注释掉的代码是用来执行链码的,有兴趣的读者可以取消注释了测试一下。

好了,测试完成,我们使用CA新生成的用户是可以在不改变网络的情况下直接使用的。

欢迎大家指出文章中存在的问题或留言讨论。

你可能感兴趣的:(Fabric,超级账本,证书,区块链)