sm-crypto-wasm 0.3.0 → 0.7.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.
Files changed (76) hide show
  1. package/README.md +76 -7
  2. package/dist/asn1/decode.d.ts +74 -0
  3. package/dist/asn1/decode.js +338 -0
  4. package/dist/asn1/encode.d.ts +68 -0
  5. package/dist/asn1/encode.js +275 -0
  6. package/dist/asn1/index.d.ts +11 -0
  7. package/dist/asn1/index.js +11 -0
  8. package/dist/asn1/oid.d.ts +22 -0
  9. package/dist/asn1/oid.js +107 -0
  10. package/dist/asn1/time.d.ts +39 -0
  11. package/dist/asn1/time.js +111 -0
  12. package/dist/asn1/types.d.ts +76 -0
  13. package/dist/asn1/types.js +71 -0
  14. package/dist/cms/errors.d.ts +13 -0
  15. package/dist/cms/errors.js +11 -0
  16. package/dist/cms/index.d.ts +13 -0
  17. package/dist/cms/index.js +10 -0
  18. package/dist/cms/oids.d.ts +17 -0
  19. package/dist/cms/oids.js +17 -0
  20. package/dist/cms/parse.d.ts +75 -0
  21. package/dist/cms/parse.js +326 -0
  22. package/dist/cms/sign.d.ts +43 -0
  23. package/dist/cms/sign.js +142 -0
  24. package/dist/cms/verify.d.ts +42 -0
  25. package/dist/cms/verify.js +173 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.js +3 -0
  28. package/dist/pem/asn1.d.ts +10 -69
  29. package/dist/pem/asn1.js +10 -292
  30. package/dist/pem/pkcs8.js +9 -1
  31. package/dist/pem/sm2.d.ts +16 -0
  32. package/dist/pem/sm2.js +37 -5
  33. package/dist/runtime/base64.core.d.ts +22 -0
  34. package/dist/runtime/base64.core.js +68 -0
  35. package/dist/runtime/convert.d.ts +12 -3
  36. package/dist/runtime/convert.js +31 -12
  37. package/dist/sm2/index.d.ts +69 -0
  38. package/dist/sm2/index.js +188 -0
  39. package/dist/sm2/sm2.wasm +0 -0
  40. package/dist/webcrypto/algorithms.d.ts +42 -0
  41. package/dist/webcrypto/algorithms.js +120 -0
  42. package/dist/webcrypto/cryptoKey.d.ts +64 -0
  43. package/dist/webcrypto/cryptoKey.js +109 -0
  44. package/dist/webcrypto/errors.d.ts +19 -0
  45. package/dist/webcrypto/errors.js +45 -0
  46. package/dist/webcrypto/index.d.ts +14 -0
  47. package/dist/webcrypto/index.js +11 -0
  48. package/dist/webcrypto/subtle.d.ts +68 -0
  49. package/dist/webcrypto/subtle.js +358 -0
  50. package/dist/x509/encode.d.ts +59 -0
  51. package/dist/x509/encode.js +175 -0
  52. package/dist/x509/errors.d.ts +14 -0
  53. package/dist/x509/errors.js +14 -0
  54. package/dist/x509/extensions.d.ts +27 -0
  55. package/dist/x509/extensions.js +215 -0
  56. package/dist/x509/index.d.ts +14 -0
  57. package/dist/x509/index.js +11 -0
  58. package/dist/x509/ip.d.ts +12 -0
  59. package/dist/x509/ip.js +101 -0
  60. package/dist/x509/issue.d.ts +44 -0
  61. package/dist/x509/issue.js +414 -0
  62. package/dist/x509/oids.d.ts +40 -0
  63. package/dist/x509/oids.js +52 -0
  64. package/dist/x509/parse.d.ts +24 -0
  65. package/dist/x509/parse.js +436 -0
  66. package/dist/x509/sha1.d.ts +9 -0
  67. package/dist/x509/sha1.js +72 -0
  68. package/dist/x509/sha256.d.ts +9 -0
  69. package/dist/x509/sha256.js +84 -0
  70. package/dist/x509/sig.d.ts +20 -0
  71. package/dist/x509/sig.js +77 -0
  72. package/dist/x509/types.d.ts +235 -0
  73. package/dist/x509/types.js +7 -0
  74. package/dist/x509/verify.d.ts +44 -0
  75. package/dist/x509/verify.js +66 -0
  76. package/package.json +16 -3
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ASN.1 通用类型与常量(DER/BER 子集)。
3
+ *
4
+ * 与旧 `src/pem/asn1.ts` 的关系:
5
+ * - 旧实现只覆盖 PKCS#8 用到的最小类型,且不保留 `constructed` 位;
6
+ * - 本层是 X.509 / CMS / WebCrypto 共用的通用基础设施,覆盖
7
+ * BOOLEAN、SET、字符串、时间、context/application/private 标签等。
8
+ *
9
+ * 设计要点:
10
+ * - 每个节点保留 `raw`(含 tag + length + value 的原始切片),X.509 的 TBS
11
+ * 与 CMS 的 `signedAttrs` 验签必须对原始字节做摘要,不能重新编码;
12
+ * - `raw` 是对输入缓冲的**视图**(不拷贝),调用方不应假定其生命周期;
13
+ * 需要长期持有时自行 `slice()`;
14
+ * - 解析错误统一为 `Asn1Error`;上层(X.509/CMS)负责转换为各自的错误类型。
15
+ */
16
+ /** ASN.1 标签类别常量。 */
17
+ export const Asn1Class = {
18
+ Universal: 0,
19
+ Application: 1,
20
+ ContextSpecific: 2,
21
+ Private: 3,
22
+ };
23
+ /** UNIVERSAL 类常用标签编号。 */
24
+ export const UniversalTag = {
25
+ EndOfContent: 0,
26
+ Boolean: 1,
27
+ Integer: 2,
28
+ BitString: 3,
29
+ OctetString: 4,
30
+ Null: 5,
31
+ ObjectIdentifier: 6,
32
+ Enumerated: 10,
33
+ Utf8String: 12,
34
+ Sequence: 16,
35
+ Set: 17,
36
+ NumericString: 18,
37
+ PrintableString: 19,
38
+ TeletexString: 20,
39
+ VideotexString: 21,
40
+ Ia5String: 22,
41
+ UtcTime: 23,
42
+ GeneralizedTime: 24,
43
+ UniversalString: 28,
44
+ BmpString: 30,
45
+ };
46
+ /** ASN.1 解析/编码错误。 */
47
+ export class Asn1Error extends Error {
48
+ name = 'Asn1Error';
49
+ }
50
+ /** 判断节点是否为指定 UNIVERSAL 类型。 */
51
+ export function isUniversal(node, tag) {
52
+ return node.cls === Asn1Class.Universal && node.tag === tag;
53
+ }
54
+ /** 断言节点为指定 UNIVERSAL 类型,否则抛 `Asn1Error`。 */
55
+ export function expectUniversal(node, tag, what) {
56
+ if (node === undefined) {
57
+ throw new Asn1Error(`${what}: missing element (expected tag ${tag})`);
58
+ }
59
+ if (!isUniversal(node, tag)) {
60
+ throw new Asn1Error(`${what}: expected UNIVERSAL tag ${tag}, got class ${node.cls} tag ${node.tag}`);
61
+ }
62
+ return node;
63
+ }
64
+ /** 断言节点为 constructed SEQUENCE 并返回子节点。 */
65
+ export function expectSequence(node, what) {
66
+ const seq = expectUniversal(node, UniversalTag.Sequence, what);
67
+ if (!seq.children) {
68
+ throw new Asn1Error(`${what}: SEQUENCE is not constructed`);
69
+ }
70
+ return seq.children;
71
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * CMS(PKCS#7 SignedData)错误类型。
3
+ */
4
+ import { SmCryptoError } from '../runtime/errors.js';
5
+ /** CMS 解析/签名/验签失败。 */
6
+ export declare class CmsError extends SmCryptoError {
7
+ readonly name: string;
8
+ constructor(message: string, options?: {
9
+ cause?: unknown;
10
+ });
11
+ }
12
+ /** `verifySignedData` 的结构化失败原因。 */
13
+ export type CmsVerifyErrorCode = 'NO_CERTIFICATE' | 'DIGEST_MISMATCH' | 'SIGNATURE_INVALID' | 'UNSUPPORTED_ALGORITHM' | 'MALFORMED';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * CMS(PKCS#7 SignedData)错误类型。
3
+ */
4
+ import { SmCryptoError } from '../runtime/errors.js';
5
+ /** CMS 解析/签名/验签失败。 */
6
+ export class CmsError extends SmCryptoError {
7
+ name = 'CmsError';
8
+ constructor(message, options) {
9
+ super(message, options);
10
+ }
11
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * CMS SignedData 子模块(`sm-crypto-wasm/cms`)。
3
+ *
4
+ * 只做签名/验签,不建立信任链;证书有效期/用途/链校验由调用方负责。
5
+ */
6
+ export { CmsError, type CmsVerifyErrorCode } from './errors.js';
7
+ export * from './oids.js';
8
+ export type { CmsAttribute, CmsSignerIdentifier, CmsSignerInfo, ParsedSignedData, } from './parse.js';
9
+ export { parseSignedData } from './parse.js';
10
+ export type { SignDataOptions, SignDataResult } from './sign.js';
11
+ export { signData } from './sign.js';
12
+ export type { VerifySignedDataOptions, VerifySignedDataResult, VerifySignerResult, } from './verify.js';
13
+ export { signerIdentifierHex, verifySignedData } from './verify.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * CMS SignedData 子模块(`sm-crypto-wasm/cms`)。
3
+ *
4
+ * 只做签名/验签,不建立信任链;证书有效期/用途/链校验由调用方负责。
5
+ */
6
+ export { CmsError } from './errors.js';
7
+ export * from './oids.js';
8
+ export { parseSignedData } from './parse.js';
9
+ export { signData } from './sign.js';
10
+ export { signerIdentifierHex, verifySignedData } from './verify.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * CMS oid 常量。
3
+ */
4
+ /** id-data(1.2.840.113549.1.7.1)。 */
5
+ export declare const OID_CMS_DATA = "1.2.840.113549.1.7.1";
6
+ /** id-signedData(1.2.840.113549.1.7.2)。 */
7
+ export declare const OID_CMS_SIGNED_DATA = "1.2.840.113549.1.7.2";
8
+ /** contentType 签名属性(1.2.840.113549.1.9.3)。 */
9
+ export declare const OID_CMS_CONTENT_TYPE = "1.2.840.113549.1.9.3";
10
+ /** messageDigest 签名属性(1.2.840.113549.1.9.4)。 */
11
+ export declare const OID_CMS_MESSAGE_DIGEST = "1.2.840.113549.1.9.4";
12
+ /** signingTime 签名属性(1.2.840.113549.1.9.5)。 */
13
+ export declare const OID_CMS_SIGNING_TIME = "1.2.840.113549.1.9.5";
14
+ /** SM3 摘要算法(1.2.156.10197.1.401)。 */
15
+ export declare const OID_SM3 = "1.2.156.10197.1.401";
16
+ /** SM3withSM2(1.2.156.10197.1.501)。 */
17
+ export declare const OID_SM3_WITH_SM2 = "1.2.156.10197.1.501";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * CMS oid 常量。
3
+ */
4
+ /** id-data(1.2.840.113549.1.7.1)。 */
5
+ export const OID_CMS_DATA = '1.2.840.113549.1.7.1';
6
+ /** id-signedData(1.2.840.113549.1.7.2)。 */
7
+ export const OID_CMS_SIGNED_DATA = '1.2.840.113549.1.7.2';
8
+ /** contentType 签名属性(1.2.840.113549.1.9.3)。 */
9
+ export const OID_CMS_CONTENT_TYPE = '1.2.840.113549.1.9.3';
10
+ /** messageDigest 签名属性(1.2.840.113549.1.9.4)。 */
11
+ export const OID_CMS_MESSAGE_DIGEST = '1.2.840.113549.1.9.4';
12
+ /** signingTime 签名属性(1.2.840.113549.1.9.5)。 */
13
+ export const OID_CMS_SIGNING_TIME = '1.2.840.113549.1.9.5';
14
+ /** SM3 摘要算法(1.2.156.10197.1.401)。 */
15
+ export const OID_SM3 = '1.2.156.10197.1.401';
16
+ /** SM3withSM2(1.2.156.10197.1.501)。 */
17
+ export const OID_SM3_WITH_SM2 = '1.2.156.10197.1.501';
@@ -0,0 +1,75 @@
1
+ /**
2
+ * CMS SignedData 解析(只读)。
3
+ *
4
+ * 关键点(红线 #1):
5
+ * `signedAttrs` 的签名输入是**原始 DER 字节**(SET OF Attribute),而不是
6
+ * 解析后重新编码的结果。这里保留:
7
+ * - `raw`:把 `[0] IMPLICIT` 的首字节(0xA0)换成 SET(0x31)后的字节;
8
+ * - 对 BER indefinite-length,先编码为 definite-length 再替换首字节。
9
+ * 除首字节外不做任何重编码,避免 SET 顺序/长度编码差异破坏合法签名。
10
+ *
11
+ * BER 只在 CMS 路径启用;证书本身仍按 DER 严格解析。
12
+ */
13
+ import { type Asn1Node } from '../asn1/index.js';
14
+ import type { Certificate } from '../x509/types.js';
15
+ /** 一个签名属性(Attribute ::= SEQUENCE { attrType, attrValues SET OF }). */
16
+ export interface CmsAttribute {
17
+ readonly oid: string;
18
+ /** AttributeValue 节点(原始视图)。 */
19
+ readonly values: readonly Asn1Node[];
20
+ /** 整个 Attribute 的原始 DER。 */
21
+ readonly raw: Uint8Array;
22
+ }
23
+ /** 签名者标识。 */
24
+ export type CmsSignerIdentifier = {
25
+ readonly type: 'issuerAndSerialNumber';
26
+ /** 签发者 Name 的原始 DER,用于按字节匹配证书。 */
27
+ readonly issuerRaw: Uint8Array;
28
+ readonly serialNumber: bigint;
29
+ } | {
30
+ readonly type: 'subjectKeyIdentifier';
31
+ readonly keyIdentifier: Uint8Array;
32
+ };
33
+ /** 解析后的 SignerInfo。 */
34
+ export interface CmsSignerInfo {
35
+ readonly version: number;
36
+ readonly sid: CmsSignerIdentifier;
37
+ readonly digestAlgorithm: string;
38
+ /**
39
+ * signedAttrs 的签名输入字节(SET tag 0x31)。
40
+ * 没有 signedAttrs 时为 `undefined`(直接签内容)。
41
+ */
42
+ readonly signedAttrs?: {
43
+ readonly raw: Uint8Array;
44
+ readonly attributes: readonly CmsAttribute[];
45
+ };
46
+ readonly signatureAlgorithm: string;
47
+ readonly signature: Uint8Array;
48
+ }
49
+ /** 解析后的 SignedData。 */
50
+ export interface ParsedSignedData {
51
+ readonly version: number;
52
+ readonly digestAlgorithms: readonly string[];
53
+ readonly eContentType: string;
54
+ /** 内嵌内容;detached 时为 `undefined`。 */
55
+ readonly eContent?: Uint8Array;
56
+ readonly certificates: readonly Certificate[];
57
+ readonly signerInfos: readonly CmsSignerInfo[];
58
+ /** 整个 ContentInfo 的原始 DER。 */
59
+ readonly raw: Uint8Array;
60
+ }
61
+ /**
62
+ * 解析 CMS ContentInfo(仅接受 SignedData)。
63
+ *
64
+ * @param input PEM(`CMS` / `PKCS7`)、base64、hex 或 DER/BER 字节。
65
+ * @throws CmsError 结构非法、不是 SignedData、或尾部有多余字节。
66
+ */
67
+ export declare function parseSignedData(input: string | Uint8Array): ParsedSignedData;
68
+ /** 读取签名属性中的时间值(供验签层使用)。 */
69
+ export declare function attributeTime(attribute: CmsAttribute | undefined): Date | undefined;
70
+ /** 读取签名属性中的 OCTET STRING 值(messageDigest)。 */
71
+ export declare function attributeOctets(attribute: CmsAttribute | undefined): Uint8Array | undefined;
72
+ /** 读取签名属性中的 OID 值(contentType)。 */
73
+ export declare function attributeOid(attribute: CmsAttribute | undefined): string | undefined;
74
+ /** 十六进制便捷函数(供错误信息/调试使用)。 */
75
+ export declare function cmsHex(bytes: Uint8Array): string;
@@ -0,0 +1,326 @@
1
+ /**
2
+ * CMS SignedData 解析(只读)。
3
+ *
4
+ * 关键点(红线 #1):
5
+ * `signedAttrs` 的签名输入是**原始 DER 字节**(SET OF Attribute),而不是
6
+ * 解析后重新编码的结果。这里保留:
7
+ * - `raw`:把 `[0] IMPLICIT` 的首字节(0xA0)换成 SET(0x31)后的字节;
8
+ * - 对 BER indefinite-length,先编码为 definite-length 再替换首字节。
9
+ * 除首字节外不做任何重编码,避免 SET 顺序/长度编码差异破坏合法签名。
10
+ *
11
+ * BER 只在 CMS 路径启用;证书本身仍按 DER 严格解析。
12
+ */
13
+ import { Asn1Class, encode, expectSequence, expectUniversal, parseBer, readInteger, readOid, readTime, UniversalTag, } from '../asn1/index.js';
14
+ import { base64ToBytes, bytesToHex, hexToBytes } from '../runtime/convert.js';
15
+ import { parseCertificate } from '../x509/parse.js';
16
+ import { CmsError } from './errors.js';
17
+ import { OID_CMS_SIGNED_DATA } from './oids.js';
18
+ /**
19
+ * 解析 CMS ContentInfo(仅接受 SignedData)。
20
+ *
21
+ * @param input PEM(`CMS` / `PKCS7`)、base64、hex 或 DER/BER 字节。
22
+ * @throws CmsError 结构非法、不是 SignedData、或尾部有多余字节。
23
+ */
24
+ export function parseSignedData(input) {
25
+ const der = decodeCmsInput(input);
26
+ let top;
27
+ try {
28
+ top = parseBer(der);
29
+ }
30
+ catch (err) {
31
+ throw new CmsError(`parseSignedData: invalid DER/BER (${errorMessage(err)})`, { cause: err });
32
+ }
33
+ try {
34
+ return signedDataFromContentInfo(top, der);
35
+ }
36
+ catch (err) {
37
+ if (err instanceof CmsError)
38
+ throw err;
39
+ throw new CmsError(`parseSignedData: ${errorMessage(err)}`, { cause: err });
40
+ }
41
+ }
42
+ function signedDataFromContentInfo(top, raw) {
43
+ const kids = expectSequence(top, 'ContentInfo');
44
+ const contentType = readOid(kids[0], 'ContentInfo.contentType');
45
+ if (contentType !== OID_CMS_SIGNED_DATA) {
46
+ throw new CmsError(`parseSignedData: content type ${contentType} is not signedData`);
47
+ }
48
+ const wrapper = kids[1];
49
+ if (wrapper === undefined || wrapper.cls !== Asn1Class.ContextSpecific || wrapper.tag !== 0) {
50
+ throw new CmsError('parseSignedData: missing [0] EXPLICIT content');
51
+ }
52
+ const signedData = wrapper.children?.[0];
53
+ if (signedData === undefined) {
54
+ throw new CmsError('parseSignedData: empty SignedData content');
55
+ }
56
+ return parseSignedDataNode(signedData, raw);
57
+ }
58
+ function parseSignedDataNode(node, raw) {
59
+ const kids = expectSequence(node, 'SignedData');
60
+ if (kids.length < 4) {
61
+ throw new CmsError(`SignedData: expected at least 4 elements, got ${kids.length}`);
62
+ }
63
+ let index = 0;
64
+ const version = Number(readInteger(kids[index++], 'SignedData.version'));
65
+ const digestAlgorithms = parseAlgorithmSet(kids[index++], 'digestAlgorithms');
66
+ const encap = parseEncapContentInfo(kids[index++]);
67
+ let certificates = [];
68
+ const maybeCerts = kids[index];
69
+ if (maybeCerts !== undefined && maybeCerts.cls === Asn1Class.ContextSpecific) {
70
+ if (maybeCerts.tag === 0) {
71
+ certificates = parseCertificates(maybeCerts);
72
+ index++;
73
+ }
74
+ else if (maybeCerts.tag === 1) {
75
+ // crls:v1 不处理。
76
+ index++;
77
+ }
78
+ }
79
+ const signerSet = kids[index];
80
+ if (signerSet === undefined)
81
+ throw new CmsError('SignedData: missing signerInfos');
82
+ const signerInfos = (signerSet.children ?? []).map((child) => parseSignerInfo(expectSequence(child, 'SignerInfo')));
83
+ return {
84
+ version,
85
+ digestAlgorithms,
86
+ eContentType: encap.eContentType,
87
+ ...(encap.eContent === undefined ? {} : { eContent: encap.eContent }),
88
+ certificates,
89
+ signerInfos,
90
+ raw: new Uint8Array(raw),
91
+ };
92
+ }
93
+ function parseAlgorithmSet(node, what) {
94
+ const entries = node?.children ?? [];
95
+ return entries.map((entry) => {
96
+ const kids = expectSequence(entry, what);
97
+ return readOid(kids[0], what);
98
+ });
99
+ }
100
+ function parseEncapContentInfo(node) {
101
+ const kids = expectSequence(node, 'EncapsulatedContentInfo');
102
+ const eContentType = readOid(kids[0], 'eContentType');
103
+ const wrapper = kids[1];
104
+ if (wrapper === undefined || wrapper.cls !== Asn1Class.ContextSpecific || wrapper.tag !== 0) {
105
+ return { eContentType };
106
+ }
107
+ const octets = wrapper.children?.[0];
108
+ if (octets === undefined) {
109
+ throw new CmsError('EncapsulatedContentInfo: empty eContent');
110
+ }
111
+ return { eContentType, eContent: octetStringBytes(octets) };
112
+ }
113
+ function parseCertificates(node) {
114
+ const certificates = [];
115
+ for (const child of node.children ?? []) {
116
+ // CertificateChoices 还可能是其它证书类型;解析失败的项跳过,不影响 SignedData。
117
+ try {
118
+ certificates.push(parseCertificate(child.raw));
119
+ }
120
+ catch {
121
+ // 非 X.509 证书或损坏项:忽略。
122
+ }
123
+ }
124
+ return certificates;
125
+ }
126
+ function parseSignerInfo(kids) {
127
+ let index = 0;
128
+ const version = Number(readInteger(kids[index++], 'SignerInfo.version'));
129
+ const sidNode = kids[index++];
130
+ if (sidNode === undefined)
131
+ throw new CmsError('SignerInfo: missing sid');
132
+ const sid = parseSignerIdentifier(sidNode);
133
+ const digestNode = kids[index++];
134
+ const digestKids = expectSequence(digestNode, 'digestAlgorithm');
135
+ const digestAlgorithm = readOid(digestKids[0], 'digestAlgorithm');
136
+ let signedAttrs;
137
+ const attrsNode = kids[index];
138
+ if (attrsNode !== undefined &&
139
+ attrsNode.cls === Asn1Class.ContextSpecific &&
140
+ attrsNode.tag === 0) {
141
+ signedAttrs = parseSignedAttrs(attrsNode);
142
+ index++;
143
+ }
144
+ const signatureAlgKids = expectSequence(kids[index++], 'signatureAlgorithm');
145
+ const signatureAlgorithm = readOid(signatureAlgKids[0], 'signatureAlgorithm');
146
+ const signatureNode = kids[index++];
147
+ if (signatureNode === undefined)
148
+ throw new CmsError('SignerInfo: missing signature');
149
+ const signature = octetStringBytes(signatureNode);
150
+ return {
151
+ version,
152
+ sid,
153
+ digestAlgorithm,
154
+ ...(signedAttrs === undefined ? {} : { signedAttrs }),
155
+ signatureAlgorithm,
156
+ signature,
157
+ };
158
+ }
159
+ function parseSignerIdentifier(node) {
160
+ if (node.cls === Asn1Class.Universal && node.tag === UniversalTag.Sequence) {
161
+ const kids = expectSequence(node, 'IssuerAndSerialNumber');
162
+ const issuer = kids[0];
163
+ const serial = kids[1];
164
+ if (issuer === undefined || serial === undefined) {
165
+ throw new CmsError('IssuerAndSerialNumber: missing issuer/serialNumber');
166
+ }
167
+ return {
168
+ type: 'issuerAndSerialNumber',
169
+ issuerRaw: new Uint8Array(issuer.raw),
170
+ serialNumber: readInteger(serial, 'IssuerAndSerialNumber.serialNumber'),
171
+ };
172
+ }
173
+ if (node.cls === Asn1Class.ContextSpecific && node.tag === 0) {
174
+ return { type: 'subjectKeyIdentifier', keyIdentifier: new Uint8Array(node.value) };
175
+ }
176
+ throw new CmsError('SignerInfo: unsupported signer identifier');
177
+ }
178
+ function parseSignedAttrs(node) {
179
+ const attributes = (node.children ?? []).map((child) => {
180
+ const kids = expectSequence(child, 'Attribute');
181
+ const oidNode = kids[0];
182
+ const valuesNode = kids[1];
183
+ if (oidNode === undefined || valuesNode === undefined) {
184
+ throw new CmsError('Attribute: missing attrType or attrValues');
185
+ }
186
+ return {
187
+ oid: readOid(oidNode, 'Attribute.attrType'),
188
+ values: valuesNode.children ?? [],
189
+ raw: new Uint8Array(child.raw),
190
+ };
191
+ });
192
+ return { raw: signedAttrsDer(node), attributes };
193
+ }
194
+ /**
195
+ * 还原 signedAttrs 的签名输入字节。
196
+ *
197
+ * `[0] IMPLICIT` 与 `SET` 的唯一区别是首字节(0xA0 vs 0x31),长度和内容
198
+ * 完全相同。definite-length 时只改首字节;BER indefinite-length 时先编码为
199
+ * definite-length(子节点顺序不变),再改首字节。
200
+ */
201
+ function signedAttrsDer(node) {
202
+ if (!node.indefinite) {
203
+ const copy = new Uint8Array(node.raw);
204
+ copy[0] = 0x31;
205
+ return copy;
206
+ }
207
+ const encoded = encode(node);
208
+ encoded[0] = 0x31;
209
+ return encoded;
210
+ }
211
+ function octetStringBytes(node) {
212
+ const target = expectUniversal(node, UniversalTag.OctetString, 'OCTET STRING');
213
+ if (!target.constructed)
214
+ return new Uint8Array(target.value);
215
+ // constructed OCTET STRING(BER 分块):拼接子节点内容。
216
+ const parts = (target.children ?? []).map((child) => octetStringBytes(child));
217
+ let total = 0;
218
+ for (const part of parts)
219
+ total += part.length;
220
+ const out = new Uint8Array(total);
221
+ let offset = 0;
222
+ for (const part of parts) {
223
+ out.set(part, offset);
224
+ offset += part.length;
225
+ }
226
+ return out;
227
+ }
228
+ // ---------- 输入解码 ----------
229
+ function decodeCmsInput(input) {
230
+ if (input instanceof Uint8Array) {
231
+ if (input.length === 0)
232
+ throw new CmsError('parseSignedData: empty input');
233
+ if (isPemText(input))
234
+ return decodeCmsInput(new TextDecoder().decode(input));
235
+ return input;
236
+ }
237
+ if (typeof input !== 'string') {
238
+ throw new TypeError('parseSignedData: expected string or Uint8Array');
239
+ }
240
+ const text = input.trim();
241
+ if (text.length === 0)
242
+ throw new CmsError('parseSignedData: empty input');
243
+ const pem = /-----BEGIN (?:CMS|PKCS7)-----([\s\S]*?)-----END (?:CMS|PKCS7)-----/.exec(text);
244
+ if (pem !== null) {
245
+ try {
246
+ return base64ToBytes(pem[1].replace(/\s+/g, ''), 'cms');
247
+ }
248
+ catch (err) {
249
+ throw new CmsError('parseSignedData: invalid base64 in PEM block', { cause: err });
250
+ }
251
+ }
252
+ const compact = text.replace(/\s+/g, '');
253
+ const candidates = [];
254
+ if (/^[0-9a-fA-F]+$/.test(compact) && compact.length % 2 === 0) {
255
+ candidates.push(hexToBytes(compact, 'cms'));
256
+ }
257
+ try {
258
+ candidates.push(base64ToBytes(compact, 'cms'));
259
+ }
260
+ catch {
261
+ // 不是 base64。
262
+ }
263
+ for (const candidate of candidates) {
264
+ try {
265
+ parseBer(candidate);
266
+ return candidate;
267
+ }
268
+ catch {
269
+ // 尝试下一个。
270
+ }
271
+ }
272
+ throw new CmsError('parseSignedData: input is neither PEM, hex nor base64 DER');
273
+ }
274
+ function isPemText(bytes) {
275
+ const prefix = '-----BEGIN ';
276
+ if (bytes.length < prefix.length)
277
+ return false;
278
+ for (let i = 0; i < prefix.length; i++) {
279
+ if (bytes[i] !== prefix.charCodeAt(i))
280
+ return false;
281
+ }
282
+ return true;
283
+ }
284
+ function errorMessage(err) {
285
+ return err instanceof Error ? err.message : String(err);
286
+ }
287
+ /** 读取签名属性中的时间值(供验签层使用)。 */
288
+ export function attributeTime(attribute) {
289
+ const value = attribute?.values[0];
290
+ if (value === undefined)
291
+ return undefined;
292
+ try {
293
+ return readTime(value, 'signingTime');
294
+ }
295
+ catch {
296
+ return undefined;
297
+ }
298
+ }
299
+ /** 读取签名属性中的 OCTET STRING 值(messageDigest)。 */
300
+ export function attributeOctets(attribute) {
301
+ const value = attribute?.values[0];
302
+ if (value === undefined)
303
+ return undefined;
304
+ try {
305
+ return octetStringBytes(value);
306
+ }
307
+ catch {
308
+ return undefined;
309
+ }
310
+ }
311
+ /** 读取签名属性中的 OID 值(contentType)。 */
312
+ export function attributeOid(attribute) {
313
+ const value = attribute?.values[0];
314
+ if (value === undefined)
315
+ return undefined;
316
+ try {
317
+ return readOid(value, 'contentType');
318
+ }
319
+ catch {
320
+ return undefined;
321
+ }
322
+ }
323
+ /** 十六进制便捷函数(供错误信息/调试使用)。 */
324
+ export function cmsHex(bytes) {
325
+ return bytesToHex(bytes);
326
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * CMS SignedData 签名(detached / attached)。
3
+ *
4
+ * 签名的计算对象是 `signedAttrs` 的 DER 编码(SET tag 0x31)——与验签层
5
+ * 使用完全相同的规则;`contentType` / `messageDigest` / `signingTime` 三个
6
+ * 属性按 DER SET OF 排序后编码。
7
+ *
8
+ * 只做 SignedData,不做 EnvelopedData / RFC 3161 时间戳。
9
+ */
10
+ import type { Certificate } from '../x509/types.js';
11
+ /** `signData` 的输入。 */
12
+ export interface SignDataOptions {
13
+ /** 待签名数据;字符串按 UTF-8 编码(不是 hex/base64)。 */
14
+ readonly data: string | Uint8Array;
15
+ /** PKCS#8 / SEC1 PEM、32 字节私钥或其 hex。 */
16
+ readonly privateKey: string | Uint8Array;
17
+ /** 签名者证书;省略时取 `certificates[0]`。 */
18
+ readonly certificate?: Certificate | string | Uint8Array;
19
+ /** 额外证书(链)。 */
20
+ readonly certificates?: readonly (Certificate | string | Uint8Array)[];
21
+ /** 默认 `true`:只输出签名,内容由调用方另行传输。 */
22
+ readonly detached?: boolean;
23
+ /** 默认当前时间;`false` 表示不加入 signingTime 属性。 */
24
+ readonly signingTime?: Date | false;
25
+ /** SM2 用户标识;默认空(与 OpenSSL 的 CMS 路径一致)。 */
26
+ readonly userID?: Uint8Array;
27
+ }
28
+ /** `signData` 的结果。 */
29
+ export interface SignDataResult {
30
+ readonly der: Uint8Array;
31
+ readonly pem: string;
32
+ readonly base64: string;
33
+ }
34
+ /**
35
+ * 生成 CMS SignedData(单签名者)。
36
+ *
37
+ * 调用前需要预热 wasm:`await sm2.ensureLoaded(); await sm3.ensureLoaded();`
38
+ *
39
+ * @throws CmsError 缺少签名者证书或输入不合法。
40
+ */
41
+ export declare function signData(opts: SignDataOptions): SignDataResult;
42
+ /** 证书集合中的证书原始 DER(供外部检查)。 */
43
+ export declare function certificateChainDer(certificates: readonly Certificate[]): Uint8Array[];