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.9 kB

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

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