Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions packages/crypto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# @orkait/crypto

Cryptographic utilities for JWT/JWKS operations, hashing, encoding, and secure ID generation.

**Tree-shakable** - Import only what you need to keep bundle sizes minimal.

## Installation

```bash
npm install @orkait/crypto
```

## Tree-Shakable Imports

Import only what you need to keep bundle sizes small:

```typescript
// JWT only (lightweight, no JWKS)
import { JWTService } from '@orkait/crypto/jwt';

// JWKS with KMS support (includes JWT)
import { JWKSService } from '@orkait/crypto/jwks';

// Encoding utilities
import { base64, base62, hex } from '@orkait/crypto/encoding';

// Hashing
import { sha256, hmac } from '@orkait/crypto/hash';

// ID generation
import { id, cuid, slug } from '@orkait/crypto/id';

// Random utilities
import { randomBytes, randomHex } from '@orkait/crypto/random';

// Everything (not recommended for production)
import * as crypto from '@orkait/crypto';
```

**Bundle Size Impact:**
- JWT only: ~15KB
- JWKS + KMS: ~45KB
- Encoding: ~5KB
- Hashing: ~3KB
- Full package: ~50KB+

See [TREE_SHAKING.md](./TREE_SHAKING.md) for detailed guide.

## Modules

### JWKS Service

JSON Web Key Set service for signing and verifying JWTs with key rotation support.

```typescript
import { JWKSService } from '@orkait/crypto/jwks';

// From key pair
const jwks = await JWKSService.fromKeyPair(privateKey, publicKey, {
issuer: 'my-service',
algorithm: 'RS256'
});

// From KMS (Infisical or Vault)
const jwks = await JWKSService.fromKMSConfig(
{
provider: 'infisical',
config: {
clientId: process.env.INFISICAL_CLIENT_ID,
clientSecret: process.env.INFISICAL_CLIENT_SECRET,
projectId: process.env.INFISICAL_PROJECT_ID
}
},
'jwt-key'
);

// Sign tokens
const token = await jwks.signSessionJWT({
userId: 'usr_123',
tenantId: 'tnt_456',
sessionId: 'ses_789'
});

// Verify tokens
const result = await jwks.verifyJWT(token);
if (result.success) {
console.log(result.payload);
}
```

### JWT Service

Lightweight JWT operations without key management.

```typescript
import { JWTService } from '@orkait/crypto/jwt';

const token = await JWTService.sign(
{ userId: '123' },
privateKey,
{ issuer: 'my-service' }
);

const payload = await JWTService.verify(token, publicKey);
```

### KMS Integration

Fetch keys from external secret managers.

**Supported Providers:**
- Infisical
- HashiCorp Vault

```typescript
import { KMSStrategyFactory } from '@orkait/crypto/jwks';

// Infisical
const provider = KMSStrategyFactory.create({
provider: 'infisical',
config: {
clientId: '...',
clientSecret: '...',
projectId: '...',
cacheTtl: 300, // Optional: cache keys for 5 minutes
}
});

// Vault
const provider = KMSStrategyFactory.create({
provider: 'vault',
config: {
vaultUrl: 'https://vault.example.com',
token: '...',
cacheTtl: 300, // Optional: cache keys for 5 minutes
}
});

const { privateKey, publicKey } = await provider.getKeyPair('jwt-key');
```

**Caching:**
- Set `cacheTtl` (in seconds) to enable caching
- Reduces API calls to KMS providers
- Improves performance for frequently accessed keys
- Default: no caching (0)

### Hashing

```typescript
import { sha256, sha512, hmac } from '@orkait/crypto';

const hash = await sha256('data');
const signature = await hmac('secret', 'data', 'SHA-256');
```

### Encoding

```typescript
import { base64, base64url, base62, hex, utf8 } from '@orkait/crypto';

// Base64 - Standard encoding
const b64 = base64.encode('Hello, World!');
const str = base64.decodeToString(b64);

// Base64URL - URL-safe, no padding
const urlSafe = base64url.encode('Hello, World!');
const decoded = base64url.decodeToString(urlSafe);

// Base62 - Compact alphanumeric (0-9, A-Z, a-z)
const id = base62.encode('user-123');
const original = base62.decodeToString(id);

// Hex - Hexadecimal encoding
const hexStr = hex.encode('data');
const bytes = hex.decode(hexStr);

// UTF-8 - String/bytes conversion
const utf8Bytes = utf8.encode('Hello 👋');
const text = utf8.decode(utf8Bytes);
```

**Low-level API** (for advanced use):
```typescript
import {
base64Encode,
base64Decode,
bytesToBase62,
base62ToBytes
} from '@orkait/crypto';

const bytes = new Uint8Array([104, 101, 108, 108, 111]);
const encoded = base64Encode(bytes);
const decoded = base64Decode(encoded);
```

### ID Generation

```typescript
import { id, cuid, slug } from '@orkait/crypto';

const uniqueId = id(); // Cryptographically secure random ID
const collisionResistant = cuid(); // CUID v2
const urlSafe = slug(); // URL-safe slug
```

### Random

```typescript
import { randomBytes, randomHex } from '@orkait/crypto';

const bytes = randomBytes(32);
const hex = randomHex(16);
```

## Security Features

- **Input validation** on all cryptographic operations
- **Entropy detection** for weak keys
- **DoS prevention** with token size limits
- **Timing-safe comparisons** for secrets
- **Network timeouts** for KMS operations
- **Race condition protection** for token refresh

## Environment Variables

### KMS Configuration

```bash
# Provider selection
KMS_PROVIDER=infisical # or 'vault'

# Infisical
INFISICAL_CLIENT_ID=...
INFISICAL_CLIENT_SECRET=...
INFISICAL_PROJECT_ID=...
INFISICAL_ENVIRONMENT=production
INFISICAL_API_URL=https://app.infisical.com

# Vault
VAULT_URL=https://vault.example.com
VAULT_TOKEN=...
VAULT_NAMESPACE=...
VAULT_MOUNT_PATH=secret
VAULT_KV_VERSION=v2
```

## License

Private
69 changes: 69 additions & 0 deletions packages/crypto/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"name": "@orkait/crypto",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./jwt": {
"import": {
"types": "./dist/jwt/index.d.ts",
"default": "./dist/jwt/index.js"
}
},
"./jwks": {
"import": {
"types": "./dist/jwks/index.d.ts",
"default": "./dist/jwks/index.js"
}
},
"./encoding": {
"import": {
"types": "./dist/encoding/index.d.ts",
"default": "./dist/encoding/index.js"
}
},
"./hash": {
"import": {
"types": "./dist/hash.d.ts",
"default": "./dist/hash.js"
}
},
"./id": {
"import": {
"types": "./dist/id.d.ts",
"default": "./dist/id.js"
}
},
"./random": {
"import": {
"types": "./dist/random.d.ts",
"default": "./dist/random.js"
}
}
},
"sideEffects": false,
"scripts": {
"build": "tsc",
"type-check": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"jose": "^5.0.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240405.0",
"@vitest/coverage-v8": "^1.0.0",
"typescript": "^5.4.0",
"vitest": "^1.0.0"
}
}
Loading