package route53 import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "strings" "time" ) const ( route53Domain = "route53.amazonaws.com" dateTimeFormat = "20060102T150405Z" dateFormat = "20060102" ) // signer implements the signature v4 header based to upsert route53 domains // https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html type signer struct { accessKey string secretkey string region string service string signatureVersion string } func (s *signer) sign(method, urlPath string, payload []byte, date time.Time) ( headerValue string, ) { credentialScope := fmt.Sprintf("%s/%s/%s/%s", date.Format(dateFormat), s.region, s.service, s.signatureVersion) credential := fmt.Sprintf("%s/%s", s.accessKey, credentialScope) const signedHeaders = "content-type;host" canonicalRequest := buildCanonicalRequest(method, urlPath, signedHeaders, payload) stringToSign := buildStringToSign(date, canonicalRequest, credentialScope) signingKey := s.buildPrivateKey(date) signature := hmacSha256Sum([]byte(signingKey), []byte(stringToSign)) signatureString := hex.EncodeToString(signature) return fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s,SignedHeaders=%s,Signature=%s", credential, signedHeaders, signatureString) } func buildCanonicalRequest(method, path, headers string, payload []byte) ( canonicalRequest string, ) { canonicalHeaders := "content-type:application/xml\nhost:" + route53Domain + "\n" const canonicalQuery = "" // no query arg used payloadHashDigest := hex.EncodeToString(sha256Sum(payload)) canonicalRequest = strings.Join([]string{ strings.ToUpper(method), path, canonicalQuery, canonicalHeaders, headers, payloadHashDigest, }, "\n") return canonicalRequest } func buildStringToSign(date time.Time, canonicalRequest, credentialScope string, ) string { return "AWS4-HMAC-SHA256\n" + date.Format(dateTimeFormat) + "\n" + credentialScope + "\n" + hex.EncodeToString(sha256Sum([]byte(canonicalRequest))) } func (s *signer) buildPrivateKey(date time.Time) string { signingKey := []byte("AWS4" + s.secretkey) for _, value := range [][]byte{ []byte(date.Format(dateFormat)), []byte(s.region), []byte(s.service), []byte(s.signatureVersion), } { signingKey = hmacSha256Sum(signingKey, value) } return string(signingKey) } func sha256Sum(d []byte) []byte { hasher := sha256.New() hasher.Write(d) return hasher.Sum(nil) } func hmacSha256Sum(key, value []byte) []byte { hasher := hmac.New(sha256.New, key) hasher.Write(value) return hasher.Sum(nil) }