|
- package signer
-
- import (
- "crypto/ecdsa"
- "crypto/rand"
- "crypto/sha256"
- "crypto/x509"
- "encoding/hex"
- "fmt"
- "net/url"
- "strconv"
- "strings"
- "time"
-
- "gitlink.org.cn/cloudream/common/utils/sort2"
- )
-
- var ErrSignatureMismatch = fmt.Errorf("signature mismatch")
-
- var ErrExpired = fmt.Errorf("signature expired")
-
- const (
- DateQueryKey = "x-jcs-date"
- ExpiresQueryKey = "x-jcs-expires"
- SignatureQueryKey = "x-jcs-signature"
- AccessKeyIDQueryKey = "x-jcs-access-key-id"
- AlgorithmQueryKey = "x-jcs-algorithm"
- AlgorithmValue = "ECDSA256"
- DateFormat = "20060102T150405Z"
- )
-
- type SignKey struct {
- privateKey *ecdsa.PrivateKey
- accessKeyID string
- }
-
- func NewSignKey(privKey *ecdsa.PrivateKey) *SignKey {
- return &SignKey{
- privateKey: privKey,
- accessKeyID: CalcAccessKeyIDFromPublicKey(&privKey.PublicKey),
- }
- }
-
- func PresignURL(key *SignKey, method string, u *url.URL, signingTime time.Time, expiresSeconds int) error {
- date := signingTime.Format(DateFormat)
-
- queries := u.Query()
- queries.Set(DateQueryKey, date)
- queries.Set(ExpiresQueryKey, fmt.Sprintf("%v", expiresSeconds))
- queries.Set(AccessKeyIDQueryKey, key.accessKeyID)
- queries.Set(AlgorithmQueryKey, AlgorithmValue)
-
- ss := makeStringToSign(method, u.Host, u.Path, queries)
- ssHash := sha256.Sum256([]byte(ss))
-
- sign, err := ecdsa.SignASN1(rand.Reader, key.privateKey, ssHash[:])
- if err != nil {
- return err
- }
-
- queries.Set(SignatureQueryKey, hex.EncodeToString(sign))
- u.RawQuery = queries.Encode()
- return nil
- }
-
- type VerifyKey struct {
- publicKey *ecdsa.PublicKey
- }
-
- func NewVerifyKey(publicKey *ecdsa.PublicKey) *VerifyKey {
- return &VerifyKey{publicKey: publicKey}
- }
-
- func GetAccessKeyID(u *url.URL) string {
- return u.Query().Get(AccessKeyIDQueryKey)
- }
-
- func VerifyPresigned(key *VerifyKey, method string, u *url.URL) error {
- queries := u.Query()
-
- dateStr := queries.Get(DateQueryKey)
- if dateStr == "" {
- return fmt.Errorf("missing date in query string")
- }
-
- date, err := time.Parse(DateFormat, dateStr)
- if err != nil {
- return fmt.Errorf("invalid date format: %v", err)
- }
-
- expiresStr := queries.Get(ExpiresQueryKey)
- if expiresStr == "" {
- return fmt.Errorf("missing expires in query string")
- }
-
- expires, err := strconv.Atoi(expiresStr)
- if err != nil {
- return fmt.Errorf("invalid expires format: %v", err)
- }
-
- if time.Now().After(date.Add(time.Duration(expires) * time.Second)) {
- return ErrExpired
- }
-
- signatureStr := queries.Get(SignatureQueryKey)
- signature, err := hex.DecodeString(signatureStr)
- if err != nil {
- return fmt.Errorf("invalid signature format: %v", err)
- }
-
- queries.Del(SignatureQueryKey)
- ss := makeStringToSign(method, u.Host, u.Path, queries)
- ssHash := sha256.Sum256([]byte(ss))
-
- if !ecdsa.VerifyASN1(key.publicKey, ssHash[:], signature) {
- return ErrSignatureMismatch
- }
-
- return nil
- }
-
- func CalcAccessKeyIDFromPublicKey(publicKey *ecdsa.PublicKey) string {
- pubKeyDer, _ := x509.MarshalPKIXPublicKey(publicKey)
- pubHash := sha256.Sum256(pubKeyDer)
- return hex.EncodeToString(pubHash[:])
- }
-
- func makeStringToSign(method string, host string, path string, queries url.Values) string {
- type queryKv struct {
- Key string
- Values []string
- }
-
- var queryKvs []queryKv
- for k, vs := range queries {
- vs = sort2.SortAsc(vs)
- queryKvs = append(queryKvs, queryKv{Key: k, Values: vs})
- }
-
- queryKvs = sort2.Sort(queryKvs, func(left, right queryKv) int {
- return strings.Compare(left.Key, right.Key)
- })
-
- var str strings.Builder
- str.WriteString(method)
- str.WriteByte('\n')
- str.WriteString(host)
- str.WriteByte('\n')
- if !strings.HasPrefix(path, "/") {
- str.WriteByte('/')
- }
- str.WriteString(path)
- str.WriteByte('\n')
- for _, kv := range queryKvs {
- str.WriteString(kv.Key)
- str.WriteByte('=')
-
- for i, v := range kv.Values {
- if i > 0 {
- str.WriteByte(',')
- }
- str.WriteString(v)
- }
- str.WriteByte('\n')
- }
- return str.String()
- }
|