@push.rocks/smartlinuxrepo 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,263 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { LinuxRepoError } from '../shared/errors.js';
3
+ import { assertBytes, assertString, assertTimestamp } from '../shared/validation.js';
4
+ import { dearmor, decodeArmorBytes, readPacketTags, splitCleartextSignedMessage, } from './openpgpframing.js';
5
+ const HASH_ALGORITHMS = new Map([
6
+ [plugins.openpgp.enums.hash.sha256, 'sha256'],
7
+ [plugins.openpgp.enums.hash.sha384, 'sha384'],
8
+ [plugins.openpgp.enums.hash.sha512, 'sha512'],
9
+ ]);
10
+ /** Subpacket types openpgp.js reads in the unhashed area (issuer, embedded signature, issuer fingerprint). */
11
+ const UNHASHED_SUBPACKETS_READ = new Set([16, 32, 33]);
12
+ /**
13
+ * The openpgp.js settings verification depends on, pinned instead of taken
14
+ * from the process-wide `openpgp.config`. The values are openpgp.js 6.3.2's
15
+ * defaults except `ignoreUnsupportedPackets`.
16
+ */
17
+ const VERIFY_CONFIG = {
18
+ ...plugins.openpgp.config,
19
+ ignoreUnsupportedPackets: false,
20
+ ignoreMalformedPackets: false,
21
+ enableParsingV5Entities: false,
22
+ additionalAllowedPackets: [],
23
+ allowMissingKeyFlags: false,
24
+ allowInsecureVerificationWithReformattedKeys: false,
25
+ minRSABits: 2047,
26
+ knownNotations: [],
27
+ rejectHashAlgorithms: new Set([plugins.openpgp.enums.hash.md5, plugins.openpgp.enums.hash.ripemd]),
28
+ rejectMessageHashAlgorithms: new Set([
29
+ plugins.openpgp.enums.hash.md5,
30
+ plugins.openpgp.enums.hash.ripemd,
31
+ plugins.openpgp.enums.hash.sha1,
32
+ ]),
33
+ rejectPublicKeyAlgorithms: new Set([plugins.openpgp.enums.publicKey.elgamal, plugins.openpgp.enums.publicKey.dsa]),
34
+ rejectCurves: new Set([plugins.openpgp.enums.curve.secp256k1]),
35
+ };
36
+ /**
37
+ * Builds a verifier from one armored OpenPGP public key: exactly one
38
+ * transferable v4 public key (the primary key with its user IDs, subkeys and
39
+ * self-signatures, all v4) in exactly one `PUBLIC KEY BLOCK`. A private key
40
+ * block, several blocks or keys, a v6 key, text around the block, an armor
41
+ * CRC-24 that does not match or any other garbage is `invalid-key`
42
+ * (`detail.field` `publicKeyArmored`). Only the structure is checked here;
43
+ * whether a key can sign depends on time and is checked at verification.
44
+ */
45
+ export async function createOpenPgpVerifier(inputArg) {
46
+ if (inputArg === null || typeof inputArg !== 'object') {
47
+ throw new LinuxRepoError('invalid-input', 'input must be an object', { field: 'input' });
48
+ }
49
+ assertString(inputArg.publicKeyArmored, 'publicKeyArmored');
50
+ const keyBytes = dearmor(inputArg.publicKeyArmored, 'PUBLIC KEY BLOCK', { code: 'invalid-key', field: 'publicKeyArmored' });
51
+ let keys;
52
+ try {
53
+ keys = await plugins.openpgp.readKeys({ binaryKeys: keyBytes, config: VERIFY_CONFIG });
54
+ }
55
+ catch (error) {
56
+ throw new LinuxRepoError('invalid-key', 'the OpenPGP public key cannot be read', { field: 'publicKeyArmored' }, {
57
+ cause: error,
58
+ });
59
+ }
60
+ if (keys.length !== 1) {
61
+ throw new LinuxRepoError('invalid-key', `expected exactly one OpenPGP public key, got ${keys.length}`, {
62
+ field: 'publicKeyArmored',
63
+ });
64
+ }
65
+ const key = keys[0];
66
+ if (key.isPrivate()) {
67
+ throw new LinuxRepoError('invalid-key', 'a private key is not accepted as the verification key', {
68
+ field: 'publicKeyArmored',
69
+ });
70
+ }
71
+ if (key.keyPacket.version !== 4 || key.subkeys.some((subkey) => subkey.keyPacket.version !== 4)) {
72
+ throw new LinuxRepoError('invalid-key', 'only OpenPGP v4 keys and subkeys are accepted', { field: 'publicKeyArmored' });
73
+ }
74
+ const publicKey = key;
75
+ return {
76
+ fingerprint: publicKey.getFingerprint().toUpperCase(),
77
+ verifyDetachedSignature: async (input) => {
78
+ assertInputObject(input);
79
+ assertBytes(input.data, 'data');
80
+ assertBytes(input.signature, 'signature');
81
+ assertTimestamp(input.date, 'date');
82
+ return verifySignature({
83
+ key: publicKey,
84
+ packets: input.signature,
85
+ content: { binary: input.data },
86
+ signatureType: plugins.openpgp.enums.signature.binary,
87
+ date: input.date,
88
+ field: 'signature',
89
+ headerHashAlgorithms: [],
90
+ });
91
+ },
92
+ verifyArmoredDetachedSignature: async (input) => {
93
+ assertInputObject(input);
94
+ assertBytes(input.data, 'data');
95
+ assertBytes(input.signatureArmored, 'signatureArmored');
96
+ assertTimestamp(input.date, 'date');
97
+ const failure = { code: 'malformed-signature', field: 'signatureArmored' };
98
+ return verifySignature({
99
+ key: publicKey,
100
+ packets: dearmor(decodeArmorBytes(input.signatureArmored, failure), 'SIGNATURE', failure),
101
+ content: { binary: input.data },
102
+ signatureType: plugins.openpgp.enums.signature.binary,
103
+ date: input.date,
104
+ field: 'signatureArmored',
105
+ headerHashAlgorithms: [],
106
+ });
107
+ },
108
+ verifyCleartextMessage: async (input) => {
109
+ assertInputObject(input);
110
+ assertBytes(input.message, 'message');
111
+ assertTimestamp(input.date, 'date');
112
+ const parts = splitCleartextSignedMessage(input.message, { code: 'malformed-signature', field: 'message' });
113
+ const verification = await verifySignature({
114
+ key: publicKey,
115
+ packets: parts.signature,
116
+ // a text signature hashes the text with CRLF line endings; the split refused trailing whitespace,
117
+ // so this equals the cleartext digest of RFC 9580 section 7.1
118
+ content: { text: parts.signedText },
119
+ signatureType: plugins.openpgp.enums.signature.text,
120
+ date: input.date,
121
+ field: 'message',
122
+ headerHashAlgorithms: parts.headerHashAlgorithms,
123
+ });
124
+ return { ...verification, signedText: parts.signedText };
125
+ },
126
+ };
127
+ }
128
+ function hexByte(valueArg) {
129
+ return valueArg === null ? 'none' : `0x${valueArg.toString(16).padStart(2, '0')}`;
130
+ }
131
+ function assertInputObject(inputArg) {
132
+ if (inputArg === null || typeof inputArg !== 'object') {
133
+ throw new LinuxRepoError('invalid-input', 'input must be an object', { field: 'input' });
134
+ }
135
+ }
136
+ /**
137
+ * The verification steps, each with its own error code: structure
138
+ * (`malformed-signature`), issuer (`wrong-key`), the signature's own time
139
+ * rules (`bad-signature`), the issuing key's ability to sign at creation and
140
+ * at `date` (`unusable-key`), then the cryptographic check (`bad-signature`).
141
+ */
142
+ async function verifySignature(inputArg) {
143
+ const { key, date, field } = inputArg;
144
+ const malformed = (messageArg, causeArg) => new LinuxRepoError('malformed-signature', messageArg, { field }, causeArg === undefined ? undefined : { cause: causeArg });
145
+ // 1. structure, before any cryptography
146
+ const tags = readPacketTags(inputArg.packets, { code: 'malformed-signature', field });
147
+ if (tags.length !== 1 || tags[0] !== plugins.openpgp.enums.packet.signature) {
148
+ throw malformed(`expected exactly one signature packet, got packet tags [${tags.join(', ')}]`);
149
+ }
150
+ let signature;
151
+ try {
152
+ signature = await plugins.openpgp.readSignature({ binarySignature: inputArg.packets, config: VERIFY_CONFIG });
153
+ }
154
+ catch (error) {
155
+ throw malformed('the signature packet cannot be read', error);
156
+ }
157
+ const packet = signature.packets[0];
158
+ if (signature.packets.length !== 1 || !(packet instanceof plugins.openpgp.SignaturePacket)) {
159
+ throw malformed('expected exactly one signature packet');
160
+ }
161
+ if (packet.version !== 4) {
162
+ throw malformed(`only v4 signatures are accepted, got v${packet.version}`);
163
+ }
164
+ if (packet.signatureType !== inputArg.signatureType) {
165
+ throw malformed(`expected signature type ${hexByte(inputArg.signatureType)}, got ${hexByte(packet.signatureType)}`);
166
+ }
167
+ const hashId = packet.hashAlgorithm;
168
+ const hashAlgorithm = hashId === null ? undefined : HASH_ALGORITHMS.get(hashId);
169
+ if (hashId === null || hashAlgorithm === undefined) {
170
+ throw malformed(`hash algorithm ${hashId} is not SHA-256, SHA-384 or SHA-512`);
171
+ }
172
+ if (inputArg.headerHashAlgorithms.length > 0 && !inputArg.headerHashAlgorithms.includes(hashId)) {
173
+ throw malformed('the Hash: header does not name the signature hash algorithm');
174
+ }
175
+ if (packet.created === null) {
176
+ throw malformed('the signature has no creation time');
177
+ }
178
+ const issuerKeyId = packet.issuerKeyID.toHex();
179
+ if (!/^[0-9a-f]{16}$/.test(issuerKeyId) || /^0+$/.test(issuerKeyId)) {
180
+ throw malformed('the signature names no issuer, or the wildcard issuer');
181
+ }
182
+ if (packet.unknownSubpackets.some((subpacket) => subpacket.critical)) {
183
+ throw malformed('the signature has an unknown critical subpacket');
184
+ }
185
+ if (packet.unhashedSubpackets.some((subpacket) => subpacket.critical && !UNHASHED_SUBPACKETS_READ.has(subpacket.type))) {
186
+ throw malformed('the signature has a critical unhashed subpacket the verifier does not read');
187
+ }
188
+ if (packet.rawNotations.some((notation) => notation.critical)) {
189
+ throw malformed('the signature has a critical notation');
190
+ }
191
+ // 2. issuer: the primary key or one of its subkeys, by key ID and, when present, by fingerprint
192
+ const issuerFingerprint = packet.issuerFingerprint === null ? null : Buffer.from(packet.issuerFingerprint).toString('hex');
193
+ const issuers = [key, ...key.subkeys].filter((candidate) => candidate.getKeyID().equals(packet.issuerKeyID) && (issuerFingerprint === null || candidate.getFingerprint() === issuerFingerprint));
194
+ if (issuers.length === 0) {
195
+ throw new LinuxRepoError('wrong-key', 'the signature was not issued by this key or one of its subkeys', {
196
+ field,
197
+ value: (issuerFingerprint ?? issuerKeyId).toUpperCase(),
198
+ });
199
+ }
200
+ // 3. the signature's own time rules
201
+ if (packet.isExpired(date)) {
202
+ const reason = packet.created.getTime() > date.getTime() ? 'was created after the verification date' : 'has expired at the verification date';
203
+ throw new LinuxRepoError('bad-signature', `the signature ${reason}`, { field });
204
+ }
205
+ // 4. the issuing key or subkey must be able to sign at the creation time and at `date`
206
+ const signingKeyAt = async (whenArg) => {
207
+ let signingKey;
208
+ try {
209
+ signingKey = await key.getSigningKey(packet.issuerKeyID, whenArg, undefined, VERIFY_CONFIG);
210
+ }
211
+ catch (error) {
212
+ throw new LinuxRepoError('unusable-key', `key ${issuerKeyId.toUpperCase()} cannot sign at ${whenArg.toISOString()}`, { field: 'publicKeyArmored', value: issuers[0].getFingerprint().toUpperCase() }, { cause: error });
213
+ }
214
+ if (!issuers.includes(signingKey)) {
215
+ throw new LinuxRepoError('unusable-key', `key ${issuerKeyId.toUpperCase()} cannot sign at ${whenArg.toISOString()}`, {
216
+ field: 'publicKeyArmored',
217
+ value: issuers[0].getFingerprint().toUpperCase(),
218
+ });
219
+ }
220
+ return signingKey;
221
+ };
222
+ const atCreation = await signingKeyAt(packet.created);
223
+ const atDate = await signingKeyAt(date);
224
+ if (atCreation !== atDate) {
225
+ throw new LinuxRepoError('unusable-key', `key ${issuerKeyId.toUpperCase()} is not the same signing key at creation and at the verification date`, {
226
+ field: 'publicKeyArmored',
227
+ value: issuers[0].getFingerprint().toUpperCase(),
228
+ });
229
+ }
230
+ // 5. cryptography
231
+ try {
232
+ const content = inputArg.content;
233
+ const { signatures } = 'text' in content
234
+ ? await plugins.openpgp.verify({
235
+ message: await plugins.openpgp.createMessage({ text: content.text }),
236
+ signature,
237
+ verificationKeys: key,
238
+ date,
239
+ config: VERIFY_CONFIG,
240
+ })
241
+ : await plugins.openpgp.verify({
242
+ message: await plugins.openpgp.createMessage({ binary: content.binary }),
243
+ signature,
244
+ verificationKeys: key,
245
+ date,
246
+ format: 'binary',
247
+ config: VERIFY_CONFIG,
248
+ });
249
+ if (signatures.length !== 1) {
250
+ throw new Error(`openpgp.js evaluated ${signatures.length} signatures, expected 1`);
251
+ }
252
+ await signatures[0].verified;
253
+ }
254
+ catch (error) {
255
+ throw new LinuxRepoError('bad-signature', 'the signature does not verify', { field }, { cause: error });
256
+ }
257
+ return {
258
+ signingKeyFingerprint: atDate.getFingerprint().toUpperCase(),
259
+ created: new Date(packet.created.getTime()),
260
+ hashAlgorithm,
261
+ };
262
+ }
263
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyaWZpZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvc2lnbmluZy92ZXJpZmllcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3JELE9BQU8sRUFBRSxXQUFXLEVBQUUsWUFBWSxFQUFFLGVBQWUsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3JGLE9BQU8sRUFDTCxPQUFPLEVBQ1AsZ0JBQWdCLEVBQ2hCLGNBQWMsRUFDZCwyQkFBMkIsR0FFNUIsTUFBTSxxQkFBcUIsQ0FBQztBQWdFN0IsTUFBTSxlQUFlLEdBQStDLElBQUksR0FBRyxDQUFDO0lBQzFFLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUM7SUFDN0MsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQztJQUM3QyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsUUFBUSxDQUFDO0NBQzlDLENBQUMsQ0FBQztBQUVILDhHQUE4RztBQUM5RyxNQUFNLHdCQUF3QixHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBRXZEOzs7O0dBSUc7QUFDSCxNQUFNLGFBQWEsR0FBMkI7SUFDNUMsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLE1BQU07SUFDekIsd0JBQXdCLEVBQUUsS0FBSztJQUMvQixzQkFBc0IsRUFBRSxLQUFLO0lBQzdCLHVCQUF1QixFQUFFLEtBQUs7SUFDOUIsd0JBQXdCLEVBQUUsRUFBRTtJQUM1QixvQkFBb0IsRUFBRSxLQUFLO0lBQzNCLDRDQUE0QyxF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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartlinuxrepo",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "A pure codec for Linux package repositories (Alpine apk, Debian apt, RPM dnf, Arch pacman): read packages into records, build index file sets and sign them.",
6
6
  "main": "dist_ts/index.js",
package/readme.md CHANGED
@@ -16,7 +16,7 @@ Node.js `^24.20.0 || >=26.6.0`: the releases whose `node:zlib` reports a truncat
16
16
 
17
17
  ## Status
18
18
 
19
- All four formats read, index and sign: `readAlpinePackage`, `readDebianPackage`, `readRpmPackage` and `readArchPackage` read packages into records; `buildAlpineIndex`, `buildDebianIndex`, `buildRpmIndex` and `buildArchIndex` build signed index file sets; `signRpmPackage` and `signArchPackage` sign packages at upload. Each format is checked against Gitea 28.2.0's own output and installed from with apk, apt, dnf and pacman. The built-in OpenPGP signer does not stream: openpgp.js 6.3.2 holds a detached-signed document in memory (see Signing).
19
+ All four formats read, index and sign: `readAlpinePackage`, `readDebianPackage`, `readRpmPackage` and `readArchPackage` read packages into records; `buildAlpineIndex`, `buildDebianIndex`, `buildRpmIndex` and `buildArchIndex` build signed index file sets; `signRpmPackage` and `signArchPackage` sign packages at upload. Each format is checked against Gitea 28.2.0's own output and installed from with apk, apt, dnf and pacman. `createOpenPgpVerifier` verifies OpenPGP detached and cleartext signatures (see Verification). The built-in OpenPGP signer does not stream: openpgp.js 6.3.2 holds a detached-signed document in memory (see Signing).
20
20
 
21
21
  ## Records
22
22
 
@@ -36,7 +36,7 @@ Readers take `Partial<ILinuxPackageLimits>` per call over `DEFAULT_LINUX_PACKAGE
36
36
  | `maxFileListBytes` | 16 MiB | total Arch file-name bytes |
37
37
  | `maxHeaderBytes` | 4 MiB | one tar header chain or metadata line |
38
38
 
39
- Every failure is a `LinuxRepoError` with a stable `code` (`limit-exceeded`, `unsupported-compression`, `malformed-archive`, `malformed-header`, `missing-field`, `invalid-field`, `unsupported-package-format`, `truncated`, `digest-mismatch`, `invalid-input`, `invalid-key`, `signing-failed`) and a `detail` naming the field or limit. Errors from your own source or signer, and failures inside node:crypto or openpgp.js during key generation, are passed through unchanged.
39
+ Every failure is a `LinuxRepoError` with a stable `code` (`limit-exceeded`, `unsupported-compression`, `malformed-archive`, `malformed-header`, `missing-field`, `invalid-field`, `unsupported-package-format`, `truncated`, `digest-mismatch`, `invalid-input`, `invalid-key`, `signing-failed`, and from the verifier `malformed-signature`, `wrong-key`, `unusable-key`, `bad-signature`) and a `detail` naming the field or limit. Errors from your own source or signer, and failures inside node:crypto or openpgp.js during key generation, are passed through unchanged. New codes arrive in minor releases (1.1.0 added the four verifier codes), each raised only by a new function: a `switch` over `code` needs a default branch.
40
40
 
41
41
  ## Signing
42
42
 
@@ -65,6 +65,37 @@ const pgpSigner = await createOpenPgpSigner({ privateKeyArmored: pgpKeys.private
65
65
 
66
66
  `isIndexableArchitecture(architecture)` tells whether an architecture can name an index path; builders refuse entries that fail it.
67
67
 
68
+ ## Verification
69
+
70
+ ```ts
71
+ import { createOpenPgpVerifier } from '@push.rocks/smartlinuxrepo';
72
+
73
+ const verifier = await createOpenPgpVerifier({ publicKeyArmored }); // invalid-key unless one v4 public key
74
+ const now = new Date();
75
+ const signature = await pgpSigner.signBinary((async function* () { yield data; })(), now); // a raw packet, as an Arch .sig
76
+ await verifier.verifyDetachedSignature({ data, signature, date: now });
77
+ await verifier.verifyArmoredDetachedSignature({ data: release, signatureArmored: releaseGpg, date: now }); // Release.gpg, repomd.xml.asc
78
+ const { signedText } = await verifier.verifyCleartextMessage({ message: inRelease, date: now }); // InRelease
79
+ ```
80
+
81
+ `createOpenPgpVerifier` reads exactly one transferable OpenPGP v4 public key (primary key, user IDs, subkeys, self-signatures) in exactly one `PUBLIC KEY BLOCK`. A private key, several blocks or keys, a v6 key, text around the block or an armor CRC-24 that does not match is `invalid-key`. Armor headers (`Comment:`, `Version:`) are ignored; a malformed header line is refused. Whether the key can sign is checked at verification, where the time is known.
82
+
83
+ The three methods take the served bytes as `Uint8Array` and a required `date`; the codec reads no clock. `verifyDetachedSignature` takes a raw signature packet (what `signBinary` returns, an Arch `.sig`), `verifyArmoredDetachedSignature` an ASCII-armored one, and nothing is sniffed. A signature is valid when it is exactly one v4 signature packet (type 0x00 detached, 0x01 cleartext) hashed with SHA-256, SHA-384 or SHA-512, issued by the primary key or one of its subkeys, created at or before `date` and not expired at `date`, made by a key that can sign both at the signature's creation time and at `date` (not expired, not revoked, sign flag, valid binding, openpgp.js's algorithm floor), and when it verifies. A result names the key or subkey that verified (`signingKeyFingerprint`, 40 uppercase hex), the creation time and the hash algorithm. Every failure is a `LinuxRepoError`, never a boolean:
84
+
85
+ | Code | Meaning |
86
+ | --- | --- |
87
+ | `malformed-signature` | armor, encoding or framing, not exactly one v4 signature packet, the wrong signature type, a hash outside SHA-256/384/512, a `Hash:` header without the signature's hash, no creation time, no or a wildcard issuer, an unknown critical subpacket or notation |
88
+ | `wrong-key` | issued by neither the primary key nor a subkey, or the issuer fingerprint disagrees (`detail.value`: the issuer in hex) |
89
+ | `unusable-key` | the issuing key or subkey cannot sign at the creation time or at `date`: expired, revoked, no sign flag, created after the signature, below the algorithm floor (`detail.value`: the fingerprint of the key or subkey that cannot sign) |
90
+ | `bad-signature` | the cryptographic check failed, or the signature was created after `date` or has expired at `date` |
91
+ | `invalid-input` | an argument that is not a `Uint8Array`, or an invalid `date` |
92
+
93
+ A cleartext message is strict UTF-8 with LF line endings, as apt splits it: the `-----BEGIN PGP SIGNED MESSAGE-----` line first, optional `Hash:` headers and no others, the dash-escaped text, one signature block, then at most one line break. Text before or after, a CR, a line ending in a space or tab, or a line starting with `-` but not `- ` is `malformed-signature`. `signedText` is the text as signed: dash-escaping removed, without the line break before the signature block. For the codec's and GnuPG's `InRelease` that is `Release` without its final line break; Gitea 28.2.0's `InRelease` (go-crypto) signs that line break too, so its `signedText` is the whole `Release`.
94
+
95
+ Two refusals depart from RFC 9580: an armor CRC-24 that does not match (§6.1; gpgv refuses it, sqv accepts it) and a `Hash:` header that does not name the signature's hash (§6.2.2.3).
96
+
97
+ The verdicts match gpgv as apt reads it (`EXPKEYSIG` and `REVKEYSIG` are refusals). sqv, apt 3's verifier, checks the key only at the signature's creation time: it accepts a signature whose subkey expired, or whose key was soft-revoked, after signing, which the codec refuses at a later `date`.
98
+
68
99
  Every builder returns the complete file set for its input as `ILinuxIndexFile[]` (`path`, `bytes`, `sha256`, `mediaType`, `stage`, `aliases`); a previously written file that is not in the set is stale. Write every `content` file before any `pointer` file: the pointer files are the signed documents that name the content files.
69
100
 
70
101
  ## Alpine
@@ -109,7 +140,7 @@ const files = await buildDebianIndex({
109
140
 
110
141
  `readDebianPackage` reads the `control.tar` member (plain, `.gz`, `.xz` or `.zst`) and hashes the rest of the file without decompressing it. The record keeps Gitea's derived values exactly, and its `fileName` is `<name>_<version>_<architecture>.deb`.
111
142
 
112
- `buildDebianIndex` returns the complete set for one distribution. For every component and architecture with packages it writes `dists/<distribution>/<component>/binary-<architecture>/Packages`, `Packages.gz` and `Packages.xz` (`content` files). Packages of architecture `all` are listed only under `binary-all`, as in Gitea. Then it writes `dists/<distribution>/Release`, its armored detached signature `Release.gpg` and the cleartext-signed `InRelease` (`pointer` files). `distribution` and `component` must match `^[a-zA-Z0-9][a-zA-Z0-9.~+_-]*$`; `origin` and `label` are each one non-empty line without surrounding whitespace. Your route serves:
143
+ `buildDebianIndex` returns the complete set for one distribution. For every component and architecture with packages it writes `dists/<distribution>/<component>/binary-<architecture>/Packages`, `Packages.gz` and `Packages.xz` (`content` files). Packages of architecture `all` are listed only under `binary-all`, as in Gitea. Then it writes `dists/<distribution>/Release`, its armored detached signature `Release.gpg` and the cleartext-signed `InRelease` (`pointer` files). `distribution` and `component` must match `^[a-zA-Z0-9][a-zA-Z0-9.~+_-]*$` (`isDebianSymbol`); `origin` and `label` are each one non-empty line without surrounding whitespace (`isDebianReleaseValue`). Both predicates are the builder's own rules, for checking configuration before a build. Entries are `IDebianIndexEntry` objects (`{ component, record }`). Your route serves:
113
144
 
114
145
  - every file at its `path` and at each of its `aliases`, `by-hash/MD5Sum`, `SHA1`, `SHA256` and `SHA512/<hex>` next to the `Packages` file. `Release` sets `Acquire-By-Hash: yes`, and apt 2.6 and 3.x fetch `Packages.xz` through `by-hash/SHA512/<hex>`;
115
146
  - each package file at `pool/<distribution>/<component>/<fileName>`, the `Filename:` its stanza names;
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartlinuxrepo',
6
- version: '1.0.0',
6
+ version: '1.1.0',
7
7
  description: 'A pure codec for Linux package repositories (Alpine apk, Debian apt, RPM dnf, Arch pacman): read packages into records, build index file sets and sign them.'
8
8
  }
@@ -11,8 +11,6 @@ import { parseMailAddress } from './mailaddress.js';
11
11
  */
12
12
  const NAME_PATTERN = /^[a-z0-9][a-z0-9+-.]+$/;
13
13
  const VERSION_PATTERN = /^(?:(0|[1-9][0-9]*):)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?$/;
14
- /** Gitea's distribution and component rule (`metadata.go:67`, applied at upload). */
15
- export const DEBIAN_SYMBOL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9.~+_-]*$/;
16
14
 
17
15
  export interface IDebianControl {
18
16
  name: string;
@@ -2,11 +2,11 @@ import * as plugins from '../plugins.js';
2
2
  import { isIndexableArchitecture } from '../shared/architecture.js';
3
3
  import { LinuxRepoError } from '../shared/errors.js';
4
4
  import type { IDebianPackageRecord, ILinuxIndexFile } from '../shared/types.js';
5
- import { assertSegmentName, assertString, assertTimestamp, hasControlCharacter } from '../shared/validation.js';
5
+ import { assertSegmentName, assertString, assertTimestamp } from '../shared/validation.js';
6
6
  import type { IOpenPgpSigner } from '../signing/signers.js';
7
7
  import { createCleartextSignedMessage, createDetachedArmoredSignature } from '../signing/openpgpframing.js';
8
- import { DEBIAN_SYMBOL_PATTERN } from './control.js';
9
8
  import { compareUtf8, goTrimSpace, utf8ByteString } from './gostring.js';
9
+ import { DEBIAN_SYMBOL_PATTERN, isDebianReleaseValue, isDebianSymbol } from './rules.js';
10
10
 
11
11
  /** The fields the `Packages` stanza writes itself; a control file's own copies are dropped. */
12
12
  const INDEX_OWNED_FIELDS = new Set(['filename', 'size', 'md5sum', 'sha1', 'sha256', 'sha512']);
@@ -29,6 +29,7 @@ const HASH_PATTERNS = {
29
29
  sha512: /^[0-9a-f]{128}$/,
30
30
  };
31
31
 
32
+ /** One package of a distribution's index: the record and the component it is listed under. */
32
33
  export interface IDebianIndexEntry {
33
34
  component: string;
34
35
  record: IDebianPackageRecord;
@@ -52,7 +53,7 @@ export interface IDebianIndexEntry {
52
53
  */
53
54
  export async function buildDebianIndex(input: {
54
55
  distribution: string;
55
- entries: Array<{ component: string; record: IDebianPackageRecord }>;
56
+ entries: IDebianIndexEntry[];
56
57
  origin: string;
57
58
  label: string;
58
59
  signer: IOpenPgpSigner;
@@ -282,7 +283,7 @@ function assertRecord(recordArg: IDebianPackageRecord): void {
282
283
 
283
284
  function assertSymbol(valueArg: unknown, fieldArg: string): asserts valueArg is string {
284
285
  assertString(valueArg, fieldArg);
285
- if (!DEBIAN_SYMBOL_PATTERN.test(valueArg)) {
286
+ if (!isDebianSymbol(valueArg)) {
286
287
  throw new LinuxRepoError('invalid-input', `${fieldArg} must match ${DEBIAN_SYMBOL_PATTERN.source}`, {
287
288
  field: fieldArg,
288
289
  value: valueArg,
@@ -293,7 +294,7 @@ function assertSymbol(valueArg: unknown, fieldArg: string): asserts valueArg is
293
294
  /** A Release header value: one line, non-empty, no leading or trailing whitespace (canonical cleartext). */
294
295
  function assertReleaseValue(valueArg: unknown, fieldArg: string): asserts valueArg is string {
295
296
  assertString(valueArg, fieldArg);
296
- if (valueArg === '' || hasControlCharacter(valueArg) || valueArg !== valueArg.trim()) {
297
+ if (!isDebianReleaseValue(valueArg)) {
297
298
  throw new LinuxRepoError(
298
299
  'invalid-input',
299
300
  `${fieldArg} must be non-empty, without control characters or surrounding whitespace`,
@@ -0,0 +1,23 @@
1
+ import { hasControlCharacter } from '../shared/validation.js';
2
+
3
+ /** Gitea's distribution and component rule (`modules/packages/debian/metadata.go:67`, applied at upload). */
4
+ export const DEBIAN_SYMBOL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9.~+_-]*$/;
5
+
6
+ /**
7
+ * Whether a value is a distribution or component name `buildDebianIndex`
8
+ * accepts: Gitea's rule, `^[a-zA-Z0-9][a-zA-Z0-9.~+_-]*$`. A non-string is
9
+ * not one.
10
+ */
11
+ export function isDebianSymbol(valueArg: string): boolean {
12
+ return typeof valueArg === 'string' && DEBIAN_SYMBOL_PATTERN.test(valueArg);
13
+ }
14
+
15
+ /**
16
+ * Whether a value can be a Release header value (`Origin`, `Label`) that
17
+ * `buildDebianIndex` accepts: non-empty, no control characters (so one line)
18
+ * and no leading or trailing whitespace (canonical cleartext). A non-string
19
+ * is not one.
20
+ */
21
+ export function isDebianReleaseValue(valueArg: string): boolean {
22
+ return typeof valueArg === 'string' && valueArg !== '' && !hasControlCharacter(valueArg) && valueArg === valueArg.trim();
23
+ }
package/ts/index.ts CHANGED
@@ -29,10 +29,19 @@ export type { TLinuxRepoErrorCode, ILinuxRepoErrorDetail } from './shared/errors
29
29
  export { isIndexableArchitecture } from './shared/architecture.js';
30
30
  export { createAlpineSigner, createOpenPgpSigner } from './signing/signers.js';
31
31
  export type { IAlpineSigner, IOpenPgpSigner } from './signing/signers.js';
32
+ export { createOpenPgpVerifier } from './signing/verifiers.js';
33
+ export type {
34
+ IOpenPgpCleartextVerification,
35
+ IOpenPgpSignatureVerification,
36
+ IOpenPgpVerifier,
37
+ TOpenPgpHashAlgorithm,
38
+ } from './signing/verifiers.js';
32
39
  export { generateAlpineKeyPair, generateOpenPgpKeyPair, giteaAlpineKeyName } from './signing/keys.js';
33
40
  export type { IAlpineKeyPair, IOpenPgpKeyPair } from './signing/keys.js';
34
41
  export { readDebianPackage } from './debian/reader.js';
35
42
  export { buildDebianIndex } from './debian/indexbuilder.js';
43
+ export type { IDebianIndexEntry } from './debian/indexbuilder.js';
44
+ export { isDebianReleaseValue, isDebianSymbol } from './debian/rules.js';
36
45
  export { readRpmPackage } from './rpm/reader.js';
37
46
  export { signRpmPackage } from './rpm/sign.js';
38
47
  export { buildRpmIndex } from './rpm/builder.js';
@@ -81,6 +81,15 @@ function decodeGoUtf8Slow(bytesArg: Uint8Array): string {
81
81
  return parts.join('');
82
82
  }
83
83
 
84
+ /** Decodes strict UTF-8, keeping a leading BOM; `null` when any sequence is invalid. */
85
+ export function decodeStrictUtf8(bytesArg: Uint8Array): string | null {
86
+ try {
87
+ return strictUtf8.decode(bytesArg);
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
84
93
  /** ASCII/Latin-1 view of header bytes (magic values, numeric fields). */
85
94
  export function decodeLatin1(bytesArg: Uint8Array): string {
86
95
  let text = '';
@@ -14,6 +14,19 @@ import type { ILinuxPackageLimits } from './limits.js';
14
14
  * - `invalid-input`: a bad argument from the caller.
15
15
  * - `invalid-key`: a key the codec cannot use.
16
16
  * - `signing-failed`: a codec signer failed, or a signer returned something that is not signature bytes.
17
+ * - `malformed-signature`: an OpenPGP signature or cleartext-signed message the verifier refuses before
18
+ * any cryptography: armor, encoding, framing, packet count, version, signature type, a hash outside
19
+ * SHA-256/384/512, a `Hash:` header that does not name the signature's hash, a missing creation time,
20
+ * a missing or wildcard issuer, or an unknown critical subpacket or notation.
21
+ * - `wrong-key`: the signature's issuer is neither the verifier's primary key nor one of its subkeys, or
22
+ * its issuer fingerprint disagrees (`detail.value` carries the issuer in hex).
23
+ * - `unusable-key`: the issuing key or subkey cannot sign at the signature's creation time or at the
24
+ * verification date: expired, revoked, no sign flag, a bad binding or back-signature, created after
25
+ * the signature, or below the algorithm floor.
26
+ * - `bad-signature`: the cryptographic check failed, the signature was created after the verification
27
+ * date, or it has expired at that date.
28
+ *
29
+ * New codes arrive in minor releases: a consumer that switches over `code` needs a default branch.
17
30
  *
18
31
  * The codec never wraps an error it did not raise: an error from the caller's
19
32
  * source or from a caller-supplied signer propagates unchanged. An underlying
@@ -31,7 +44,11 @@ export type TLinuxRepoErrorCode =
31
44
  | 'digest-mismatch'
32
45
  | 'invalid-input'
33
46
  | 'invalid-key'
34
- | 'signing-failed';
47
+ | 'signing-failed'
48
+ | 'malformed-signature'
49
+ | 'wrong-key'
50
+ | 'unusable-key'
51
+ | 'bad-signature';
35
52
 
36
53
  /** Names the field, structure or limit an error is about. */
37
54
  export interface ILinuxRepoErrorDetail {