目前,浏览器中开启HTTPS是比较简单的事情。首先我们需要一个域名,然后找一家可信CA机构申请证书并将证书安装到服务器(例如:RapidSSL、Trustwave SSL、Let’s Encrypt等)。但对TCP协议的服务使用self-signed证书我们应该如何完成?
证书的分类验证方式
DV SSL证书(域名验证) OV SSL证书(企业验证) EV SSL证书(企业增强/扩展验证)功能分类
UCC/SAN SSL证书(多域名) SGC SSL证书(强加密) Wildcard SSL证书(通配符) Code Signing SSL证书(代码签名)认证流程
one-way authentication(单向认证) two-way(mutual) authentication(双向认证)浏览器中大部分应用使用单向认证,即服务器认证客户端,客户端无需认证服务器。大型企业有很多域名一般使用OV SSL和SAN SSL证书,而个人网站则使用DV SSL证书。
证书使用什么时候使用可信CA签发证书,什么时候使用自签证书? 什么时候使用单向证书或双向证书。
这些都应根据应用的实际业务而定,浏览器应用中使用可信CA签发的证书,这样浏览器访问你的网站时就不会提示 not secure 错误,这是因浏览器中预置可信CA机构的CA证书。这也不是绝对的,例如12306网站就使用的自签证书,你会看到 not secure 错误,导入12306网站的CA证书可以消除这个提示。自签证书不是说比可信CA机构签发的证书有更高的安全性。因为加密算法都是公开的,而是私钥文件自己保管。对于安全性高的场景,例如金融行业对账,就应该使用自签和双向认证。
证书自签生成自签证书脚本
#!/bin/sh mkdir -p {certs,crl,newcerts} touch index.txt echo 1000 > serial # CA private key (unencrypted) openssl genrsa -out ca.key 4096 # Certificate Authority (self-signed certificate) openssl req -config openssl.cnf -new -x509 -days 3650 -sha256 -key ca.key -extensions v3_ca -out ca.crt -subj "/CN=fake-ca" # Server private key (unencrypted) openssl genrsa -out server.key 2048 # Server certificate signing request (CSR) openssl req -config openssl.cnf -new -sha256 -key server.key -out server.csr -subj "/CN=fake.grpc" # Certificate Authority signs CSR to grant a certificate openssl ca -batch -config openssl.cnf -extensions server_cert -days 365 -notext -md sha256 -in server.csr -out server.crt -cert ca.crt -keyfile ca.key # Client private key (unencrypted) openssl genrsa -out client.key 2048 # Signed client certificate signing request (CSR) openssl req -config openssl.cnf -new -sha256 -key client.key -out client.csr -subj "/CN=fake.client" # Certificate Authority signs CSR to grant a certificate openssl ca -batch -config openssl.cnf -extensions usr_cert -days 365 -notext -md sha256 -in client.csr -out client.crt -cert ca.crt -keyfile ca.key #openssl x509 -text -noout -in ca.crt rm *.csr
自签证书配置
SAN = [ ca ] # `man ca` default_ca = CA_default [ CA_default ] # Directory and file locations. dir = . certs = $dir/certs crl_dir = $dir/crl new_certs_dir = $dir/newcerts database = $dir/index.txt serial = $dir/serial # certificate revocation lists. crlnumber = $dir/crlnumber crl = $dir/crl/intermediate-ca.crl crl_extensions = crl_ext default_crl_days = 30 default_md = sha256 name_opt = ca_default cert_opt = ca_default default_days = 375 preserve = no policy = policy_loose [ policy_loose ] # Allow the CA to sign a range of certificates. countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] # `man req` default_bits = 4096 distinguished_name = req_distinguished_name string_mask = utf8only default_md = sha256 [ req_distinguished_name ] countryName = Country Name (2 letter code) stateOrProvinceName = State or Province Name localityName = Locality Name 0.organizationName = Organization Name organizationalUnitName = Organizational Unit Name commonName = Common Name # Certificate extensions (`man x509v3_config`) [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true, pathlen:0 keyUsage = critical, digitalSignature, cRLSign, keyCertSign [ usr_cert ] basicConstraints = CA:FALSE nsCertType = client nsComment = "OpenSSL Generated Client Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth [ server_cert ] basicConstraints = CA:FALSE nsCertType = server nsComment = "OpenSSL Generated Server Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = $ENV::SAN 示例代码 var ( argAddress string argCrtFile string argKeyFile string argCAFile string ) var verbose bool var rootCmd *cobra.Command func init() { rootCmd = &cobra.Command{ Use: "grpc", Short: "demo service", Long: "Top level command for demo service, it provides GRPC service", Run: run, } rootCmd.Flags().StringVarP(&argAddress, "address", "a", ":3264", "address to listen on") rootCmd.Flags().StringVar(&argCrtFile, "cert-file", "", "certificate file for gRPC TLS authentication") rootCmd.Flags().StringVar(&argKeyFile, "key-file", "", "key file for gRPC TLS authentication") rootCmd.Flags().StringVar(&argCAFile, "ca-file", "", "ca file for gRPC client") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") } type Service struct { } func (s *Service) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, grpc.Errorf(codes.Unauthenticated, "no metadata") } token := md.Get("token") if len(token) == 0 { return nil, grpc.Errorf(codes.Unauthenticated, "no token") } fmt.Println("requst:", token[0], req.Name) return &pb.HelloReply{ Message: "hello, " + req.Name, }, nil } func main() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } func run(cmd *cobra.Command, _ []string) { listener, err := net.Listen("tcp", argAddress) if err != nil { panic(err) } // Create the TLS credentials var opts []grpc.ServerOption if argCrtFile != "" && argKeyFile != "" { fmt.Println("enable credentials in the grpc") if argCAFile == "" { creds, err := credentials.NewServerTLSFromFile(argCrtFile, argKeyFile) if err != nil { panic(err) } opts = append(opts, grpc.Creds(creds)) } else { // Parse certificates from certificate file and key file for server. cert, err := tls.LoadX509KeyPair(argCrtFile, argKeyFile) if err != nil { panic(err) //return fmt.Errorf("invalid config: error parsing gRPC certificate file: %v", err) } // Parse certificates from client CA file to a new CertPool. cPool := x509.NewCertPool() clientCert, err := ioutil.ReadFile(argCAFile) if err != nil { panic(err) //return fmt.Errorf("invalid config: reading from client CA file: %v", err) } if cPool.AppendCertsFromPEM(clientCert) != true { panic(err) //return errors.New("invalid config: failed to parse client CA") } tlsConfig := tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: cPool, } opts = append(opts, grpc.Creds(credentials.NewTLS(&tlsConfig)), ) } } server := grpc.NewServer(opts...) pb.RegisterGreeterServer(server, &Service{}) logrus.WithField("addr", argAddress).Println("Starting server") server.Serve(listener) }客户端代码
var ( argGRPCAddr string argCrtFile string argKeyFile string argCAFile string argCNOverride string ) var verbose bool var rootCmd *cobra.Command func init() { rootCmd = &cobra.Command{ Use: "grpc-client", Short: "demo client", Long: "Top level command for demo client", Run: run, } rootCmd.Flags().StringVarP(&argGRPCAddr, "grpc-addr", "a", "127.0.0.1:3264", "grpc address") rootCmd.Flags().StringVar(&argCrtFile, "cert-file", "", "certificate file for gRPC TLS authentication") rootCmd.Flags().StringVar(&argKeyFile, "key-file", "", "key file for gRPC TLS authentication") rootCmd.Flags().StringVar(&argCAFile, "ca-file", "", "ca file for gRPC client") rootCmd.Flags().StringVar(&argCNOverride, "cn-override", "", "domain name override") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") } // customCredential 自定义认证 type customCredential struct { token string security bool } func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "token": c.token, }, nil } func (c customCredential) RequireTransportSecurity() bool { return c.security } func main() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } func run(cmd *cobra.Command, _ []string) { var opts []grpc.DialOption customCred := &customCredential{token: "custom-token"} if argCAFile != "" { fmt.Println("enable credentials in the grpc") if argCrtFile != "" && argKeyFile != "" { cPool := x509.NewCertPool() caCert, err := ioutil.ReadFile(argCAFile) if err != nil { panic(err) //return nil, fmt.Errorf("invalid CA crt file: %s", caPath) } if cPool.AppendCertsFromPEM(caCert) != true { panic(err) //return nil, fmt.Errorf("failed to parse CA crt") } clientCert, err := tls.LoadX509KeyPair(argCrtFile, argKeyFile) if err != nil { panic(err) //return nil, fmt.Errorf("invalid client crt file: %s", caPath) } clientTLSConfig := &tls.Config{ RootCAs: cPool, Certificates: []tls.Certificate{clientCert}, } creds := credentials.NewTLS(clientTLSConfig) opts = append(opts, grpc.WithTransportCredentials(creds)) } else { // target is common name(host name) in the cert file creds, err := credentials.NewClientTLSFromFile(argCAFile, argCNOverride) if err != nil { panic(err) } opts = append(opts, grpc.WithTransportCredentials(creds)) } customCred.security = true } else { opts = append(opts, grpc.WithInsecure()) } // custom credentials opts = append(opts, grpc.WithPerRPCCredentials(customCred)) conn, err := grpc.Dial(argGRPCAddr, opts...) if err != nil { panic(err) } greeterClient := pb.NewGreeterClient(conn) reply, err := greeterClient.SayHello(context.Background(), &pb.HelloRequest{Name: "luoji"}) if err != nil { logrus.WithError(err).Fatal("unable to sayhello") } logrus.Info("reply:", reply.Message) conn.Close() }