npm package: @gibme/stir-shaken
Description: STIR/SHAKEN CallerID Attestation & Call Signing
Author: Brandon Lehmann
Latest version: 22.0.0

# @gibme/stir-shaken

[![NPM](https://img.shields.io/npm/v/@gibme/stir-shaken)](https://www.npmjs.com/package/@gibme/stir-shaken)
[![License](https://img.shields.io/npm/l/@gibme/stir-shaken)](https://github.com/gibme-npm/stir-shaken/blob/master/LICENSE)
[![Node.js](https://img.shields.io/node/v/@gibme/stir-shaken)](https://nodejs.org)

[STIR/SHAKEN](https://en.wikipedia.org/wiki/STIR/SHAKEN) caller ID attestation and call signing for Node.js with built-in Asterisk AGI server integration and Iconectiv API support.

## Overview

STIR/SHAKEN (Secure Telephone Identity Revisited / Signature-based Handling of Asserted information using toKENs) is a framework that cryptographically verifies the legitimacy of the calling party and their authorization to use a particular telephone number, helping prevent caller ID spoofing and robocall fraud.

This package provides:

- **AGI Server** for automatic STIR/SHAKEN call signing in Asterisk PBX systems
- **Identity header generation** using ES256-signed JWTs
- **Iconectiv API client** for SPC token provisioning, CRL retrieval, and CA trust list management
- **Service Provider Code (SPC)** ASN.1 DER encoding and decoding
- **ECDSA key generation** and fingerprinting utilities
- Auto formats caller ID and called numbers into [E.164](https://en.wikipedia.org/wiki/E.164) format when signing

## Requirements

- Node.js >= 22

## Installation

```bash
npm install @gibme/stir-shaken
# or
yarn add @gibme/stir-shaken
```

## Quick Start

### AGI Server

Set up an Asterisk AGI server that automatically signs outbound calls:

```typescript
import { AGIStirShakenServer } from '@gibme/stir-shaken';

const server = new AGIStirShakenServer({
    port: 4573,
    certificatePath: './certificate.pem',
    privateKeyPath: './private-key.pem',
    publicCertificateURL: 'https://example.com/certificate.pem',
    attestationProvider: async (callerId, channel) => {
        // Determine attestation level based on your business logic
        return 'A';
    },
    logger: (entry) => {
        console.log(`[${entry.event}] ${entry.message}`);
    }
});

await server.start();
```

### Generate Identity Headers Directly

```typescript
import { generateIdentityHeader } from '@gibme/stir-shaken';
import { readFileSync } from 'fs';

const privateKey = readFileSync('./private-key.pem', 'utf8');

const header = generateIdentityHeader(
    '+15550100',        // caller ID
    '+15550200',        // called number
    'unique-call-id',   // unique ID
    'A',                // attestation level
    'https://example.com/cert.pem',
    privateKey
);
```

### Key Generation

```typescript
import { generateAccountKeys } from '@gibme/stir-shaken';

const { publicKey, privateKey } = generateAccountKeys();
// Returns PEM-encoded ECDSA P-256 keys
```

### Service Provider Code Encoding

```typescript
import { SPC } from '@gibme/stir-shaken';

// Encode a numeric SPC to DER/Base64
const spc = new SPC('8864');
console.log(spc.tkvalue); // 'MAigBhYEODg2NA=='

// Decode back to numeric
const decoded = SPC.from('MAigBhYEODg2NA==');
console.log(decoded.value); // '8864'
```

### Iconectiv API

```typescript
import { Iconectiv, generateAccountKeys } from '@gibme/stir-shaken';

// Static methods (no authentication required)
const crl = await Iconectiv.crl();
const caList = await Iconectiv.STICertificateAuthorities();

// Authenticated methods
const api = new Iconectiv('username', 'password');
const { publicKey } = generateAccountKeys();
const token = await api.ServiceProviderToken('8864', publicKey);
```

## Attestation Levels

| Level | Name | Description |
|-------|------|-------------|
| **A** | Full | Originating carrier has authenticated the caller and verified they are authorized to use the calling number |
| **B** | Partial | Originating carrier has authenticated the call source but cannot verify the caller's authorization for the number |
| **C** | Gateway | Call originated from a gateway where the carrier cannot authenticate the source |

## API Reference

### Classes

#### `AGIStirShakenServer`

Asterisk AGI server that listens for incoming channel connections and signs calls with STIR/SHAKEN Identity headers.

**Options:**

| Property | Type | Description |
|----------|------|-------------|
| `port` | `number` | Port to bind to |
| `host` | `string` | Host/IP address (default: `"0.0.0.0"`) |
| `certificatePath` | `string` | Path to STI certificate file |
| `privateKeyPath` | `string` | Path to the private key file |
| `publicCertificateURL` | `string` | Publicly reachable URL to the certificate |
| `attestationProvider` | `(callerId, channel?) => AttestationLevel` | Callback to determine attestation level |
| `logger` | `(entry) => void` | Optional structured logging callback |

#### `Iconectiv`

Client for the Iconectiv STIR/SHAKEN service provider API.

**Static Methods:**
- `accessible()` - Check if the Iconectiv API is reachable
- `crl()` - Fetch the Certificate Revocation List
- `STICertificateAuthorities()` - Retrieve the STI CA trust list

**Instance Methods:**
- `ServiceProviderToken(spc, accountKey, ca?)` - Provision an SPC token

#### `SPC`

Service Provider Code encoder/decoder using ASN.1 DER encoding.

- `new SPC(value)` - Create from numeric SPC
- `SPC.from(tkvalue)` - Decode from Base64 DER token
- `.value` - Numeric SPC string
- `.tkvalue` - Base64-encoded DER representation

### Functions

- **`generateIdentityHeader(callerId, calledNumber, uniqueId, attestationLevel, certificateUrl, privateKey)`** - Generate a signed STIR/SHAKEN Identity header
- **`generateAccountKeys(namedCurve?)`** - Generate ECDSA key pairs (default: P-256)
- **`generateKeyFingerprint(input)`** - Compute SHA256 fingerprint of a key

## Certificate Requirements

- **Certificate**: X.509 STI certificate with CN or SAN containing the Service Provider Code
- **Private Key**: PEM-encoded ECDSA key corresponding to the certificate
- **Public URL**: The certificate must be publicly accessible for call recipients to verify signatures

## Documentation

Full API documentation is available at [https://gibme-npm.github.io/stir-shaken/](https://gibme-npm.github.io/stir-shaken/)

## License

[MIT](LICENSE)