github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/https/client_certificate.go (about) 1 package https 2 3 import ( 4 "crypto/sha256" 5 "crypto/x509" 6 "encoding/base64" 7 "encoding/hex" 8 "encoding/pem" 9 "errors" 10 "fmt" 11 "net/http" 12 "net/url" 13 "strings" 14 15 log "github.com/golang/glog" 16 cpb "github.com/google/fleetspeak/fleetspeak/src/server/components/proto/fleetspeak_components" 17 ) 18 19 // GetClientCert returns the client certificate from either the request header or TLS connection state. 20 func GetClientCert(req *http.Request, frontendConfig *cpb.FrontendConfig) (*x509.Certificate, error) { 21 // Default to using mTLS if frontend_config or frontend_mode have not been set 22 if frontendConfig.GetFrontendMode() == nil { 23 return getCertFromTLS(req) 24 } 25 26 switch { 27 case frontendConfig.GetMtlsConfig() != nil: 28 return getCertFromTLS(req) 29 case frontendConfig.GetCleartextHeaderConfig() != nil: 30 return getCertFromHeader(frontendConfig.GetCleartextHeaderConfig().GetClientCertificateHeader(), req.Header) 31 case frontendConfig.GetHttpsHeaderConfig() != nil: 32 return getCertFromHeader(frontendConfig.GetHttpsHeaderConfig().GetClientCertificateHeader(), req.Header) 33 case frontendConfig.GetCleartextHeaderChecksumConfig() != nil: 34 cert, err := getCertFromHeader(frontendConfig.GetCleartextHeaderChecksumConfig().GetClientCertificateHeader(), req.Header) 35 if err != nil { 36 return nil, err 37 } 38 err = verifyCertSha256Checksum(req.Header.Get(frontendConfig.GetCleartextHeaderChecksumConfig().GetClientCertificateHeader()), 39 req.Header.Get(frontendConfig.GetCleartextHeaderChecksumConfig().GetClientCertificateChecksumHeader())) 40 if err != nil { 41 return nil, err 42 } 43 return cert, nil 44 case frontendConfig.GetHttpsHeaderChecksumConfig() != nil: 45 cert, err := getCertFromHeader(frontendConfig.GetHttpsHeaderChecksumConfig().GetClientCertificateHeader(), req.Header) 46 if err != nil { 47 return nil, err 48 } 49 err = verifyCertSha256Checksum(req.Header.Get(frontendConfig.GetHttpsHeaderChecksumConfig().GetClientCertificateHeader()), 50 req.Header.Get(frontendConfig.GetHttpsHeaderChecksumConfig().GetClientCertificateChecksumHeader())) 51 if err != nil { 52 return nil, err 53 } 54 return cert, nil 55 case frontendConfig.GetCleartextXfccConfig() != nil: 56 cert, err := getCertFromXfcc(frontendConfig.GetCleartextXfccConfig().GetClientCertificateHeader(), req.Header) 57 if err != nil { 58 return nil, err 59 } 60 return cert, nil 61 } 62 63 // Given the above switch statement is exhaustive, this error should never be reached 64 return nil, errors.New("invalid frontend_config") 65 } 66 67 // This function is calculating the client certificate checksum in the same fashion the GLB7 does. 68 // We can also do so on the command line using openssl to calculate the certificate checksum. 69 // openssl x509 -in mclient.crt -outform DER | openssl dgst -sha256 | cut -d ' ' -f2 | xxd -r -p - | openssl enc -a 70 // For more info check out: https://gist.github.com/salrashid123/6e2a1eb9be95fb49506f1554e2d3d392 71 func calculateClientCertificateChecksum(clientCert string) string { 72 // Most certificates are URL PEM encoded 73 if decodedCert, err := url.PathUnescape(clientCert); err != nil { 74 return "" 75 } else { 76 clientCert = decodedCert 77 } 78 // Decode the PEM string 79 block, rest := pem.Decode([]byte(clientCert)) 80 if block == nil || len(rest) != 0 { 81 log.Warningln("Failed to decode PEM certificate") 82 return "" 83 } 84 // Calculate the SHA-256 digest of the DER certificate 85 sha256Digest := sha256.Sum256(block.Bytes) 86 87 // Convert the SHA-256 digest to a hexadecimal string 88 // sha256HexStr equivalent to: openssl x509 -n mclient.crt -outform DER | openssl dgst -sha256 89 sha256HexStr := fmt.Sprintf("%x", sha256Digest) 90 91 // sha256Binaryequivalent to: openssl x509 -n mclient.crt -outform DER | openssl dgst -sha256 | xxd -r -p - 92 sha256Binary, err := hex.DecodeString(sha256HexStr) 93 if err != nil { 94 log.Warningf("error decoding hexdump: %v\n", err) 95 return "" 96 } 97 98 // Convert the hexadecimal string to a base64 encoded string 99 // base64EncodedStr equivalent to: openssl x509 -n mclient.crt -outform DER | openssl dgst -sha256 | xxd -r -p - | openssl enc -a 100 // It also removes trailing "=" padding characters 101 base64EncodedStr := strings.TrimRight(base64.StdEncoding.EncodeToString(sha256Binary), "=") 102 103 // Return the base64 encoded string 104 return base64EncodedStr 105 } 106 107 func verifyCertSha256Checksum(headerCert string, clientCertSha256Checksum string) error { 108 if clientCertSha256Checksum == "" { 109 return errors.New("no client certificate checksum received in header") 110 } 111 112 calculatedClientCertSha256 := calculateClientCertificateChecksum(headerCert) 113 if calculatedClientCertSha256 != clientCertSha256Checksum { 114 return errors.New("received client certificate checksum is invalid") 115 } 116 117 return nil 118 } 119 120 const ( 121 key int = iota 122 valueStart 123 value 124 quotedValue 125 ) 126 127 type xfccParser struct { 128 header string 129 } 130 131 func (x *xfccParser) Next() (string, string) { 132 var keyStr, valueStr strings.Builder 133 state := key 134 var i int 135 L: 136 for i = 0; i < len(x.header); i++ { 137 switch state { 138 case key: 139 if string(x.header[i]) == "=" { 140 state = valueStart 141 } else { 142 keyStr.Write([]byte{x.header[i]}) 143 } 144 case valueStart: 145 if string(x.header[i]) == `"` { 146 state = quotedValue 147 continue L 148 } else { 149 state = value 150 } 151 fallthrough 152 case value: 153 if string(x.header[i]) == ";" { 154 break L 155 } else if string(x.header[i]) == "," { 156 break L 157 } 158 if string(x.header[i]) == `\` { 159 if len(x.header) == i+1 { 160 return "", "" 161 } 162 i++ 163 } 164 valueStr.Write([]byte{x.header[i]}) 165 case quotedValue: 166 if string(x.header[i]) == `"` { 167 state = value 168 continue L 169 } 170 if string(x.header[i]) == `\` { 171 if len(x.header) == i+1 { 172 return "", "" 173 } 174 i++ 175 } 176 valueStr.Write([]byte{x.header[i]}) 177 } 178 } 179 if len(x.header) > i { 180 x.header = x.header[i+1:] 181 } else { 182 x.header = "" 183 } 184 return keyStr.String(), valueStr.String() 185 } 186 187 // parses the X-Forwarded-Client-Cert header as defined by envoy 188 // see: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-client-cert 189 // if multiple client certs are found, takes the first one 190 func extractField(fieldName, headerCert string) string { 191 headerReader := &xfccParser{ 192 header: headerCert, 193 } 194 for { 195 keyStr, valueStr := headerReader.Next() 196 if keyStr == "" { 197 return "" 198 } 199 if keyStr == fieldName { 200 return valueStr 201 } 202 } 203 } 204 205 func getCertFromXfcc(hn string, rh http.Header) (*x509.Certificate, error) { 206 headerCert := rh.Get(hn) 207 if headerCert == "" { 208 return nil, errors.New("no certificate found in header") 209 } 210 // support for envoy encoded xfcc header: 211 if certField := extractField("Cert", headerCert); certField != "" { 212 headerCert = certField 213 } 214 // Most certificates are URL PEM encoded 215 if decodedCert, err := url.PathUnescape(headerCert); err != nil { 216 return nil, err 217 } else { 218 headerCert = decodedCert 219 } 220 block, rest := pem.Decode([]byte(headerCert)) 221 if block == nil || block.Type != "CERTIFICATE" { 222 return nil, errors.New("failed to decode PEM block containing certificate") 223 } 224 if len(rest) != 0 { 225 return nil, errors.New("received more than 1 client cert") 226 } 227 cert, err := x509.ParseCertificate(block.Bytes) 228 return cert, err 229 } 230 231 func getCertFromHeader(hn string, rh http.Header) (*x509.Certificate, error) { 232 headerCert := rh.Get(hn) 233 if headerCert == "" { 234 return nil, fmt.Errorf("no certificate found in header with name %q", hn) 235 } 236 237 decodedCert, err := url.PathUnescape(headerCert) 238 if err != nil { 239 return nil, err 240 } 241 242 // Most certificates are URL PEM encoded 243 block, rest := pem.Decode([]byte(decodedCert)) 244 if block == nil { 245 return nil, errors.New("failed to decode PEM block") 246 } 247 if block.Type != "CERTIFICATE" { 248 return nil, errors.New("PEM block is not a certificate") 249 } 250 if len(rest) != 0 { 251 return nil, errors.New("received more than 1 client cert") 252 } 253 return x509.ParseCertificate(block.Bytes) 254 } 255 256 func getCertFromTLS(req *http.Request) (*x509.Certificate, error) { 257 if req.TLS == nil { 258 return nil, errors.New("TLS information not found") 259 } 260 if len(req.TLS.PeerCertificates) != 1 { 261 return nil, fmt.Errorf("expected 1 client cert, received %v", len(req.TLS.PeerCertificates)) 262 } 263 cert := req.TLS.PeerCertificates[0] 264 return cert, nil 265 }