mirror of
https://github.com/alibaba/higress.git
synced 2026-03-02 23:51:11 +08:00
189 lines
4.5 KiB
Go
189 lines
4.5 KiB
Go
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package oc
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type CookieData struct {
|
|
IDToken string
|
|
Secret string
|
|
Nonce []byte
|
|
CreatedAt time.Time
|
|
ExpiresOn time.Time
|
|
}
|
|
|
|
type CookieOption struct {
|
|
Name string
|
|
Domain string
|
|
Secret string
|
|
value string
|
|
Path string
|
|
SameSite string
|
|
Expire time.Time
|
|
Secure bool
|
|
HTTPOnly bool
|
|
}
|
|
|
|
// SerializeAndEncrypt 将 CookieData 对象序列化并加密为一个安全的cookie header
|
|
func SerializeAndEncryptCookieData(data *CookieData, keySecret string, cookieSettings *CookieOption) (string, error) {
|
|
return buildSecureCookieHeader(data, keySecret, cookieSettings)
|
|
}
|
|
|
|
// DeserializedeCookieData 将一个安全的cookie header解密并反序列化为 CookieData 对象
|
|
func DeserializedeCookieData(cookievalue string) (*CookieData, error) {
|
|
|
|
data, err := retrieveCookieData(cookievalue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if checkCookieExpiry(data) {
|
|
return nil, fmt.Errorf("cookie is expired")
|
|
}
|
|
return data, nil
|
|
}
|
|
func Set32Bytes(key string) string {
|
|
const desiredLength = 32
|
|
keyLength := len(key)
|
|
|
|
var adjustedKey string
|
|
if keyLength > desiredLength {
|
|
adjustedKey = key[:desiredLength]
|
|
} else if keyLength < desiredLength {
|
|
padding := strings.Repeat("0", desiredLength-keyLength)
|
|
adjustedKey = key + padding
|
|
} else {
|
|
adjustedKey = key
|
|
}
|
|
return adjustedKey
|
|
}
|
|
|
|
// 必须是16/24/32字节长
|
|
func Decrypt(ciphertext string, key string) (string, error) {
|
|
block, err := aes.NewCipher([]byte(key))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
decodedCiphertext, err := base64.URLEncoding.DecodeString(ciphertext)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(decodedCiphertext) < aes.BlockSize {
|
|
return "", fmt.Errorf("ciphertext is too short")
|
|
}
|
|
|
|
iv := decodedCiphertext[:aes.BlockSize]
|
|
decodedCiphertext = decodedCiphertext[aes.BlockSize:]
|
|
|
|
stream := cipher.NewCFBDecrypter(block, iv)
|
|
|
|
stream.XORKeyStream(decodedCiphertext, decodedCiphertext)
|
|
|
|
return string(decodedCiphertext), nil
|
|
}
|
|
|
|
func encrypt(plainText string, key string) (string, error) {
|
|
block, err := aes.NewCipher([]byte(key))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
ciphertext := make([]byte, aes.BlockSize+len(plainText))
|
|
iv := ciphertext[:aes.BlockSize]
|
|
|
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
stream := cipher.NewCFBEncrypter(block, iv)
|
|
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(plainText))
|
|
|
|
return base64.URLEncoding.EncodeToString(ciphertext), nil
|
|
}
|
|
|
|
func buildSecureCookieHeader(data *CookieData, keySecret string, cookieSettings *CookieOption) (string, error) {
|
|
jsonData, err := json.Marshal(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
encryptedValue, err := encrypt(string(jsonData), keySecret)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
encodedValue := url.QueryEscape(encryptedValue)
|
|
cookieSettings.value = encodedValue
|
|
|
|
return generateCookie(cookieSettings), nil
|
|
}
|
|
|
|
func retrieveCookieData(cookieValue string) (*CookieData, error) {
|
|
var data CookieData
|
|
err := json.Unmarshal([]byte(cookieValue), &data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &data, nil
|
|
}
|
|
|
|
func generateCookie(settings *CookieOption) string {
|
|
var secureFlag, httpOnlyFlag, sameSiteFlag string
|
|
if settings.Secure {
|
|
secureFlag = "Secure;"
|
|
}
|
|
|
|
if settings.HTTPOnly {
|
|
httpOnlyFlag = "HttpOnly;"
|
|
}
|
|
|
|
if settings.SameSite != "" {
|
|
sameSiteFlag = fmt.Sprintf("SameSite=%s;", settings.SameSite)
|
|
}
|
|
|
|
expiresStr := settings.Expire.Format(time.RFC1123)
|
|
maxAge := int(settings.Expire.Sub(time.Now()).Seconds())
|
|
|
|
cookie := fmt.Sprintf("%s=%s; Path=%s; Domain=%s; Expires=%s; Max-Age=%d; %s %s %s",
|
|
settings.Name,
|
|
settings.value,
|
|
settings.Path,
|
|
settings.Domain,
|
|
expiresStr,
|
|
maxAge,
|
|
secureFlag,
|
|
httpOnlyFlag,
|
|
sameSiteFlag,
|
|
)
|
|
return cookie
|
|
}
|
|
|
|
func checkCookieExpiry(data *CookieData) bool {
|
|
return data.ExpiresOn.Before(time.Now())
|
|
}
|