github.com/Ingenico-ePayments/connect-sdk-go@v0.0.0-20240318153750-1f8cd329b9c9/webhooks/validation/SignatureValidator.go (about) 1 package validation 2 3 import ( 4 "crypto/hmac" 5 "crypto/sha256" 6 "crypto/subtle" 7 "encoding/base64" 8 "errors" 9 "fmt" 10 "strings" 11 12 "github.com/Ingenico-ePayments/connect-sdk-go/communicator/communication" 13 ) 14 15 var ( 16 // ErrNilSecretKeyStore occurs when the provided secretKeyStore is nil 17 ErrNilSecretKeyStore = errors.New("nil secretKeyStore") 18 19 multipleHeaderFormat = `encountered multiple occurrences of header "%v"` 20 headerNotFoundFormat = `could not find header "%v"` 21 compareFailedFormat = `failed to validate signature "%v"` 22 ) 23 24 // SignatureValidator is a validator for webhooks signatures. Immutable and thread-safe. 25 type SignatureValidator struct { 26 secretKeyStore SecretKeyStore 27 } 28 29 // SecretKeyStore returns the configured secret key store 30 func (v *SignatureValidator) SecretKeyStore() SecretKeyStore { 31 return v.secretKeyStore 32 } 33 34 // Validate validates the given body using the given request headers 35 func (v *SignatureValidator) Validate(body string, requestHeaders []communication.Header) error { 36 signature, err := getHeaderValue(requestHeaders, "X-GCS-Signature") 37 if err != nil { 38 return err 39 } 40 41 keyID, err := getHeaderValue(requestHeaders, "X-GCS-KeyId") 42 if err != nil { 43 return err 44 } 45 46 secretKey, err := v.secretKeyStore.GetSecretKey(keyID) 47 if err != nil { 48 return err 49 } 50 51 mac := hmac.New(sha256.New, []byte(secretKey)) 52 53 _, err = mac.Write([]byte(body)) 54 if err != nil { 55 return err 56 } 57 58 unencodedResult := mac.Sum(nil) 59 60 encoder := base64.StdEncoding 61 expectedSignature := make([]byte, encoder.EncodedLen(len(unencodedResult))) 62 encoder.Encode(expectedSignature, unencodedResult) 63 64 isValid := subtle.ConstantTimeCompare([]byte(signature), expectedSignature) == 1 65 if !isValid { 66 return NewSignatureValidationError(fmt.Sprintf(compareFailedFormat, signature)) 67 } 68 69 return nil 70 } 71 72 // NewSignatureValidator creates a signature validator with the given secretKeyStore 73 func NewSignatureValidator(secretKeyStore SecretKeyStore) (*SignatureValidator, error) { 74 if secretKeyStore == nil { 75 return nil, ErrNilSecretKeyStore 76 } 77 78 return &SignatureValidator{secretKeyStore}, nil 79 } 80 81 func getHeaderValue(headers []communication.Header, name string) (string, error) { 82 lowerName := strings.ToLower(name) 83 var value string 84 85 found := false 86 87 for _, header := range headers { 88 if strings.ToLower(header.Name()) == lowerName { 89 if found { 90 sve := NewSignatureValidationError(fmt.Sprintf(multipleHeaderFormat, name)) 91 92 return "", sve 93 } 94 95 found, value = true, header.Value() 96 } 97 } 98 99 if !found { 100 sve := NewSignatureValidationError(fmt.Sprintf(headerNotFoundFormat, name)) 101 102 return "", sve 103 } 104 105 return value, nil 106 }