github.com/Ingenico-ePayments/connect-sdk-go@v0.0.0-20240318153750-1f8cd329b9c9/defaultimpl/DefaultAuthenticator.go (about) 1 package defaultimpl 2 3 import ( 4 "bytes" 5 "crypto/hmac" 6 "crypto/sha256" 7 "encoding/base64" 8 "errors" 9 "net/url" 10 "regexp" 11 "sort" 12 "strings" 13 14 "github.com/Ingenico-ePayments/connect-sdk-go/communicator/communication" 15 ) 16 17 // AuthorizationType represents the type of authentication that is used. Here only v1HMAC 18 type AuthorizationType string 19 20 const ( 21 // V1HMAC represent the authorization type for the HMAC algorithm, version 1 22 V1HMAC = "v1HMAC" 23 ) 24 25 //DefaultAuthenticator represents the default implementation of an authenticator 26 type DefaultAuthenticator struct { 27 authType AuthorizationType 28 apiKeyID string 29 secretAPIKey string 30 } 31 32 // NewDefaultAuthenticator creates a DefaultAuthenticator with the given authType, apiKeyID and secretAPIKey 33 //- Based on the Authenticationtype value both the Ingenico ePayments platform and the merchant know which security implementation is used. 34 // A version number is used for backward compatibility in the future. 35 //- The apiKeyID is an identifier for the secret API key. The apiKeyID can be retrieved from the Configuration Center 36 // This identifier is visible in the HTTP request and is also used to identify the correct account. 37 //- secretAPIKey is a shared secret. The shared secret can be retrieved from the Configuration Center. 38 // An apiKeyID and secretAPIKey always go hand-in-hand, the difference is that secretAPIKey is never visible in the HTTP request. 39 // This secret is used as input for the HMAC algorithm. 40 func NewDefaultAuthenticator(authType AuthorizationType, apiKeyID string, secretAPIKey string) (*DefaultAuthenticator, error) { 41 if strings.TrimSpace(apiKeyID) == "" { 42 return nil, errors.New("apikeyid is required") 43 } 44 if strings.TrimSpace(secretAPIKey) == "" { 45 return nil, errors.New("secretAPIKey is required") 46 } 47 defaultAuthenticator := DefaultAuthenticator{authType: authType, apiKeyID: apiKeyID, secretAPIKey: secretAPIKey} 48 return &defaultAuthenticator, nil 49 } 50 51 // CreateSimpleAuthenticationSignature creates an authentication signature for the given httpMethod, resourceURI and requestHeaders 52 func (a DefaultAuthenticator) CreateSimpleAuthenticationSignature(httpMethod string, resourceURI url.URL, requestHeaders []communication.Header) (output string, err error) { 53 if strings.TrimSpace(httpMethod) == "" { 54 err = errors.New("httpMethod is required") 55 return 56 } 57 dataToSign, err := toDataToSign(httpMethod, resourceURI, requestHeaders) 58 if err != nil { 59 return 60 } 61 signedData, err := a.signData(dataToSign) 62 if err != nil { 63 return 64 } 65 output = "GCS " + string(a.authType) + ":" + a.apiKeyID + ":" + signedData 66 return 67 } 68 69 func toDataToSign(httpMethod string, resourceURI url.URL, requestHeaders []communication.Header) (output string, err error) { 70 xgcsHTTPHeaders := communication.Headers{} 71 var contentType, date string 72 for _, header := range requestHeaders { 73 if strings.ToLower(header.Name()) == "content-type" { 74 contentType = header.Value() 75 } else if strings.ToLower(header.Name()) == "date" { 76 date = header.Value() 77 } else if strings.HasPrefix(strings.ToLower(header.Name()), "x-gcs") { 78 var newHeader *communication.Header 79 var newValue string 80 newValue, err = toCanonicalizedHeaderValue(header.Value()) 81 if err != nil { 82 return 83 } 84 newHeader, err = communication.NewHeader(strings.ToLower(header.Name()), newValue) 85 if err != nil { 86 return 87 } 88 89 xgcsHTTPHeaders = append(xgcsHTTPHeaders, *newHeader) 90 } 91 } 92 sort.Sort(xgcsHTTPHeaders) 93 94 dataToSign := bytes.Buffer{} 95 dataToSign.WriteString(httpMethod) 96 dataToSign.WriteRune('\n') 97 dataToSign.WriteString(contentType) 98 dataToSign.WriteRune('\n') 99 dataToSign.WriteString(date) 100 dataToSign.WriteRune('\n') 101 for _, header := range xgcsHTTPHeaders { 102 dataToSign.WriteString(header.Name() + ":" + header.Value()) 103 dataToSign.WriteRune('\n') 104 } 105 var canonizalizedResource string 106 canonizalizedResource, err = toCanonicalizedResource(resourceURI) 107 dataToSign.WriteString(canonizalizedResource) 108 dataToSign.WriteRune('\n') 109 110 output = dataToSign.String() 111 return 112 } 113 114 func toCanonicalizedResource(resourceURI url.URL) (string, error) { 115 resource := bytes.Buffer{} 116 117 resource.WriteString(resourceURI.EscapedPath()) 118 if resourceURI.RawQuery != "" { 119 query, err := url.QueryUnescape(resourceURI.RawQuery) 120 if err != nil { 121 return "", err 122 } 123 124 resource.WriteString("?" + query) 125 } 126 127 return resource.String(), nil 128 } 129 130 func toCanonicalizedHeaderValue(s string) (string, error) { 131 regex, err := regexp.Compile("\r?\n[\t\f ]*") 132 if err != nil { 133 return "", err 134 } 135 return strings.TrimSpace(regex.ReplaceAllString(s, " ")), nil 136 } 137 138 func (a *DefaultAuthenticator) signData(s string) (string, error) { 139 mac := hmac.New(sha256.New, []byte(a.secretAPIKey)) 140 mac.Write([]byte(s)) 141 hmacOutput := mac.Sum(nil) 142 143 writableBuffer := bytes.Buffer{} 144 encoder := base64.NewEncoder(base64.StdEncoding, &writableBuffer) 145 _, err := encoder.Write(hmacOutput) 146 if err != nil { 147 return "", err 148 } 149 err = encoder.Close() 150 if err != nil { 151 return "", err 152 } 153 output := writableBuffer.String() 154 return output, nil 155 }