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  }