contentsecurity_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package security
  2. import (
  3. "crypto/hmac"
  4. "crypto/md5"
  5. "crypto/sha256"
  6. "encoding/base64"
  7. "fmt"
  8. "io"
  9. "log"
  10. "net/http"
  11. "os"
  12. "strconv"
  13. "strings"
  14. "testing"
  15. "time"
  16. "git.i2edu.net/i2/go-zero/core/codec"
  17. "git.i2edu.net/i2/go-zero/core/fs"
  18. "git.i2edu.net/i2/go-zero/rest/httpx"
  19. "github.com/stretchr/testify/assert"
  20. )
  21. const (
  22. pubKey = `-----BEGIN PUBLIC KEY-----
  23. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyeDYV2ieOtNDi6tuNtAbmUjN9
  24. pTHluAU5yiKEz8826QohcxqUKP3hybZBcm60p+rUxMAJFBJ8Dt+UJ6sEMzrf1rOF
  25. YOImVvORkXjpFU7sCJkhnLMs/kxtRzcZJG6ADUlG4GDCNcZpY/qELEvwgm2kCcHi
  26. tGC2mO8opFFFHTR0aQIDAQAB
  27. -----END PUBLIC KEY-----`
  28. priKey = `-----BEGIN RSA PRIVATE KEY-----
  29. MIICXQIBAAKBgQCyeDYV2ieOtNDi6tuNtAbmUjN9pTHluAU5yiKEz8826QohcxqU
  30. KP3hybZBcm60p+rUxMAJFBJ8Dt+UJ6sEMzrf1rOFYOImVvORkXjpFU7sCJkhnLMs
  31. /kxtRzcZJG6ADUlG4GDCNcZpY/qELEvwgm2kCcHitGC2mO8opFFFHTR0aQIDAQAB
  32. AoGAcENv+jT9VyZkk6karLuG75DbtPiaN5+XIfAF4Ld76FWVOs9V88cJVON20xpx
  33. ixBphqexCMToj8MnXuHJEN5M9H15XXx/9IuiMm3FOw0i6o0+4V8XwHr47siT6T+r
  34. HuZEyXER/2qrm0nxyC17TXtd/+TtpfQWSbivl6xcAEo9RRECQQDj6OR6AbMQAIDn
  35. v+AhP/y7duDZimWJIuMwhigA1T2qDbtOoAEcjv3DB1dAswJ7clcnkxI9a6/0RDF9
  36. 0IEHUcX9AkEAyHdcegWiayEnbatxWcNWm1/5jFnCN+GTRRFrOhBCyFr2ZdjFV4T+
  37. acGtG6omXWaZJy1GZz6pybOGy93NwLB93QJARKMJ0/iZDbOpHqI5hKn5mhd2Je25
  38. IHDCTQXKHF4cAQ+7njUvwIMLx2V5kIGYuMa5mrB/KMI6rmyvHv3hLewhnQJBAMMb
  39. cPUOENMllINnzk2oEd3tXiscnSvYL4aUeoErnGP2LERZ40/YD+mMZ9g6FVboaX04
  40. 0oHf+k5mnXZD7WJyJD0CQQDJ2HyFbNaUUHK+lcifCibfzKTgmnNh9ZpePFumgJzI
  41. EfFE5H+nzsbbry2XgJbWzRNvuFTOLWn4zM+aFyy9WvbO
  42. -----END RSA PRIVATE KEY-----`
  43. body = "hello world!"
  44. )
  45. var key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
  46. func TestContentSecurity(t *testing.T) {
  47. tests := []struct {
  48. name string
  49. mode string
  50. extraKey string
  51. extraSecret string
  52. extraTime string
  53. err error
  54. code int
  55. }{
  56. {
  57. name: "encrypted",
  58. mode: "1",
  59. },
  60. {
  61. name: "unencrypted",
  62. mode: "0",
  63. },
  64. {
  65. name: "bad content type",
  66. mode: "a",
  67. err: ErrInvalidContentType,
  68. },
  69. {
  70. name: "bad secret",
  71. mode: "1",
  72. extraSecret: "any",
  73. err: ErrInvalidSecret,
  74. },
  75. {
  76. name: "bad key",
  77. mode: "1",
  78. extraKey: "any",
  79. err: ErrInvalidKey,
  80. },
  81. {
  82. name: "bad time",
  83. mode: "1",
  84. extraTime: "any",
  85. code: httpx.CodeSignatureInvalidHeader,
  86. },
  87. }
  88. for _, test := range tests {
  89. test := test
  90. t.Run(test.name, func(t *testing.T) {
  91. t.Parallel()
  92. r, err := http.NewRequest(http.MethodPost, "http://localhost:3333/a/b?c=first&d=second",
  93. strings.NewReader(body))
  94. assert.Nil(t, err)
  95. timestamp := time.Now().Unix()
  96. sha := sha256.New()
  97. sha.Write([]byte(body))
  98. bodySign := fmt.Sprintf("%x", sha.Sum(nil))
  99. contentOfSign := strings.Join([]string{
  100. strconv.FormatInt(timestamp, 10),
  101. http.MethodPost,
  102. r.URL.Path,
  103. r.URL.RawQuery,
  104. bodySign,
  105. }, "\n")
  106. sign := hs256(key, contentOfSign)
  107. content := strings.Join([]string{
  108. "version=v1",
  109. "type=" + test.mode,
  110. fmt.Sprintf("key=%s", base64.StdEncoding.EncodeToString(key)) + test.extraKey,
  111. "time=" + strconv.FormatInt(timestamp, 10) + test.extraTime,
  112. }, "; ")
  113. encrypter, err := codec.NewRsaEncrypter([]byte(pubKey))
  114. if err != nil {
  115. log.Fatal(err)
  116. }
  117. output, err := encrypter.Encrypt([]byte(content))
  118. if err != nil {
  119. log.Fatal(err)
  120. }
  121. encryptedContent := base64.StdEncoding.EncodeToString(output)
  122. r.Header.Set("X-Content-Security", strings.Join([]string{
  123. fmt.Sprintf("key=%s", fingerprint(pubKey)),
  124. "secret=" + encryptedContent + test.extraSecret,
  125. "signature=" + sign,
  126. }, "; "))
  127. file, err := fs.TempFilenameWithText(priKey)
  128. assert.Nil(t, err)
  129. defer os.Remove(file)
  130. dec, err := codec.NewRsaDecrypter(file)
  131. assert.Nil(t, err)
  132. header, err := ParseContentSecurity(map[string]codec.RsaDecrypter{
  133. fingerprint(pubKey): dec,
  134. }, r)
  135. assert.Equal(t, test.err, err)
  136. if err != nil {
  137. return
  138. }
  139. assert.Equal(t, test.code, VerifySignature(r, header, time.Minute))
  140. })
  141. }
  142. }
  143. func fingerprint(key string) string {
  144. h := md5.New()
  145. io.WriteString(h, key)
  146. return base64.StdEncoding.EncodeToString(h.Sum(nil))
  147. }
  148. func hs256(key []byte, body string) string {
  149. h := hmac.New(sha256.New, key)
  150. io.WriteString(h, body)
  151. return base64.StdEncoding.EncodeToString(h.Sum(nil))
  152. }