first commit
This commit is contained in:
49
docs/security-stack/crypto/crypto-rsa/src/pem/labels.md
Normal file
49
docs/security-stack/crypto/crypto-rsa/src/pem/labels.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# `pem/labels`
|
||||
|
||||
## Purpose
|
||||
|
||||
Centralised PEM textual labels per RFC 7468 §5/§10/§11 used by the public
|
||||
and private PEM parsers and serialisers in this package. Keeping the labels
|
||||
in one file means the parser whitelist and serialiser emission share a
|
||||
single source of truth.
|
||||
|
||||
## Features
|
||||
|
||||
- `PEM_LABEL_PUBLIC_KEY` — `"PUBLIC KEY"` (SPKI body).
|
||||
- `PEM_LABEL_RSA_PUBLIC_KEY` — `"RSA PUBLIC KEY"` (PKCS#1 public body).
|
||||
- `PEM_LABEL_PRIVATE_KEY` — `"PRIVATE KEY"` (PKCS#8 body).
|
||||
- `PEM_LABEL_RSA_PRIVATE_KEY` — `"RSA PRIVATE KEY"` (PKCS#1 private body).
|
||||
- `PEM_LINE_LENGTH = 64` — RFC 7468 §3 maximum body line length.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { PEM_LABEL_PUBLIC_KEY } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const armor = `-----BEGIN ${PEM_LABEL_PUBLIC_KEY}-----`;
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
PEM_LABEL_PRIVATE_KEY,
|
||||
PEM_LABEL_RSA_PRIVATE_KEY,
|
||||
} from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
function classifyPrivatePem(label: string): "pkcs8" | "pkcs1" | "unknown" {
|
||||
if (label === PEM_LABEL_PRIVATE_KEY) return "pkcs8";
|
||||
if (label === PEM_LABEL_RSA_PRIVATE_KEY) return "pkcs1";
|
||||
return "unknown";
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All exports are `string` constants.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
RFC 7468 §3 fixes the body line length at 64 base64 characters. Some legacy
|
||||
emitters use 76 (the historic RFC 1421 PEM length); we accept both on parse
|
||||
but always emit 64.
|
||||
@@ -0,0 +1,50 @@
|
||||
# `pem/parse-private`
|
||||
|
||||
## Purpose
|
||||
|
||||
Parse an RSA private key from RFC 7468 PEM textual encoding. Accepts the
|
||||
two canonical private-key armor labels — `"PRIVATE KEY"` (PKCS#8 per RFC
|
||||
5208 / RFC 5958) and `"RSA PRIVATE KEY"` (PKCS#1 per RFC 8017 Appendix
|
||||
A.1.2) — and dispatches to the matching DER parser.
|
||||
|
||||
## Features
|
||||
|
||||
- Dual-label support (`"PRIVATE KEY"` and `"RSA PRIVATE KEY"`).
|
||||
- Delegates to existing DER parsers.
|
||||
- Strict-grammar body validation via `decodePem`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { parseRsaPrivateKeyPem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const key = parseRsaPrivateKeyPem(`
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAAS...
|
||||
-----END PRIVATE KEY-----
|
||||
`);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { parseRsaPrivateKeyPem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const fromPkcs8 = parseRsaPrivateKeyPem(pkcs8Pem);
|
||||
const fromPkcs1 = parseRsaPrivateKeyPem(pkcs1Pem);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `parseRsaPrivateKeyPem(pem: string): RsaPrivateKey`
|
||||
|
||||
Throws `CryptoRsaError(CRYPTO_RSA.PEM_MALFORMED)` on framing errors,
|
||||
`CRYPTO_RSA.INVALID_KEY` / `CRYPTO_RSA.UNSUPPORTED_FORMAT` on inner-DER errors.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Same dispatcher pattern as `parse-public.ts` — `Record<label, parser>` with
|
||||
the allowlist derived from the dispatcher keys. Encrypted PEM bodies
|
||||
(legacy `Proc-Type: 4,ENCRYPTED` headers, OpenSSL `DEK-Info` lines) are
|
||||
not supported ; consumers needing those should decrypt to a
|
||||
plain PKCS#8 body before calling this function.
|
||||
@@ -0,0 +1,52 @@
|
||||
# `pem/parse-public`
|
||||
|
||||
## Purpose
|
||||
|
||||
Parse an RSA public key from RFC 7468 PEM textual encoding. Accepts both
|
||||
canonical armor labels — `"PUBLIC KEY"` (SPKI per RFC 5280) and
|
||||
`"RSA PUBLIC KEY"` (PKCS#1 per RFC 8017) — and dispatches to the
|
||||
appropriate DER parser. Mismatched BEGIN/END, unknown labels, or non-base64
|
||||
body content surface as `CRYPTO_RSA.PEM_MALFORMED`.
|
||||
|
||||
## Features
|
||||
|
||||
- Dual-label support (`"PUBLIC KEY"` and `"RSA PUBLIC KEY"`).
|
||||
- Delegates to existing DER parsers — no DER logic duplicated.
|
||||
- Strict-grammar body validation via `decodePem`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { parseRsaPublicKeyPem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const key = parseRsaPublicKeyPem(`
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
||||
-----END PUBLIC KEY-----
|
||||
`);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { parseRsaPublicKeyPem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
// Either label is accepted.
|
||||
const fromSpki = parseRsaPublicKeyPem(spkiPem);
|
||||
const fromPkcs1 = parseRsaPublicKeyPem(pkcs1Pem);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `parseRsaPublicKeyPem(pem: string): RsaPublicKey`
|
||||
|
||||
Throws `CryptoRsaError(CRYPTO_RSA.PEM_MALFORMED)` on framing errors and
|
||||
`CryptoRsaError(CRYPTO_RSA.INVALID_KEY | CRYPTO_RSA.UNSUPPORTED_FORMAT)`
|
||||
on inner-DER errors per the underlying DER parsers.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The dispatcher is a `Record<string, parser>` lookup keyed by label. Building
|
||||
the allowed-label list from the dispatcher's keys ensures the codec's
|
||||
allowlist and the dispatcher's coverage cannot drift — adding a new label
|
||||
requires changing exactly one place.
|
||||
61
docs/security-stack/crypto/crypto-rsa/src/pem/pem-codec.md
Normal file
61
docs/security-stack/crypto/crypto-rsa/src/pem/pem-codec.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# `pem/pem-codec`
|
||||
|
||||
## Purpose
|
||||
|
||||
Underlying RFC 7468 textual-encoding codec used by the four PEM
|
||||
parse/serialise entry points in this module. Splits the codec from the
|
||||
public-key-specific dispatchers so the parser logic (label whitelist,
|
||||
mismatched BEGIN/END detection, base64 strict-grammar enforcement) lives
|
||||
in exactly one place.
|
||||
|
||||
## Features
|
||||
|
||||
- `decodePem(pem, allowedLabels)` — line-tolerant scan for matching
|
||||
BEGIN/END armor, label whitelist enforcement, body base64 round-trip.
|
||||
- `encodePem(label, body)` — canonical 64-char body line emission with
|
||||
trailing newline.
|
||||
- All failure modes raise `CryptoRsaError(CRYPTO_RSA.PEM_MALFORMED)`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { decodePem, encodePem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const decoded = decodePem(pemString, ["PUBLIC KEY"]);
|
||||
const back = encodePem("PUBLIC KEY", decoded.body);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { decodePem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const { label, body } = decodePem(input, ["PUBLIC KEY", "RSA PUBLIC KEY"]);
|
||||
// Dispatch on label to the appropriate DER parser.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `decodePem(pem: string, allowedLabels: readonly string[]): DecodedPem`
|
||||
|
||||
Returns `{ label, body }`. Throws `PEM_MALFORMED` on missing BEGIN line,
|
||||
unknown label, missing matching END line, empty body, non-base64 body
|
||||
characters, or base64-decode failure.
|
||||
|
||||
### `encodePem(label: string, body: Uint8Array): string`
|
||||
|
||||
Returns the canonical PEM block.
|
||||
|
||||
### `interface DecodedPem { readonly label: string; readonly body: Uint8Array }`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The body grammar is enforced before base64 decoding because some base64
|
||||
implementations silently accept non-canonical characters (whitespace, `_`,
|
||||
`-`) that violate strict RFC 7468. We require the strict alphabet
|
||||
`[A-Za-z0-9+/=]` to ensure round-trip determinism.
|
||||
|
||||
The codec accepts both LF and CRLF line endings on parse but always emits
|
||||
LF — the canonical form recommended by RFC 7468 §2 for systems with
|
||||
flexible line-ending support. CRLF emission is left to the consumer (e.g.
|
||||
HTTP frameworks) as a transport-layer concern.
|
||||
@@ -0,0 +1,43 @@
|
||||
# `pem/serialize-private`
|
||||
|
||||
## Purpose
|
||||
|
||||
Inverse of `parseRsaPrivateKeyPem`. Emits PEM textual encoding for an RSA
|
||||
private key in either PKCS#8 (`"PRIVATE KEY"`, the modern default) or
|
||||
PKCS#1 (`"RSA PRIVATE KEY"`, the legacy OpenSSL form) per RFC 7468 §3.
|
||||
|
||||
## Features
|
||||
|
||||
- 64-char body line emission.
|
||||
- Discriminated `format` option (`"pkcs8"` default | `"pkcs1"`).
|
||||
- Reuses the underlying DER serialisers.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { serializeRsaPrivateKeyPem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const pem = serializeRsaPrivateKeyPem(key);
|
||||
// -----BEGIN PRIVATE KEY----- ...
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { serializeRsaPrivateKeyPem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const legacy = serializeRsaPrivateKeyPem(key, { format: "pkcs1" });
|
||||
// -----BEGIN RSA PRIVATE KEY----- ...
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `serializeRsaPrivateKeyPem(key, options?: { format?: "pkcs8" | "pkcs1" }): string`
|
||||
|
||||
Returns the PEM string with trailing newline.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
We default to PKCS#8 for the same reason `serialize-public` defaults to
|
||||
SPKI — modern PKI tooling, KMS providers, and JOSE libraries all consume
|
||||
PKCS#8. PKCS#1 remains for legacy OpenSSL and ssh-keygen interop.
|
||||
@@ -0,0 +1,44 @@
|
||||
# `pem/serialize-public`
|
||||
|
||||
## Purpose
|
||||
|
||||
Inverse of `parseRsaPublicKeyPem`. Emits PEM textual encoding for an RSA
|
||||
public key in either SPKI (`"PUBLIC KEY"`, the default modern form) or
|
||||
PKCS#1 (`"RSA PUBLIC KEY"`, the legacy OpenSSL form) per RFC 7468 §3.
|
||||
|
||||
## Features
|
||||
|
||||
- 64-char body line emission (RFC 7468 §3 canonical).
|
||||
- Discriminated `format` option (`"spki"` default | `"pkcs1"`).
|
||||
- Reuses the underlying DER serialisers — no DER logic duplicated.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { serializeRsaPublicKeyPem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const pem = serializeRsaPublicKeyPem(key);
|
||||
// -----BEGIN PUBLIC KEY----- ... (SPKI form)
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { serializeRsaPublicKeyPem } from "@suaveplan/crypto-rsa/pem";
|
||||
|
||||
const legacyOpenSsl = serializeRsaPublicKeyPem(key, { format: "pkcs1" });
|
||||
// -----BEGIN RSA PUBLIC KEY----- ... (PKCS#1 form)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `serializeRsaPublicKeyPem(key, options?: { format?: "spki" | "pkcs1" }): string`
|
||||
|
||||
Returns the PEM string with a trailing newline.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The default is `format: "spki"` because the modern OpenSSL toolchain and
|
||||
all PKI standards (X.509 / TLS / OIDC / WebAuthn) emit and consume SPKI-
|
||||
labeled PEM. The PKCS#1 variant remains for compatibility with legacy
|
||||
OpenSSL `openssl rsa -RSAPublicKey_out` and ssh-keygen tooling.
|
||||
Reference in New Issue
Block a user