【加解密】使用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-----\n.....\n-----END CERTIFICATE-----\n"
}

 

從如上的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-----\n......\n-----END CERTIFICATE-----\n"
}

如上可以看到,證書文件的內容中

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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM