diff --git a/AGENTS.md b/AGENTS.md index 972d6aa..715560c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,7 +41,7 @@ This file is mandatory for AI and coding agents working in this repository. Read - UTF-8 / Hex has the permanent tool ID `utf8-hex` and route `/tools/utf8-hex`. It uses strict UTF-8 conversion. Hex input accepts continuous text or chunks separated by whitespace, English commas, or Chinese commas, with an optional `0x` prefix on each chunk. Invalid UTF-8 must fail rather than silently replace bytes. - Base64 supports standard and URL-safe input. Decoded output supports strict UTF-8, continuous Hex, and `0xNN, ...` byte arrays. Reject invalid characters, padding placement, lengths, and non-canonical trailing bits. - AES-CMAC accepts exactly 16, 24, or 32 key bytes for AES-128, AES-192, or AES-256, defaulting to 16 bytes. Never pad or truncate the key. Key and Hex-message input accepts continuous Hex or whitespace/comma/Chinese-comma-separated chunks with optional `0x` prefixes. The CMAC tag remains exactly 16 bytes for every key size. Preserve RFC 4493 AES-128 and NIST SP 800-38B AES-192/256 vectors. -- CRC supports BIN and multi-block Intel HEX, CRC-8/16/32/64 presets, and custom `Width`, `Poly`, `Init`, `XorOut`, `RefIn`, and `RefOut`. The Chinese UI labels `RefIn`/`RefOut` as input/output bit reversal; these flags do not reverse byte order or the complete input. Combined data is ordered by address without filling gaps, and per-block results remain available. +- CRC supports inline Hex bytes, BIN, and multi-block Intel HEX, with CRC-8/16/32/64 presets and custom `Width`, `Poly`, `Init`, `XorOut`, `RefIn`, and `RefOut`. Inline Hex uses the shared strict byte parser: accept continuous Hex or chunks separated by whitespace, English commas, or Chinese commas, with an optional `0x` prefix on each chunk; reject empty input. Limit it to 1 MiB after parsing and 6 MiB of source characters, and parse/calculate it in a dedicated Worker. The Chinese UI labels `RefIn`/`RefOut` as input/output bit reversal; these flags do not reverse byte order or the complete input. Combined file data is ordered by address without filling gaps, and per-block results remain available. - Firmware Hash accepts Intel HEX and computes SHA-256 and SHA-512. Effective-data mode concatenates sorted blocks without gap filling. Block-header mode prepends exactly two binary uint32 values, `startAddress` and data-only `dataLength`, to every block. Both fields are four bytes; Big Endian is the default; total input is `Header1 || Data1 || Header2 || Data2 ...`. Never hash a textual Hex representation of the header. - RSA allows 2048, 3072, and 4096-bit keys with public exponent 65537. Prefer Web Crypto; dynamically load the node-forge fallback only when needed, and seed it from `crypto.getRandomValues`. Export public SPKI PEM, private PKCS#8 PEM, and separately copyable N/E continuous Hex or byte arrays. - RSA Signature Verification is verification-only. Default to RSA-PSS; support switching between PKCS#1 v1.5 and RSA-PSS with SHA-256/384/512, and do not add SHA-1. Accept one SPKI, PKCS#1 public-key, or X.509 certificate PEM block, or N/E Hex parameters. Treat E as an unsigned Hex integer: accept odd-length input by padding one zero nibble on the left, so `10001` and `010001` both mean decimal 65537; other Hex byte inputs remain even-length. X.509 support only extracts the RSA public key and displays metadata; never represent it as certificate-chain, issuer, usage, validity, or trust validation. Verification accepts 1024-8192-bit moduli and warns below 2048 bits; key generation remains restricted to 2048/3072/4096 bits. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a1eef7..f1cfc88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ 本文档记录面向使用者和维护者的重要变更。版本号以 `package.json` 为准。 +## 未发布 + +### Added + +- CRC 工具增加“Hex 输入 / 文件”模式切换;内联 Hex 支持连续字节、空白、中英文逗号和逐段可选 `0x` 前缀,并在独立 Web Worker 中完成受限解析与计算。 + +### Fixed + +- 共享 Hex 字节解析器现在拒绝连续中英文逗号形成的空片段,避免输入格式错误被静默忽略。 + +### Security + +- 将开发工具链中的传递依赖 `nanoid` 更新到 `3.3.18`,消除自定义生成器在长度为零时可能无限循环的已知漏洞。 + ## 0.3.0 - 2026-08-13 ### Added diff --git a/README.md b/README.md index ef45b93..4c17b60 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The authoritative application version is the `version` field in `package.json`. - UTF-8 and Hex conversion at the canonical route `/tools/utf8-hex` - Base64 and Base64 URL encoding/decoding, with UTF-8, continuous Hex, and source-code byte-array output - AES-CMAC with selectable AES-128, AES-192, and AES-256 keys -- CRC-8/16/32/64 presets and custom parameters for BIN and multi-block Intel HEX files +- CRC-8/16/32/64 presets and custom parameters for inline Hex bytes, BIN, and multi-block Intel HEX files - Firmware SHA-256/SHA-512 over Intel HEX effective data, optionally with an address/length header for each block - RSA 2048/3072/4096-bit key-pair generation with PEM, N/E Hex, and byte-array output - RSA PKCS#1 v1.5 and RSA-PSS signature verification (RSA-PSS by default) with SHA-256/384/512, PEM/X.509/N-E public keys, and text, Hex, raw-file, Intel HEX effective-data, or Intel HEX block-header input @@ -39,7 +39,7 @@ See [README.zh-CN.md](README.zh-CN.md) for the exact algorithm contracts, input ## Resource limits -The browser rejects oversized or structurally dense inputs before costly allocation. The current limits are 64 MiB for firmware/signature files and merged Intel HEX effective data, 1 MiB for parsed inline signature-verification data with a 6 MiB input-character guard, 1,024-8,192 bits for verification RSA moduli, 500,000 non-empty Intel HEX records, 1,024 Intel HEX blocks, 32 MiB for compressed PNG files, 16,384 pixels per PNG side and 32,000,000 decoded pixels, 1-65,536 bytes for secure random output (default 16), and 100,000 characters/10,000 tokens for syntax highlighting. Highlighting falls back to complete plain text; it never truncates a result. +The browser rejects oversized or structurally dense inputs before costly allocation. The current limits are 64 MiB for firmware/signature files and merged Intel HEX effective data, 1 MiB each for parsed inline CRC Hex and inline signature-verification data with a 6 MiB input-character guard, 1,024-8,192 bits for verification RSA moduli, 500,000 non-empty Intel HEX records, 1,024 Intel HEX blocks, 32 MiB for compressed PNG files, 16,384 pixels per PNG side and 32,000,000 decoded pixels, 1-65,536 bytes for secure random output (default 16), and 100,000 characters/10,000 tokens for syntax highlighting. Highlighting falls back to complete plain text; it never truncates a result. ## Docker deployment diff --git a/README.zh-CN.md b/README.zh-CN.md index 0220b52..04ba2fe 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -11,7 +11,7 @@ - UTF-8 与 Hex 互转,固定路径为 `/tools/utf8-hex` - Base64 与 Base64 URL 编码、解码,解码结果支持 UTF-8、连续 Hex 和代码字节数组 - AES-CMAC,支持 AES-128、AES-192 和 AES-256 密钥 -- CRC-8/16/32/64 预设与自定义参数,支持 BIN 和多 block Intel HEX +- CRC-8/16/32/64 预设与自定义参数,支持内联 Hex 字节、BIN 和多 block Intel HEX - 固件 SHA-256 / SHA-512,支持 Intel HEX 有效数据和带地址/长度的 Block 校验头 - RSA 2048/3072/4096 位密钥对,导出 PEM,以及可分别复制的 N/E Hex 或代码字节数组 - RSA 签名验证,支持 PKCS#1 v1.5、RSA-PSS(默认)、SHA-256/384/512、PEM/X.509/N-E 公钥,以及文本、Hex、原始文件、Intel HEX 有效数据和 Block 校验头 @@ -72,7 +72,7 @@ CHANGELOG.md 版本变更、修复和验证记录 - UTF-8 / Hex 工具的固定 ID 为 `utf8-hex`,路径为 `/tools/utf8-hex`。它按 UTF-8 字节转换文本;Hex 输入接受连续形式,或以空白、英文逗号、中文逗号分隔的片段,并允许每个片段使用可选的 `0x` 前缀。无效 UTF-8 字节必须报错,不能静默替换字符。 - Base64 支持标准和 URL-safe 形式;解码结果可显示为严格 UTF-8、连续 Hex 或 `0x48, 0x9F, ...` 字节数组。无效字符、错误填充和非规范编码必须报错。 - AES-CMAC 密钥长度可选 128、192 或 256 位,默认 128 位,并分别严格要求 16、24 或 32 字节;不自动补零或截断。密钥和 Hex 消息接受连续 Hex,或空格、换行、英文/中文逗号分隔,并允许片段使用可选的 `0x` 前缀。CMAC 标签始终为 128 位;AES-128 向量遵循 RFC 4493,AES-192/256 向量遵循 NIST SP 800-38B。 -- CRC 支持 BIN 和多 block Intel HEX,提供 CRC-8/16/32/64 预设,并允许自定义 `Width`、`Poly`、`Init`、`XorOut`、`RefIn` 和 `RefOut`。`RefIn`/`RefOut` 在页面中标为输入/输出位反转,不表示字节顺序或整个数据倒序。总 CRC 对按地址升序拼接的有效数据计算,不填充地址空洞,同时输出每个 block 的结果。 +- CRC 支持内联 Hex 字节、BIN 和多 block Intel HEX,提供 CRC-8/16/32/64 预设,并允许自定义 `Width`、`Poly`、`Init`、`XorOut`、`RefIn` 和 `RefOut`。内联 Hex 接受连续形式,或以空白、英文逗号、中文逗号分隔的片段,并允许每个片段使用可选的 `0x` 前缀;空输入和无效字节格式会被拒绝,解析与计算均在独立 Web Worker 中完成。`RefIn`/`RefOut` 在页面中标为输入/输出位反转,不表示字节顺序或整个数据倒序。文件模式的总 CRC 对按地址升序拼接的有效数据计算,不填充地址空洞,同时输出每个 block 的结果。 - 固件 Hash 只接受 Intel HEX,输出 SHA-256 和 SHA-512。“有效数据”与“Block 校验头”模式的确切字节流见“Intel HEX 与 Block 校验头固定语义”;默认校验头字节序为 Big Endian。 - RSA 密钥对工具只生成 2048、3072、4096 位密钥,公钥指数固定为 65537。优先使用 Web Crypto,只有本地提供者不可用或失败时才动态加载 `node-forge` 回退,其随机种子仍只来自 `crypto.getRandomValues`;公钥为 SPKI PEM,私钥为 PKCS#8 PEM,N/E 可分别显示为连续 Hex 或代码字节数组。 - RSA 签名验证工具只执行验证,不接收私钥或生成签名。它默认选择 RSA-PSS,并支持切换到 RSA PKCS#1 v1.5;Hash 支持 SHA-256/384/512,不支持 SHA-1。PSS 的 Salt Length 必须显式给出,默认取 Hash 长度与当前模数编码上限中的较小值,并受当前模数与 Hash 的编码上限约束。公钥可使用单个 SPKI `PUBLIC KEY`、PKCS#1 `RSA PUBLIC KEY`、X.509 `CERTIFICATE` PEM 块或 N/E Hex 参数;E 按无符号 Hex 整数解析,奇数位输入会在最左侧补一个 `0`,因此 `10001`、`010001` 均表示十进制 65537,其他 Hex 字节输入仍要求偶数位。X.509 只用于提取 RSA 公钥和展示 Subject、序列号、有效期,不验证证书链、颁发者、用途或可信状态。验证接受 1024 至 8192 位 RSA 公钥,低于 2048 位时显示兼容性警告。 @@ -98,6 +98,7 @@ CHANGELOG.md 版本变更、修复和验证记录 | 功能 | 当前限制 | 超限行为 | | --- | --- | --- | | CRC 固件文件 | 最大 64 MiB | 首次选择时拒绝;替换时保留当前文件和结果 | +| CRC 内联 Hex | 解析后最大 1 MiB;原始输入最多 6 MiB 字符 | 在 Worker 中解析与计算,超限或空输入直接拒绝 | | Intel HEX 非空记录 | 最多 500,000 条 | Worker 停止解析并显示错误 | | Intel HEX 数据块 | 最多 1,024 个 block | Worker 停止解析并显示错误 | | Intel HEX 合并后的有效数据 | 最大 64 MiB | 合并分配前拒绝,Worker 停止解析并显示错误 | @@ -319,7 +320,7 @@ npm audit --audit-level=low `npm run check` 会依次运行全部 Vitest 测试、TypeScript/Vue 类型检查和生产构建。服务端测试使用随机本地端口验证 SPA 直达路由、静态资源 404 与缓存策略、导航 API、请求大小限制及跨源浏览器写入拦截。 -自动化测试还覆盖标准算法向量、Base64 规范性、Intel HEX 记录与 block 边界、固件 Hash 与大小端 Block 头向量、RSA PKCS#1 v1.5/PSS 三种 Hash、PEM/X.509/N-E 公钥解析、签名编码与 PSS 盐长边界、原始文件、Intel HEX 有效数据与大小端 Block 校验头的验签字节语义、文件替换成功提交、失败保留旧状态、过期 Worker 响应隔离和 Worker 生命周期异常、PNG 头部与像素限制、导航排序纯函数、目录/图标/懒加载组件一致性,以及原子写入失败后的临时文件清理。 +自动化测试还覆盖标准算法向量、Base64 规范性、CRC 内联 Hex 已知向量/多种分隔格式/资源上限、Intel HEX 记录与 block 边界、固件 Hash 与大小端 Block 头向量、RSA PKCS#1 v1.5/PSS 三种 Hash、PEM/X.509/N-E 公钥解析、签名编码与 PSS 盐长边界、原始文件、Intel HEX 有效数据与大小端 Block 校验头的验签字节语义、文件替换成功提交、失败保留旧状态、过期 Worker 响应隔离和 Worker 生命周期异常、PNG 头部与像素限制、导航排序纯函数、目录/图标/懒加载组件一致性,以及原子写入失败后的临时文件清理。 ## 增加工具 diff --git a/package-lock.json b/package-lock.json index c593b4b..96118da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1167,9 +1167,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.17", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", - "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", diff --git a/shared/tool-catalog.json b/shared/tool-catalog.json index 6ac17d8..c8f4e2b 100644 --- a/shared/tool-catalog.json +++ b/shared/tool-catalog.json @@ -54,7 +54,7 @@ "id": "crc", "title": "CRC", "category": "checksum", - "description": "解析 BIN 或 Intel HEX 数据并计算 CRC。", + "description": "输入 Hex 字节或解析 BIN、Intel HEX 数据并计算 CRC。", "icon": "BadgeCheck" }, { diff --git a/src/lib/bytes.ts b/src/lib/bytes.ts index 2db4959..8afe889 100644 --- a/src/lib/bytes.ts +++ b/src/lib/bytes.ts @@ -36,11 +36,13 @@ export function parseHex(input: string, options?: number | ParseHexOptions): Uin let tokenHasDigits = false let tokenPrefixSeen = false let sawToken = false + let commaNeedsValue = false for (let index = 0; index < trimmed.length; index += 1) { const character = trimmed[index] if (isHexSeparator(character)) { - if ((character === ',' || character === ',') && !sawToken) throw new Error('十六进制片段缺少数值') + if ((character === ',' || character === ',') && (!sawToken || commaNeedsValue)) throw new Error('十六进制片段缺少数值') if (tokenActive && !tokenHasDigits) throw new Error('十六进制片段缺少数值') + if (character === ',' || character === ',') commaNeedsValue = true tokenActive = false tokenHasDigits = false tokenPrefixSeen = false @@ -56,9 +58,10 @@ export function parseHex(input: string, options?: number | ParseHexOptions): Uin digits += 1 tokenHasDigits = true sawToken = true + commaNeedsValue = false } if (tokenActive && !tokenHasDigits) throw new Error('十六进制片段缺少数值') - if (/[,,]$/.test(trimmed)) throw new Error('十六进制片段缺少数值') + if (commaNeedsValue) throw new Error('十六进制片段缺少数值') if (digits % 2 !== 0) throw new Error('十六进制字符数量必须为偶数') const byteLength = digits / 2 diff --git a/src/lib/crcWorker.ts b/src/lib/crcWorker.ts index 51fe1e9..cd8499e 100644 --- a/src/lib/crcWorker.ts +++ b/src/lib/crcWorker.ts @@ -1,4 +1,8 @@ import { calculateCrc, calculateCrcChunks, formatCrc, type CrcParameters } from '@/lib/crc' +import { parseHex } from '@/lib/bytes' + +export const MAX_INLINE_CRC_DATA_BYTES = 1024 * 1024 +export const MAX_INLINE_CRC_INPUT_CHARS = 6 * 1024 * 1024 export interface CrcWorkerBlock { startAddress: number @@ -17,7 +21,14 @@ export interface CrcWorkerCalculateRequest { parameters: CrcParameters } -export type CrcWorkerRequest = CrcWorkerParseRequest | CrcWorkerCalculateRequest +export interface CrcWorkerCalculateHexRequest { + type: 'calculate-hex' + id: number + input: string + parameters: CrcParameters +} + +export type CrcWorkerRequest = CrcWorkerParseRequest | CrcWorkerCalculateRequest | CrcWorkerCalculateHexRequest export interface CrcBlockResult { startAddress: number @@ -26,25 +37,58 @@ export interface CrcBlockResult { crc: string } -export type CrcWorkerResponse = { +type CrcWorkerParsedResponse = { type: 'parsed' id: number format: 'Intel HEX' | 'BIN' totalBytes: number blockCount: number warnings: string[] -} | { +} + +type CrcWorkerCalculatedResponse = { type: 'calculated' id: number combinedResult: string blockResults: CrcBlockResult[] -} | { +} + +type CrcWorkerCalculatedHexResponse = { + type: 'calculated-hex' + id: number + combinedResult: string + totalBytes: number +} + +type CrcWorkerErrorResponse = { type: 'error' id: number - operation: CrcWorkerRequest['type'] + operation: TOperation error: string } +export type CrcFileWorkerResponse = CrcWorkerParsedResponse + | CrcWorkerCalculatedResponse + | CrcWorkerErrorResponse<'parse' | 'calculate'> + +export type CrcHexWorkerResponse = CrcWorkerCalculatedHexResponse + | CrcWorkerErrorResponse<'calculate-hex'> + +export type CrcWorkerResponse = CrcFileWorkerResponse | CrcHexWorkerResponse + +export function calculateCrcHexInput(input: string, parameters: CrcParameters) { + const data = parseHex(input, { + maxBytes: MAX_INLINE_CRC_DATA_BYTES, + maxInputChars: MAX_INLINE_CRC_INPUT_CHARS, + limitLabel: 'Hex 数据', + }) + if (data.length === 0) throw new Error('请输入 Hex 数据') + return { + combinedResult: formatCrc(calculateCrc(data, parameters), parameters.width), + totalBytes: data.length, + } +} + export function calculateCrcReport(blocks: CrcWorkerBlock[], parameters: CrcParameters) { const combinedValue = calculateCrcChunks(blocks.map((block) => block.data), parameters) const combinedResult = formatCrc(combinedValue, parameters.width) diff --git a/src/styles.css b/src/styles.css index 580bdec..87350ee 100644 --- a/src/styles.css +++ b/src/styles.css @@ -226,6 +226,8 @@ button[aria-disabled='true'], input[aria-disabled='true'], select[aria-disabled= .file-drop-summary.is-replacing .file-drop-feedback { color: var(--muted); } .file-drop-spinner { animation: file-drop-spin .8s linear infinite; } @keyframes file-drop-spin { to { transform: rotate(360deg); } } +.crc-source-controls { margin-bottom: 12px; } +.crc-hex-editor { margin-bottom: 16px; } .crc-config { display: grid; grid-template-columns: minmax(190px, 1.35fr) minmax(96px, .65fr) repeat(3, minmax(145px, 1fr)); gap: 10px; align-items: end; } .parameter-field { min-width: 0; display: grid; gap: 6px; color: var(--muted); font-size: 11px; font-weight: 600; } .parameter-field .select-input, .parameter-field .text-input { width: 100%; font-size: 12px; font-weight: 400; } diff --git a/src/tools/CrcTool.vue b/src/tools/CrcTool.vue index ad4d353..0f254bd 100644 --- a/src/tools/CrcTool.vue +++ b/src/tools/CrcTool.vue @@ -1,14 +1,25 @@