github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/utils/grpcutils/grpc.go (about) 1 package grpcutils 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "encoding/hex" 7 "fmt" 8 9 lcrypto "github.com/libp2p/go-libp2p/core/crypto" 10 libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls" 11 "github.com/onflow/crypto" 12 13 "github.com/onflow/flow-go/network/p2p/keyutils" 14 ) 15 16 // NoCompressor use when no specific compressor name provided, which effectively means no compression. 17 const NoCompressor = "" 18 19 // DefaultMaxMsgSize use 1 GiB as the default message size limit. 20 // This enforces a sane max message size, while still allowing for reasonably large messages. 21 // grpc library default is 4 MiB. 22 const DefaultMaxMsgSize = 1 << (10 * 3) // 1 GiB 23 24 // CertificateConfig is used to configure an Certificate 25 type CertificateConfig struct { 26 opts []libp2ptls.IdentityOption 27 } 28 29 // CertificateOption transforms an CertificateConfig to apply optional settings. 30 type CertificateOption func(r *CertificateConfig) 31 32 // WithCertTemplate specifies the template to use when generating a new certificate. 33 func WithCertTemplate(template *x509.Certificate) CertificateOption { 34 return func(c *CertificateConfig) { 35 c.opts = append(c.opts, libp2ptls.WithCertTemplate(template)) 36 } 37 } 38 39 // X509Certificate generates a self-signed x509 TLS certificate from the given key. The generated certificate 40 // includes a libp2p extension that specifies the public key and the signature. The certificate does not include any 41 // SAN extension. 42 func X509Certificate(privKey crypto.PrivateKey, opts ...CertificateOption) (*tls.Certificate, error) { 43 config := CertificateConfig{} 44 for _, opt := range opts { 45 opt(&config) 46 } 47 48 // convert the Flow crypto private key to a Libp2p private crypto key 49 libP2PKey, err := keyutils.LibP2PPrivKeyFromFlow(privKey) 50 if err != nil { 51 return nil, fmt.Errorf("could not convert Flow key to libp2p key: %w", err) 52 } 53 54 // create a libp2p Identity from the libp2p private key 55 id, err := libp2ptls.NewIdentity(libP2PKey, config.opts...) 56 if err != nil { 57 return nil, fmt.Errorf("could not generate identity: %w", err) 58 } 59 60 // extract the TLSConfig from it which will contain the generated x509 certificate 61 // (ignore the public key that is returned - it is the public key of the private key used to generate the ID) 62 libp2pTlsConfig, _ := id.ConfigForPeer("") 63 64 // verify that exactly one certificate was generated for the given key 65 certCount := len(libp2pTlsConfig.Certificates) 66 if certCount != 1 { 67 return nil, fmt.Errorf("invalid count for the generated x509 certificate: %d", certCount) 68 } 69 70 return &libp2pTlsConfig.Certificates[0], nil 71 } 72 73 // DefaultServerTLSConfig returns the default TLS server config with the given cert for a secure GRPC server 74 func DefaultServerTLSConfig(cert *tls.Certificate) *tls.Config { 75 76 // TODO(rbtz): remove after we pick up https://github.com/securego/gosec/pull/903 77 // #nosec G402 78 tlsConfig := &tls.Config{ 79 MinVersion: tls.VersionTLS13, 80 Certificates: []tls.Certificate{*cert}, 81 ClientAuth: tls.NoClientCert, 82 } 83 return tlsConfig 84 } 85 86 // ServerAuthError is an error returned when the server authentication fails 87 type ServerAuthError struct { 88 message string 89 } 90 91 // newServerAuthError constructs a new ServerAuthError 92 func newServerAuthError(msg string, args ...interface{}) *ServerAuthError { 93 return &ServerAuthError{message: fmt.Sprintf(msg, args...)} 94 } 95 96 func (e ServerAuthError) Error() string { 97 return e.message 98 } 99 100 // IsServerAuthError checks if the input error is of a ServerAuthError type 101 func IsServerAuthError(err error) bool { 102 _, ok := err.(*ServerAuthError) 103 return ok 104 } 105 106 // DefaultClientTLSConfig returns the default TLS client config with the given public key for a secure GRPC client 107 // The TLSConfig verifies that the server certifcate is valid and has the correct signature 108 func DefaultClientTLSConfig(publicKey crypto.PublicKey) (*tls.Config, error) { 109 110 // #nosec G402 111 config := &tls.Config{ 112 MinVersion: tls.VersionTLS13, 113 // This is not insecure here. We will verify the cert chain ourselves. 114 InsecureSkipVerify: true, 115 ClientAuth: tls.RequireAnyClientCert, 116 } 117 118 verifyPeerCertFunc, err := verifyPeerCertificateFunc(publicKey) 119 if err != nil { 120 return nil, err 121 } 122 config.VerifyPeerCertificate = verifyPeerCertFunc 123 124 return config, nil 125 } 126 127 func verifyPeerCertificateFunc(expectedPublicKey crypto.PublicKey) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) { 128 129 // convert the Flow.crypto key to LibP2P key for easy comparision using LibP2P TLS utils 130 remotePeerLibP2PID, err := keyutils.PeerIDFromFlowPublicKey(expectedPublicKey) 131 if err != nil { 132 return nil, fmt.Errorf("failed to derive the libp2p Peer ID from the Flow key: %w", err) 133 } 134 135 // We're using InsecureSkipVerify, so the verifiedChains parameter will always be empty. 136 // We need to parse the certificates ourselves from the raw certs. 137 verifyFunc := func(rawCerts [][]byte, _ [][]*x509.Certificate) error { 138 139 chain := make([]*x509.Certificate, len(rawCerts)) 140 for i := 0; i < len(rawCerts); i++ { 141 cert, err := x509.ParseCertificate(rawCerts[i]) 142 if err != nil { 143 return newServerAuthError(err.Error()) 144 } 145 chain[i] = cert 146 } 147 148 // libp2ptls.PubKeyFromCertChain verifies the certificate, verifies that the certificate contains the special libp2p 149 // extension, extract the remote's public key and finally verifies the signature included in the certificate 150 actualLibP2PKey, err := libp2ptls.PubKeyFromCertChain(chain) 151 if err != nil { 152 return newServerAuthError(err.Error()) 153 } 154 155 // verify that the public key received is the one that is expected 156 if !remotePeerLibP2PID.MatchesPublicKey(actualLibP2PKey) { 157 actualKeyHex, err := libP2PKeyToHexString(actualLibP2PKey) 158 if err != nil { 159 return err 160 } 161 return newServerAuthError("invalid public key received: expected %s, got %s", expectedPublicKey.String(), actualKeyHex) 162 } 163 return nil 164 } 165 166 return verifyFunc, nil 167 } 168 169 func libP2PKeyToHexString(key lcrypto.PubKey) (string, *ServerAuthError) { 170 keyRaw, err := key.Raw() 171 if err != nil { 172 return "", newServerAuthError(err.Error()) 173 } 174 return hex.EncodeToString(keyRaw), nil 175 }