You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

signer.go 3.5 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package signer
  2. import (
  3. "crypto/ecdsa"
  4. "crypto/rand"
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "fmt"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "gitlink.org.cn/cloudream/common/utils/sort2"
  13. )
  14. var ErrSignatureMismatch = fmt.Errorf("signature mismatch")
  15. var ErrExpired = fmt.Errorf("signature expired")
  16. const (
  17. DateQueryKey = "x-jcs-date"
  18. ExpiresQueryKey = "x-jcs-expires"
  19. SignatureQueryKey = "x-jcs-signature"
  20. AccessKeyIDQueryKey = "x-jcs-access-key-id"
  21. AlgorithmQueryKey = "x-jcs-algorithm"
  22. AlgorithmValue = "ECDSA256"
  23. DateFormat = "20060102T150405Z"
  24. )
  25. type SignKey struct {
  26. privateKey *ecdsa.PrivateKey
  27. accessKeyID string
  28. }
  29. func PresignURL(key *SignKey, method string, u *url.URL, signingTime time.Time, expiresSeconds int) error {
  30. date := signingTime.Format(DateFormat)
  31. queries := u.Query()
  32. queries.Set(DateQueryKey, date)
  33. queries.Set(ExpiresQueryKey, fmt.Sprintf("%v", expiresSeconds))
  34. queries.Set(AccessKeyIDQueryKey, key.accessKeyID)
  35. queries.Set(AlgorithmQueryKey, AlgorithmValue)
  36. ss := makeStringToSign(method, u.Host, u.Path, queries)
  37. ssHash := sha256.Sum256([]byte(ss))
  38. sign, err := ecdsa.SignASN1(rand.Reader, key.privateKey, ssHash[:])
  39. if err != nil {
  40. return err
  41. }
  42. queries.Set(SignatureQueryKey, hex.EncodeToString(sign))
  43. u.RawQuery = queries.Encode()
  44. return nil
  45. }
  46. type VerifyKey struct {
  47. publicKey *ecdsa.PublicKey
  48. }
  49. func NewVerifyKey(publicKey *ecdsa.PublicKey) *VerifyKey {
  50. return &VerifyKey{publicKey: publicKey}
  51. }
  52. func GetAccessKeyID(u *url.URL) string {
  53. return u.Query().Get(AccessKeyIDQueryKey)
  54. }
  55. func VerifyPresigned(key *VerifyKey, method string, u *url.URL) error {
  56. queries := u.Query()
  57. dateStr := queries.Get(DateQueryKey)
  58. if dateStr == "" {
  59. return fmt.Errorf("missing date in query string")
  60. }
  61. date, err := time.Parse(DateFormat, dateStr)
  62. if err != nil {
  63. return fmt.Errorf("invalid date format: %v", err)
  64. }
  65. expiresStr := queries.Get(ExpiresQueryKey)
  66. if expiresStr == "" {
  67. return fmt.Errorf("missing expires in query string")
  68. }
  69. expires, err := strconv.Atoi(expiresStr)
  70. if err != nil {
  71. return fmt.Errorf("invalid expires format: %v", err)
  72. }
  73. if time.Now().After(date.Add(time.Duration(expires) * time.Second)) {
  74. return ErrExpired
  75. }
  76. signatureStr := queries.Get(SignatureQueryKey)
  77. signature, err := hex.DecodeString(signatureStr)
  78. if err != nil {
  79. return fmt.Errorf("invalid signature format: %v", err)
  80. }
  81. queries.Del(SignatureQueryKey)
  82. ss := makeStringToSign(method, u.Host, u.Path, queries)
  83. ssHash := sha256.Sum256([]byte(ss))
  84. if !ecdsa.VerifyASN1(key.publicKey, ssHash[:], signature) {
  85. return ErrSignatureMismatch
  86. }
  87. return nil
  88. }
  89. func makeStringToSign(method string, host string, path string, queries url.Values) string {
  90. type queryKv struct {
  91. Key string
  92. Values []string
  93. }
  94. var queryKvs []queryKv
  95. for k, vs := range queries {
  96. vs = sort2.SortAsc(vs)
  97. queryKvs = append(queryKvs, queryKv{Key: k, Values: vs})
  98. }
  99. queryKvs = sort2.Sort(queryKvs, func(left, right queryKv) int {
  100. return strings.Compare(left.Key, right.Key)
  101. })
  102. var str strings.Builder
  103. str.WriteString(method)
  104. str.WriteByte('\n')
  105. str.WriteString(host)
  106. str.WriteByte('\n')
  107. str.WriteString(path)
  108. str.WriteByte('\n')
  109. for _, kv := range queryKvs {
  110. str.WriteString(kv.Key)
  111. str.WriteByte('=')
  112. for i, v := range kv.Values {
  113. if i > 0 {
  114. str.WriteByte(',')
  115. }
  116. str.WriteString(v)
  117. }
  118. str.WriteByte('\n')
  119. }
  120. return str.String()
  121. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。