mirror of
https://github.com/ferdzo/fs.git
synced 2026-04-05 08:56:26 +00:00
Initial FS CLI
This commit is contained in:
166
cmd/admin_sigv4.go
Normal file
166
cmd/admin_sigv4.go
Normal file
@@ -0,0 +1,166 @@
|
||||
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[:])
|
||||
}
|
||||
Reference in New Issue
Block a user