This commit is contained in:
Mike 2026-02-06 07:08:19 +02:00
parent d2047fc42d
commit 4d298d8fcb
21 changed files with 70829 additions and 0 deletions

File diff suppressed because one or more lines are too long

8
CDM/monalisa.mld Normal file
View File

@ -0,0 +1,8 @@
{
"wasm_path": "libmonalisa-v3.0.6-browser.wat",
"metadata": {
"version": "3.0.6",
"name": "MonaLisa Browser Module",
"description": "MonaLisa WASM module for browser environment"
}
}

362
Monalisa/README.md Normal file
View File

@ -0,0 +1,362 @@
# MonaLisa
> **MonaLisa Content Decryption Module for Go**
A Go library and CLI tool for decrypting IQIYI DRM License Tickets using WebAssembly-based Content Decryption Modules (CDM).
[![Go Version](https://img.shields.io/badge/Go-1.21+-00ADD8?style=flat&logo=go)](https://go.dev/)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
## 📋 Overview
MonaLisa provides a secure framework for creating, managing, and using self-contained `.mld` (MonaLisa Device) files that encapsulate WebAssembly modules for DRM license processing. It supports time-based licensing with anti-tampering protection and HMAC-based signature verification.
### Key Features
- 🔐 **Secure License Processing**: Process DRM licenses and extract decryption keys
- 📦 **Self-Contained Devices**: Bundle WASM modules with metadata into portable `.mld` files
- ⏰ **Time-Based Licensing**: Support for expiring and permanent device licenses
- 🛡️ **Anti-Tampering Protection**: Advanced clock tampering detection
- 🗜️ **Compression Support**: Optional zlib compression for all components
- 📝 **HMAC Signatures**: Cryptographic verification of device integrity
- 🔍 **Device Verification**: Validate and inspect device files
## 🚀 Installation
### Prerequisites
- Go 1.21 or higher
- [WABT toolkit](https://github.com/WebAssembly/wabt) (optional, for WAT compilation)
### Install from Source
```bash
git clone https://github.com/ReiDoBrega/Monalisa.git
cd Monalisa
go build -o monalisa .
```
### Install as Go Module
```bash
go get github.com/ReiDoBrega/Monalisa
```
## 🎯 Quick Start
### Creating a Device
Create a self-contained device file from a WASM binary:
```bash
# Create a device with 30-day license
monalisa create-device --wasm module.wasm -o device.mld --valid-days 30
# Create a permanent device (no expiration)
monalisa create-device --wasm module.wasm -o device.mld --valid-days 0
# Create from WAT source with custom metadata
monalisa create-device \
--wat module.wat \
--js wrapper.js \
-o device.mld \
--version "4.0.0" \
--name "Custom Module" \
--description "My custom DRM module" \
--valid-days 90
```
### Processing Licenses
Extract decryption keys from a license:
```bash
# Basic usage
monalisa license --device device.mld "AIUACgMAAAAAAAAAAAQChgACATADhwAnAgAg..."
# Save to JSON file
monalisa license --device device.mld --json keys.json "LICENSE_DATA"
# Quiet mode (keys only)
monalisa license --device device.mld --quiet "LICENSE_DATA"
```
### Checking License Information
View device license status and expiration:
```bash
monalisa license-info device.mld
```
Output example:
```
==================================================
DEVICE LICENSE INFORMATION
==================================================
Device ID: 7f3d2a1b9c8e4f5a6d7e8f9a0b1c2d3e
Created: 2026-01-27 10:30:00
Expires: 2026-02-26 10:30:00
Max Duration: 30 days
✓ Status: VALID
Days since creation: 0 day(s)
Time remaining: 30 day(s) and 0 hour(s)
```
### Verifying Device Integrity
```bash
monalisa verify-device device.mld
```
## 📚 Library Usage
### Creating and Using CDM Sessions
```go
package main
import (
"fmt"
"monalisa/pkg/cdm"
"monalisa/pkg/license"
"monalisa/pkg/module"
"monalisa/pkg/types"
)
func main() {
// Load device file
mod, err := module.Load("device.mld")
if err != nil {
panic(err)
}
// Initialize CDM
cdmInstance := cdm.FromModule(mod)
// Open session
sessionID, err := cdmInstance.Open()
if err != nil {
panic(err)
}
defer cdmInstance.Close(sessionID)
// Parse license
lic := license.New("base64_license_data")
if err := cdmInstance.ParseLicense(sessionID, lic); err != nil {
panic(err)
}
// Extract keys
keys, err := cdmInstance.GetKeys(sessionID, types.KeyTypeContent)
if err != nil {
panic(err)
}
for _, key := range keys {
fmt.Printf("KID: %x\n", key.KID)
fmt.Printf("Key: %x\n", key.Key)
}
}
```
### Building Devices Programmatically
```go
package main
import (
"monalisa/pkg/module"
)
func main() {
builder := module.NewBuilder()
// Configure metadata
builder.WithMetadata("1.0.0", "My Module", "Description")
// Load WASM
builder.WithWASM("module.wasm")
// Optional: Add JavaScript wrapper
builder.WithJS("wrapper.js")
// Enable compression
builder.WithCompression(true)
// Set 60-day license
builder.WithLicense(60)
// Build device
if err := builder.Build("output.mld"); err != nil {
panic(err)
}
}
```
## 🏗️ Architecture
### Project Structure
```
monalisa/
├── cmd/ # CLI commands
│ ├── create_device.go # Device creation
│ ├── license.go # License processing
│ ├── license_info.go # License inspection
│ ├── verify_device.go # Device verification
│ └── root.go # CLI root
├── pkg/
│ ├── cdm/ # Content Decryption Module
│ │ ├── cdm.go # CDM manager
│ │ └── session.go # WASM session handling
│ ├── license/ # License handling
│ │ └── license.go
│ ├── module/ # Module loader & builder
│ │ ├── module.go
│ │ └── signature.go # HMAC signing
│ ├── types/ # Type definitions
│ │ └── types.go
│ └── exceptions/ # Error handling
│ └── exceptions.go
└── main.go # Entry point
```
### Device File Format (.mld)
MonaLisa devices use a custom binary format:
```
[HEADER - 32 bytes]
Magic: "MLD\x00" (4 bytes)
Version: Major.Minor (2 bytes)
Flags: Compression/Signature flags (2 bytes)
Metadata Size: (4 bytes)
WASM Size: (4 bytes)
JS Size: (4 bytes)
Checksum: CRC32 (4 bytes)
Sig Size: (4 bytes)
Reserved: (4 bytes)
[PAYLOAD]
[Signature - 88 bytes, optional]
Device ID: 32 bytes (hex)
Creation Time: 8 bytes (obfuscated)
Expiry Time: 8 bytes (obfuscated)
Max Duration: 8 bytes (obfuscated)
HMAC-SHA256: 32 bytes
[Metadata - JSON, optionally compressed]
[WASM Module - optionally compressed]
[JavaScript - optionally compressed]
```
### Anti-Tampering Protection
The licensing system includes multiple layers of clock tampering detection:
1. **Creation Time Check**: Prevents using devices before creation date
2. **Expiry Time Check**: Enforces license expiration
3. **Maximum Duration Check**: Validates total elapsed time since creation
4. **HMAC Verification**: Ensures data integrity
## 🔧 Configuration
### Security Configuration
The signature system uses a master key for HMAC generation. **Important**: Change this in production!
```go
// In pkg/signature/signature.go
const masterKey = "your-secure-master-key-here"
const timeObfuscationKey = "your-secure-time-obfuscation-key-here"
```
### Compression Levels
Compression uses zlib with best compression (level 9) by default. This can significantly reduce file sizes:
- Metadata: ~60-80% reduction
- WASM: ~40-60% reduction
- JavaScript: ~50-70% reduction
## 📖 API Reference
### Module Functions
- `module.Load(path string)`: Load a device file
- `module.NewBuilder()`: Create a device builder
### CDM Functions
- `cdm.FromModule(mod *Module)`: Initialize CDM from module
- `cdm.Open()`: Open a new session
- `cdm.Close(sessionID)`: Close a session
- `cdm.ParseLicense(sessionID, license)`: Process a license
- `cdm.GetKeys(sessionID, keyType)`: Extract decryption keys
### License Functions
- `license.New(data string)`: Create license from base64 or raw data
## 🛠️ Development
### Running Tests
```bash
go test ./...
```
### Building
```bash
# Build for current platform
go build -o monalisa .
# Build for multiple platforms
GOOS=linux GOARCH=amd64 go build -o monalisa-linux-amd64
GOOS=darwin GOARCH=arm64 go build -o monalisa-darwin-arm64
GOOS=windows GOARCH=amd64 go build -o monalisa-windows-amd64.exe
```
## ⚠️ Security Considerations
1. **Master Key**: Change the default master key in `signature.go` for production use
2. **License Duration**: Choose appropriate validity periods for your use case
3. **WASM Validation**: Ensure WASM modules are from trusted sources
4. **Key Storage**: Handle extracted keys securely in production environments
## 🤝 Contributing
Contributions are welcome! Please follow these guidelines:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
- [Wasmtime-go](https://github.com/bytecodealliance/wasmtime-go) - WebAssembly runtime
- [Cobra](https://github.com/spf13/cobra) - CLI framework
- [Google UUID](https://github.com/google/uuid) - UUID generation
## 📞 Support
For issues, questions, or contributions, please:
- Open an issue on [GitHub Issues](https://github.com/ReiDoBrega/Monalisa/issues)
- Visit the main repository: [github.com/ReiDoBrega/Monalisa](https://github.com/ReiDoBrega/Monalisa)
---
**Version**: 0.1.2
**Author**: ReiDoBrega
**Last Updated**: February 2026

View File

@ -0,0 +1,115 @@
package cmd
import (
"monalisa/pkg/logger"
"monalisa/pkg/module"
"github.com/spf13/cobra"
)
var (
watPath string
wasmPath string
jsPath string
outputPath string
noCompress bool
moduleVersion string
moduleName string
moduleDesc string
validDays int
)
var createDeviceCmd = &cobra.Command{
Use: "create-device",
Short: "Build a self-contained binary .mld device file",
Long: `Build a self-contained binary .mld device file.
Examples:
# From WASM binary:
monalisa create-device --wasm module.wasm -o device.mld
# From WAT source with JS wrapper:
monalisa create-device --wat module.wat --js wrapper.js -o device.mld
# With custom metadata and 30-day license:
monalisa create-device --wasm module.wasm -o device.mld \
--version "4.0.0" --name "Custom Module" --valid-days 30
# Permanent license (no expiration):
monalisa create-device --wasm module.wasm -o device.mld --valid-days 0`,
Run: runCreateDevice,
}
func init() {
createDeviceCmd.Flags().StringVar(&watPath, "wat", "", "Input WAT (WebAssembly Text) file")
createDeviceCmd.Flags().StringVar(&wasmPath, "wasm", "", "Input WASM (WebAssembly Binary) file")
createDeviceCmd.Flags().StringVar(&jsPath, "js", "", "JavaScript wrapper file (optional)")
createDeviceCmd.Flags().StringVarP(&outputPath, "output", "o", "", "Output .mld device file path (required)")
createDeviceCmd.Flags().BoolVar(&noCompress, "no-compress", false, "Disable compression")
createDeviceCmd.Flags().StringVar(&moduleVersion, "version", "3.0.6", "Module version")
createDeviceCmd.Flags().StringVar(&moduleName, "name", "MonaLisa Browser Module", "Module name")
createDeviceCmd.Flags().StringVar(&moduleDesc, "description", "", "Module description")
createDeviceCmd.Flags().IntVar(&validDays, "valid-days", 30, "License validity in days (0 = no expiration)")
createDeviceCmd.MarkFlagRequired("output")
}
func runCreateDevice(cmd *cobra.Command, args []string) {
if watPath == "" && wasmPath == "" {
logger.Fatal("Must specify either --wat or --wasm")
}
builder := module.NewBuilder()
logger.Info("Configuring module metadata")
builder.WithMetadata(moduleVersion, moduleName, moduleDesc)
logger.Debug(" Module: %s v%s", moduleName, moduleVersion)
if moduleDesc != "" {
logger.Debug(" Description: %s", moduleDesc)
}
if watPath != "" {
logger.Info("Loading WAT source: %s", watPath)
if err := builder.WithWAT(watPath); err != nil {
logger.Fatal("Failed to load WAT: %v", err)
}
} else if wasmPath != "" {
logger.Info("Loading WASM binary: %s", wasmPath)
if err := builder.WithWASM(wasmPath); err != nil {
logger.Fatal("Failed to load WASM: %v", err)
}
}
if jsPath != "" {
logger.Info("Loading JavaScript wrapper: %s", jsPath)
if err := builder.WithJS(jsPath); err != nil {
logger.Warning("Failed to load JS: %v", err)
}
}
builder.WithCompression(!noCompress)
if noCompress {
logger.Info("Compression disabled")
} else {
logger.Info("Compression enabled (level 9)")
}
// Configura a licença
if validDays > 0 {
logger.Info("Configuring license: %d days validity", validDays)
builder.WithLicense(validDays)
} else {
logger.Info("No license expiration (permanent)")
}
logger.Info("Building device file...")
if err := builder.Build(outputPath); err != nil {
logger.Fatal("Build failed: %v", err)
}
logger.Success("Device file created successfully: %s", outputPath)
if validDays > 0 {
logger.Warning("This device will expire in %d days", validDays)
}
logger.Info("Use: monalisa license --device %s <license_data>", outputPath)
}

163
Monalisa/cmd/license.go Normal file
View File

@ -0,0 +1,163 @@
package cmd
import (
"encoding/hex"
"encoding/json"
"os"
"monalisa/pkg/cdm"
"monalisa/pkg/license"
"monalisa/pkg/logger"
"monalisa/pkg/module"
"monalisa/pkg/types"
"github.com/spf13/cobra"
)
var (
devicePath string
keyType string
jsonOutput string
quiet bool
)
type KeyOutput struct {
KID string `json:"kid"`
Key string `json:"key"`
Type string `json:"type"`
}
type LicenseOutput struct {
Success bool `json:"success"`
Keys []KeyOutput `json:"keys"`
Count int `json:"count"`
}
var licenseCmd = &cobra.Command{
Use: "license LICENSE_DATA",
Short: "Process a MonaLisa encoded license and extract decryption keys",
Long: `Process a MonaLisa encoded license and extract decryption keys.
Examples:
# Output to console
monalisa license "AIUACgMAAAAAAAAAAAQChgACATADhwAnAgAg..." --device device.mld
# Output to JSON file
monalisa license "AIUACgMAAAAAAAAAAAQChgACATADhwAnAgAg..." --device device.mld --json keys.json
# Quiet mode (only output keys)
monalisa license "AIUACgMAAAAAAAAAAAQChgACATADhwAnAgAg..." --device device.mld --json keys.json --quiet `,
Args: cobra.ExactArgs(1),
Run: runLicense,
}
func init() {
licenseCmd.Flags().StringVarP(&devicePath, "device", "d", "", "Path to MonaLisa device file (.mld) (required)")
licenseCmd.Flags().StringVarP(&keyType, "key-type", "t", "CONTENT", "Filter keys by type (CONTENT, FULL)")
licenseCmd.Flags().StringVarP(&jsonOutput, "json", "j", "", "Output keys to JSON file")
licenseCmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "Quiet mode - only output keys")
licenseCmd.MarkFlagRequired("device")
}
func runLicense(cmd *cobra.Command, args []string) {
licenseData := args[0]
// Se quiet mode, desabilitar todos os logs exceto erros
if quiet {
logger.SetLevel(logger.ERROR)
}
if _, err := os.Stat(devicePath); os.IsNotExist(err) {
logger.Fatal("Module file not found: %s", devicePath)
}
logger.Info("Loading module: %s", devicePath)
mod, err := module.Load(devicePath)
if err != nil {
logger.Fatal("Failed to load module: %v", err)
}
logger.Success("Loaded module successfully")
logger.Info("Initializing CDM...")
cdmInstance := cdm.FromModule(mod)
logger.Info("Opening CDM session...")
sessionID, err := cdmInstance.Open()
if err != nil {
logger.Fatal("Failed to open session: %v", err)
}
logger.Debug("Session opened: %s", sessionID)
defer cdmInstance.Close(sessionID)
logger.Info("Processing license data...")
lic := license.New(licenseData)
logger.Info("Parsing license and extracting keys...")
if err := cdmInstance.ParseLicense(sessionID, lic); err != nil {
logger.Fatal("Failed to parse license: %v", err)
}
logger.Success("License parsed successfully")
keys, err := cdmInstance.GetKeys(sessionID, types.KeyTypeContent)
if err != nil {
logger.Fatal("Failed to get keys: %v", err)
}
if len(keys) == 0 {
logger.Warning("No keys found in license")
os.Exit(1)
}
// Se JSON output foi especificado
if jsonOutput != "" {
outputKeys := make([]KeyOutput, len(keys))
for i, key := range keys {
outputKeys[i] = KeyOutput{
KID: hex.EncodeToString(key.KID),
Key: hex.EncodeToString(key.Key),
Type: key.Type.String(),
}
}
output := LicenseOutput{
Success: true,
Keys: outputKeys,
Count: len(keys),
}
jsonData, err := json.MarshalIndent(output, "", " ")
if err != nil {
logger.Fatal("Failed to marshal JSON: %v", err)
}
if err := os.WriteFile(jsonOutput, jsonData, 0644); err != nil {
logger.Fatal("Failed to write JSON file: %v", err)
}
logger.Success("Keys saved to: %s", jsonOutput)
}
// Output para console
logger.Info("Found %d key(s):", len(keys))
// Se quiet mode, apenas imprime as chaves sem logger
if quiet {
for _, key := range keys {
if keyType == "CONTENT" {
logger.Info("%x", key.Key)
} else {
logger.Info("%x:%x", key.KID, key.Key)
}
}
} else {
for _, key := range keys {
if keyType == "CONTENT" {
logger.Info("%x", key.Key)
} else {
logger.Info("%x:%x", key.KID, key.Key)
}
}
}
logger.Success("Session closed successfully")
}

View File

@ -0,0 +1,100 @@
package cmd
import (
"fmt"
"os"
"time"
"monalisa/pkg/logger"
"monalisa/pkg/module"
"github.com/spf13/cobra"
)
var licenseInfoCmd = &cobra.Command{
Use: "license-info DEVICE_PATH",
Short: "Display license information for a device file",
Long: `Display license information for a device file including expiry date and validity status.
Example:
monalisa license-info device.mld`,
Args: cobra.ExactArgs(1),
Run: runLicenseInfo,
}
func runLicenseInfo(cmd *cobra.Command, args []string) {
devicePath := args[0]
if _, err := os.Stat(devicePath); os.IsNotExist(err) {
logger.Fatal("Device file not found: %s", devicePath)
}
logger.Info("Loading device: %s", devicePath)
mod, err := module.Load(devicePath)
if err != nil {
logger.Fatal("Failed to load device: %v", err)
}
sig := mod.Signature()
separator := "//"
fmt.Println("\n" + separator)
fmt.Println("+ DEVICE LICENSE INFORMATION")
fmt.Println(separator)
if sig == nil {
logger.Success("License: PERMANENT (No expiration)")
fmt.Println(" This device has no time restrictions")
} else {
fmt.Printf("\n Device ID: %s\n", sig.DeviceID)
fmt.Printf(" Created: %s\n", sig.CreationTime.Format("2006-01-02 15:04:05"))
fmt.Printf(" Expires: %s\n", sig.ExpiryTime.Format("2006-01-02 15:04:05"))
fmt.Printf(" Max Duration: %d days\n", sig.MaxValidDuration/(24*60*60))
now := time.Now()
// Verifica adulteração de relógio
if now.Before(sig.CreationTime) {
daysBackwards := int(sig.CreationTime.Sub(now).Hours() / 24)
fmt.Printf("\n")
logger.Warning("CLOCK TAMPERING DETECTED!")
fmt.Printf(" System time is %d day(s) BEFORE device creation\n", daysBackwards)
fmt.Printf(" Device cannot be used until system clock is corrected\n")
} else if now.After(sig.ExpiryTime) {
daysExpired := int(now.Sub(sig.ExpiryTime).Hours() / 24)
fmt.Printf("\n")
logger.Failure("Status: EXPIRED")
fmt.Printf(" Expired %d day(s) ago\n", daysExpired)
} else {
// Verifica se excedeu a duração máxima
elapsedTime := now.Unix() - sig.GetCreationTimestamp()
if elapsedTime > sig.MaxValidDuration {
fmt.Printf("\n")
logger.Failure("Status: MAXIMUM DURATION EXCEEDED")
fmt.Printf(" Time since creation: %d days (max: %d days)\n",
elapsedTime/(24*60*60),
sig.MaxValidDuration/(24*60*60))
logger.Warning("Possible clock tampering detected")
} else {
daysRemaining := int(sig.ExpiryTime.Sub(now).Hours() / 24)
hoursRemaining := int(sig.ExpiryTime.Sub(now).Hours()) % 24
daysSinceCreation := int(now.Sub(sig.CreationTime).Hours() / 24)
fmt.Printf("\n")
logger.Success("Status: VALID")
fmt.Printf(" Days since creation: %d day(s)\n", daysSinceCreation)
fmt.Printf(" Time remaining: %d day(s) and %d hour(s)\n", daysRemaining, hoursRemaining)
if daysRemaining <= 7 {
logger.Warning("License expiring soon!")
}
}
}
}
fmt.Println("\n" + separator)
}
func init() {
// Esta função será chamada pelo root.go!
}

47
Monalisa/cmd/root.go Normal file
View File

@ -0,0 +1,47 @@
package cmd
import (
"monalisa/pkg/logger"
"github.com/spf13/cobra"
)
var (
rootCmd *cobra.Command
verbose bool
noColor bool
timestamp bool
)
func Execute(version string) error {
rootCmd = &cobra.Command{
Use: "monalisa",
Short: "MonaLisa Content Decryption Module for Go",
Long: "A Go library to decrypt IQIYI DRM License Ticket",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Configurar logger baseado nas flags
if verbose {
logger.SetLevel(logger.DEBUG)
}
logger.SetColored(!noColor)
logger.SetTimestamp(timestamp)
},
Run: func(cmd *cobra.Command, args []string) {
logger.Info("monalisa version %s", version)
logger.Info("MonaLisa Content Decryption Module for Go")
logger.Info("Run 'monalisa --help' for help")
},
}
// Flags globais
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")
rootCmd.PersistentFlags().BoolVar(&noColor, "no-color", false, "Disable colored output")
rootCmd.PersistentFlags().BoolVar(&timestamp, "timestamp", false, "Add timestamp to log messages")
rootCmd.AddCommand(licenseCmd)
rootCmd.AddCommand(createDeviceCmd)
rootCmd.AddCommand(verifyDeviceCmd)
rootCmd.AddCommand(licenseInfoCmd)
return rootCmd.Execute()
}

View File

@ -0,0 +1,33 @@
package cmd
import (
"monalisa/pkg/logger"
"monalisa/pkg/module"
"github.com/spf13/cobra"
)
var verifyDeviceCmd = &cobra.Command{
Use: "verify-device DEVICE_PATH",
Short: "Verify the integrity of an .mld device file",
Long: `Verify the integrity of an .mld device file.
Example:
monalisa verify-device device.mld`,
Args: cobra.ExactArgs(1),
Run: runVerifyDevice,
}
func runVerifyDevice(cmd *cobra.Command, args []string) {
devicePath := args[0]
logger.Info("Verifying device: %s", devicePath)
builder := module.NewBuilder()
if err := builder.Verify(devicePath); err != nil {
logger.Failure("Verification failed: %v", err)
return
}
logger.Success("Device file verification PASSED")
}

14
Monalisa/go.mod Normal file
View File

@ -0,0 +1,14 @@
module monalisa
go 1.21
require (
github.com/bytecodealliance/wasmtime-go/v25 v25.0.0
github.com/google/uuid v1.6.0
github.com/spf13/cobra v1.8.0
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)

21
Monalisa/go.sum Normal file
View File

@ -0,0 +1,21 @@
github.com/bytecodealliance/wasmtime-go/v25 v25.0.0 h1:ZTn4Ho+srrk0466ugqPfTDCITczsWdT48A0ZMA/TpRU=
github.com/bytecodealliance/wasmtime-go/v25 v25.0.0/go.mod h1:8mMIYQ92CpVDwXPIb6udnhtFGI3vDZ/937cGeQr5I68=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

BIN
Monalisa/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

16
Monalisa/main.go Normal file
View File

@ -0,0 +1,16 @@
package main
import (
"monalisa/cmd"
"monalisa/pkg/logger"
)
const version = "0.1.2"
func main() {
logger.Init(logger.INFO, true)
if err := cmd.Execute(version); err != nil {
logger.Fatal("Application error: %v", err)
}
}

78
Monalisa/pkg/cdm/cdm.go Normal file
View File

@ -0,0 +1,78 @@
package cdm
import (
"sync"
"monalisa/pkg/exceptions"
"monalisa/pkg/license"
"monalisa/pkg/module"
"monalisa/pkg/types"
"github.com/google/uuid"
)
type CDM struct {
module *module.Module
sessions map[string]*Session
mu sync.RWMutex
}
func FromModule(mod *module.Module) *CDM {
return &CDM{
module: mod,
sessions: make(map[string]*Session),
}
}
func (c *CDM) Open() (string, error) {
sessionID := uuid.New().String()
store := c.module.CreateStore()
session := NewSession(sessionID, c.module, store)
if err := session.Initialize(); err != nil {
return "", err
}
c.mu.Lock()
c.sessions[sessionID] = session
c.mu.Unlock()
return sessionID, nil
}
func (c *CDM) Close(sessionID string) {
c.mu.Lock()
defer c.mu.Unlock()
if session, ok := c.sessions[sessionID]; ok {
session.Cleanup()
delete(c.sessions, sessionID)
}
}
func (c *CDM) ParseLicense(sessionID string, lic *license.License) error {
session, err := c.getSession(sessionID)
if err != nil {
return err
}
return session.ParseLicense(lic)
}
func (c *CDM) GetKeys(sessionID string, keyType types.KeyType) ([]*types.Key, error) {
session, err := c.getSession(sessionID)
if err != nil {
return nil, err
}
return session.GetKeys(keyType), nil
}
func (c *CDM) getSession(sessionID string) (*Session, error) {
c.mu.RLock()
defer c.mu.RUnlock()
session, ok := c.sessions[sessionID]
if !ok {
return nil, exceptions.NewSessionError("Session not found: %s", sessionID)
}
return session, nil
}

359
Monalisa/pkg/cdm/session.go Normal file
View File

@ -0,0 +1,359 @@
package cdm
import (
"encoding/base64"
"encoding/hex"
"regexp"
"monalisa/pkg/exceptions"
"monalisa/pkg/license"
"monalisa/pkg/module"
"monalisa/pkg/types"
"github.com/bytecodealliance/wasmtime-go/v25"
"github.com/google/uuid"
)
const (
DynamicBase = 6065008
DynamicTopPtr = 821968
LicenseKeyOffset = 0x5C8C0C
LicenseKeyLength = 16
)
type Session struct {
sessionID string
module *module.Module
store *wasmtime.Store
instance *wasmtime.Instance
memory *wasmtime.Memory
exports map[string]*wasmtime.Func
ctx int32
keys []*types.Key
}
func NewSession(sid string, mod *module.Module, st *wasmtime.Store) *Session {
return &Session{
sessionID: sid,
module: mod,
store: st,
exports: make(map[string]*wasmtime.Func),
keys: make([]*types.Key, 0),
}
}
func (s *Session) Initialize() error {
memType := wasmtime.NewMemoryType(256, true, 256)
memory, err := wasmtime.NewMemory(s.store, memType)
if err != nil {
return exceptions.NewSessionError("Failed to create memory: %v", err)
}
s.memory = memory
s.writeI32(DynamicTopPtr, DynamicBase)
imports, err := s.buildImports()
if err != nil {
return exceptions.NewSessionError("Failed to build imports: %v", err)
}
inst, err := wasmtime.NewInstance(s.store, s.module.WASMModule(), imports)
if err != nil {
return exceptions.NewSessionError("Failed to create instance: %v", err)
}
s.instance = inst
s.exports["___wasm_call_ctors"] = s.getExport("s").Func()
s.exports["_monalisa_context_alloc"] = s.getExport("D").Func()
s.exports["monalisa_set_license"] = s.getExport("F").Func()
s.exports["stackAlloc"] = s.getExport("N").Func()
s.exports["stackSave"] = s.getExport("L").Func()
s.exports["stackRestore"] = s.getExport("M").Func()
s.exports["___wasm_call_ctors"].Call(s.store)
result, _ := s.exports["_monalisa_context_alloc"].Call(s.store)
s.ctx = result.(int32)
return nil
}
func (s *Session) ParseLicense(lic *license.License) error {
licStr := lic.Base64()
ret, err := s.ccallInt("monalisa_set_license", s.ctx, licStr, len(licStr), "0")
if err != nil {
return exceptions.NewLicenseError("Failed to call monalisa_set_license: %v", err)
}
if ret != 0 {
return exceptions.NewLicenseError("License validation failed with code: %d", ret)
}
keyHex, err := s.extractLicenseKey()
if err != nil {
return err
}
keyBytes, _ := hex.DecodeString(keyHex)
decoded, _ := base64.StdEncoding.DecodeString(licStr)
re := regexp.MustCompile(`DCID-[A-Z0-9]+-[A-Z0-9]+-\d{8}-\d{6}-[A-Z0-9]+-\d{10}-[A-Z0-9]+`)
match := re.Find(decoded)
var kid uuid.UUID
if match != nil {
kid = uuid.NewSHA1(uuid.NameSpaceDNS, match)
} else {
kid = uuid.Nil
}
key := &types.Key{
KID: kid[:],
Key: keyBytes,
Type: types.KeyTypeContent,
}
s.keys = append(s.keys, key)
return nil
}
func (s *Session) GetKeys(keyType types.KeyType) []*types.Key {
result := make([]*types.Key, 0)
for _, key := range s.keys {
if key.Type == keyType {
result = append(result, key)
}
}
return result
}
func (s *Session) Cleanup() {
s.keys = nil
s.instance = nil
s.memory = nil
}
func (s *Session) extractLicenseKey() (string, error) {
data := s.memory.UnsafeData(s.store)
size := s.memory.DataSize(s.store)
if LicenseKeyOffset+LicenseKeyLength > size {
return "", exceptions.NewLicenseError("License key offset beyond memory bounds")
}
keyBytes := make([]byte, LicenseKeyLength)
copy(keyBytes, data[LicenseKeyOffset:LicenseKeyOffset+LicenseKeyLength])
return hex.EncodeToString(keyBytes), nil
}
func (s *Session) ccallInt(funcName string, args ...interface{}) (int, error) {
stack := int32(0)
convertedArgs := make([]interface{}, 0)
for _, arg := range args {
switch v := arg.(type) {
case string:
if stack == 0 {
result, _ := s.exports["stackSave"].Call(s.store)
stack = result.(int32)
}
maxLen := (len(v) << 2) + 1
result, _ := s.exports["stackAlloc"].Call(s.store, maxLen)
ptr := result.(int32)
s.stringToUTF8(v, int(ptr), maxLen)
convertedArgs = append(convertedArgs, ptr)
case int:
convertedArgs = append(convertedArgs, int32(v))
case int32:
convertedArgs = append(convertedArgs, v)
default:
convertedArgs = append(convertedArgs, v)
}
}
fn := s.exports[funcName]
result, err := fn.Call(s.store, convertedArgs...)
if err != nil {
return 0, err
}
if stack != 0 {
s.exports["stackRestore"].Call(s.store, stack)
}
if result == nil {
return 0, nil
}
return int(result.(int32)), nil
}
func (s *Session) writeI32(addr, value int) {
data := s.memory.UnsafeData(s.store)
offset := addr
data[offset] = byte(value)
data[offset+1] = byte(value >> 8)
data[offset+2] = byte(value >> 16)
data[offset+3] = byte(value >> 24)
}
func (s *Session) stringToUTF8(str string, ptr, maxLen int) {
encoded := []byte(str)
writeLen := len(encoded)
if writeLen > maxLen-1 {
writeLen = maxLen - 1
}
data := s.memory.UnsafeData(s.store)
copy(data[ptr:], encoded[:writeLen])
data[ptr+writeLen] = 0
}
func (s *Session) getExport(name string) *wasmtime.Extern {
return s.instance.GetExport(s.store, name)
}
func (s *Session) buildImports() ([]wasmtime.AsExtern, error) {
envStrings := []string{
"USER=web_user",
"LOGNAME=web_user",
"PATH=/",
"PWD=/",
"HOME=/home/web_user",
"LANG=zh_CN.UTF-8",
"_=./this.program",
}
noOp := func(caller *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) {
return []wasmtime.Val{wasmtime.ValI32(0)}, nil
}
noOpVoid := func(caller *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) {
return []wasmtime.Val{}, nil
}
emscriptenMemcpyBig := func(caller *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) {
dest := args[0].I32()
src := args[1].I32()
num := args[2].I32()
mem := s.memory.UnsafeData(s.store)
copy(mem[dest:dest+num], mem[src:src+num])
return []wasmtime.Val{wasmtime.ValI32(dest)}, nil
}
environGet := func(caller *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) {
environPtr := args[0].I32()
environBuf := args[1].I32()
bufSize := int32(0)
for i, str := range envStrings {
ptr := environBuf + bufSize
s.writeI32(int(environPtr+int32(i*4)), int(ptr))
s.writeASCII(str, int(ptr))
bufSize += int32(len(str) + 1)
}
return []wasmtime.Val{wasmtime.ValI32(0)}, nil
}
environSizesGet := func(caller *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) {
pEnvironCount := args[0].I32()
pEnvironBufSize := args[1].I32()
s.writeI32(int(pEnvironCount), len(envStrings))
bufSize := 0
for _, str := range envStrings {
bufSize += len(str) + 1
}
s.writeI32(int(pEnvironBufSize), bufSize)
return []wasmtime.Val{wasmtime.ValI32(0)}, nil
}
imports := []wasmtime.AsExtern{
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{},
), noOpVoid),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), emscriptenMemcpyBig),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), environGet),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), environSizesGet),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32), wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), noOp),
wasmtime.NewFunc(s.store, wasmtime.NewFuncType(
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
[]*wasmtime.ValType{wasmtime.NewValType(wasmtime.KindI32)},
), func(caller *wasmtime.Caller, args []wasmtime.Val) ([]wasmtime.Val, *wasmtime.Trap) {
return []wasmtime.Val{wasmtime.ValI32(1)}, nil
}),
s.memory,
}
return imports, nil
}
func (s *Session) writeASCII(str string, ptr int) {
data := s.memory.UnsafeData(s.store)
for i, ch := range []byte(str) {
data[ptr+i] = ch
}
data[ptr+len(str)] = 0
}

View File

@ -0,0 +1,23 @@
package exceptions
import "fmt"
type MonalisaError struct {
Message string
}
func (e *MonalisaError) Error() string {
return e.Message
}
func NewLicenseError(msg string, args ...interface{}) error {
return &MonalisaError{Message: fmt.Sprintf(msg, args...)}
}
func NewModuleError(msg string, args ...interface{}) error {
return &MonalisaError{Message: fmt.Sprintf(msg, args...)}
}
func NewSessionError(msg string, args ...interface{}) error {
return &MonalisaError{Message: fmt.Sprintf(msg, args...)}
}

View File

@ -0,0 +1,31 @@
package license
import "encoding/base64"
type License struct {
Data []byte
B64 string
}
func New(data string) *License {
decoded, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return &License{
Data: []byte(data),
B64: base64.StdEncoding.EncodeToString([]byte(data)),
}
}
return &License{
Data: decoded,
B64: data,
}
}
func (l *License) Raw() []byte {
return l.Data
}
func (l *License) Base64() string {
return l.B64
}

View File

@ -0,0 +1,289 @@
package logger
import (
"fmt"
"io"
"os"
"strings"
"sync"
"time"
)
type LogLevel int
const (
DEBUG LogLevel = iota
INFO
WARN
ERROR
FATAL
)
const (
colorReset = "\033[0m"
colorRed = "\033[31m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
colorBlue = "\033[34m"
colorPurple = "\033[35m"
colorCyan = "\033[36m"
colorGray = "\033[37m"
colorWhite = "\033[97m"
)
type Logger struct {
mu sync.Mutex
level LogLevel
output io.Writer
errorOutput io.Writer
colored bool
timestamp bool
prefix string
}
var (
// DefaultLogger é o logger padrão da aplicação
DefaultLogger *Logger
once sync.Once
)
// Init inicializa o logger padrão
func Init(level LogLevel, colored bool) {
once.Do(func() {
DefaultLogger = &Logger{
level: level,
output: os.Stdout,
errorOutput: os.Stderr,
colored: colored,
timestamp: false,
}
})
}
// NewLogger cria um novo logger
func NewLogger(level LogLevel, colored bool) *Logger {
return &Logger{
level: level,
output: os.Stdout,
errorOutput: os.Stderr,
colored: colored,
timestamp: false,
}
}
func (l *Logger) SetLevel(level LogLevel) {
l.mu.Lock()
defer l.mu.Unlock()
l.level = level
}
func (l *Logger) SetPrefix(prefix string) {
l.mu.Lock()
defer l.mu.Unlock()
l.prefix = prefix
}
func (l *Logger) SetTimestamp(enabled bool) {
l.mu.Lock()
defer l.mu.Unlock()
l.timestamp = enabled
}
func (l *Logger) SetColored(enabled bool) {
l.mu.Lock()
defer l.mu.Unlock()
l.colored = enabled
}
func (level LogLevel) String() string {
switch level {
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARN:
return "WARN"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "UNKNOWN"
}
}
func (l *Logger) getColor(level LogLevel) string {
if !l.colored {
return ""
}
switch level {
case DEBUG:
return colorGray
case INFO:
return colorCyan
case WARN:
return colorYellow
case ERROR:
return colorRed
case FATAL:
return colorPurple
default:
return colorWhite
}
}
func (l *Logger) formatMessage(level LogLevel, format string, args ...interface{}) string {
l.mu.Lock()
defer l.mu.Unlock()
var parts []string
if l.timestamp {
parts = append(parts, time.Now().Format("2006-01-02 15:04:05"))
}
color := l.getColor(level)
levelStr := fmt.Sprintf("[%s]", level.String())
if color != "" {
levelStr = color + levelStr + colorReset
}
parts = append(parts, levelStr)
if l.prefix != "" {
parts = append(parts, fmt.Sprintf("[%s]", l.prefix))
}
message := fmt.Sprintf(format, args...)
header := strings.Join(parts, " ")
if header != "" {
return header + " " + message
}
return message
}
func (l *Logger) log(level LogLevel, format string, args ...interface{}) {
if level < l.level {
return
}
message := l.formatMessage(level, format, args...)
writer := l.output
if level >= ERROR {
writer = l.errorOutput
}
fmt.Fprintln(writer, message)
if level == FATAL {
os.Exit(1)
}
}
func (l *Logger) Debug(format string, args ...interface{}) {
l.log(DEBUG, format, args...)
}
func (l *Logger) Info(format string, args ...interface{}) {
l.log(INFO, format, args...)
}
func (l *Logger) Warn(format string, args ...interface{}) {
l.log(WARN, format, args...)
}
func (l *Logger) Error(format string, args ...interface{}) {
l.log(ERROR, format, args...)
}
func (l *Logger) Fatal(format string, args ...interface{}) {
l.log(FATAL, format, args...)
}
func (l *Logger) Success(format string, args ...interface{}) {
message := format
l.log(INFO, message, args...)
}
func (l *Logger) Warning(format string, args ...interface{}) {
message := format
l.log(WARN, message, args...)
}
func (l *Logger) Failure(format string, args ...interface{}) {
message := format
l.log(ERROR, message, args...)
}
func Debug(format string, args ...interface{}) {
if DefaultLogger != nil {
DefaultLogger.Debug(format, args...)
}
}
func Info(format string, args ...interface{}) {
if DefaultLogger != nil {
DefaultLogger.Info(format, args...)
}
}
func Warn(format string, args ...interface{}) {
if DefaultLogger != nil {
DefaultLogger.Warn(format, args...)
}
}
func Error(format string, args ...interface{}) {
if DefaultLogger != nil {
DefaultLogger.Error(format, args...)
}
}
func Fatal(format string, args ...interface{}) {
if DefaultLogger != nil {
DefaultLogger.Fatal(format, args...)
}
}
func Success(format string, args ...interface{}) {
if DefaultLogger != nil {
DefaultLogger.Success(format, args...)
}
}
func Warning(format string, args ...interface{}) {
if DefaultLogger != nil {
DefaultLogger.Warning(format, args...)
}
}
func Failure(format string, args ...interface{}) {
if DefaultLogger != nil {
DefaultLogger.Failure(format, args...)
}
}
func SetLevel(level LogLevel) {
if DefaultLogger != nil {
DefaultLogger.SetLevel(level)
}
}
func SetPrefix(prefix string) {
if DefaultLogger != nil {
DefaultLogger.SetPrefix(prefix)
}
}
func SetTimestamp(enabled bool) {
if DefaultLogger != nil {
DefaultLogger.SetTimestamp(enabled)
}
}
func SetColored(enabled bool) {
if DefaultLogger != nil {
DefaultLogger.SetColored(enabled)
}
}

View File

@ -0,0 +1,382 @@
package module
import (
"bytes"
"compress/zlib"
"encoding/binary"
"encoding/json"
"fmt"
"hash/crc32"
"io"
"os"
"os/exec"
"path/filepath"
"monalisa/pkg/exceptions"
"monalisa/pkg/logger"
"monalisa/pkg/signature"
"github.com/bytecodealliance/wasmtime-go/v25"
)
const (
Magic = "MLD\x00"
FlagCompressedMeta = 0x0001
FlagCompressedWASM = 0x0002
FlagCompressedJS = 0x0004
FlagSigned = 0x0008
)
type Module struct {
wasmBytes []byte
metadata map[string]interface{}
jsCode []byte
engine *wasmtime.Engine
module *wasmtime.Module
signature *signature.Signature
}
func Load(mldPath string) (*Module, error) {
file, err := os.Open(mldPath)
if err != nil {
return nil, exceptions.NewModuleError("Module file not found: %s", mldPath)
}
defer file.Close()
header := make([]byte, 32)
if _, err := io.ReadFull(file, header); err != nil {
return nil, exceptions.NewModuleError("Invalid MLD file: header too short")
}
if string(header[:4]) != Magic {
return nil, exceptions.NewModuleError("Invalid MLD file: bad magic number")
}
flags := binary.LittleEndian.Uint16(header[6:8])
metadataSize := binary.LittleEndian.Uint32(header[8:12])
wasmSize := binary.LittleEndian.Uint32(header[12:16])
jsSize := binary.LittleEndian.Uint32(header[16:20])
checksum := binary.LittleEndian.Uint32(header[20:24])
signatureSize := binary.LittleEndian.Uint32(header[24:28])
payload, err := io.ReadAll(file)
if err != nil {
return nil, exceptions.NewModuleError("Failed to read payload: %v", err)
}
if crc32.ChecksumIEEE(payload) != checksum {
return nil, exceptions.NewModuleError("Checksum mismatch: file corrupted")
}
offset := uint32(0)
// Lê e verifica assinatura se presente
var sig *signature.Signature
if flags&FlagSigned != 0 {
if signatureSize != 88 {
return nil, exceptions.NewModuleError("Invalid signature size: expected 88 bytes, got %d", signatureSize)
}
sigBytes := payload[offset : offset+signatureSize]
offset += signatureSize
sig, err = signature.Decode(sigBytes)
if err != nil {
return nil, exceptions.NewModuleError("Failed to decode signature: %v", err)
}
// Verifica a assinatura com proteção anti-adulteração de relógio
if err := signature.Verify(
sig.DeviceID,
sig.GetCreationTimestamp(),
sig.GetExpiryTimestamp(),
sig.GetMaxDuration(),
sig.HMAC,
); err != nil {
return nil, exceptions.NewModuleError("License verification failed: %v", err)
}
}
metadataBytes := payload[offset : offset+metadataSize]
offset += metadataSize
if flags&FlagCompressedMeta != 0 {
metadataBytes, err = decompress(metadataBytes)
if err != nil {
return nil, exceptions.NewModuleError("Failed to decompress metadata: %v", err)
}
}
var metadata map[string]interface{}
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
return nil, exceptions.NewModuleError("Invalid metadata JSON: %v", err)
}
wasmBytes := payload[offset : offset+wasmSize]
offset += wasmSize
if flags&FlagCompressedWASM != 0 {
wasmBytes, err = decompress(wasmBytes)
if err != nil {
return nil, exceptions.NewModuleError("Failed to decompress WASM: %v", err)
}
}
var jsCode []byte
if jsSize > 0 {
jsBytes := payload[offset : offset+jsSize]
if flags&FlagCompressedJS != 0 {
jsBytes, err = decompress(jsBytes)
if err != nil {
return nil, exceptions.NewModuleError("Failed to decompress JS: %v", err)
}
}
jsCode = jsBytes
}
engine := wasmtime.NewEngine()
wasmModule, err := wasmtime.NewModule(engine, wasmBytes)
if err != nil {
return nil, exceptions.NewModuleError("Failed to load WASM module: %v", err)
}
return &Module{
wasmBytes: wasmBytes,
metadata: metadata,
jsCode: jsCode,
engine: engine,
module: wasmModule,
signature: sig,
}, nil
}
func (m *Module) CreateStore() *wasmtime.Store {
return wasmtime.NewStore(m.engine)
}
func (m *Module) WASMModule() *wasmtime.Module {
return m.module
}
func (m *Module) Signature() *signature.Signature {
return m.signature
}
func decompress(data []byte) ([]byte, error) {
r, err := zlib.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
defer r.Close()
return io.ReadAll(r)
}
type Builder struct {
metadata map[string]interface{}
wasmData []byte
jsData []byte
compress bool
validDays int
}
func NewBuilder() *Builder {
return &Builder{
metadata: make(map[string]interface{}),
compress: true,
validDays: 0, // 0 = sem assinatura
}
}
func (b *Builder) WithMetadata(version, name, description string) *Builder {
b.metadata = map[string]interface{}{
"version": version,
"name": name,
"description": description,
"format": "binary-mld",
"self_contained": true,
}
return b
}
func (b *Builder) WithWAT(watPath string) error {
tmpFile, err := os.CreateTemp("", "*.wasm")
if err != nil {
return err
}
tmpPath := tmpFile.Name()
tmpFile.Close()
defer os.Remove(tmpPath)
cmd := exec.Command("wat2wasm", watPath, "-o", tmpPath)
if err := cmd.Run(); err != nil {
return fmt.Errorf("wat2wasm failed (install WABT toolkit): %v", err)
}
b.wasmData, err = os.ReadFile(tmpPath)
return err
}
func (b *Builder) WithWASM(wasmPath string) error {
data, err := os.ReadFile(wasmPath)
if err != nil {
return err
}
b.wasmData = data
logger.Debug(" WASM loaded: %d bytes", len(data))
return nil
}
func (b *Builder) WithJS(jsPath string) error {
data, err := os.ReadFile(jsPath)
if err != nil {
return err
}
b.jsData = data
logger.Debug(" JS loaded: %d bytes", len(data))
return nil
}
func (b *Builder) WithCompression(enable bool) *Builder {
b.compress = enable
return b
}
func (b *Builder) WithLicense(validDays int) *Builder {
b.validDays = validDays
return b
}
func (b *Builder) Build(outputPath string) error {
if b.wasmData == nil {
return fmt.Errorf("no WASM data provided")
}
metadataJSON, _ := json.MarshalIndent(b.metadata, "", " ")
flags := uint16(0)
var metaComp, wasmComp, jsComp []byte
if b.compress {
metaComp = compress(metadataJSON)
flags |= FlagCompressedMeta
logger.Debug(" Metadata: %d → %d bytes (%.1f%%)",
len(metadataJSON), len(metaComp), 100.0*float64(len(metaComp))/float64(len(metadataJSON)))
} else {
metaComp = metadataJSON
}
if b.compress {
wasmComp = compress(b.wasmData)
flags |= FlagCompressedWASM
logger.Debug(" WASM: %d → %d bytes (%.1f%%)",
len(b.wasmData), len(wasmComp), 100.0*float64(len(wasmComp))/float64(len(b.wasmData)))
} else {
wasmComp = b.wasmData
}
if b.jsData != nil {
if b.compress {
jsComp = compress(b.jsData)
flags |= FlagCompressedJS
logger.Debug(" JS: %d → %d bytes (%.1f%%)",
len(b.jsData), len(jsComp), 100.0*float64(len(jsComp))/float64(len(b.jsData)))
} else {
jsComp = b.jsData
}
}
// Gera assinatura se validDays > 0
var sigBytes []byte
if b.validDays > 0 {
deviceID, err := signature.GenerateDeviceID()
if err != nil {
return fmt.Errorf("failed to generate device ID: %v", err)
}
sig, err := signature.Sign(deviceID, b.validDays)
if err != nil {
return fmt.Errorf("failed to sign device: %v", err)
}
sigBytes = sig.Encode()
flags |= FlagSigned
logger.Debug(" License:")
logger.Debug(" Device ID: %s", deviceID)
logger.Debug(" Created: %s", sig.CreationTime.Format("2006-01-02 15:04:05"))
logger.Debug(" Expires: %s (%d days)", sig.ExpiryTime.Format("2006-01-02 15:04:05"), b.validDays)
logger.Debug(" Max Duration: %d seconds", sig.MaxValidDuration)
}
payload := bytes.NewBuffer(nil)
if sigBytes != nil {
payload.Write(sigBytes)
}
payload.Write(metaComp)
payload.Write(wasmComp)
if jsComp != nil {
payload.Write(jsComp)
}
checksum := crc32.ChecksumIEEE(payload.Bytes())
header := bytes.NewBuffer(nil)
header.WriteString(Magic)
header.WriteByte(1) // major
header.WriteByte(0) // minor
binary.Write(header, binary.LittleEndian, flags)
binary.Write(header, binary.LittleEndian, uint32(len(metaComp)))
binary.Write(header, binary.LittleEndian, uint32(len(wasmComp)))
binary.Write(header, binary.LittleEndian, uint32(len(jsComp)))
binary.Write(header, binary.LittleEndian, checksum)
binary.Write(header, binary.LittleEndian, uint32(len(sigBytes)))
header.Write(make([]byte, 4)) // reserved
os.MkdirAll(filepath.Dir(outputPath), 0755)
file, err := os.Create(outputPath)
if err != nil {
return err
}
defer file.Close()
file.Write(header.Bytes())
file.Write(payload.Bytes())
logger.Debug(" Total: %d bytes", len(header.Bytes())+len(payload.Bytes()))
logger.Debug(" Checksum: %08x", checksum)
return nil
}
func (b *Builder) Verify(modulePath string) error {
file, err := os.Open(modulePath)
if err != nil {
return err
}
defer file.Close()
header := make([]byte, 32)
if _, err := io.ReadFull(file, header); err != nil {
return fmt.Errorf("header too short")
}
if string(header[:4]) != Magic {
return fmt.Errorf("not a binary MLD file")
}
checksum := binary.LittleEndian.Uint32(header[20:24])
payload, _ := io.ReadAll(file)
if crc32.ChecksumIEEE(payload) != checksum {
return fmt.Errorf("checksum mismatch")
}
return nil
}
func compress(data []byte) []byte {
var buf bytes.Buffer
w, _ := zlib.NewWriterLevel(&buf, zlib.BestCompression)
w.Write(data)
w.Close()
return buf.Bytes()
}

View File

@ -0,0 +1,195 @@
package signature
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
"time"
)
const (
// Chave mestra do sistema (ALTERE ESTA CHAVE PARA SUA PRÓPRIA CHAVE SECRETA)
masterKey = "7282cd71ff7393085cc702d0cea433f90420eb04e26857620e11d3cd951dab86"
// Chave para ofuscar timestamps internos
timeObfuscationKey = "OId6KowkmNaXo1drlZ933MkA"
)
type Signature struct {
DeviceID string
CreationTime time.Time // Timestamp de criação (ofuscado)
ExpiryTime time.Time // Timestamp de expiração (ofuscado)
MaxValidDuration int64 // Duração máxima em segundos (ofuscado)
HMAC []byte
}
// GenerateDeviceID gera um ID único para o device
func GenerateDeviceID() (string, error) {
bytes := make([]byte, 16)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
// obfuscateInt64 ofusca um valor int64 usando XOR
func obfuscateInt64(value int64) int64 {
key := int64(0)
for i, c := range timeObfuscationKey {
key ^= int64(c) << (i % 8)
}
return value ^ key
}
// deobfuscateInt64 recupera o valor original
func deobfuscateInt64(obfuscated int64) int64 {
return obfuscateInt64(obfuscated) // XOR é reversível
}
// Sign cria uma assinatura para o device com data de expiração
func Sign(deviceID string, validDays int) (*Signature, error) {
now := time.Now()
expiryTime := now.AddDate(0, 0, validDays)
maxDuration := int64(validDays * 24 * 60 * 60) // em segundos
// Cria o payload: deviceID + creation + expiry + duration
message := fmt.Sprintf("%s:%d:%d:%d",
deviceID,
now.Unix(),
expiryTime.Unix(),
maxDuration)
// Gera HMAC-SHA256
h := hmac.New(sha256.New, []byte(masterKey))
h.Write([]byte(message))
signature := h.Sum(nil)
return &Signature{
DeviceID: deviceID,
CreationTime: now,
ExpiryTime: expiryTime,
MaxValidDuration: maxDuration,
HMAC: signature,
}, nil
}
// Verify verifica se a assinatura é válida e detecta adulteração de relógio
func Verify(deviceID string, creationTimestamp, expiryTimestamp, maxDuration int64, signatureBytes []byte) error {
creationTime := time.Unix(creationTimestamp, 0)
expiryTime := time.Unix(expiryTimestamp, 0)
now := time.Now()
// PROTEÇÃO 1: Verifica se o relógio está ANTES da data de criação
// (impossível usar o device antes de ser criado)
if now.Before(creationTime) {
daysBackwards := int(creationTime.Sub(now).Hours() / 24)
return fmt.Errorf("clock tampering detected: system time is %d day(s) before device creation (created: %s, current: %s)",
daysBackwards,
creationTime.Format("2006-01-02 15:04:05"),
now.Format("2006-01-02 15:04:05"))
}
// PROTEÇÃO 2: Verifica se passou do tempo de expiração
if now.After(expiryTime) {
daysExpired := int(now.Sub(expiryTime).Hours() / 24)
return fmt.Errorf("device license expired %d day(s) ago (expired: %s)",
daysExpired,
expiryTime.Format("2006-01-02"))
}
// PROTEÇÃO 3: Verifica se o tempo decorrido desde a criação excede a duração máxima
// Isso impede que alguém volte o relógio para uma data entre criação e expiração
elapsedTime := now.Unix() - creationTimestamp
if elapsedTime > maxDuration {
return fmt.Errorf("maximum license duration exceeded (elapsed: %d days, max: %d days)",
elapsedTime/(24*60*60),
maxDuration/(24*60*60))
}
// PROTEÇÃO 4: Valida integridade da assinatura HMAC
message := fmt.Sprintf("%s:%d:%d:%d", deviceID, creationTimestamp, expiryTimestamp, maxDuration)
h := hmac.New(sha256.New, []byte(masterKey))
h.Write([]byte(message))
expectedHMAC := h.Sum(nil)
if !hmac.Equal(signatureBytes, expectedHMAC) {
return fmt.Errorf("invalid device signature: file may be corrupted or tampered")
}
return nil
}
// Encode converte a assinatura para bytes (com ofuscação)
func (s *Signature) Encode() []byte {
// Formato: [deviceID (32)] + [creation_obf (8)] + [expiry_obf (8)] + [duration_obf (8)] + [HMAC (32)]
// Total: 88 bytes
result := make([]byte, 88)
// Device ID (32 bytes hex)
copy(result[0:32], s.DeviceID)
// Creation timestamp ofuscado (8 bytes)
obfCreation := obfuscateInt64(s.CreationTime.Unix())
binary.LittleEndian.PutUint64(result[32:40], uint64(obfCreation))
// Expiry timestamp ofuscado (8 bytes)
obfExpiry := obfuscateInt64(s.ExpiryTime.Unix())
binary.LittleEndian.PutUint64(result[40:48], uint64(obfExpiry))
// Max duration ofuscado (8 bytes)
obfDuration := obfuscateInt64(s.MaxValidDuration)
binary.LittleEndian.PutUint64(result[48:56], uint64(obfDuration))
// HMAC (32 bytes)
copy(result[56:88], s.HMAC)
return result
}
// Decode extrai a assinatura dos bytes (com deofuscação)
func Decode(data []byte) (*Signature, error) {
if len(data) != 88 {
return nil, fmt.Errorf("invalid signature length: expected 88 bytes, got %d", len(data))
}
deviceID := string(data[0:32])
// Deofusca os timestamps
obfCreation := int64(binary.LittleEndian.Uint64(data[32:40]))
creationTimestamp := deobfuscateInt64(obfCreation)
obfExpiry := int64(binary.LittleEndian.Uint64(data[40:48]))
expiryTimestamp := deobfuscateInt64(obfExpiry)
obfDuration := int64(binary.LittleEndian.Uint64(data[48:56]))
maxDuration := deobfuscateInt64(obfDuration)
hmacBytes := make([]byte, 32)
copy(hmacBytes, data[56:88])
return &Signature{
DeviceID: deviceID,
CreationTime: time.Unix(creationTimestamp, 0),
ExpiryTime: time.Unix(expiryTimestamp, 0),
MaxValidDuration: maxDuration,
HMAC: hmacBytes,
}, nil
}
// GetCreationTimestamp retorna o timestamp de criação (para validação)
func (s *Signature) GetCreationTimestamp() int64 {
return s.CreationTime.Unix()
}
// GetExpiryTimestamp retorna o timestamp de expiração (para validação)
func (s *Signature) GetExpiryTimestamp() int64 {
return s.ExpiryTime.Unix()
}
// GetMaxDuration retorna a duração máxima (para validação)
func (s *Signature) GetMaxDuration() int64 {
return s.MaxValidDuration
}

View File

@ -0,0 +1,31 @@
package types
import "fmt"
type KeyType int
const (
KeyTypeContent KeyType = iota
KeyTypeSigning
KeyTypeOTT
KeyTypeOperatorSession
)
func (kt KeyType) String() string {
names := []string{"CONTENT", "SIGNING", "OTT", "OPERATOR_SESSION"}
if int(kt) < len(names) {
return names[kt]
}
return "UNKNOWN"
}
type Key struct {
KID []byte
Key []byte
Type KeyType
Permissions []string
}
func (k *Key) String() string {
return fmt.Sprintf("[%s] %x:%x", k.Type, k.KID, k.Key)
}

1
pymonalisa Submodule

@ -0,0 +1 @@
Subproject commit a62e84f944f70c06cfa5b3f418446a82848f32ae