Skip to content

Commit f266c0a

Browse files
committed
feat: implement aksk gen sign and verify
1 parent 56720bc commit f266c0a

File tree

5 files changed

+399
-2
lines changed

5 files changed

+399
-2
lines changed

auth/aksk/gen.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package aksk
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/hex"
6+
"io"
7+
)
8+
9+
func GenerateAksk() (string, string, error) {
10+
akBytes, err := io.ReadAll(io.LimitReader(rand.Reader, 16))
11+
if err != nil {
12+
return "", "", err
13+
}
14+
ak := hex.EncodeToString(akBytes)
15+
16+
skBytes, err := io.ReadAll(io.LimitReader(rand.Reader, 16))
17+
if err != nil {
18+
return "", "", err
19+
}
20+
sk := hex.EncodeToString(skBytes)
21+
return ak, sk, nil
22+
}

auth/aksk/sign.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package aksk
2+
3+
import (
4+
"crypto/hmac"
5+
"crypto/sha256"
6+
"encoding/base64"
7+
"net/http"
8+
"net/url"
9+
"sort"
10+
"strings"
11+
"time"
12+
)
13+
14+
const (
15+
signatureVersion = "0"
16+
signatureMethod = "HmacSHA256"
17+
timeFormat = "2006-01-02T15:04:05Z"
18+
)
19+
20+
type Signer interface {
21+
Sign(req *http.Request) error
22+
}
23+
24+
var _ Signer = (*V0Signer)(nil)
25+
26+
type V0Signer struct {
27+
accessKey, secretKey string
28+
}
29+
30+
func NewV0Signer(accessKey string, secretKey string) *V0Signer {
31+
return &V0Signer{accessKey: accessKey, secretKey: secretKey}
32+
}
33+
34+
func (voSigner V0Signer) Sign(req *http.Request) error {
35+
// http verb
36+
37+
curTime := time.Now()
38+
// set query parameter
39+
query := req.URL.Query()
40+
query.Set("AWSAccessKeyId", voSigner.accessKey)
41+
query.Set("SignatureVersion", signatureVersion)
42+
query.Set("SignatureMethod", signatureMethod)
43+
query.Set("Timestamp", curTime.UTC().Format(timeFormat))
44+
45+
req.Header.Del("Signature")
46+
47+
method := req.Method
48+
host := req.URL.Host
49+
path := req.URL.Path
50+
if path == "" {
51+
path = "/"
52+
}
53+
54+
// obtain all of the query keys and sort them
55+
queryKeys := make([]string, 0, len(query))
56+
for key := range query {
57+
queryKeys = append(queryKeys, key)
58+
}
59+
sort.Strings(queryKeys)
60+
61+
// build URL-encoded query keys and values
62+
queryKeysAndValues := make([]string, len(queryKeys))
63+
for i, key := range queryKeys {
64+
k := strings.Replace(url.QueryEscape(key), "+", "%20", -1)
65+
v := strings.Replace(url.QueryEscape(query.Get(key)), "+", "%20", -1)
66+
queryKeysAndValues[i] = k + "=" + v
67+
}
68+
69+
// join into one query string
70+
queryString := strings.Join(queryKeysAndValues, "&")
71+
72+
// build the canonical string for the V2 signature
73+
stringToSign := strings.Join([]string{
74+
method,
75+
host,
76+
path,
77+
queryString,
78+
}, "\n")
79+
80+
hash := hmac.New(sha256.New, []byte(voSigner.secretKey))
81+
hash.Write([]byte(stringToSign))
82+
signature := base64.StdEncoding.EncodeToString(hash.Sum(nil))
83+
query.Set("Signature", signature)
84+
85+
req.URL.RawQuery = query.Encode()
86+
return nil
87+
}

auth/aksk/verifier.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package aksk
2+
3+
import (
4+
"crypto/hmac"
5+
"crypto/sha256"
6+
"encoding/base64"
7+
"fmt"
8+
"net/http"
9+
"net/url"
10+
"sort"
11+
"strings"
12+
"time"
13+
)
14+
15+
type V0Verifier interface {
16+
Verify(req *http.Request) error
17+
}
18+
19+
type SkGetter interface {
20+
Get(ak string) (string, error)
21+
}
22+
23+
var _ V0Verifier = (*V0Verier)(nil)
24+
25+
type V0Verier struct {
26+
skGetter SkGetter
27+
}
28+
29+
func NewV0Verier(skGetter SkGetter) *V0Verier {
30+
return &V0Verier{skGetter: skGetter}
31+
}
32+
33+
func (v *V0Verier) Verify(req *http.Request) error {
34+
query := req.URL.Query()
35+
accessKey := query.Get("AWSAccessKeyId")
36+
if len(accessKey) == 0 {
37+
return fmt.Errorf("ak not found")
38+
}
39+
40+
secretKey, err := v.skGetter.Get(accessKey)
41+
if err != nil {
42+
return fmt.Errorf("access key not correct")
43+
}
44+
45+
sigMethod := query.Get("SignatureMethod")
46+
if sigMethod != signatureMethod {
47+
return fmt.Errorf("invalid signature method %s", sigMethod)
48+
}
49+
50+
sigVersion := query.Get("SignatureVersion")
51+
if sigVersion != signatureVersion {
52+
return fmt.Errorf("invalid signature method %s", sigMethod)
53+
}
54+
55+
reqTime := query.Get("Timestamp")
56+
t, err := time.Parse(timeFormat, reqTime)
57+
if err != nil {
58+
return fmt.Errorf("invalid timestamp %s", reqTime)
59+
}
60+
if t.Before(time.Now().Add(-5 * time.Minute)) {
61+
return fmt.Errorf("request is out of data")
62+
}
63+
expectSignature := query.Get("Signature")
64+
query.Del("Signature")
65+
66+
method := req.Method
67+
host := req.URL.Host
68+
path := req.URL.Path
69+
if path == "" {
70+
path = "/"
71+
}
72+
73+
// obtain all of the query keys and sort them
74+
queryKeys := make([]string, 0, len(query))
75+
for key := range query {
76+
queryKeys = append(queryKeys, key)
77+
}
78+
sort.Strings(queryKeys)
79+
80+
// build URL-encoded query keys and values
81+
queryKeysAndValues := make([]string, len(queryKeys))
82+
for i, key := range queryKeys {
83+
k := strings.Replace(url.QueryEscape(key), "+", "%20", -1)
84+
v := strings.Replace(url.QueryEscape(query.Get(key)), "+", "%20", -1)
85+
queryKeysAndValues[i] = k + "=" + v
86+
}
87+
88+
// join into one query string
89+
queryString := strings.Join(queryKeysAndValues, "&")
90+
91+
// build the canonical string for the V2 signature
92+
stringToSign := strings.Join([]string{
93+
method,
94+
host,
95+
path,
96+
queryString,
97+
}, "\n")
98+
hash := hmac.New(sha256.New, []byte(secretKey))
99+
hash.Write([]byte(stringToSign))
100+
actualSig := base64.StdEncoding.EncodeToString(hash.Sum(nil))
101+
if actualSig != expectSignature {
102+
return fmt.Errorf("signature not correct")
103+
}
104+
return nil
105+
}

0 commit comments

Comments
 (0)