本文以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服务器,可以手动使用命令启动,也可以使用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服务器容器,端口分别为7054
和8054
,对应org1和org2。本文的工作就是使用org1的CA来增加一个User2。
让我们来看一下生成身份后的对应部分的目录结构,主要看User1的:
可以看到我们生成了一个组织管理员Admin和一个用户User1,现在让我们在不重新启动网络的情况下生成一个用户User2。
为了方便,我们就在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 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.1
到 ca.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
目录下。
发行身份前必须先注册,运行下面的命令注册:
./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
使用刚才注册的用户名和密码来发行身份:
./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
中对应的目录结构中。
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中复制的。因为我们两种方式cryptogen
和Fabric CA
使用了同一个私钥,所以证书的信任根肯定是相同的,不然没有办法互通。有兴趣的读者可以检查一下,最后复制的源文件和目标文件内容是否完全一致,如果不一致就说明哪里出错了。
到这里我们的新用户User2
就发行成功了。
这里有一个笔者遇到的坑,就是注册User2时,不要指定--id.type
,让它默认为client
就好。
测试这里稍微复杂一点,涉及到fabric-sdk-go的应用。但这里有一篇文章可以参考一下:使用fabric-sdk-go操作链码 。可以将源码中(不是配置文件中)使用Admin
的地方替换为User1
和User2
进行测试。这里笔者写了一个简单的测试文件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新生成的用户是可以在不改变网络的情况下直接使用的。
欢迎大家指出文章中存在的问题或留言讨论。