Upload
This commit is contained in:
parent
d2047fc42d
commit
4d298d8fcb
68561
CDM/libmonalisa-v3.0.6-browser.wat
Normal file
68561
CDM/libmonalisa-v3.0.6-browser.wat
Normal file
File diff suppressed because one or more lines are too long
8
CDM/monalisa.mld
Normal file
8
CDM/monalisa.mld
Normal 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
362
Monalisa/README.md
Normal 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).
|
||||||
|
|
||||||
|
[](https://go.dev/)
|
||||||
|
[](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
|
||||||
115
Monalisa/cmd/create_device.go
Normal file
115
Monalisa/cmd/create_device.go
Normal 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
163
Monalisa/cmd/license.go
Normal 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")
|
||||||
|
}
|
||||||
100
Monalisa/cmd/license_info.go
Normal file
100
Monalisa/cmd/license_info.go
Normal 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
47
Monalisa/cmd/root.go
Normal 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(×tamp, "timestamp", false, "Add timestamp to log messages")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(licenseCmd)
|
||||||
|
rootCmd.AddCommand(createDeviceCmd)
|
||||||
|
rootCmd.AddCommand(verifyDeviceCmd)
|
||||||
|
rootCmd.AddCommand(licenseInfoCmd)
|
||||||
|
|
||||||
|
return rootCmd.Execute()
|
||||||
|
}
|
||||||
33
Monalisa/cmd/verify_device.go
Normal file
33
Monalisa/cmd/verify_device.go
Normal 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
14
Monalisa/go.mod
Normal 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
21
Monalisa/go.sum
Normal 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
BIN
Monalisa/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 301 KiB |
16
Monalisa/main.go
Normal file
16
Monalisa/main.go
Normal 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
78
Monalisa/pkg/cdm/cdm.go
Normal 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
359
Monalisa/pkg/cdm/session.go
Normal 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
|
||||||
|
}
|
||||||
23
Monalisa/pkg/exceptions/exceptions.go
Normal file
23
Monalisa/pkg/exceptions/exceptions.go
Normal 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...)}
|
||||||
|
}
|
||||||
31
Monalisa/pkg/license/license.go
Normal file
31
Monalisa/pkg/license/license.go
Normal 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
|
||||||
|
}
|
||||||
289
Monalisa/pkg/logger/logger.go
Normal file
289
Monalisa/pkg/logger/logger.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
382
Monalisa/pkg/module/module.go
Normal file
382
Monalisa/pkg/module/module.go
Normal 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()
|
||||||
|
}
|
||||||
195
Monalisa/pkg/signature/signature.go
Normal file
195
Monalisa/pkg/signature/signature.go
Normal 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
|
||||||
|
}
|
||||||
31
Monalisa/pkg/types/types.go
Normal file
31
Monalisa/pkg/types/types.go
Normal 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
1
pymonalisa
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit a62e84f944f70c06cfa5b3f418446a82848f32ae
|
||||||
Loading…
Reference in New Issue
Block a user