@f-o-t/digital-certificate 1.0.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.
- package/dist/index.d.ts +184 -0
- package/dist/index.js +149 -0
- package/dist/mtls.d.ts +100 -0
- package/dist/mtls.js +41 -0
- package/dist/shared/chunk-ft0pdf3s.js +15 -0
- package/dist/shared/chunk-rkg27a9a.js +101 -0
- package/dist/xml-signer.d.ts +79 -0
- package/dist/xml-signer.js +119 -0
- package/package.json +80 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digital Certificate Types
|
|
3
|
+
*
|
|
4
|
+
* Types for Brazilian A1 digital certificate handling,
|
|
5
|
+
* XML-DSig signing, and mTLS configuration.
|
|
6
|
+
*/
|
|
7
|
+
interface CertificateSubject {
|
|
8
|
+
commonName: string | null;
|
|
9
|
+
organization: string | null;
|
|
10
|
+
organizationalUnit: string | null;
|
|
11
|
+
country: string | null;
|
|
12
|
+
state: string | null;
|
|
13
|
+
locality: string | null;
|
|
14
|
+
/** Full raw subject string */
|
|
15
|
+
raw: string;
|
|
16
|
+
}
|
|
17
|
+
interface CertificateIssuer {
|
|
18
|
+
commonName: string | null;
|
|
19
|
+
organization: string | null;
|
|
20
|
+
country: string | null;
|
|
21
|
+
raw: string;
|
|
22
|
+
}
|
|
23
|
+
interface CertificateValidity {
|
|
24
|
+
notBefore: Date;
|
|
25
|
+
notAfter: Date;
|
|
26
|
+
}
|
|
27
|
+
interface BrazilianFields {
|
|
28
|
+
/** CNPJ extracted from certificate (14 digits) */
|
|
29
|
+
cnpj: string | null;
|
|
30
|
+
/** CPF extracted from certificate (11 digits) */
|
|
31
|
+
cpf: string | null;
|
|
32
|
+
}
|
|
33
|
+
interface CertificateInfo {
|
|
34
|
+
/** Certificate serial number */
|
|
35
|
+
serialNumber: string;
|
|
36
|
+
/** Subject (who the certificate was issued to) */
|
|
37
|
+
subject: CertificateSubject;
|
|
38
|
+
/** Issuer (who issued the certificate) */
|
|
39
|
+
issuer: CertificateIssuer;
|
|
40
|
+
/** Validity period */
|
|
41
|
+
validity: CertificateValidity;
|
|
42
|
+
/** SHA-256 fingerprint of the certificate */
|
|
43
|
+
fingerprint: string;
|
|
44
|
+
/** Whether the certificate is currently valid */
|
|
45
|
+
isValid: boolean;
|
|
46
|
+
/** Brazilian-specific fields (CNPJ, CPF) */
|
|
47
|
+
brazilian: BrazilianFields;
|
|
48
|
+
/** PEM-encoded certificate */
|
|
49
|
+
certPem: string;
|
|
50
|
+
/** PEM-encoded private key */
|
|
51
|
+
keyPem: string;
|
|
52
|
+
/** Raw PFX buffer */
|
|
53
|
+
pfxBuffer: Buffer;
|
|
54
|
+
/** PFX password */
|
|
55
|
+
pfxPassword: string;
|
|
56
|
+
}
|
|
57
|
+
type SignatureAlgorithm = "sha1" | "sha256";
|
|
58
|
+
interface SignOptions {
|
|
59
|
+
/** The parsed certificate to use for signing */
|
|
60
|
+
certificate: CertificateInfo;
|
|
61
|
+
/** The URI of the element to sign (e.g., "#NFe123") */
|
|
62
|
+
referenceUri: string;
|
|
63
|
+
/** Hash algorithm for digest and signature (default: "sha256") */
|
|
64
|
+
algorithm?: SignatureAlgorithm;
|
|
65
|
+
/** Tag name of the element where Signature will be inserted.
|
|
66
|
+
* If not specified, the signature is appended to the root element. */
|
|
67
|
+
signatureParent?: string;
|
|
68
|
+
/** Whether to include the XML declaration in output (default: true) */
|
|
69
|
+
includeDeclaration?: boolean;
|
|
70
|
+
}
|
|
71
|
+
interface PemPair {
|
|
72
|
+
cert: string;
|
|
73
|
+
key: string;
|
|
74
|
+
}
|
|
75
|
+
interface MtlsOptions {
|
|
76
|
+
/** The parsed certificate to use */
|
|
77
|
+
certificate: CertificateInfo;
|
|
78
|
+
/** Additional CA certificates (PEM-encoded) */
|
|
79
|
+
caCerts?: string[];
|
|
80
|
+
/** Whether to reject unauthorized connections (default: true) */
|
|
81
|
+
rejectUnauthorized?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parse a .pfx/.p12 certificate file and extract all relevant information
|
|
85
|
+
*
|
|
86
|
+
* @param pfx - The PFX/P12 file contents as a Buffer
|
|
87
|
+
* @param password - The password to decrypt the PFX file
|
|
88
|
+
* @returns Parsed certificate information
|
|
89
|
+
* @throws {Error} If the PFX cannot be parsed or the password is wrong
|
|
90
|
+
*/
|
|
91
|
+
declare function parseCertificate(pfx: Buffer, password: string): CertificateInfo;
|
|
92
|
+
/**
|
|
93
|
+
* Check if a certificate is currently valid (not expired)
|
|
94
|
+
*/
|
|
95
|
+
declare function isCertificateValid(cert: CertificateInfo): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Get the number of days until certificate expiry
|
|
98
|
+
* Returns negative if already expired
|
|
99
|
+
*/
|
|
100
|
+
declare function daysUntilExpiry(cert: CertificateInfo): number;
|
|
101
|
+
/**
|
|
102
|
+
* Get PEM pair (certificate + private key) from certificate info
|
|
103
|
+
*/
|
|
104
|
+
declare function getPemPair(cert: CertificateInfo): {
|
|
105
|
+
cert: string;
|
|
106
|
+
key: string;
|
|
107
|
+
};
|
|
108
|
+
import { z } from "zod";
|
|
109
|
+
declare const signatureAlgorithmSchema: z.ZodEnum<{
|
|
110
|
+
sha1: "sha1";
|
|
111
|
+
sha256: "sha256";
|
|
112
|
+
}>;
|
|
113
|
+
declare const signOptionsSchema: z.ZodObject<{
|
|
114
|
+
referenceUri: z.ZodString;
|
|
115
|
+
algorithm: z.ZodDefault<z.ZodEnum<{
|
|
116
|
+
sha1: "sha1";
|
|
117
|
+
sha256: "sha256";
|
|
118
|
+
}>>;
|
|
119
|
+
signatureParent: z.ZodOptional<z.ZodString>;
|
|
120
|
+
includeDeclaration: z.ZodDefault<z.ZodBoolean>;
|
|
121
|
+
}, z.core.$strip>;
|
|
122
|
+
declare const mtlsOptionsSchema: z.ZodObject<{
|
|
123
|
+
caCerts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
124
|
+
rejectUnauthorized: z.ZodDefault<z.ZodBoolean>;
|
|
125
|
+
}, z.core.$strip>;
|
|
126
|
+
/**
|
|
127
|
+
* Utility functions for digital certificate handling
|
|
128
|
+
*
|
|
129
|
+
* PEM encoding, OID maps for Brazilian certificate fields,
|
|
130
|
+
* and ASN.1 helpers.
|
|
131
|
+
*/
|
|
132
|
+
/**
|
|
133
|
+
* OIDs used in Brazilian ICP-Brasil certificates
|
|
134
|
+
* to encode CNPJ and CPF in subject fields
|
|
135
|
+
*/
|
|
136
|
+
declare const BRAZILIAN_OIDS: Record<string, string>;
|
|
137
|
+
/**
|
|
138
|
+
* Parse an X.509 subject/issuer string into key-value pairs
|
|
139
|
+
*
|
|
140
|
+
* Handles formats like:
|
|
141
|
+
* - "CN=Name, O=Org, C=BR"
|
|
142
|
+
* - "/CN=Name/O=Org/C=BR"
|
|
143
|
+
*/
|
|
144
|
+
declare function parseDistinguishedName(dn: string): Record<string, string>;
|
|
145
|
+
/**
|
|
146
|
+
* Extract CNPJ from certificate subject string
|
|
147
|
+
* Looks for patterns like:
|
|
148
|
+
* - OU with CNPJ: "OU=CNPJ:12345678000190"
|
|
149
|
+
* - Direct in CN or subject alternative names
|
|
150
|
+
*/
|
|
151
|
+
declare function extractCnpj(subjectRaw: string): string | null;
|
|
152
|
+
/**
|
|
153
|
+
* Extract CPF from certificate subject string
|
|
154
|
+
*/
|
|
155
|
+
declare function extractCpf(subjectRaw: string): string | null;
|
|
156
|
+
/** Extract base64 content from PEM string (strips headers/whitespace) */
|
|
157
|
+
declare function pemToBase64(pem: string): string;
|
|
158
|
+
declare const XMLDSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
|
|
159
|
+
declare const EXC_C14N_NS = "http://www.w3.org/2001/10/xml-exc-c14n#";
|
|
160
|
+
declare const DIGEST_ALGORITHMS: {
|
|
161
|
+
readonly sha1: {
|
|
162
|
+
readonly uri: "http://www.w3.org/2000/09/xmldsig#sha1";
|
|
163
|
+
readonly nodeAlgo: "sha1";
|
|
164
|
+
};
|
|
165
|
+
readonly sha256: {
|
|
166
|
+
readonly uri: "http://www.w3.org/2001/04/xmlenc#sha256";
|
|
167
|
+
readonly nodeAlgo: "sha256";
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
declare const SIGNATURE_ALGORITHMS: {
|
|
171
|
+
readonly sha1: {
|
|
172
|
+
readonly uri: "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
|
|
173
|
+
readonly nodeAlgo: "sha1";
|
|
174
|
+
};
|
|
175
|
+
readonly sha256: {
|
|
176
|
+
readonly uri: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
|
|
177
|
+
readonly nodeAlgo: "sha256";
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
declare const TRANSFORM_ALGORITHMS: {
|
|
181
|
+
readonly envelopedSignature: "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
|
|
182
|
+
readonly excC14n: "http://www.w3.org/2001/10/xml-exc-c14n#";
|
|
183
|
+
};
|
|
184
|
+
export { signatureAlgorithmSchema, signOptionsSchema, pemToBase64, parseDistinguishedName, parseCertificate, mtlsOptionsSchema, isCertificateValid, getPemPair, extractCpf, extractCnpj, daysUntilExpiry, XMLDSIG_NS, TRANSFORM_ALGORITHMS, SignatureAlgorithm, SignOptions, SIGNATURE_ALGORITHMS, PemPair, MtlsOptions, EXC_C14N_NS, DIGEST_ALGORITHMS, CertificateValidity, CertificateSubject, CertificateIssuer, CertificateInfo, BrazilianFields, BRAZILIAN_OIDS };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BRAZILIAN_OIDS,
|
|
3
|
+
DIGEST_ALGORITHMS,
|
|
4
|
+
EXC_C14N_NS,
|
|
5
|
+
SIGNATURE_ALGORITHMS,
|
|
6
|
+
TRANSFORM_ALGORITHMS,
|
|
7
|
+
XMLDSIG_NS,
|
|
8
|
+
extractCnpj,
|
|
9
|
+
extractCpf,
|
|
10
|
+
parseDistinguishedName,
|
|
11
|
+
pemToBase64
|
|
12
|
+
} from "./shared/chunk-rkg27a9a.js";
|
|
13
|
+
import {
|
|
14
|
+
mtlsOptionsSchema,
|
|
15
|
+
signOptionsSchema,
|
|
16
|
+
signatureAlgorithmSchema
|
|
17
|
+
} from "./shared/chunk-ft0pdf3s.js";
|
|
18
|
+
|
|
19
|
+
// src/certificate.ts
|
|
20
|
+
import { execSync } from "node:child_process";
|
|
21
|
+
import crypto from "node:crypto";
|
|
22
|
+
function parseCertificate(pfx, password) {
|
|
23
|
+
const { certPem, keyPem } = extractPemFromPfx(pfx, password);
|
|
24
|
+
const x509 = new crypto.X509Certificate(certPem);
|
|
25
|
+
const subject = parseSubject(x509.subject);
|
|
26
|
+
const issuer = parseIssuer(x509.issuer);
|
|
27
|
+
const validity = parseValidity(x509);
|
|
28
|
+
const fingerprint = x509.fingerprint256.replace(/:/g, "").toLowerCase();
|
|
29
|
+
const isValid = checkValidity(validity);
|
|
30
|
+
const brazilian = extractBrazilianFields(x509.subject, x509.subjectAltName);
|
|
31
|
+
return {
|
|
32
|
+
serialNumber: x509.serialNumber,
|
|
33
|
+
subject,
|
|
34
|
+
issuer,
|
|
35
|
+
validity,
|
|
36
|
+
fingerprint,
|
|
37
|
+
isValid,
|
|
38
|
+
brazilian,
|
|
39
|
+
certPem,
|
|
40
|
+
keyPem,
|
|
41
|
+
pfxBuffer: pfx,
|
|
42
|
+
pfxPassword: password
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function isCertificateValid(cert) {
|
|
46
|
+
return checkValidity(cert.validity);
|
|
47
|
+
}
|
|
48
|
+
function daysUntilExpiry(cert) {
|
|
49
|
+
const now = new Date;
|
|
50
|
+
const diff = cert.validity.notAfter.getTime() - now.getTime();
|
|
51
|
+
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
52
|
+
}
|
|
53
|
+
function getPemPair(cert) {
|
|
54
|
+
return { cert: cert.certPem, key: cert.keyPem };
|
|
55
|
+
}
|
|
56
|
+
function extractPemFromPfx(pfx, password) {
|
|
57
|
+
const escapedPassword = escapeShellArg(password);
|
|
58
|
+
const certPem = opensslExtract(pfx, `-nokeys -clcerts -passin pass:${escapedPassword}`);
|
|
59
|
+
if (!certPem.includes("-----BEGIN CERTIFICATE-----")) {
|
|
60
|
+
throw new Error("Failed to extract certificate from PFX. Ensure the file is a valid PKCS#12 archive and the password is correct.");
|
|
61
|
+
}
|
|
62
|
+
const keyPem = opensslExtract(pfx, `-nocerts -passin pass:${escapedPassword} -passout pass: -nodes`);
|
|
63
|
+
if (!keyPem.includes("-----BEGIN PRIVATE KEY-----") && !keyPem.includes("-----BEGIN RSA PRIVATE KEY-----")) {
|
|
64
|
+
throw new Error("Failed to extract private key from PFX. Ensure the file is a valid PKCS#12 archive and the password is correct.");
|
|
65
|
+
}
|
|
66
|
+
return { certPem: certPem.trim(), keyPem: keyPem.trim() };
|
|
67
|
+
}
|
|
68
|
+
function opensslExtract(pfx, args) {
|
|
69
|
+
try {
|
|
70
|
+
return execSync(`openssl pkcs12 -in /dev/stdin ${args} -legacy`, {
|
|
71
|
+
input: pfx,
|
|
72
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
73
|
+
}).toString();
|
|
74
|
+
} catch {
|
|
75
|
+
try {
|
|
76
|
+
return execSync(`openssl pkcs12 -in /dev/stdin ${args}`, {
|
|
77
|
+
input: pfx,
|
|
78
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
79
|
+
}).toString();
|
|
80
|
+
} catch (e) {
|
|
81
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
82
|
+
throw new Error(`OpenSSL PKCS12 extraction failed: ${message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function escapeShellArg(arg) {
|
|
87
|
+
return arg.replace(/'/g, "'\\''");
|
|
88
|
+
}
|
|
89
|
+
function parseSubject(subjectStr) {
|
|
90
|
+
const fields = parseDistinguishedName(subjectStr);
|
|
91
|
+
return {
|
|
92
|
+
commonName: fields.CN ?? null,
|
|
93
|
+
organization: fields.O ?? null,
|
|
94
|
+
organizationalUnit: fields.OU ?? null,
|
|
95
|
+
country: fields.C ?? null,
|
|
96
|
+
state: fields.ST ?? null,
|
|
97
|
+
locality: fields.L ?? null,
|
|
98
|
+
raw: subjectStr
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function parseIssuer(issuerStr) {
|
|
102
|
+
const fields = parseDistinguishedName(issuerStr);
|
|
103
|
+
return {
|
|
104
|
+
commonName: fields.CN ?? null,
|
|
105
|
+
organization: fields.O ?? null,
|
|
106
|
+
country: fields.C ?? null,
|
|
107
|
+
raw: issuerStr
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function parseValidity(x509) {
|
|
111
|
+
return {
|
|
112
|
+
notBefore: new Date(x509.validFrom),
|
|
113
|
+
notAfter: new Date(x509.validTo)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function checkValidity(validity) {
|
|
117
|
+
const now = new Date;
|
|
118
|
+
return now >= validity.notBefore && now <= validity.notAfter;
|
|
119
|
+
}
|
|
120
|
+
function extractBrazilianFields(subject, subjectAltName) {
|
|
121
|
+
let cnpj = extractCnpj(subject);
|
|
122
|
+
let cpf = extractCpf(subject);
|
|
123
|
+
if (subjectAltName) {
|
|
124
|
+
if (!cnpj)
|
|
125
|
+
cnpj = extractCnpj(subjectAltName);
|
|
126
|
+
if (!cpf)
|
|
127
|
+
cpf = extractCpf(subjectAltName);
|
|
128
|
+
}
|
|
129
|
+
return { cnpj, cpf };
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
signatureAlgorithmSchema,
|
|
133
|
+
signOptionsSchema,
|
|
134
|
+
pemToBase64,
|
|
135
|
+
parseDistinguishedName,
|
|
136
|
+
parseCertificate,
|
|
137
|
+
mtlsOptionsSchema,
|
|
138
|
+
isCertificateValid,
|
|
139
|
+
getPemPair,
|
|
140
|
+
extractCpf,
|
|
141
|
+
extractCnpj,
|
|
142
|
+
daysUntilExpiry,
|
|
143
|
+
XMLDSIG_NS,
|
|
144
|
+
TRANSFORM_ALGORITHMS,
|
|
145
|
+
SIGNATURE_ALGORITHMS,
|
|
146
|
+
EXC_C14N_NS,
|
|
147
|
+
DIGEST_ALGORITHMS,
|
|
148
|
+
BRAZILIAN_OIDS
|
|
149
|
+
};
|
package/dist/mtls.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import https from "node:https";
|
|
2
|
+
import tls from "node:tls";
|
|
3
|
+
/**
|
|
4
|
+
* Digital Certificate Types
|
|
5
|
+
*
|
|
6
|
+
* Types for Brazilian A1 digital certificate handling,
|
|
7
|
+
* XML-DSig signing, and mTLS configuration.
|
|
8
|
+
*/
|
|
9
|
+
interface CertificateSubject {
|
|
10
|
+
commonName: string | null;
|
|
11
|
+
organization: string | null;
|
|
12
|
+
organizationalUnit: string | null;
|
|
13
|
+
country: string | null;
|
|
14
|
+
state: string | null;
|
|
15
|
+
locality: string | null;
|
|
16
|
+
/** Full raw subject string */
|
|
17
|
+
raw: string;
|
|
18
|
+
}
|
|
19
|
+
interface CertificateIssuer {
|
|
20
|
+
commonName: string | null;
|
|
21
|
+
organization: string | null;
|
|
22
|
+
country: string | null;
|
|
23
|
+
raw: string;
|
|
24
|
+
}
|
|
25
|
+
interface CertificateValidity {
|
|
26
|
+
notBefore: Date;
|
|
27
|
+
notAfter: Date;
|
|
28
|
+
}
|
|
29
|
+
interface BrazilianFields {
|
|
30
|
+
/** CNPJ extracted from certificate (14 digits) */
|
|
31
|
+
cnpj: string | null;
|
|
32
|
+
/** CPF extracted from certificate (11 digits) */
|
|
33
|
+
cpf: string | null;
|
|
34
|
+
}
|
|
35
|
+
interface CertificateInfo {
|
|
36
|
+
/** Certificate serial number */
|
|
37
|
+
serialNumber: string;
|
|
38
|
+
/** Subject (who the certificate was issued to) */
|
|
39
|
+
subject: CertificateSubject;
|
|
40
|
+
/** Issuer (who issued the certificate) */
|
|
41
|
+
issuer: CertificateIssuer;
|
|
42
|
+
/** Validity period */
|
|
43
|
+
validity: CertificateValidity;
|
|
44
|
+
/** SHA-256 fingerprint of the certificate */
|
|
45
|
+
fingerprint: string;
|
|
46
|
+
/** Whether the certificate is currently valid */
|
|
47
|
+
isValid: boolean;
|
|
48
|
+
/** Brazilian-specific fields (CNPJ, CPF) */
|
|
49
|
+
brazilian: BrazilianFields;
|
|
50
|
+
/** PEM-encoded certificate */
|
|
51
|
+
certPem: string;
|
|
52
|
+
/** PEM-encoded private key */
|
|
53
|
+
keyPem: string;
|
|
54
|
+
/** Raw PFX buffer */
|
|
55
|
+
pfxBuffer: Buffer;
|
|
56
|
+
/** PFX password */
|
|
57
|
+
pfxPassword: string;
|
|
58
|
+
}
|
|
59
|
+
interface PemPair {
|
|
60
|
+
cert: string;
|
|
61
|
+
key: string;
|
|
62
|
+
}
|
|
63
|
+
interface MtlsOptions {
|
|
64
|
+
/** The parsed certificate to use */
|
|
65
|
+
certificate: CertificateInfo;
|
|
66
|
+
/** Additional CA certificates (PEM-encoded) */
|
|
67
|
+
caCerts?: string[];
|
|
68
|
+
/** Whether to reject unauthorized connections (default: true) */
|
|
69
|
+
rejectUnauthorized?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a TLS SecureContext for use with mTLS connections
|
|
73
|
+
*
|
|
74
|
+
* @param certificate - The parsed certificate
|
|
75
|
+
* @param options - Additional mTLS options
|
|
76
|
+
* @returns A tls.SecureContext configured for mTLS
|
|
77
|
+
*/
|
|
78
|
+
declare function createTlsContext(certificate: CertificateInfo, options?: Omit<MtlsOptions, "certificate">): tls.SecureContext;
|
|
79
|
+
/**
|
|
80
|
+
* Create an HTTPS Agent configured for mTLS
|
|
81
|
+
*
|
|
82
|
+
* Use this agent with Node.js http/https requests or compatible
|
|
83
|
+
* HTTP clients (e.g., axios, node-fetch with agent option).
|
|
84
|
+
*
|
|
85
|
+
* @param certificate - The parsed certificate
|
|
86
|
+
* @param options - Additional mTLS options
|
|
87
|
+
* @returns An https.Agent configured for mTLS
|
|
88
|
+
*/
|
|
89
|
+
declare function createHttpsAgent(certificate: CertificateInfo, options?: Omit<MtlsOptions, "certificate">): https.Agent;
|
|
90
|
+
/**
|
|
91
|
+
* Get PEM pair (certificate + private key) for use with custom HTTP clients
|
|
92
|
+
*
|
|
93
|
+
* Useful when you need to pass cert/key as strings to HTTP libraries
|
|
94
|
+
* that don't accept PFX directly (e.g., Bun.fetch with tls option).
|
|
95
|
+
*
|
|
96
|
+
* @param certificate - The parsed certificate
|
|
97
|
+
* @returns Object with cert and key PEM strings
|
|
98
|
+
*/
|
|
99
|
+
declare function getMtlsPemPair(certificate: CertificateInfo): PemPair;
|
|
100
|
+
export { getMtlsPemPair, createTlsContext, createHttpsAgent };
|
package/dist/mtls.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mtlsOptionsSchema
|
|
3
|
+
} from "./shared/chunk-ft0pdf3s.js";
|
|
4
|
+
|
|
5
|
+
// src/mtls.ts
|
|
6
|
+
import https from "node:https";
|
|
7
|
+
import tls from "node:tls";
|
|
8
|
+
function createTlsContext(certificate, options) {
|
|
9
|
+
const opts = mtlsOptionsSchema.parse(options ?? {});
|
|
10
|
+
const contextOptions = {
|
|
11
|
+
pfx: certificate.pfxBuffer,
|
|
12
|
+
passphrase: certificate.pfxPassword
|
|
13
|
+
};
|
|
14
|
+
if (opts.caCerts && opts.caCerts.length > 0) {
|
|
15
|
+
contextOptions.ca = opts.caCerts;
|
|
16
|
+
}
|
|
17
|
+
return tls.createSecureContext(contextOptions);
|
|
18
|
+
}
|
|
19
|
+
function createHttpsAgent(certificate, options) {
|
|
20
|
+
const opts = mtlsOptionsSchema.parse(options ?? {});
|
|
21
|
+
const agentOptions = {
|
|
22
|
+
pfx: certificate.pfxBuffer,
|
|
23
|
+
passphrase: certificate.pfxPassword,
|
|
24
|
+
rejectUnauthorized: opts.rejectUnauthorized
|
|
25
|
+
};
|
|
26
|
+
if (opts.caCerts && opts.caCerts.length > 0) {
|
|
27
|
+
agentOptions.ca = opts.caCerts;
|
|
28
|
+
}
|
|
29
|
+
return new https.Agent(agentOptions);
|
|
30
|
+
}
|
|
31
|
+
function getMtlsPemPair(certificate) {
|
|
32
|
+
return {
|
|
33
|
+
cert: certificate.certPem,
|
|
34
|
+
key: certificate.keyPem
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
getMtlsPemPair,
|
|
39
|
+
createTlsContext,
|
|
40
|
+
createHttpsAgent
|
|
41
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/schemas.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var signatureAlgorithmSchema = z.enum(["sha1", "sha256"]);
|
|
4
|
+
var signOptionsSchema = z.object({
|
|
5
|
+
referenceUri: z.string(),
|
|
6
|
+
algorithm: signatureAlgorithmSchema.default("sha256"),
|
|
7
|
+
signatureParent: z.string().optional(),
|
|
8
|
+
includeDeclaration: z.boolean().default(true)
|
|
9
|
+
});
|
|
10
|
+
var mtlsOptionsSchema = z.object({
|
|
11
|
+
caCerts: z.array(z.string()).optional(),
|
|
12
|
+
rejectUnauthorized: z.boolean().default(true)
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export { signatureAlgorithmSchema, signOptionsSchema, mtlsOptionsSchema };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
var BRAZILIAN_OIDS = {
|
|
3
|
+
"2.16.76.1.3.1": "otherName-CPF",
|
|
4
|
+
"2.16.76.1.3.2": "otherName-ICP-Brasil-Name",
|
|
5
|
+
"2.16.76.1.3.3": "otherName-CNPJ",
|
|
6
|
+
"2.16.76.1.3.4": "otherName-ICP-Brasil-Responsible",
|
|
7
|
+
"2.16.76.1.3.5": "otherName-ICP-Brasil-Voter",
|
|
8
|
+
"2.16.76.1.3.6": "otherName-ICP-Brasil-INSS",
|
|
9
|
+
"2.16.76.1.3.7": "otherName-ICP-Brasil-CEI",
|
|
10
|
+
"2.16.76.1.3.8": "otherName-ICP-Brasil-OAB"
|
|
11
|
+
};
|
|
12
|
+
function parseDistinguishedName(dn) {
|
|
13
|
+
const result = {};
|
|
14
|
+
if (!dn)
|
|
15
|
+
return result;
|
|
16
|
+
let normalized = dn;
|
|
17
|
+
if (dn.startsWith("/")) {
|
|
18
|
+
normalized = dn.slice(1).split("/").join(", ");
|
|
19
|
+
} else if (dn.includes(`
|
|
20
|
+
`)) {
|
|
21
|
+
normalized = dn.split(`
|
|
22
|
+
`).join(", ");
|
|
23
|
+
}
|
|
24
|
+
const parts = splitDnParts(normalized);
|
|
25
|
+
for (const part of parts) {
|
|
26
|
+
const eqIdx = part.indexOf("=");
|
|
27
|
+
if (eqIdx !== -1) {
|
|
28
|
+
const key = part.slice(0, eqIdx).trim();
|
|
29
|
+
const value = part.slice(eqIdx + 1).trim();
|
|
30
|
+
result[key] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
function splitDnParts(dn) {
|
|
36
|
+
const parts = [];
|
|
37
|
+
let current = "";
|
|
38
|
+
let inQuotes = false;
|
|
39
|
+
for (let i = 0;i < dn.length; i++) {
|
|
40
|
+
const ch = dn[i];
|
|
41
|
+
if (ch === '"') {
|
|
42
|
+
inQuotes = !inQuotes;
|
|
43
|
+
current += ch;
|
|
44
|
+
} else if (ch === "," && !inQuotes) {
|
|
45
|
+
parts.push(current.trim());
|
|
46
|
+
current = "";
|
|
47
|
+
} else {
|
|
48
|
+
current += ch;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (current.trim()) {
|
|
52
|
+
parts.push(current.trim());
|
|
53
|
+
}
|
|
54
|
+
return parts;
|
|
55
|
+
}
|
|
56
|
+
function extractCnpj(subjectRaw) {
|
|
57
|
+
const cnpjMatch = subjectRaw.match(/CNPJ[:\s=]+(\d{14})/i);
|
|
58
|
+
if (cnpjMatch?.[1])
|
|
59
|
+
return cnpjMatch[1];
|
|
60
|
+
const ouMatch = subjectRaw.match(/OU\s*=\s*[^,]*?(\d{14})/);
|
|
61
|
+
if (ouMatch?.[1])
|
|
62
|
+
return ouMatch[1];
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function extractCpf(subjectRaw) {
|
|
66
|
+
const cpfMatch = subjectRaw.match(/CPF[:\s=]+(\d{11})/i);
|
|
67
|
+
if (cpfMatch?.[1])
|
|
68
|
+
return cpfMatch[1];
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function pemToBase64(pem) {
|
|
72
|
+
return pem.replace(/-----BEGIN [^-]+-----/, "").replace(/-----END [^-]+-----/, "").replace(/\s+/g, "");
|
|
73
|
+
}
|
|
74
|
+
var XMLDSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
|
|
75
|
+
var EXC_C14N_NS = "http://www.w3.org/2001/10/xml-exc-c14n#";
|
|
76
|
+
var DIGEST_ALGORITHMS = {
|
|
77
|
+
sha1: {
|
|
78
|
+
uri: "http://www.w3.org/2000/09/xmldsig#sha1",
|
|
79
|
+
nodeAlgo: "sha1"
|
|
80
|
+
},
|
|
81
|
+
sha256: {
|
|
82
|
+
uri: "http://www.w3.org/2001/04/xmlenc#sha256",
|
|
83
|
+
nodeAlgo: "sha256"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var SIGNATURE_ALGORITHMS = {
|
|
87
|
+
sha1: {
|
|
88
|
+
uri: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
|
|
89
|
+
nodeAlgo: "sha1"
|
|
90
|
+
},
|
|
91
|
+
sha256: {
|
|
92
|
+
uri: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
|
93
|
+
nodeAlgo: "sha256"
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var TRANSFORM_ALGORITHMS = {
|
|
97
|
+
envelopedSignature: "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
|
|
98
|
+
excC14n: "http://www.w3.org/2001/10/xml-exc-c14n#"
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export { BRAZILIAN_OIDS, parseDistinguishedName, extractCnpj, extractCpf, pemToBase64, XMLDSIG_NS, EXC_C14N_NS, DIGEST_ALGORITHMS, SIGNATURE_ALGORITHMS, TRANSFORM_ALGORITHMS };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digital Certificate Types
|
|
3
|
+
*
|
|
4
|
+
* Types for Brazilian A1 digital certificate handling,
|
|
5
|
+
* XML-DSig signing, and mTLS configuration.
|
|
6
|
+
*/
|
|
7
|
+
interface CertificateSubject {
|
|
8
|
+
commonName: string | null;
|
|
9
|
+
organization: string | null;
|
|
10
|
+
organizationalUnit: string | null;
|
|
11
|
+
country: string | null;
|
|
12
|
+
state: string | null;
|
|
13
|
+
locality: string | null;
|
|
14
|
+
/** Full raw subject string */
|
|
15
|
+
raw: string;
|
|
16
|
+
}
|
|
17
|
+
interface CertificateIssuer {
|
|
18
|
+
commonName: string | null;
|
|
19
|
+
organization: string | null;
|
|
20
|
+
country: string | null;
|
|
21
|
+
raw: string;
|
|
22
|
+
}
|
|
23
|
+
interface CertificateValidity {
|
|
24
|
+
notBefore: Date;
|
|
25
|
+
notAfter: Date;
|
|
26
|
+
}
|
|
27
|
+
interface BrazilianFields {
|
|
28
|
+
/** CNPJ extracted from certificate (14 digits) */
|
|
29
|
+
cnpj: string | null;
|
|
30
|
+
/** CPF extracted from certificate (11 digits) */
|
|
31
|
+
cpf: string | null;
|
|
32
|
+
}
|
|
33
|
+
interface CertificateInfo {
|
|
34
|
+
/** Certificate serial number */
|
|
35
|
+
serialNumber: string;
|
|
36
|
+
/** Subject (who the certificate was issued to) */
|
|
37
|
+
subject: CertificateSubject;
|
|
38
|
+
/** Issuer (who issued the certificate) */
|
|
39
|
+
issuer: CertificateIssuer;
|
|
40
|
+
/** Validity period */
|
|
41
|
+
validity: CertificateValidity;
|
|
42
|
+
/** SHA-256 fingerprint of the certificate */
|
|
43
|
+
fingerprint: string;
|
|
44
|
+
/** Whether the certificate is currently valid */
|
|
45
|
+
isValid: boolean;
|
|
46
|
+
/** Brazilian-specific fields (CNPJ, CPF) */
|
|
47
|
+
brazilian: BrazilianFields;
|
|
48
|
+
/** PEM-encoded certificate */
|
|
49
|
+
certPem: string;
|
|
50
|
+
/** PEM-encoded private key */
|
|
51
|
+
keyPem: string;
|
|
52
|
+
/** Raw PFX buffer */
|
|
53
|
+
pfxBuffer: Buffer;
|
|
54
|
+
/** PFX password */
|
|
55
|
+
pfxPassword: string;
|
|
56
|
+
}
|
|
57
|
+
type SignatureAlgorithm = "sha1" | "sha256";
|
|
58
|
+
interface SignOptions {
|
|
59
|
+
/** The parsed certificate to use for signing */
|
|
60
|
+
certificate: CertificateInfo;
|
|
61
|
+
/** The URI of the element to sign (e.g., "#NFe123") */
|
|
62
|
+
referenceUri: string;
|
|
63
|
+
/** Hash algorithm for digest and signature (default: "sha256") */
|
|
64
|
+
algorithm?: SignatureAlgorithm;
|
|
65
|
+
/** Tag name of the element where Signature will be inserted.
|
|
66
|
+
* If not specified, the signature is appended to the root element. */
|
|
67
|
+
signatureParent?: string;
|
|
68
|
+
/** Whether to include the XML declaration in output (default: true) */
|
|
69
|
+
includeDeclaration?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Sign an XML document with an enveloped XML-DSig signature
|
|
73
|
+
*
|
|
74
|
+
* @param xml - The XML string to sign
|
|
75
|
+
* @param options - Signing options (certificate, reference URI, algorithm)
|
|
76
|
+
* @returns The signed XML string
|
|
77
|
+
*/
|
|
78
|
+
declare function signXml(xml: string, options: SignOptions): string;
|
|
79
|
+
export { signXml };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DIGEST_ALGORITHMS,
|
|
3
|
+
EXC_C14N_NS,
|
|
4
|
+
SIGNATURE_ALGORITHMS,
|
|
5
|
+
TRANSFORM_ALGORITHMS,
|
|
6
|
+
XMLDSIG_NS,
|
|
7
|
+
pemToBase64
|
|
8
|
+
} from "./shared/chunk-rkg27a9a.js";
|
|
9
|
+
import {
|
|
10
|
+
signOptionsSchema
|
|
11
|
+
} from "./shared/chunk-ft0pdf3s.js";
|
|
12
|
+
|
|
13
|
+
// src/xml-signer.ts
|
|
14
|
+
import crypto from "node:crypto";
|
|
15
|
+
import {
|
|
16
|
+
getAttributeValue,
|
|
17
|
+
parseXml,
|
|
18
|
+
serializeXml,
|
|
19
|
+
XML_NODE_TYPES
|
|
20
|
+
} from "@f-o-t/xml";
|
|
21
|
+
import { canonicalize } from "@f-o-t/xml/canonicalize";
|
|
22
|
+
function signXml(xml, options) {
|
|
23
|
+
const opts = signOptionsSchema.parse(options);
|
|
24
|
+
const algorithm = opts.algorithm;
|
|
25
|
+
const doc = parseXml(xml);
|
|
26
|
+
if (!doc.root) {
|
|
27
|
+
throw new Error("XML document has no root element");
|
|
28
|
+
}
|
|
29
|
+
const targetElement = findSignTarget(doc.root, opts.referenceUri);
|
|
30
|
+
if (!targetElement) {
|
|
31
|
+
throw new Error(`Could not find element with URI reference: ${opts.referenceUri}`);
|
|
32
|
+
}
|
|
33
|
+
const canonicalXml = canonicalize(targetElement, {
|
|
34
|
+
exclusive: true,
|
|
35
|
+
withComments: false
|
|
36
|
+
});
|
|
37
|
+
const digestValue = computeDigest(canonicalXml, algorithm);
|
|
38
|
+
const signedInfoXml = buildSignedInfoXml(opts.referenceUri, algorithm, digestValue);
|
|
39
|
+
const signedInfoDoc = parseXml(signedInfoXml);
|
|
40
|
+
const canonicalSignedInfo = canonicalize(signedInfoDoc.root, {
|
|
41
|
+
exclusive: true,
|
|
42
|
+
withComments: false
|
|
43
|
+
});
|
|
44
|
+
const signatureValue = computeSignature(canonicalSignedInfo, options.certificate.keyPem, algorithm);
|
|
45
|
+
const signatureXml = buildSignatureXml(signedInfoXml, signatureValue, options.certificate.certPem);
|
|
46
|
+
const signatureDoc = parseXml(signatureXml);
|
|
47
|
+
const signatureElement = signatureDoc.root;
|
|
48
|
+
const signatureParent = opts.signatureParent ? findElementByName(doc.root, opts.signatureParent) : targetElement;
|
|
49
|
+
if (!signatureParent) {
|
|
50
|
+
throw new Error(`Could not find signature parent element: ${opts.signatureParent}`);
|
|
51
|
+
}
|
|
52
|
+
signatureElement.parent = signatureParent;
|
|
53
|
+
signatureParent.children.push(signatureElement);
|
|
54
|
+
return serializeXml(doc, {
|
|
55
|
+
declaration: opts.includeDeclaration,
|
|
56
|
+
indent: "",
|
|
57
|
+
selfClose: true
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function findSignTarget(root, referenceUri) {
|
|
61
|
+
if (referenceUri === "") {
|
|
62
|
+
return root;
|
|
63
|
+
}
|
|
64
|
+
const id = referenceUri.startsWith("#") ? referenceUri.slice(1) : referenceUri;
|
|
65
|
+
return findElementById(root, id);
|
|
66
|
+
}
|
|
67
|
+
function findElementById(element, id) {
|
|
68
|
+
const idAttrs = ["Id", "id", "ID"];
|
|
69
|
+
for (const attrName of idAttrs) {
|
|
70
|
+
const val = getAttributeValue(element, attrName);
|
|
71
|
+
if (val === id)
|
|
72
|
+
return element;
|
|
73
|
+
}
|
|
74
|
+
for (const child of element.children) {
|
|
75
|
+
if (child.type === XML_NODE_TYPES.ELEMENT) {
|
|
76
|
+
const found = findElementById(child, id);
|
|
77
|
+
if (found)
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function findElementByName(element, name) {
|
|
84
|
+
if (element.name === name || element.localName === name) {
|
|
85
|
+
return element;
|
|
86
|
+
}
|
|
87
|
+
for (const child of element.children) {
|
|
88
|
+
if (child.type === XML_NODE_TYPES.ELEMENT) {
|
|
89
|
+
const found = findElementByName(child, name);
|
|
90
|
+
if (found)
|
|
91
|
+
return found;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function computeDigest(data, algorithm) {
|
|
97
|
+
const algo = DIGEST_ALGORITHMS[algorithm];
|
|
98
|
+
const hash = crypto.createHash(algo.nodeAlgo);
|
|
99
|
+
hash.update(data, "utf8");
|
|
100
|
+
return hash.digest("base64");
|
|
101
|
+
}
|
|
102
|
+
function computeSignature(data, privateKeyPem, algorithm) {
|
|
103
|
+
const algo = SIGNATURE_ALGORITHMS[algorithm];
|
|
104
|
+
const sign = crypto.createSign(`RSA-${algo.nodeAlgo.toUpperCase()}`);
|
|
105
|
+
sign.update(data, "utf8");
|
|
106
|
+
return sign.sign(privateKeyPem, "base64");
|
|
107
|
+
}
|
|
108
|
+
function buildSignedInfoXml(referenceUri, algorithm, digestValue) {
|
|
109
|
+
const sigAlgo = SIGNATURE_ALGORITHMS[algorithm];
|
|
110
|
+
const digAlgo = DIGEST_ALGORITHMS[algorithm];
|
|
111
|
+
return `<SignedInfo xmlns="${XMLDSIG_NS}"><CanonicalizationMethod Algorithm="${EXC_C14N_NS}"/><SignatureMethod Algorithm="${sigAlgo.uri}"/><Reference URI="${referenceUri}"><Transforms><Transform Algorithm="${TRANSFORM_ALGORITHMS.envelopedSignature}"/><Transform Algorithm="${TRANSFORM_ALGORITHMS.excC14n}"/></Transforms><DigestMethod Algorithm="${digAlgo.uri}"/><DigestValue>${digestValue}</DigestValue></Reference></SignedInfo>`;
|
|
112
|
+
}
|
|
113
|
+
function buildSignatureXml(signedInfoXml, signatureValue, certPem) {
|
|
114
|
+
const certBase64 = pemToBase64(certPem);
|
|
115
|
+
return `<Signature xmlns="${XMLDSIG_NS}">${signedInfoXml}<SignatureValue>${signatureValue}</SignatureValue><KeyInfo><X509Data><X509Certificate>${certBase64}</X509Certificate></X509Data></KeyInfo></Signature>`;
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
signXml
|
|
119
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@f-o-t/digital-certificate",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Brazilian A1 digital certificate handling with .pfx/.p12 parsing, XML-DSig signing, certificate management, and mTLS support",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"bun": "./src/index.ts",
|
|
12
|
+
"import": {
|
|
13
|
+
"default": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"types": "./src/index.ts"
|
|
17
|
+
},
|
|
18
|
+
"./xml-signer": {
|
|
19
|
+
"bun": "./src/xml-signer.ts",
|
|
20
|
+
"import": {
|
|
21
|
+
"default": "./dist/xml-signer.js",
|
|
22
|
+
"types": "./dist/xml-signer.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"types": "./src/xml-signer.ts"
|
|
25
|
+
},
|
|
26
|
+
"./mtls": {
|
|
27
|
+
"bun": "./src/mtls.ts",
|
|
28
|
+
"import": {
|
|
29
|
+
"default": "./dist/mtls.js",
|
|
30
|
+
"types": "./dist/mtls.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"types": "./src/mtls.ts"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "bunup",
|
|
41
|
+
"check": "biome check --write .",
|
|
42
|
+
"dev": "bunup --watch",
|
|
43
|
+
"release": "bumpp --commit --push --tag",
|
|
44
|
+
"test": "bun test",
|
|
45
|
+
"test:coverage": "bun test --coverage",
|
|
46
|
+
"test:watch": "bun test --watch",
|
|
47
|
+
"typecheck": "tsc"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@f-o-t/xml": "1.0.0",
|
|
51
|
+
"zod": "4.3.6"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@biomejs/biome": "2.3.12",
|
|
55
|
+
"@types/bun": "1.3.6",
|
|
56
|
+
"bumpp": "10.4.0",
|
|
57
|
+
"bunup": "0.16.20",
|
|
58
|
+
"typescript": "5.9.3"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"typescript": ">=4.5.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"typescript": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"license": "MIT",
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "https://github.com/F-O-T/contentta-nx.git"
|
|
72
|
+
},
|
|
73
|
+
"homepage": "https://github.com/F-O-T/contentta-nx/blob/master/libraries/digital-certificate",
|
|
74
|
+
"bugs": {
|
|
75
|
+
"url": "https://github.com/F-O-T/contentta-nx/issues"
|
|
76
|
+
},
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public"
|
|
79
|
+
}
|
|
80
|
+
}
|