💙 SDK Oficial para Flutter & Dart (Manual Técnico & API de Métodos)
El paquete valid_license es el SDK oficial de ValidLicence para aplicaciones móviles (Android, iOS) y de escritorio (Windows, macOS, Linux). Implementa validación criptográfica asimétrica basada en el algoritmo Ed25519 (RFC 8032) con capacidad de operación offline ininterrumpida y protección contra manipulación de hardware.
🏛️ 1. Estructura y Módulos del SDK
valid_license/
├── lib/
│ ├── valid_license.dart # Exportador principal de la librería
│ └── src/
│ ├── valid_license_client.dart # Orquestador del ciclo de vida y verificación de licencias
│ ├── machine_id_provider.dart # Extracción determinista de la huella de hardware
│ ├── ed25519_signature_helper.dart # Motor criptográfico sobre curvas de Edwards (Curve25519)
│ └── valid_license_record.dart # Modelo de datos inmutable de la licencia
⚙️ 2. Módulo ValidLicenseClient (Clase Principal)
Es el núcleo del SDK encargado de realizar la comunicación con el servidor VPS y gestionar el respaldo criptográfico local.
Constructor
ValidLicenseClient({
required String serverUrl,
String? publicKeyHex,
String? hmacSecret,
String clientVersion = '2026.1',
})
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
serverUrl | String | Sí | URL base del servidor VPS (ej: https://licencias.labtec.sbs). |
publicKeyHex | String? | Opcional | Clave pública Ed25519 de 32 bytes en formato Hex (64 caracteres) para verificación asimétrica sin secretos. |
hmacSecret | String? | Opcional | Secreto simétrico para retrocompatibilidad con esquemas legacy HMAC-SHA256. |
clientVersion | String | No | Versión de compilación del software cliente (por defecto 2026.1). |
Método getMachineId() (Estático)
static Future<String> getMachineId()
- Propósito: Retorna la huella única de hardware del equipo actual en formato
REX-XXXX-XXXX-XXXX. - Comportamiento:
- En Windows: Consulta
HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid. - En Linux: Lee
/etc/machine-ido/var/lib/dbus/machine-id. - En macOS: Consulta
IOPlatformUUIDvíaioreg. - Calcula un hash SHA-256 determinista y memoiza el resultado en memoria para evitar accesos repetitivos a disco.
- En Windows: Consulta
Método verify() (Verificación Híbrida)
Future<ValidLicenseRecord> verify({
String? companyNit,
String? companyName,
String? currentToken,
Map<String, dynamic>? customData,
List<String>? features,
bool isPrimaryHost = true,
void Function()? onRevoked,
String? customBaseUrl,
})
Parámetros:
companyNit: NIT o documento tributario de la empresa cliente.companyName: Nombre comercial o razón social registrada.currentToken: Token criptográfico almacenado localmente en la base de datos segura.customData: Diccionario con metadatos personalizados (ej:{'sucursal': 'Norte', 'caja': 2}).features: Lista de módulos requeridos para operar (ej:['electronic_billing', 'ai_assistant']).isPrimaryHost: Booleano que indica si el equipo actúa como servidor principal de la red local.onRevoked: Función callback que se ejecuta inmediatamente si la licencia fue suspendida o eliminada desde el panel web del VPS.customBaseUrl: Permite sobreescribir temporalmente el host para reintentos transparentes (HTTP/HTTPS).
Flujo Interno de Ejecución:
- Fase Online (Handshake HTTP):
- Realiza un
POSTcon timeout de 6 segundos a/api/v1/license/verify. - Si el servidor responde
200 OK, valida el estado. Si está marcado comoisRevokedostatus == 'DELETED', disparaonRevoked()y retorna un registro bloqueado. - Si el servidor entrega un nuevo
tokenfirmado, lo decodifica y verifica con la clave pública Ed25519.
- Realiza un
- Fase Fallback Offline (Criptografía Pura):
- Si la red falla o el servidor está en mantenimiento, analiza el
currentTokenlocal. - Valida matemáticamente que la firma corresponda a los datos y pertenezca al
Machine IDdel computador. - Si la firma es legítima y la fecha
expiresAtes futura, autoriza el acceso normalmente.
- Si la red falla o el servidor está en mantenimiento, analiza el
Método decodeAndVerifyToken() (Estático)
static Future<ValidLicenseRecord?> decodeAndVerifyToken(
String token, {
required String expectedMachineId,
String? publicKeyHex,
String? hmacSecret,
})
- Propósito: Analiza la cadena del token (
Base64URL.FirmaHex), extrae el payload JSON y valida la firma digital sin realizar conexiones de red. - Validación de Hardware: Si el token fue emitido para otra máquina (
targetMachineId != expectedMachineIdytargetMachineId != 'REX-UNIVERSAL-ALL'), el método rechaza el token retornandonull.
🔑 3. Módulo Ed25519SignatureHelper
Contiene la implementación criptográfica sobre la curva elíptica de Edwards (Curve25519):
Métodos del Helper
getCanonicalString(Map<String, dynamic> payload): Genera la cadena canónica estandarizada concatenando los campos críticos:companyName|companyNit|machineId|plan|issuedAt|expiresAt|dianQuota|maxUsersverifySignature(Map payload, String signature, {pubKeyHex, hmacSecret}):- Convierte los 64 caracteres de
pubKeyHexen 32 bytes crudos. - Convierte los 128 caracteres de
signatureen 64 bytes de firma. - Ejecuta la validación estricta de la curva elíptica mediante el paquete
cryptography.
- Convierte los 64 caracteres de
📊 4. Módulo ValidLicenseRecord (Modelo Inmutable)
Representa el estado completo y los privilegios autorizados de la empresa:
Propiedades y Getters
| Propiedad | Tipo | Descripción |
|---|---|---|
isActive | bool | true si el estado es ACTIVE y la fecha de expiración no ha pasado. |
isUsable | bool | Alias de isActive para validaciones de arranque rápido. |
isExpired | bool | true si la fecha y hora actual del sistema superó expiresAt. |
daysRemaining | int | Número entero de días restantes de servicio antes del vencimiento. |
companyName | String? | Razón social de la empresa cliente. |
companyNit | String? | NIT tributario de la empresa. |
machineId | String | Identificador de hardware ligado a la licencia. |
plan | String | Nombre comercial del plan contratado (ej: Enterprise Cloud). |
maxUsers | int | Límite máximo de usuarios simultáneos permitidos. |
dianDocumentsLimit | int | Cuota de documentos electrónicos autorizados ante la DIAN. |
dianDocumentsUsed | int | Cantidad de facturas electrónicas ya emitidas en el ciclo. |
issuedAt | String | Fecha de emisión en formato ISO 8601. |
expiresAt | String | Fecha de expiración en formato ISO 8601. |
isTrial | bool | Indica si la licencia se encuentra en período de prueba gratuito. |
status | String | Código de estado: ACTIVE, TRIAL, SUSPENDED, REVOKED, EXPIRED. |
serverMessage | String? | Mensaje informativo o motivo de rechazo emitido por el servidor. |
rawToken | String? | Cadena compacta original del token firmado. |
Métodos Auxiliares
// 1. Verificar si un módulo específico está habilitado
bool canUseAi = license.hasFeature('ai_copilot');
// 2. Extraer metadatos personalizados
String? sucursal = license.getCustom<String>('sucursal');
🛡️ 5. Módulo AntiTamperHelper (Defensa contra Parcheo de Binarios)
Proporciona contramedidas avanzadas para mitigar la ingeniería inversa y el atraso de reloj:
Métodos del AntiTamperHelper
deriveEncryptionKey(String rawToken, {String salt}):- Deriva una clave AES-256 de 32 bytes a partir de la firma Ed25519 del token legítimo.
- Se utiliza para inicializar SQLCipher o cifrar datos sensibles. Si un atacante parchea la validación booleana (
JZaJNZ), no tendrá la firma matemática y la base de datos no podrá descifrarse.
verifyClockIntegrity({...}):- Comprueba que el reloj del sistema operativo no haya sido atrasado deliberadamente comparando el timestamp actual contra el último registro y validando el checksum criptográfico.
isDebuggerAttached():- Detecta si la aplicación se está ejecutando bajo la inspección de un depurador en tiempo de ejecución.
🔒 6. Módulo UiGuard (Protección de Vistas & Widgets en Flutter)
Permite condicionar el renderizado de pantallas completas o componentes específicos:
// 1. Proteger una pantalla o widget completo
Widget screen = UiGuard.guardView(
license: license,
builder: () => const FacturacionView(), // Solo se construye si la licencia es válida
fallback: () => const PantallaBloqueoWidget(), // Fallback si está suspendida
);
// 2. Proteger un módulo específico por Feature Flag
Widget aiPanel = UiGuard.guardFeature(
license: license,
requiredFeature: 'ai_copilot',
builder: () => const PanelAsistenteIa(),
fallback: () => const SizedBox.shrink(),
);
// 3. Ejecutar cálculos de negocio protegidos
final total = UiGuard.guardAction(
license: license,
onAuthorized: () => calcularRetencionesReales(),
onUnauthorized: () => 0.0,
);
🚀 7. Ejemplo de Integración Completa en Flutter
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:valid_license/valid_license.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: '.env');
// 1. Configurar cliente
final client = ValidLicenseClient(
serverUrl: dotenv.env['VALID_LICENSE_SERVER_URL'] ?? 'https://licencias.labtec.sbs',
publicKeyHex: dotenv.env['ED25519_PUBLIC_KEY'],
);
// 2. Obtener token local almacenado
final storedToken = await LocalStorage.getLicenseToken();
// 3. Ejecutar verificación
final ValidLicenseRecord license = await client.verify(
currentToken: storedToken,
companyNit: '900.888.777-1',
companyName: 'Comercializadora Global SAS',
features: ['electronic_billing', 'multi_currency'],
onRevoked: () async {
print('⛔ LICENCIA REVOCADA EN EL VPS. Eliminando credenciales...');
await LocalStorage.clearLicense();
},
);
// 4. Decisión de Flujo de UI
if (license.isActive) {
if (license.rawToken != null && license.rawToken != storedToken) {
await LocalStorage.saveLicenseToken(license.rawToken!);
}
runApp(MainApp(license: license));
} else {
runApp(LicenseLockScreen(license: license));
}
}