@owlmeans/basic-envelope
v0.1.1
Published
A lightweight cryptographic message envelope library for OwlMeans Common applications. This package provides secure message wrapping, signing, and verification capabilities using Ed25519 cryptographic signatures, designed as an alternative to JWT/JWE toke
Readme
@owlmeans/basic-envelope
A lightweight cryptographic message envelope library for OwlMeans Common applications. This package provides secure message wrapping, signing, and verification capabilities using Ed25519 cryptographic signatures, designed as an alternative to JWT/JWE tokens with enhanced security and simplicity.
Overview
The @owlmeans/basic-envelope package enables secure message transmission and storage by providing:
- Message Envelopes: Lightweight containers for messages with built-in expiration and type identification
- Cryptographic Signing: Ed25519 signature support for message authentication and integrity
- Flexible Serialization: Multiple output formats including wrapped strings and tokenized representations
- TTL Support: Time-to-live functionality for automatic message expiration
- Type Safety: Full TypeScript support with generic message types
This package is particularly useful for creating secure authentication tokens, signed API requests, and encrypted inter-service communication.
Installation
npm install @owlmeans/basic-envelopeCore Concepts
Envelope Structure
An envelope is a container that wraps a message with metadata including:
- Type identifier: Categorizes the message purpose
- Message payload: The actual data (string or object)
- Signature: Optional cryptographic signature for verification
- Timestamp: Creation time for ordering and TTL calculations
- TTL: Time-to-live for automatic expiration
Envelope Kinds
The package supports different serialization formats:
- Wrap: Base64-encoded JSON representation
- Token: Compact tokenized format for transmission
API Reference
Factory Functions
The package uses factory functions for object creation following OwlMeans Common conventions:
makeEnvelopeModel<T>(type, kind?): EnvelopeModel<T>
Creates an envelope model for wrapping and managing messages with cryptographic capabilities.
import { makeEnvelopeModel, EnvelopeKind } from '@owlmeans/basic-envelope'
// Create a new envelope
const envelope = makeEnvelopeModel<UserData>('user-session')
// Create from existing wrapped envelope
const wrappedEnvelope = makeEnvelopeModel('eyJ0IjoiZXhhbXBsZSIsLi4u', EnvelopeKind.Wrap)
// Create from tokenized envelope
const tokenizedEnvelope = makeEnvelopeModel('example:msg:signature', EnvelopeKind.Token)Parameters:
type: string - Type identifier for new envelopes, or serialized envelope for reconstructionkind: EnvelopeKind (optional) - Specifies format when reconstructing from serialized data
Returns: EnvelopeModel instance with methods for message management and cryptographic operations
Generic Type Parameter:
T: Type of the message payload (defaults to string)
Core Interfaces
Envelope
Core envelope structure containing message and metadata.
interface Envelope {
t: string // Type identifier for categorizing messages
msg: string // Base64-encoded message content
sig?: string // Optional cryptographic signature
dt: number // Creation timestamp (milliseconds)
ttl: number | null // Time-to-live in milliseconds (null = no expiration)
}Properties:
t: Message type identifier used for categorization and routingmsg: Base64-encoded message payload (automatically encoded by envelope methods)sig: Optional Ed25519 signature for message authentication and integrity verificationdt: Creation timestamp used for ordering and TTL calculations (milliseconds since epoch)ttl: Time-to-live for automatic expiration, null indicates no expiration
EnvelopeModel<T>
Factory-created envelope manager providing comprehensive message operations and cryptographic functions.
interface EnvelopeModel<T extends {} | string = string> {
envelope: Envelope
// Message management methods
send<M extends T>(msg: M, ttl?: number | null): EnvelopeModel
message<M extends T>(preserve?: boolean): M
type(): string
// Serialization methods
wrap(): string
tokenize(): string
// Cryptographic methods
sign(key: KeyPairModel, kind?: EnvelopeKind): Promise<string>
verify(key: KeyPairModel): Promise<boolean>
}EnvelopeModel Methods Reference
Message Management Methods
send<M extends T>(msg: M, ttl?: number | null): EnvelopeModel
Sets the message content and optional TTL, with automatic encoding based on message type.
// Send a string message
envelope.send('Hello, World!')
// Send an object with 1-hour TTL
envelope.send({ userId: '123', action: 'login' }, 3600000)
// Send with no expiration
envelope.send(messageData, null)- Purpose: Encodes and stores message data in the envelope with optional expiration
- Behavior:
- String messages are stored directly
- Objects are JSON-serialized then Base64-encoded automatically
- Updates TTL if provided, maintains existing TTL if not specified
- Parameters:
msg: M: Message payload extending type Tttl?: number | null: Time-to-live in milliseconds, null for no expiration
- Returns: The same EnvelopeModel instance for method chaining
- Throws: No exceptions - handles all message types gracefully
message<M extends T>(preserve?: boolean): M
Retrieves and decodes the message content from the envelope.
// Get decoded object message
const userData = envelope.message<UserData>()
// Get raw string message without decoding
const rawMessage = envelope.message<string>(true)- Purpose: Extracts the original message from the envelope with configurable decoding
- Behavior:
- Default (
preservefalse): Attempts JSON parsing followed by Base64 decoding - Preserve mode (
preservetrue): Returns raw message string without processing - Gracefully falls back to string if JSON parsing fails
- Default (
- Parameters:
preserve?: boolean: If true, returns raw message without decoding
- Returns: Decoded message of type M
- Error Handling: Silent fallback to string type on decode errors
type(): string
Returns the type identifier of the envelope for message categorization.
const messageType = envelope.type() // e.g., 'user-session'- Purpose: Provides access to the envelope's type identifier
- Returns: String identifier used for message routing and categorization
- Use Cases: Message dispatching, route determination, debugging
Serialization Methods
wrap(): string
Serializes the envelope to a Base64-encoded JSON string for secure transmission.
const wrappedEnvelope = envelope.wrap()
// Returns: "eyJ0IjoiZXhhbXBsZSIsIm1zZyI6IkhlbGxvIiwiZHQiOjE2Mzk..."
// Can be reconstructed later
const restored = makeEnvelopeModel(wrappedEnvelope, EnvelopeKind.Wrap)- Purpose: Creates a wrapped representation suitable for storage or secure transmission
- Behavior: JSON-serializes the complete envelope then Base64-encodes the result
- Returns: Base64-encoded envelope string
- Use Cases: Database storage, HTTP headers, secure URL parameters
- Security: Preserves all envelope data including signatures
tokenize(): string
Creates a compact token format for efficient transmission and parsing.
const token = envelope.tokenize()
// Returns: "user-session:SGVsbG8gV29ybGQ:signature_if_present"
// Reconstruct from token
const restored = makeEnvelopeModel(token, EnvelopeKind.Token)- Purpose: Creates a human-readable, compact token representation
- Behavior: Combines type, message, and signature with colon separators
- Format:
type:base64_message:signature(signature omitted if not present) - Returns: Compact token string
- Use Cases: API tokens, authentication headers, URL fragments, debugging
- Advantages: Easily parseable, more compact than wrapped format
Cryptographic Methods
sign(key: KeyPairModel, kind?: EnvelopeKind): Promise<string>
Signs the envelope with Ed25519 cryptographic signature for authentication and integrity.
import { makeKeyPairModel } from '@owlmeans/basic-keys'
const keyPair = makeKeyPairModel()
await keyPair.generate()
// Sign and get signature only
const signature = await envelope.sign(keyPair)
// Sign and get wrapped envelope
const signedWrapped = await envelope.sign(keyPair, EnvelopeKind.Wrap)
// Sign and get tokenized envelope
const signedToken = await envelope.sign(keyPair, EnvelopeKind.Token)- Purpose: Provides cryptographic authentication and tamper-detection
- Behavior:
- Creates Ed25519 signature of envelope data (excluding existing signature field)
- Stores signature in
envelope.sigproperty for future verification - Returns formatted output based on
kindparameter
- Parameters:
key: KeyPairModel: Ed25519 key pair instance for signingkind?: EnvelopeKind: Output format - Wrap, Token, or raw signature (default)
- Returns: Promise resolving to signature string or formatted envelope
- Security: Uses industry-standard Ed25519 signatures for cryptographic authenticity
- Error Handling: Propagates key pair errors (e.g., missing private key)
verify(key: KeyPairModel): Promise<boolean>
Verifies envelope signature and validates TTL expiration for complete authenticity check.
// Verify signature and TTL
const isValid = await envelope.verify(keyPair)
if (isValid) {
console.log('Envelope is authentic and not expired')
// Safe to process message
} else {
console.log('Envelope is invalid or expired - reject')
// Handle invalid envelope
}- Purpose: Validates both cryptographic authenticity and temporal freshness
- Behavior:
- Returns
falseimmediately if no signature is present - Checks TTL expiration if TTL is set (compares
dt + ttlwith current time) - Verifies Ed25519 signature against envelope data (excluding signature field)
- All conditions must pass for
trueresult
- Returns
- Parameters:
key: KeyPairModel: Key pair instance with public key for verification
- Returns: Promise resolving to boolean validation result
- Security: Combines cryptographic verification with temporal validation
- Use Cases: Authentication token validation, message integrity checking
Factory Functions
makeEnvelopeModel<T>(type: string, kind?: EnvelopeKind): EnvelopeModel<T>
Creates a new envelope model instance.
Parameters:
type: Type identifier for the envelopekind: Optional kind for deserializing existing envelopes
Returns: EnvelopeModel instance
Example:
import { makeEnvelopeModel } from '@owlmeans/basic-envelope'
// Create new envelope
const envelope = makeEnvelopeModel<UserData>('user-profile')
// Send message
envelope.send({ userId: '123', name: 'John' }, 300000) // 5 minutes TTL
// Get wrapped representation
const wrapped = envelope.wrap()Constants and Enums
EnvelopeKind
Enumeration defining available envelope serialization formats.
enum EnvelopeKind {
Wrap = 'wrap', // Base64-encoded JSON format - secure, complete data preservation
Token = 'token' // Compact colon-separated format - human-readable, efficient
}Usage Guidelines:
- Use
Wrapfor secure storage, HTTP headers, or when preserving all metadata is critical - Use
Tokenfor API tokens, URLs, or when human readability and compactness are important
DEFAULT_TTL
Default time-to-live value applied to new envelopes.
const DEFAULT_TTL = 5 * 60 * 1000 // 5 minutes in millisecondsBehavior: Applied automatically when creating new envelopes unless explicitly overridden
Error Handling
The basic-envelope package follows OwlMeans Common error handling patterns and integrates with cryptographic operations that may throw errors.
Common Error Scenarios
Key-Related Errors
import { makeKeyPairModel } from '@owlmeans/basic-keys'
try {
const keyPair = makeKeyPairModel()
// Error: Attempting to sign without generating keys
await envelope.sign(keyPair)
} catch (error) {
console.error('Key operation failed:', error.message)
// Handle missing private key or key generation errors
}TTL Validation Errors
// Expired envelope verification
const expiredEnvelope = makeEnvelopeModel('expired-message')
expiredEnvelope.send('data', 1) // 1ms TTL
await new Promise(resolve => setTimeout(resolve, 10)) // Wait for expiration
const isValid = await expiredEnvelope.verify(keyPair)
// Returns false - envelope has expiredMalformed Data Errors
try {
// Invalid wrapped data
const malformed = makeEnvelopeModel('invalid-base64', EnvelopeKind.Wrap)
} catch (error) {
console.error('Failed to parse envelope:', error.message)
}Error Types and Handling
The package gracefully handles various error conditions:
- Silent Fallbacks: Message decoding errors fall back to string representation
- Validation Failures: Verification methods return
falserather than throwing - Key Errors: Cryptographic errors are propagated from the key pair implementation
- Parse Errors: Malformed envelope data results in constructor exceptions
Advanced Usage Examples
Authentication Token System
import { makeEnvelopeModel, EnvelopeKind } from '@owlmeans/basic-envelope'
import { makeKeyPairModel } from '@owlmeans/basic-keys'
// Setup authentication system
const authKeys = makeKeyPairModel()
await authKeys.generate()
// Create authentication token
const sessionEnvelope = makeEnvelopeModel<SessionData>('user-session')
const sessionData = {
userId: 'user123',
permissions: ['read', 'write'],
loginTime: Date.now()
}
// Sign and create token with 1-hour expiration
const authToken = await sessionEnvelope
.send(sessionData, 3600000)
.sign(authKeys, EnvelopeKind.Token)
console.log('Auth token:', authToken)
// user-session:eyJ1c2VySWQ...base64...:signature
// Later: verify token
const receivedEnvelope = makeEnvelopeModel(authToken, EnvelopeKind.Token)
const isValidToken = await receivedEnvelope.verify(authKeys)
if (isValidToken) {
const session = receivedEnvelope.message<SessionData>()
console.log('Valid session for user:', session.userId)
} else {
console.log('Invalid or expired token')
}Secure Message Transmission
// Sender side
const messageEnvelope = makeEnvelopeModel<ApiRequest>('api-request')
const apiRequest = {
method: 'POST',
endpoint: '/users',
data: { name: 'John Doe', email: '[email protected]' }
}
// Create signed message with 30-second TTL
const signedMessage = await messageEnvelope
.send(apiRequest, 30000)
.sign(senderKeys, EnvelopeKind.Wrap)
// Transmit wrapped message
await sendToServer(signedMessage)
// Receiver side
const receivedEnvelope = makeEnvelopeModel(signedMessage, EnvelopeKind.Wrap)
// Verify authenticity and freshness
if (await receivedEnvelope.verify(senderKeys)) {
const request = receivedEnvelope.message<ApiRequest>()
// Process authenticated API request
console.log('Processing request:', request.method, request.endpoint)
} else {
// Reject unauthenticated or expired request
throw new Error('Invalid request signature or expired')
}Multi-Format Message Exchange
// Create message once
const dataEnvelope = makeEnvelopeModel<UserProfile>('user-profile')
dataEnvelope.send({
id: '123',
name: 'Alice Smith',
preferences: { theme: 'dark', notifications: true }
})
// Generate multiple formats for different use cases
const forDatabase = dataEnvelope.wrap() // Secure storage
const forUrl = dataEnvelope.tokenize() // URL transmission
const forHeaders = await dataEnvelope.sign(keys, EnvelopeKind.Wrap) // Signed secure
// Each format can be reconstructed to the same data
const fromDb = makeEnvelopeModel(forDatabase, EnvelopeKind.Wrap)
const fromUrl = makeEnvelopeModel(forUrl, EnvelopeKind.Token)
const fromHeaders = makeEnvelopeModel(forHeaders, EnvelopeKind.Wrap)
// All contain the same user profile data
console.log('Same data:',
fromDb.message<UserProfile>().id ===
fromUrl.message<UserProfile>().id &&
fromHeaders.message<UserProfile>().id
)Integration Patterns
Express.js Middleware
import express from 'express'
import { makeEnvelopeModel, EnvelopeKind } from '@owlmeans/basic-envelope'
// Authentication middleware using envelope tokens
const authenticateToken = (serverKeys) => async (req, res, next) => {
const authHeader = req.headers.authorization
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing auth token' })
}
try {
const token = authHeader.substring(7)
const envelope = makeEnvelopeModel(token, EnvelopeKind.Token)
if (await envelope.verify(serverKeys)) {
req.user = envelope.message()
next()
} else {
res.status(401).json({ error: 'Invalid or expired token' })
}
} catch (error) {
res.status(400).json({ error: 'Malformed token' })
}
}
const app = express()
app.use('/api', authenticateToken(serverKeys))React Hook Integration
import { useState, useEffect } from 'react'
import { makeEnvelopeModel, EnvelopeKind } from '@owlmeans/basic-envelope'
// Custom hook for secure message handling
function useSecureMessage<T>(initialMessage?: T) {
const [envelope] = useState(() => makeEnvelopeModel<T>('react-state'))
const [message, setMessage] = useState<T | null>(initialMessage || null)
const updateMessage = (newMessage: T, ttl?: number) => {
envelope.send(newMessage, ttl)
setMessage(newMessage)
}
const getSecureToken = async (keys) => {
return await envelope.sign(keys, EnvelopeKind.Token)
}
const verifyMessage = async (token: string, keys) => {
try {
const received = makeEnvelopeModel(token, EnvelopeKind.Token)
if (await received.verify(keys)) {
const data = received.message<T>()
setMessage(data)
return true
}
} catch (error) {
console.error('Message verification failed:', error)
}
return false
}
return { message, updateMessage, getSecureToken, verifyMessage }
}Security Considerations
Cryptographic Security
- Key Management: Use
@owlmeans/basic-keysfor proper Ed25519 key generation and management - Signature Verification: Always verify signatures before processing messages in production
- TTL Usage: Implement appropriate TTL values to prevent replay attacks
- Key Rotation: Regularly rotate signing keys and update verification keys
Data Protection
- Sensitive Data: Avoid including sensitive data in message types or identifiers
- Message Encoding: Automatic Base64 encoding provides basic obfuscation but not encryption
- Transmission Security: Use HTTPS/TLS for envelope transmission over networks
- Storage Security: Encrypt wrapped envelopes before database storage
Validation Best Practices
- Input Validation: Validate envelope data before processing messages
- Type Safety: Use TypeScript generics for compile-time message type checking
- Error Handling: Implement proper error handling for verification failures
- Audit Logging: Log envelope verification results for security monitoring
Performance Considerations
Memory Usage
- Large Messages: Consider message size impact on memory usage
- Batch Operations: Process multiple envelopes efficiently rather than individually
- Key Caching: Cache key pairs to avoid repeated generation operations
Serialization Performance
- Format Selection: Choose appropriate format (wrap vs token) based on use case
- Compression: Consider compression for large messages before envelope wrapping
- JSON Parsing: Message decoding involves JSON parsing - optimize for large objects
Best Practices
- TTL Management: Set appropriate TTL values based on use case security requirements
- Type Safety: Always use TypeScript generics for compile-time message validation
- Error Handling: Implement comprehensive error handling for all envelope operations
- Key Management: Use proper key generation and storage practices from
@owlmeans/basic-keys - Format Selection: Choose envelope format (wrap vs token) based on transmission requirements
- Verification: Always verify envelopes in production environments before processing
- Logging: Implement appropriate logging for debugging without exposing sensitive data
Package Structure
Following OwlMeans Common library structure:
- types: TypeScript interfaces (
Envelope,EnvelopeModel) - model: Factory functions (
makeEnvelopeModel) - consts: Constants and enumerations (
EnvelopeKind,DEFAULT_TTL) - helper: Utility functions for envelope operations
- utils: Internal utilities for serialization and parsing
Dependencies
This package depends on:
@owlmeans/basic-keys- Ed25519 key pair management for cryptographic operations@scure/base- Base64 and UTF-8 encoding utilitiesuuid- UUID generation (if needed for message identification)
Related Packages
@owlmeans/basic-keys- Cryptographic key management@owlmeans/auth- Authentication system integration@owlmeans/error- Error handling system
