|
|
|
package security
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/md5"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/zeromicro/go-zero/core/codec"
|
|
|
|
"github.com/zeromicro/go-zero/core/fs"
|
|
|
|
"github.com/zeromicro/go-zero/rest/httpx"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
pubKey = `-----BEGIN PUBLIC KEY-----
|
|
|
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyeDYV2ieOtNDi6tuNtAbmUjN9
|
|
|
|
pTHluAU5yiKEz8826QohcxqUKP3hybZBcm60p+rUxMAJFBJ8Dt+UJ6sEMzrf1rOF
|
|
|
|
YOImVvORkXjpFU7sCJkhnLMs/kxtRzcZJG6ADUlG4GDCNcZpY/qELEvwgm2kCcHi
|
|
|
|
tGC2mO8opFFFHTR0aQIDAQAB
|
|
|
|
-----END PUBLIC KEY-----`
|
|
|
|
priKey = `-----BEGIN RSA PRIVATE KEY-----
|
|
|
|
MIICXQIBAAKBgQCyeDYV2ieOtNDi6tuNtAbmUjN9pTHluAU5yiKEz8826QohcxqU
|
|
|
|
KP3hybZBcm60p+rUxMAJFBJ8Dt+UJ6sEMzrf1rOFYOImVvORkXjpFU7sCJkhnLMs
|
|
|
|
/kxtRzcZJG6ADUlG4GDCNcZpY/qELEvwgm2kCcHitGC2mO8opFFFHTR0aQIDAQAB
|
|
|
|
AoGAcENv+jT9VyZkk6karLuG75DbtPiaN5+XIfAF4Ld76FWVOs9V88cJVON20xpx
|
|
|
|
ixBphqexCMToj8MnXuHJEN5M9H15XXx/9IuiMm3FOw0i6o0+4V8XwHr47siT6T+r
|
|
|
|
HuZEyXER/2qrm0nxyC17TXtd/+TtpfQWSbivl6xcAEo9RRECQQDj6OR6AbMQAIDn
|
|
|
|
v+AhP/y7duDZimWJIuMwhigA1T2qDbtOoAEcjv3DB1dAswJ7clcnkxI9a6/0RDF9
|
|
|
|
0IEHUcX9AkEAyHdcegWiayEnbatxWcNWm1/5jFnCN+GTRRFrOhBCyFr2ZdjFV4T+
|
|
|
|
acGtG6omXWaZJy1GZz6pybOGy93NwLB93QJARKMJ0/iZDbOpHqI5hKn5mhd2Je25
|
|
|
|
IHDCTQXKHF4cAQ+7njUvwIMLx2V5kIGYuMa5mrB/KMI6rmyvHv3hLewhnQJBAMMb
|
|
|
|
cPUOENMllINnzk2oEd3tXiscnSvYL4aUeoErnGP2LERZ40/YD+mMZ9g6FVboaX04
|
|
|
|
0oHf+k5mnXZD7WJyJD0CQQDJ2HyFbNaUUHK+lcifCibfzKTgmnNh9ZpePFumgJzI
|
|
|
|
EfFE5H+nzsbbry2XgJbWzRNvuFTOLWn4zM+aFyy9WvbO
|
|
|
|
-----END RSA PRIVATE KEY-----`
|
|
|
|
body = "hello world!"
|
|
|
|
)
|
|
|
|
|
|
|
|
var key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
|
|
|
|
|
|
|
|
func TestContentSecurity(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
mode string
|
|
|
|
extraKey string
|
|
|
|
extraSecret string
|
|
|
|
extraTime string
|
|
|
|
err error
|
|
|
|
code int
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "encrypted",
|
|
|
|
mode: "1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unencrypted",
|
|
|
|
mode: "0",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bad content type",
|
|
|
|
mode: "a",
|
|
|
|
err: ErrInvalidContentType,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bad secret",
|
|
|
|
mode: "1",
|
|
|
|
extraSecret: "any",
|
|
|
|
err: ErrInvalidSecret,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bad key",
|
|
|
|
mode: "1",
|
|
|
|
extraKey: "any",
|
|
|
|
err: ErrInvalidKey,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bad time",
|
|
|
|
mode: "1",
|
|
|
|
extraTime: "any",
|
|
|
|
code: httpx.CodeSignatureInvalidHeader,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
r, err := http.NewRequest(http.MethodPost, "http://localhost:3333/a/b?c=first&d=second",
|
|
|
|
strings.NewReader(body))
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
timestamp := time.Now().Unix()
|
|
|
|
sha := sha256.New()
|
|
|
|
sha.Write([]byte(body))
|
|
|
|
bodySign := fmt.Sprintf("%x", sha.Sum(nil))
|
|
|
|
contentOfSign := strings.Join([]string{
|
|
|
|
strconv.FormatInt(timestamp, 10),
|
|
|
|
http.MethodPost,
|
|
|
|
r.URL.Path,
|
|
|
|
r.URL.RawQuery,
|
|
|
|
bodySign,
|
|
|
|
}, "\n")
|
|
|
|
sign := hs256(key, contentOfSign)
|
|
|
|
content := strings.Join([]string{
|
|
|
|
"version=v1",
|
|
|
|
"type=" + test.mode,
|
|
|
|
fmt.Sprintf("key=%s", base64.StdEncoding.EncodeToString(key)) + test.extraKey,
|
|
|
|
"time=" + strconv.FormatInt(timestamp, 10) + test.extraTime,
|
|
|
|
}, "; ")
|
|
|
|
|
|
|
|
encrypter, err := codec.NewRsaEncrypter([]byte(pubKey))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
output, err := encrypter.Encrypt([]byte(content))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
encryptedContent := base64.StdEncoding.EncodeToString(output)
|
|
|
|
r.Header.Set("X-Content-Security", strings.Join([]string{
|
|
|
|
fmt.Sprintf("key=%s", fingerprint(pubKey)),
|
|
|
|
"secret=" + encryptedContent + test.extraSecret,
|
|
|
|
"signature=" + sign,
|
|
|
|
}, "; "))
|
|
|
|
|
|
|
|
file, err := fs.TempFilenameWithText(priKey)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
defer os.Remove(file)
|
|
|
|
|
|
|
|
dec, err := codec.NewRsaDecrypter(file)
|
|
|
|
assert.Nil(t, err)
|
|
|
|
|
|
|
|
header, err := ParseContentSecurity(map[string]codec.RsaDecrypter{
|
|
|
|
fingerprint(pubKey): dec,
|
|
|
|
}, r)
|
|
|
|
assert.Equal(t, test.err, err)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, test.code, VerifySignature(r, header, time.Minute))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func fingerprint(key string) string {
|
|
|
|
h := md5.New()
|
|
|
|
io.WriteString(h, key)
|
|
|
|
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
|
|
|
}
|
|
|
|
|
|
|
|
func hs256(key []byte, body string) string {
|
|
|
|
h := hmac.New(sha256.New, key)
|
|
|
|
io.WriteString(h, body)
|
|
|
|
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
|
|
|
}
|