Files
summercms/modules/lagoon/laravel_decrypt.go
Jakub Zych 5e50b166ef refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
2026-09-28 02:21:02 +02:00

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
}