mirror of
https://github.com/ferdzo/fs.git
synced 2026-04-04 20:36:25 +00:00
167 lines
4.3 KiB
Go
167 lines
4.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const sigV4Algorithm = "AWS4-HMAC-SHA256"
|
|
|
|
func signSigV4Request(req *http.Request, body []byte, accessKey, secretKey, region, service string) error {
|
|
if req == nil {
|
|
return fmt.Errorf("nil request")
|
|
}
|
|
if strings.TrimSpace(accessKey) == "" || strings.TrimSpace(secretKey) == "" {
|
|
return fmt.Errorf("missing signing credentials")
|
|
}
|
|
if strings.TrimSpace(region) == "" || strings.TrimSpace(service) == "" {
|
|
return fmt.Errorf("missing signing scope")
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
amzDate := now.Format("20060102T150405Z")
|
|
shortDate := now.Format("20060102")
|
|
scope := shortDate + "/" + region + "/" + service + "/aws4_request"
|
|
|
|
payloadHash := sha256Hex(body)
|
|
req.Header.Set("x-amz-date", amzDate)
|
|
req.Header.Set("x-amz-content-sha256", payloadHash)
|
|
|
|
host := req.URL.Host
|
|
signedHeaders := []string{"host", "x-amz-content-sha256", "x-amz-date"}
|
|
canonicalRequest, signedHeadersRaw := buildCanonicalRequest(req, signedHeaders, payloadHash)
|
|
stringToSign := buildStringToSign(amzDate, scope, canonicalRequest)
|
|
signature := hex.EncodeToString(hmacSHA256(deriveSigningKey(secretKey, shortDate, region, service), stringToSign))
|
|
|
|
authHeader := fmt.Sprintf(
|
|
"%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
|
|
sigV4Algorithm,
|
|
accessKey,
|
|
scope,
|
|
signedHeadersRaw,
|
|
signature,
|
|
)
|
|
req.Header.Set("Authorization", authHeader)
|
|
req.Host = host
|
|
return nil
|
|
}
|
|
|
|
func buildCanonicalRequest(req *http.Request, signedHeaders []string, payloadHash string) (string, string) {
|
|
canonicalHeaders, signedHeadersRaw := canonicalHeaders(req, signedHeaders)
|
|
return strings.Join([]string{
|
|
req.Method,
|
|
canonicalPath(req.URL),
|
|
canonicalQuery(req.URL),
|
|
canonicalHeaders,
|
|
signedHeadersRaw,
|
|
payloadHash,
|
|
}, "\n"), signedHeadersRaw
|
|
}
|
|
|
|
func canonicalPath(u *url.URL) string {
|
|
if u == nil {
|
|
return "/"
|
|
}
|
|
path := u.EscapedPath()
|
|
if path == "" {
|
|
return "/"
|
|
}
|
|
return path
|
|
}
|
|
|
|
func canonicalQuery(u *url.URL) string {
|
|
if u == nil {
|
|
return ""
|
|
}
|
|
values := u.Query()
|
|
type pair struct {
|
|
key string
|
|
value string
|
|
}
|
|
pairs := make([]pair, 0, len(values))
|
|
for key, vals := range values {
|
|
if len(vals) == 0 {
|
|
pairs = append(pairs, pair{key: key, value: ""})
|
|
continue
|
|
}
|
|
for _, v := range vals {
|
|
pairs = append(pairs, pair{key: key, value: v})
|
|
}
|
|
}
|
|
sort.Slice(pairs, func(i, j int) bool {
|
|
if pairs[i].key == pairs[j].key {
|
|
return pairs[i].value < pairs[j].value
|
|
}
|
|
return pairs[i].key < pairs[j].key
|
|
})
|
|
out := make([]string, 0, len(pairs))
|
|
for _, p := range pairs {
|
|
out = append(out, awsEncodeQuery(p.key)+"="+awsEncodeQuery(p.value))
|
|
}
|
|
return strings.Join(out, "&")
|
|
}
|
|
|
|
func awsEncodeQuery(value string) string {
|
|
encoded := url.QueryEscape(value)
|
|
encoded = strings.ReplaceAll(encoded, "+", "%20")
|
|
encoded = strings.ReplaceAll(encoded, "*", "%2A")
|
|
encoded = strings.ReplaceAll(encoded, "%7E", "~")
|
|
return encoded
|
|
}
|
|
|
|
func canonicalHeaders(req *http.Request, headers []string) (string, string) {
|
|
names := make([]string, 0, len(headers))
|
|
lines := make([]string, 0, len(headers))
|
|
for _, h := range headers {
|
|
name := strings.ToLower(strings.TrimSpace(h))
|
|
if name == "" {
|
|
continue
|
|
}
|
|
var value string
|
|
if name == "host" {
|
|
value = req.URL.Host
|
|
} else {
|
|
value = strings.Join(req.Header.Values(http.CanonicalHeaderKey(name)), ",")
|
|
}
|
|
value = strings.Join(strings.Fields(strings.TrimSpace(value)), " ")
|
|
names = append(names, name)
|
|
lines = append(lines, name+":"+value)
|
|
}
|
|
return strings.Join(lines, "\n") + "\n", strings.Join(names, ";")
|
|
}
|
|
|
|
func buildStringToSign(amzDate, scope, canonicalRequest string) string {
|
|
hash := sha256.Sum256([]byte(canonicalRequest))
|
|
return strings.Join([]string{
|
|
sigV4Algorithm,
|
|
amzDate,
|
|
scope,
|
|
hex.EncodeToString(hash[:]),
|
|
}, "\n")
|
|
}
|
|
|
|
func deriveSigningKey(secret, date, region, service string) []byte {
|
|
kDate := hmacSHA256([]byte("AWS4"+secret), date)
|
|
kRegion := hmacSHA256(kDate, region)
|
|
kService := hmacSHA256(kRegion, service)
|
|
return hmacSHA256(kService, "aws4_request")
|
|
}
|
|
|
|
func hmacSHA256(key []byte, message string) []byte {
|
|
mac := hmac.New(sha256.New, key)
|
|
_, _ = mac.Write([]byte(message))
|
|
return mac.Sum(nil)
|
|
}
|
|
|
|
func sha256Hex(payload []byte) string {
|
|
sum := sha256.Sum256(payload)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|