first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
# ecdsa-p384
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides ECDSA (Elliptic Curve Digital Signature Algorithm) operations using the NIST P-384 curve and SHA-384 hash, backed by `crypto.subtle`. P-384 offers 192 bits of security — stronger than P-256's 128 bits — and is required by many compliance frameworks (NIST SP 800-57, CNSA Suite 2.0, NSA Suite B). This module is intended for contexts where P-384 is mandated by policy or interoperability requirements.
The public key format is the 97-byte uncompressed point representation (0x04 prefix followed by 48-byte X and Y coordinates). The private key is PKCS8 DER. Both formats are standard and directly importable by other Web Crypto implementations.
## Features
- `generateKeypair()` — generates a P-384 ECDSA keypair using `crypto.subtle.generateKey`
- `sign(privateKey, message)` — signs with ECDSA-P384 / SHA-384
- `verify(publicKey, signature, message)` — verifies an ECDSA-P384 signature
- Public key: 97-byte uncompressed point (raw format)
- Private key: PKCS8 DER bytes
- Signature format: IEEE P1363 (R || S) as returned by `crypto.subtle`
- `assertWebCryptoAvailable()` guard on every call
- Typed `BrowserCryptoError` for all failure paths
## Usage: Basic Example
```ts
import { ecdsaP384 } from "@suaveplan/crypto-browser/ecdsa-p384/ecdsa-p384";
const kp = await ecdsaP384.generateKeypair();
// kp.publicKey is 97 bytes (uncompressed P-384 point)
// kp.privateKey is PKCS8 DER bytes
const message = new TextEncoder().encode("document to sign");
const signature = await ecdsaP384.sign(kp.privateKey, message);
const valid = await ecdsaP384.verify(kp.publicKey, signature, message);
console.log(valid); // true
```
## Usage: Advanced Example
```ts
import { ecdsaP384 } from "@suaveplan/crypto-browser/ecdsa-p384/ecdsa-p384";
import { BrowserCryptoError } from "@suaveplan/crypto-browser/errors";
// Issue a signed certificate-like structure
async function issueAttestation(
issuerPrivateKey: Uint8Array,
payload: Uint8Array
): Promise<{ signature: Uint8Array; algorithm: string }> {
try {
const signature = await ecdsaP384.sign(issuerPrivateKey, payload);
return { signature, algorithm: "ECDSA-P384-SHA384" };
} catch (err) {
if (err instanceof BrowserCryptoError) {
throw new Error(`Signing failed: ${err.code}`);
}
throw err;
}
}
// Verify the attestation with a stored public key
async function verifyAttestation(
publicKey: Uint8Array,
payload: Uint8Array,
signature: Uint8Array
): Promise<boolean> {
return ecdsaP384.verify(publicKey, signature, payload);
}
```
## API Reference
### `ecdsaP384.generateKeypair(): Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }>`
Generates a P-384 ECDSA keypair.
**Returns:** `Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }>` — 97-byte uncompressed public key and PKCS8 DER private key.
**Throws:** `BrowserCryptoError` — `BROWSER_CRYPTO.KEY.GENERATION_FAILED` or `BROWSER_CRYPTO.NOT_AVAILABLE`.
---
### `ecdsaP384.sign(privateKey, message): Promise<Uint8Array>`
Signs `message` using ECDSA-P384 with SHA-384.
| Parameter | Type | Description |
|-----------|------|-------------|
| `privateKey` | `Uint8Array` | PKCS8 DER private key bytes |
| `message` | `Uint8Array` | Bytes to sign |
**Returns:** `Promise<Uint8Array>` — IEEE P1363 signature (96 bytes for P-384).
**Throws:** `BrowserCryptoError` — `BROWSER_CRYPTO.OPERATION.FAILED` or `BROWSER_CRYPTO.NOT_AVAILABLE`.
---
### `ecdsaP384.verify(publicKey, signature, message): Promise<boolean>`
Verifies an ECDSA-P384 / SHA-384 signature.
| Parameter | Type | Description |
|-----------|------|-------------|
| `publicKey` | `Uint8Array` | 97-byte uncompressed P-384 public key |
| `signature` | `Uint8Array` | IEEE P1363 signature bytes |
| `message` | `Uint8Array` | Original message bytes |
**Returns:** `Promise<boolean>` — `true` if signature is valid.
**Throws:** `BrowserCryptoError` — `BROWSER_CRYPTO.OPERATION.FAILED` or `BROWSER_CRYPTO.NOT_AVAILABLE`.
## Implementation Notes
SHA-384 is paired with P-384 because the security level of the hash should match that of the curve. Using SHA-256 with P-384 would create a mismatch: the 192-bit curve security would be limited to the 128-bit collision resistance of SHA-256.
The signature output format from `crypto.subtle.sign` for ECDSA is IEEE P1363 (raw R || S concatenation), not DER/ASN.1. This differs from OpenSSL's default DER encoding. When interoperating with systems that expect DER signatures, a conversion layer is needed.
P-384 key generation is typically 2–5× slower than P-256 due to the larger field size. For high-throughput signing workloads where P-256 security is sufficient, consider using `webCryptoEcdsaP256Engine` instead.