141 lines
3.6 KiB
Go
141 lines
3.6 KiB
Go
package jwt
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/ua-parser/uap-go/uaparser"
|
|
"net"
|
|
)
|
|
|
|
type TokenAuthorizationInfo struct {
|
|
Ip net.IP `json:"ip,omitempty"`
|
|
UserAgent string `json:"ua,omitempty"`
|
|
DeviceId string `json:"di,omitempty"`
|
|
CookieData string `json:"cd,omitempty"`
|
|
secret string
|
|
}
|
|
|
|
func (tai *TokenAuthorizationInfo) WithSecret(secret string) *TokenAuthorizationInfo {
|
|
tai.secret = secret
|
|
return tai
|
|
}
|
|
|
|
func (tai *TokenAuthorizationInfo) GetUserAgentInfo() *uaparser.Client {
|
|
parser := uaparser.NewFromSaved()
|
|
return parser.Parse(tai.UserAgent)
|
|
}
|
|
|
|
func (tai *TokenAuthorizationInfo) Encode() (string, error) {
|
|
payload, err := json.Marshal(tai)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
ciphertext, err := GetAESEncrypted(string(payload), tai.secret[0:32], tai.secret[0:16])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return ciphertext, nil
|
|
}
|
|
|
|
func DecodeTokenAuthorizationInfo(ciphertext string, secret string) (*TokenAuthorizationInfo, error) {
|
|
payload, err := GetAESDecrypted(ciphertext, secret[0:32], secret[0:16])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tai := TokenAuthorizationInfo{}
|
|
if err := json.Unmarshal(payload, &tai); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &tai, nil
|
|
}
|
|
|
|
func (tai *TokenAuthorizationInfo) Equal(requestAuthorizationInfo TokenAuthorizationInfo) bool {
|
|
// todo проработать логику проверки данных клиента. Возможно добавим finger print / http only cookie. Защита от потери токенов
|
|
if tai.Ip.String() == requestAuthorizationInfo.Ip.String() {
|
|
return true
|
|
}
|
|
|
|
uaClient := tai.GetUserAgentInfo()
|
|
requestUaClient := tai.GetUserAgentInfo()
|
|
if uaClient.UserAgent.Family == requestUaClient.UserAgent.Family &&
|
|
uaClient.UserAgent.Major == requestUaClient.UserAgent.Major &&
|
|
uaClient.Os.Family == requestUaClient.Os.Family &&
|
|
uaClient.Os.Major == requestUaClient.Os.Major &&
|
|
uaClient.Device.Family == requestUaClient.Device.Family &&
|
|
uaClient.Device.Model == requestUaClient.Device.Model &&
|
|
uaClient.Device.Brand == requestUaClient.Device.Brand {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func GetAESDecrypted(encrypted string, key string, iv string) ([]byte, error) {
|
|
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, err := aes.NewCipher([]byte(key))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(ciphertext)%aes.BlockSize != 0 {
|
|
return nil, fmt.Errorf("block size cant be zero")
|
|
}
|
|
|
|
mode := cipher.NewCBCDecrypter(block, []byte(iv))
|
|
mode.CryptBlocks(ciphertext, ciphertext)
|
|
ciphertext = PKCS5UnPadding(ciphertext)
|
|
|
|
return ciphertext, nil
|
|
}
|
|
|
|
// PKCS5UnPadding pads a certain blob of data with necessary data to be used in AES block cipher
|
|
func PKCS5UnPadding(src []byte) []byte {
|
|
length := len(src)
|
|
unpadding := int(src[length-1])
|
|
|
|
return src[:(length - unpadding)]
|
|
}
|
|
|
|
// GetAESEncrypted encrypts given text in AES 256 CBC
|
|
func GetAESEncrypted(plaintext string, key string, iv string) (string, error) {
|
|
var plainTextBlock []byte
|
|
length := len(plaintext)
|
|
|
|
if length%16 != 0 {
|
|
extendBlock := 16 - (length % 16)
|
|
plainTextBlock = make([]byte, length+extendBlock)
|
|
copy(plainTextBlock[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock))
|
|
} else {
|
|
plainTextBlock = make([]byte, length)
|
|
}
|
|
|
|
copy(plainTextBlock, plaintext)
|
|
block, err := aes.NewCipher([]byte(key))
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
ciphertext := make([]byte, len(plainTextBlock))
|
|
mode := cipher.NewCBCEncrypter(block, []byte(iv))
|
|
mode.CryptBlocks(ciphertext, plainTextBlock)
|
|
|
|
str := base64.StdEncoding.EncodeToString(ciphertext)
|
|
|
|
return str, nil
|
|
}
|