- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
106 lines
3.1 KiB
Go
106 lines
3.1 KiB
Go
package lagoon
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// laravelPayload is Laravel's Encrypter JSON body (iv/value/mac).
|
|
type laravelPayload struct {
|
|
IV string `json:"iv"`
|
|
Value string `json:"value"`
|
|
MAC string `json:"mac"`
|
|
}
|
|
|
|
// DecryptLaravelPayload decrypts a Laravel AES-256-CBC+HMAC "encrypted" payload
|
|
// (base64 JSON {iv,value,mac}) using the raw APP_KEY bytes.
|
|
//
|
|
// Cutover-import-only; never called from Encrypted's live Scan/Value path (D-10).
|
|
func DecryptLaravelPayload(payloadJSON string, appKey []byte) ([]byte, error) {
|
|
if len(appKey) != encryptedKeySize {
|
|
return nil, fmt.Errorf("lagoon: laravel payload key must be 32 bytes")
|
|
}
|
|
body, err := decodeLaravelJSON(payloadJSON)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var payload laravelPayload
|
|
if err := json.Unmarshal(body, &payload); err != nil {
|
|
return nil, fmt.Errorf("lagoon: laravel payload json: %w", err)
|
|
}
|
|
if payload.IV == "" || payload.Value == "" || payload.MAC == "" {
|
|
return nil, fmt.Errorf("lagoon: laravel payload missing iv, value, or mac")
|
|
}
|
|
wantMAC, err := hex.DecodeString(payload.MAC)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lagoon: laravel payload mac: %w", err)
|
|
}
|
|
mac := hmac.New(sha256.New, appKey)
|
|
_, _ = mac.Write([]byte(payload.IV + payload.Value))
|
|
gotMAC := mac.Sum(nil)
|
|
if !hmac.Equal(wantMAC, gotMAC) {
|
|
return nil, fmt.Errorf("lagoon: laravel payload mac mismatch")
|
|
}
|
|
iv, err := base64.StdEncoding.DecodeString(payload.IV)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lagoon: laravel payload iv: %w", err)
|
|
}
|
|
ct, err := base64.StdEncoding.DecodeString(payload.Value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lagoon: laravel payload value: %w", err)
|
|
}
|
|
if len(iv) != aes.BlockSize {
|
|
return nil, fmt.Errorf("lagoon: laravel payload iv length %d", len(iv))
|
|
}
|
|
if len(ct) == 0 || len(ct)%aes.BlockSize != 0 {
|
|
return nil, fmt.Errorf("lagoon: laravel payload ciphertext length %d", len(ct))
|
|
}
|
|
block, err := aes.NewCipher(appKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
plain := make([]byte, len(ct))
|
|
cipher.NewCBCDecrypter(block, iv).CryptBlocks(plain, ct)
|
|
return pkcs7Unpad(plain, aes.BlockSize)
|
|
}
|
|
|
|
func decodeLaravelJSON(payloadJSON string) ([]byte, error) {
|
|
s := strings.TrimSpace(payloadJSON)
|
|
if s == "" {
|
|
return nil, fmt.Errorf("lagoon: laravel payload is empty")
|
|
}
|
|
if decoded, err := base64.StdEncoding.DecodeString(s); err == nil {
|
|
trimmed := strings.TrimSpace(string(decoded))
|
|
if strings.HasPrefix(trimmed, "{") {
|
|
return []byte(trimmed), nil
|
|
}
|
|
}
|
|
if strings.HasPrefix(s, "{") {
|
|
return []byte(s), nil
|
|
}
|
|
return nil, fmt.Errorf("lagoon: laravel payload is not base64 JSON")
|
|
}
|
|
|
|
func pkcs7Unpad(b []byte, blockSize int) ([]byte, error) {
|
|
if blockSize <= 0 || len(b) == 0 || len(b)%blockSize != 0 {
|
|
return nil, fmt.Errorf("lagoon: laravel payload padding")
|
|
}
|
|
pad := int(b[len(b)-1])
|
|
if pad == 0 || pad > blockSize || pad > len(b) {
|
|
return nil, fmt.Errorf("lagoon: laravel payload padding")
|
|
}
|
|
for i := len(b) - pad; i < len(b); i++ {
|
|
if int(b[i]) != pad {
|
|
return nil, fmt.Errorf("lagoon: laravel payload padding")
|
|
}
|
|
}
|
|
return b[:len(b)-pad], nil
|
|
}
|