zoukankan      html  css  js  c++  java
  • 【加解密】使用CFSSL生成证书并使用gRPC验证证书

    写在前面的话

    CFSSL是CloudFlare旗下的PKI/TLS工具。可以用于数字签名,签名验证和TLS证书捆绑的命令行工具和HTTP API服务器。

    是使用golang语言开发的证书工具。

    官方地址:

    github地址:https://github.com/cloudflare/cfssl

    下载cfssl工具链

    https://github.com/cloudflare/cfssl/releases 

    下载如下文件

    cfssl_1.6.0_darwin_amd64 表示cfssl的工具

    cfssljson_1.6.0_darwin_amd64 表示使用json展示的工具

    cfssl-certinfo_1.6.0_darwin_amd64 表示证书的查看工具

    软连接生成cfssl和cfssljson

    ln -s ./cfssl_1.6.0_darwin_amd64 cfssl
    ln -s ./cfssl-certinfo_1.6.0_darwin_amd64 cfssl-certinfo
    ln -s ./cfssljson_1.6.0_darwin_amd64 cfssljson

      

    使用cfssl生成证书步骤

    1. 编写CA根证书的证书签名请求文件

        证书签名请求(Certificate Signing Request)文件,文件格式为ca-csr.json,【文件名含义是CA of Certificate Signing Request】

        ca-csr.json文件中包含如下内容简要说明

    • CN: Common Name,表示业务的名称或者对外的域名。

    • C: Country, 表示国家

    • L: Locality,表示地区或城市

    • O: Organization Name,表示组织名称或公司名称

    • OU: Organizational Unit 表示组织单元名称
    • ST: State,表示 州,省OU: Organization Unit Name,组织单位名称或者部门

    • ca.expiry 表示证书的有效期,此处是20年
    • key.algo 表示证书的签名算法,目前cfssl支持的签名算法只有rsa和ecdsa两种。
    • hosts 表示要签名的域名,此处是根证书,所以空着,用于签名其他的证书。

        ca-csr.json文件内容如下:

    ➜  certs cat ca-csr.json|python -m json.tool
    {
        "CN": "voipman",
        "ca": {
            "expiry": "175200h"
        },
        "hosts": [],
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [
            {
                "C": "CN",
                "L": "BeiJing",
                "O": "MyCompany",
                "OU": "MyTemp",
                "ST": "BeiJing"
            }
        ]
    }

    如果key的签名算法选择rsa时,size可以取值2048,3072和4096这三个值

    如果可用的签名算法选择ecdsa时,将如上内容的key改成如下内容,size可以取值256,384和521三个值

        "key":{
            "algo":"ecdsa",
            "size":256
        }

    2. 使用CA跟证书签名请求文件生成CA根证书。

    ➜  cfssl gencert -initca  ca-csr.json|cfssljson -bare ca
    2021/08/18 10:37:00 [INFO] generating a new CA key and certificate from CSR
    2021/08/18 10:37:00 [INFO] generate received request
    2021/08/18 10:37:00 [INFO] received CSR
    2021/08/18 10:37:00 [INFO] generating key: rsa-2048
    2021/08/18 10:37:00 [INFO] encoded CSR
    2021/08/18 10:37:00 [INFO] signed certificate with serial number 116851465485290360665710914818380982850969052112

    通过执行上面的命令,产生如下文件:

      ca.pem 表示CA根证书,可以公开

      ca-key.pem 表示CA根证书的密钥,不要公开

      ca.csr 表示CA证书签名请求

    我们主要分析一下ca.pem证书文件中的内容信息

    3. 查看 ca.pem CA根证书

    里面包含什么信息呢,可以通过cfssl-certinfo工具查看证书内容

    cfssl-certinfo -cert ca.pem
    {
      "subject": {
        "common_name": "voipman",
        "country": "CN",
        "organization": "MyCompany",
        "organizational_unit": "MyTemp",
        "locality": "BeiJing",
        "province": "BeiJing",
        "names": [
          "CN",
          "BeiJing",
          "BeiJing",
          "MyCompany",
          "MyTemp",
          "voipman"
        ]
      },
      "issuer": {
        "common_name": "voipman",
        "country": "CN",
        "organization": "MyCompany",
        "organizational_unit": "MyTemp",
        "locality": "BeiJing",
        "province": "BeiJing",
        "names": [
          "CN",
          "BeiJing",
          "BeiJing",
          "MyCompany",
          "MyTemp",
          "voipman"
        ]
      },
      "serial_number": "116851465485290360665710914818380982850969052112",
      "not_before": "2021-08-18T02:32:00Z",
      "not_after": "2041-08-13T02:32:00Z",
      "sigalg": "SHA256WithRSA",
      "authority_key_id": "",
      "subject_key_id": "34:9C:3B:8B:54:02:6F:2F:D3:F4:29:9B:23:23:6C:47:0D:0A:16:2B",
      "pem": "-----BEGIN CERTIFICATE-----
    .....
    -----END CERTIFICATE-----
    "
    }

    从如上的ca.pem中可以看到

    签名算法:"sigalg": "SHA256WithRSA"

    如果选择ecdsa签名算法,签名算法就是: "sigalg": "ECDSAWithSHA256"

    证书生效时间:"not_before": "2021-08-18T02:32:00Z"

    证书失效时间:"not_after": "2041-08-13T02:32:00Z",说明证书签名请求文件中设置的20年的证书有效期。

    序列化:"serial_number": "116851465485290360665710914818380982850969052112",在前面生成证书时会打印出来。

    证书内容:"pem": "-----BEGIN CERTIFICATE-----...,此处省调证书内容信息。

    其他字段在证书签名请求时已经做过介绍,此处忽略。

    4. 编写CA签名配置文件ca-config.json

    cat ca-config.json |python -m json.tool
    {
        "signing": {
            "default": {
                "expiry": "175200h"
            },
            "profiles": {
                "client": {
                    "expiry": "175200h",
                    "usages": [
                        "signing",
                        "key encipherment",
                        "client auth"
                    ]
                },
                "peer": {
                    "expiry": "175200h",
                    "usages": [
                        "signing",
                        "key encipherment",
                        "client auth",
                        "server auth"
                    ]
                },
                "server": {
                    "expiry": "175200h",
                    "usages": [
                        "signing",
                        "key encipherment",
                        "server auth"
                    ]
                }
            }
        }
    }

    字段说明如下

    • signing, 表示ca.pem证书可用于签名其它证书

    • profile中的peer配置的client auth 和 server auth
    • profile中的client配置的client auth
    • profile中的server配置的server auth
    • server auth:表示 客户端client 可以用 CA证书 对 服务端server的证书进行签名验证。
    • client auth:表示 服务端server 可以用 CA证书 对 客户端client 提供的证书进行签名验证。
    • server auth和client auth都存在时,说明客户端和服务端双向验证。

    如下业务域名证书生成选择的profle是peer,表示双向验证。

    同样证书的失效日期是20年。

    5. 编写业务域名的证书签名请求文件 voipman-csr.json

    cat voipman-csr.json |python -m json.tool
    {
        "CN": "voipman",
        "hosts": [
            "127.0.0.1",
            "*.voipman.com",
            "localhost",
            "voipman.com",
            "*.vipman.com",
            "vipman.com"
        ],
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [
            {
                "C": "CN",
                "L": "BeiJing",
                "O": "voipman",
                "OU": "MyTeam",
                "ST": "BeiJing"
            }
        ]
    }

    这个业务域名签名请求文件的内容和ca-csr.json内容含义类似,关键部分是增加了hosts的配置,将需要签名认证的ip地址和域名增加到hosts列表中。

    6. 生成业务域名的证书和私钥

    cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer voipman-csr.json|cfssljson -bare voipman-peer
    2021/08/18 11:15:09 [INFO] generate received request
    2021/08/18 11:15:09 [INFO] received CSR
    2021/08/18 11:15:09 [INFO] generating key: rsa-2048
    2021/08/18 11:15:09 [INFO] encoded CSR
    2021/08/18 11:15:09 [INFO] signed certificate with serial number 178028283460672116734375677106692089761404461988

    此命令的参数说明

    生成证书命令:cfssh gencerts

    使用ca证书:-ca=ca.pem

    使用ca的密钥:-ca-key=ca-key.pem

    使用ca签名证书的配置:-config=ca-config.json

    选择ca签名证书配置的profile项:-profile=peer

    选择业务域名的证书签名请求文件:voipman-csr.json

    生成业务域名的私钥和证书文件:cfssljson -bare voipman-peer 会生成voipman-peer.pem证书文件和voipman-peer-key.pem的私钥文件。

    执行如上命令后,产生如下文件

    voipman-peer.pem 业务域名的证书文件,可以直接公开给请求端使用。
    voipman-peer-key.pem 业务域名的私钥,不可公开。
    voipman-peer.csr 业务域名的证书签名请求文件。

    查看 业务域名的证书文件voipman-peer.pem的内容

    cfssl-certinfo -cert voipman-peer.pem
    {
      "subject": {
        "common_name": "voipman",
        "country": "CN",
        "organization": "voipman",
        "organizational_unit": "MyTeam",
        "locality": "BeiJing",
        "province": "BeiJing",
        "names": [
          "CN",
          "BeiJing",
          "BeiJing",
          "voipman",
          "MyTeam",
          "voipman"
        ]
      },
      "issuer": {
        "common_name": "voipman",
        "country": "CN",
        "organization": "MyCompany",
        "organizational_unit": "MyTemp",
        "locality": "BeiJing",
        "province": "BeiJing",
        "names": [
          "CN",
          "BeiJing",
          "BeiJing",
          "MyCompany",
          "MyTemp",
          "voipman"
        ]
      },
      "serial_number": "178028283460672116734375677106692089761404461988",
      "sans": [
        "*.voipman.com",
        "localhost",
        "voipman.com",
        "*.vipman.com",
        "vipman.com",
        "127.0.0.1"
      ],
      "not_before": "2021-08-18T03:10:00Z",
      "not_after": "2041-08-13T03:10:00Z",
      "sigalg": "SHA256WithRSA",
      "authority_key_id": "34:9C:3B:8B:54:02:6F:2F:D3:F4:29:9B:23:23:6C:47:0D:0A:16:2B",
      "subject_key_id": "AD:54:74:A0:BF:67:E7:B7:18:50:20:0A:77:57:F7:16:D3:62:80:F6",
      "pem": "-----BEGIN CERTIFICATE-----
    ......
    -----END CERTIFICATE-----
    "
    }

    如上可以看到,证书文件的内容中

    sans表示证书的支持的域名列表

    authority_key_id 表示是本证书是从哪个CA证书签名生成的证书,业务域名正式的authority_key_id等于CA证书的subject_key_id

    其他字段说明见签名对CA证书的字段说明。

    使用如下命令查询业务域名的证书签名请求信息

    如下内容省略了部分内容,重点说明一下内容中的公钥PublicKey(N模数,E公钥指数)

    cfssl certinfo -csr voipman-peer.csr
    {
      "Raw": "xxx",
      "RawTBSCertificateRequest": "xxx,
      "RawSubject": "xxx",
      "Version": 0,
      "Signature": "xxx",
      "SignatureAlgorithm": 4,
      "PublicKeyAlgorithm": 1,
      "PublicKey": {
        "N": 252598034519828318357090360116071250272xxxxxxxxxxxxxx....xxxxx7,
        "E": 65537
      }
    
      "Subject": {
        
      },
      "DNSNames": [
        "*.voipman.com",
        "localhost",
        "voipman.com",
        "*.vipman.com",
        "vipman.com"
      ],
      "EmailAddresses": null,
      "IPAddresses": [
        "127.0.0.1"
      ],
      "URIs": null
    }

    如果选择ecdsa签名算法时,公钥PublicKey信息为如下形式

      "PublicKey": {
        "Curve": {
          "P": 11579xxxxx1,
          "N": 11579xxxxxx8,
          "B": 410583xxxx1,
          "Gx": 484395xxx6,
          "Gy": 36134xxxxx09,
          "BitSize": 256,
          "Name": "P-256"
        },
        "X": 1069xx854,
        "Y": 1105xx971
      }

    P: 代表有限域Fp的那个质数
    G: 椭圆曲线上的一个基点G = (Gx, Gy)
    N:G在Fp中规定的序号

    7. 使用golang的grpc验证业务域名的证书和私钥

    编写proto接口文件,文件命名为test.proto,定义一个EchoService接口,服务端实现时,将请求数据转成大写返回。

    syntax = "proto3";
    package test;
    
    service EchoService {
      rpc Echo (Request) returns (Response) {}
    }
    
    message Request {
      string data = 1;
    }
    
    message Response {
      string data = 1;
    }

    生成go的grpc代码

    mkdir -p ../src/test  && protoc --go_out=plugins=grpc:../src/test/ ./test.proto

    会生成 src/test/test.pb.go代码文件。

    编写gRPC的服务端验证代码,配置证书voipman-peer.pem和私钥voipman-peer-key.pem

    代码如下所示

    package main
    import (
        "cert-verify/src/test"
        "context"
        "fmt"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials"
        "google.golang.org/grpc/reflection"
        "log"
        "net"
        "strings"
    )
    type EchoServer struct{
    }
    
    func (s *EchoServer) Echo(ctx context.Context, in *test.Request) (*test.Response, error) {
        fmt.Println("RequestData: " + in.Data)
        return &test.Response{Data: strings.ToUpper(in.Data)}, nil
    }
    
    func main() {
        listen, err := net.Listen("tcp", "0.0.0.0:9025")
        if err != nil {
            log.Fatalf("Failed to listen: %v", err)
        }
        creds, cerErr := credentials.NewServerTLSFromFile("./certs/voipman-peer.pem", "./certs/voipman-peer-key.pem")
        if cerErr != nil {
            log.Fatalf("Failed to load cert error: %v", cerErr)
        }
        var grpcServer *grpc.Server
        grpcServer = grpc.NewServer(grpc.Creds(creds))
        test.RegisterEchoServiceServer(grpcServer, &EchoServer{})
        reflection.Register(grpcServer)
        grpcServer.Serve(listen)
    }

    编写gRPC的客户端代码,使用voipman-peer.pem证书请求如上的gRPC服务端

    package main
    import (
        "cert-verify/src/test"
        "golang.org/x/net/context"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials"
        "log"
    )
    
    func main() {
        hostNameList := []string {
            "dev.voipman.com",
            "test.voipman.com",
            "voipman.com",
            "127.0.0.1",
            "localhost",
            "dev.vipman.com",
            "test.vipman.com",
            "vipman.com",
            "dev.unknown.com",
        }
        for _, hostName := range hostNameList {
            url := "127.0.0.1:9025"
            creds, err := credentials.NewClientTLSFromFile("./certs/voipman-peer.pem", hostName)
            if err != nil {
                log.Printf("new rpc client tls fail %v", err)
            }
            clientConn, err := grpc.DialContext(context.Background(), url, grpc.WithTransportCredentials(creds))
            if err != nil {
                log.Printf("dail rpc server fail url:%v, err:%v", url, err)
            }
            if err != nil {
                log.Printf(err.Error())
            }
            defer clientConn.Close()
            cli := test.NewEchoServiceClient(clientConn)
            response, err := cli.Echo(context.Background(), &test.Request{Data: hostName})
            if err != nil {
                log.Fatalf("could not greet: %v", err)
            }
            log.Printf("HostName:%v, Response: %s", hostName, response.Data)
        }
    }

    如上代码中,我们验证voipman-csr.json中定义的hosts列表,如下测试域名应该可以通过证书的验证。

    "dev.voipman.com",
    "test.voipman.com",
    "voipman.com",
    "127.0.0.1",
    "localhost",
    "dev.vipman.com",
    "test.vipman.com",
    "vipman.com",

    另外增加一条错误的域名地址,使用证书验证应该不成功。

    运行结果如下
    2021/08/18 13:38:12 HostName:dev.voipman.com, Response: DEV.VOIPMAN.COM
    2021/08/18 13:38:12 HostName:test.voipman.com, Response: TEST.VOIPMAN.COM
    2021/08/18 13:38:12 HostName:voipman.com, Response: VOIPMAN.COM
    2021/08/18 13:38:12 HostName:127.0.0.1, Response: 127.0.0.1
    2021/08/18 13:38:12 HostName:localhost, Response: LOCALHOST
    2021/08/18 13:38:12 HostName:dev.vipman.com, Response: DEV.VIPMAN.COM
    2021/08/18 13:38:12 HostName:test.vipman.com, Response: TEST.VIPMAN.COM
    2021/08/18 13:38:12 HostName:vipman.com, Response: VIPMAN.COM
    2021/08/18 13:38:12 could not greet: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate is valid for *.voipman.com, localhost, voipman.com, *.vipman.com, vipman.com, not dev.unknown.com"

    从运行的结果来看,最后一条域名,使用证书和服务端建立认证时出现错误

    could not greet: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate is valid for *.voipman.com, localhost, voipman.com, *.vipman.com, vipman.com, not dev.unknown.com"

    这就说明了证书voipman.pem中的hosts含义所在。

    另外,在cfssl中hosts支持 ip地址,DNS域名,email地址和URI这四类。

    如下:

    "cloudflare.com"
    "www.cloudflare.com"
     "192.168.0.1"
    "jdoe@example.com"
    "https://www.cloudflare.com"

    祝玩的开心~

    done.

  • 相关阅读:
    Autofac小例子
    Spring自带mock测试Controller
    [转载]转Java 几个memcached 连接客户端对比 选择
    danga的MemcachedClient的几个缺陷
    linux查看memcached状态
    Linux下配置安装PHP环境(Apache2)
    使用memcache.php监控memcached
    memcached运行情况监测
    memcached监控的几种方法(nagios等)
    xmemcached使用的几点优化建议
  • 原文地址:https://www.cnblogs.com/voipman/p/15155593.html
Copyright © 2011-2022 走看看