@baruk/core 0.5.0 → 0.6.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/.baruk-provenance.json +4 -4
- package/dist/cjs/clients/ibge.js +14 -0
- package/dist/cjs/clients/ibge.js.map +1 -1
- package/dist/cjs/clients/registry/http.js +157 -0
- package/dist/cjs/clients/registry/http.js.map +1 -0
- package/dist/cjs/clients/registry/ibge-municipios.js +28 -0
- package/dist/cjs/clients/registry/ibge-municipios.js.map +1 -0
- package/dist/cjs/clients/registry/ibge.js +63 -0
- package/dist/cjs/clients/registry/ibge.js.map +1 -0
- package/dist/cjs/clients/registry/lookup.js +191 -0
- package/dist/cjs/clients/registry/lookup.js.map +1 -0
- package/dist/cjs/clients/registry/provider.js +34 -0
- package/dist/cjs/clients/registry/provider.js.map +1 -0
- package/dist/cjs/clients/registry/resolve.js +47 -0
- package/dist/cjs/clients/registry/resolve.js.map +1 -0
- package/dist/cjs/clients/registry/serpro.js +149 -0
- package/dist/cjs/clients/registry/serpro.js.map +1 -0
- package/dist/cjs/clients/registry/service.js +230 -0
- package/dist/cjs/clients/registry/service.js.map +1 -0
- package/dist/cjs/clients/registry/synthetic.js +165 -0
- package/dist/cjs/clients/registry/synthetic.js.map +1 -0
- package/dist/cjs/clients/repository.js +6 -2
- package/dist/cjs/clients/repository.js.map +1 -1
- package/dist/cjs/db/provision-e2e-identity.js +8 -3
- package/dist/cjs/db/provision-e2e-identity.js.map +1 -1
- package/dist/cjs/fiscal/collection/authorization.js +186 -0
- package/dist/cjs/fiscal/collection/authorization.js.map +1 -0
- package/dist/cjs/fiscal/collection/competence.js +97 -0
- package/dist/cjs/fiscal/collection/competence.js.map +1 -0
- package/dist/cjs/fiscal/collection/first-contact.js +204 -0
- package/dist/cjs/fiscal/collection/first-contact.js.map +1 -0
- package/dist/cjs/fiscal/collection/plan.js +122 -0
- package/dist/cjs/fiscal/collection/plan.js.map +1 -0
- package/dist/cjs/fiscal/coverage/model.js +11 -1
- package/dist/cjs/fiscal/coverage/model.js.map +1 -1
- package/dist/cjs/fiscal/credentials/inspect.js +182 -0
- package/dist/cjs/fiscal/credentials/inspect.js.map +1 -0
- package/dist/cjs/fiscal/credentials/material.js +1 -1
- package/dist/cjs/fiscal/credentials/material.js.map +1 -1
- package/dist/cjs/fiscal/credentials/onboarding.js +266 -0
- package/dist/cjs/fiscal/credentials/onboarding.js.map +1 -0
- package/dist/cjs/fiscal/credentials/readiness.js +29 -2
- package/dist/cjs/fiscal/credentials/readiness.js.map +1 -1
- package/dist/cjs/fiscal/enrollment.js +134 -0
- package/dist/cjs/fiscal/enrollment.js.map +1 -0
- package/dist/cjs/fiscal/transport/synthetic-tls.js +52 -0
- package/dist/cjs/fiscal/transport/synthetic-tls.js.map +1 -1
- package/dist/cjs/index.js +196 -113
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/legal/documents.js +64 -4
- package/dist/cjs/legal/documents.js.map +1 -1
- package/dist/cjs/legal/types.js.map +1 -1
- package/dist/cjs/platform/capabilities.js +29 -0
- package/dist/cjs/platform/capabilities.js.map +1 -1
- package/dist/cjs/rbac/catalog.js +78 -2
- package/dist/cjs/rbac/catalog.js.map +1 -1
- package/dist/cjs/subscription-billing/index.js +8 -1
- package/dist/cjs/subscription-billing/index.js.map +1 -1
- package/dist/cjs/subscription-billing/platform-exemption.js +121 -0
- package/dist/cjs/subscription-billing/platform-exemption.js.map +1 -0
- package/dist/cjs/subscription-billing/service.js +93 -1
- package/dist/cjs/subscription-billing/service.js.map +1 -1
- package/dist/cjs/vault/repository.js +92 -5
- package/dist/cjs/vault/repository.js.map +1 -1
- package/dist/clients/ibge.d.ts.map +1 -1
- package/dist/clients/ibge.js +14 -0
- package/dist/clients/ibge.js.map +1 -1
- package/dist/clients/registry/http.d.ts +61 -0
- package/dist/clients/registry/http.d.ts.map +1 -0
- package/dist/clients/registry/http.js +151 -0
- package/dist/clients/registry/http.js.map +1 -0
- package/dist/clients/registry/ibge-municipios.d.ts +25 -0
- package/dist/clients/registry/ibge-municipios.d.ts.map +1 -0
- package/dist/clients/registry/ibge-municipios.js +25 -0
- package/dist/clients/registry/ibge-municipios.js.map +1 -0
- package/dist/clients/registry/ibge.d.ts +62 -0
- package/dist/clients/registry/ibge.d.ts.map +1 -0
- package/dist/clients/registry/ibge.js +57 -0
- package/dist/clients/registry/ibge.js.map +1 -0
- package/dist/clients/registry/lookup.d.ts +94 -0
- package/dist/clients/registry/lookup.d.ts.map +1 -0
- package/dist/clients/registry/lookup.js +184 -0
- package/dist/clients/registry/lookup.js.map +1 -0
- package/dist/clients/registry/provider.d.ts +124 -0
- package/dist/clients/registry/provider.d.ts.map +1 -0
- package/dist/clients/registry/provider.js +39 -0
- package/dist/clients/registry/provider.js.map +1 -0
- package/dist/clients/registry/resolve.d.ts +36 -0
- package/dist/clients/registry/resolve.d.ts.map +1 -0
- package/dist/clients/registry/resolve.js +44 -0
- package/dist/clients/registry/resolve.js.map +1 -0
- package/dist/clients/registry/serpro.d.ts +57 -0
- package/dist/clients/registry/serpro.d.ts.map +1 -0
- package/dist/clients/registry/serpro.js +143 -0
- package/dist/clients/registry/serpro.js.map +1 -0
- package/dist/clients/registry/service.d.ts +59 -0
- package/dist/clients/registry/service.d.ts.map +1 -0
- package/dist/clients/registry/service.js +225 -0
- package/dist/clients/registry/service.js.map +1 -0
- package/dist/clients/registry/synthetic.d.ts +30 -0
- package/dist/clients/registry/synthetic.d.ts.map +1 -0
- package/dist/clients/registry/synthetic.js +161 -0
- package/dist/clients/registry/synthetic.js.map +1 -0
- package/dist/clients/repository.d.ts +4 -0
- package/dist/clients/repository.d.ts.map +1 -1
- package/dist/clients/repository.js +6 -2
- package/dist/clients/repository.js.map +1 -1
- package/dist/db/provision-e2e-identity.js +8 -3
- package/dist/db/provision-e2e-identity.js.map +1 -1
- package/dist/fiscal/collection/authorization.d.ts +152 -0
- package/dist/fiscal/collection/authorization.d.ts.map +1 -0
- package/dist/fiscal/collection/authorization.js +178 -0
- package/dist/fiscal/collection/authorization.js.map +1 -0
- package/dist/fiscal/collection/competence.d.ts +66 -0
- package/dist/fiscal/collection/competence.d.ts.map +1 -0
- package/dist/fiscal/collection/competence.js +90 -0
- package/dist/fiscal/collection/competence.js.map +1 -0
- package/dist/fiscal/collection/first-contact.d.ts +103 -0
- package/dist/fiscal/collection/first-contact.d.ts.map +1 -0
- package/dist/fiscal/collection/first-contact.js +201 -0
- package/dist/fiscal/collection/first-contact.js.map +1 -0
- package/dist/fiscal/collection/plan.d.ts +74 -0
- package/dist/fiscal/collection/plan.d.ts.map +1 -0
- package/dist/fiscal/collection/plan.js +119 -0
- package/dist/fiscal/collection/plan.js.map +1 -0
- package/dist/fiscal/coverage/model.d.ts +28 -0
- package/dist/fiscal/coverage/model.d.ts.map +1 -1
- package/dist/fiscal/coverage/model.js +11 -1
- package/dist/fiscal/coverage/model.js.map +1 -1
- package/dist/fiscal/credentials/inspect.d.ts +114 -0
- package/dist/fiscal/credentials/inspect.d.ts.map +1 -0
- package/dist/fiscal/credentials/inspect.js +178 -0
- package/dist/fiscal/credentials/inspect.js.map +1 -0
- package/dist/fiscal/credentials/material.js +2 -2
- package/dist/fiscal/credentials/material.js.map +1 -1
- package/dist/fiscal/credentials/onboarding.d.ts +119 -0
- package/dist/fiscal/credentials/onboarding.d.ts.map +1 -0
- package/dist/fiscal/credentials/onboarding.js +259 -0
- package/dist/fiscal/credentials/onboarding.js.map +1 -0
- package/dist/fiscal/credentials/readiness.d.ts +30 -3
- package/dist/fiscal/credentials/readiness.d.ts.map +1 -1
- package/dist/fiscal/credentials/readiness.js +29 -2
- package/dist/fiscal/credentials/readiness.js.map +1 -1
- package/dist/fiscal/enrollment.d.ts +91 -0
- package/dist/fiscal/enrollment.d.ts.map +1 -0
- package/dist/fiscal/enrollment.js +126 -0
- package/dist/fiscal/enrollment.js.map +1 -0
- package/dist/fiscal/transport/synthetic-tls.d.ts +19 -0
- package/dist/fiscal/transport/synthetic-tls.d.ts.map +1 -1
- package/dist/fiscal/transport/synthetic-tls.js +51 -0
- package/dist/fiscal/transport/synthetic-tls.js.map +1 -1
- package/dist/index.d.ts +32 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/legal/documents.d.ts.map +1 -1
- package/dist/legal/documents.js +64 -4
- package/dist/legal/documents.js.map +1 -1
- package/dist/legal/index.d.ts +1 -1
- package/dist/legal/index.d.ts.map +1 -1
- package/dist/legal/types.d.ts +77 -0
- package/dist/legal/types.d.ts.map +1 -1
- package/dist/legal/types.js.map +1 -1
- package/dist/platform/capabilities.d.ts.map +1 -1
- package/dist/platform/capabilities.js +29 -0
- package/dist/platform/capabilities.js.map +1 -1
- package/dist/rbac/catalog.d.ts.map +1 -1
- package/dist/rbac/catalog.js +78 -2
- package/dist/rbac/catalog.js.map +1 -1
- package/dist/subscription-billing/index.d.ts +3 -1
- package/dist/subscription-billing/index.d.ts.map +1 -1
- package/dist/subscription-billing/index.js +2 -1
- package/dist/subscription-billing/index.js.map +1 -1
- package/dist/subscription-billing/platform-exemption.d.ts +89 -0
- package/dist/subscription-billing/platform-exemption.d.ts.map +1 -0
- package/dist/subscription-billing/platform-exemption.js +113 -0
- package/dist/subscription-billing/platform-exemption.js.map +1 -0
- package/dist/subscription-billing/service.d.ts +53 -0
- package/dist/subscription-billing/service.d.ts.map +1 -1
- package/dist/subscription-billing/service.js +92 -1
- package/dist/subscription-billing/service.js.map +1 -1
- package/dist/vault/repository.d.ts +69 -1
- package/dist/vault/repository.d.ts.map +1 -1
- package/dist/vault/repository.js +88 -5
- package/dist/vault/repository.js.map +1 -1
- package/package.json +3 -2
- package/src/db/migrations/0060_fiscal_credential_vault_and_run_authorization.sql +321 -0
- package/src/db/migrations/0061_client_official_registry_provenance.sql +193 -0
- package/src/db/migrations/0062_first_contact_and_capability_truth.sql +119 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O que dá para saber sobre um certificado SEM falar com ninguém.
|
|
3
|
+
*
|
|
4
|
+
* ## Por que a validação local existe, e o que ela não é
|
|
5
|
+
*
|
|
6
|
+
* Um A1 recém-cadastrado tem duas perguntas em aberto, e elas têm custos
|
|
7
|
+
* opostos. "A senha abre o arquivo?" custa milissegundos e nenhuma consequência
|
|
8
|
+
* externa. "A Receita aceita este titular?" custa uma requisição autenticada em
|
|
9
|
+
* nome de um terceiro — e, se ela for feita fora de hora, custa ao CLIENTE um
|
|
10
|
+
* CNPJ bloqueado.
|
|
11
|
+
*
|
|
12
|
+
* Este módulo responde só a primeira família de perguntas. O produto inteiro
|
|
13
|
+
* depende de nunca confundir as duas: `verified_at` (1004) continua significando
|
|
14
|
+
* "o ambiente oficial já aceitou", e nada aqui o preenche.
|
|
15
|
+
*
|
|
16
|
+
* ## Por que o SecureContext, e não uma biblioteca de PKCS#12
|
|
17
|
+
*
|
|
18
|
+
* O Node não expõe leitor de PKCS#12 na API pública — `X509Certificate` lê PEM e
|
|
19
|
+
* DER, não `.pfx`. As saídas óbvias seriam uma dependência nova (superfície
|
|
20
|
+
* grande para uma tela de upload) ou chamar o binário `openssl` (que exige o
|
|
21
|
+
* binário na imagem, e cuja saída de erro cita caminho de arquivo e sujeito do
|
|
22
|
+
* certificado).
|
|
23
|
+
*
|
|
24
|
+
* A saída usada aqui é o leitor de PKCS#12 que já está no processo: o do OpenSSL
|
|
25
|
+
* que o próprio Node embute. `tls.createSecureContext({ pfx, passphrase })` o
|
|
26
|
+
* invoca e LANÇA na hora se a senha não abrir — esse é o teste de senha, e ele
|
|
27
|
+
* não custa nem um socket. O contexto resultante guarda o certificado carregado,
|
|
28
|
+
* e o handle nativo o devolve em DER.
|
|
29
|
+
*
|
|
30
|
+
* A propriedade que isso compra é um ORÁCULO CORRETO: "abre aqui" passa a
|
|
31
|
+
* significar exatamente "abre no handshake real", porque é o mesmo código de
|
|
32
|
+
* OpenSSL, com os mesmos parâmetros, que o transporte de mTLS usaria. Um parser
|
|
33
|
+
* de terceiro poderia aceitar um arquivo que o handshake recusa — e a diferença
|
|
34
|
+
* apareceria só contra o servidor da Receita.
|
|
35
|
+
*
|
|
36
|
+
* ## A primeira escrita usava um servidor TLS em loopback, e estava errada
|
|
37
|
+
*
|
|
38
|
+
* Ela subia um servidor com o `pfx` em `127.0.0.1`, conectava um cliente do
|
|
39
|
+
* mesmo processo e lia o certificado apresentado. Funcionava — e exigia
|
|
40
|
+
* `rejectUnauthorized: false` no cliente, porque um A1 é assinado por uma AC da
|
|
41
|
+
* ICP-Brasil que não está no truststore e o nome nunca seria `localhost`.
|
|
42
|
+
*
|
|
43
|
+
* O guarda `tls-no-bypass` reprovou, e com razão. A resposta certa não era
|
|
44
|
+
* abrir uma exceção nominal para este arquivo: um controle com uma exceção tem
|
|
45
|
+
* duas na semana seguinte. Era não precisar da exceção — o que este caminho
|
|
46
|
+
* consegue, com menos código e sem socket nenhum.
|
|
47
|
+
*
|
|
48
|
+
* Nada toca o disco e nada toca a rede: `pfx` é um `Buffer`, e tudo acontece
|
|
49
|
+
* dentro do processo.
|
|
50
|
+
*/
|
|
51
|
+
/** Classe da falha. CLASSE — a mensagem do OpenSSL cita sujeito e caminho. */
|
|
52
|
+
export type CertificateInspectionFailure = 'wrong_password' | 'malformed_material' | 'unsupported_material' | 'inspection_failed';
|
|
53
|
+
export interface CertificateFacts {
|
|
54
|
+
/** sha256 do DER do certificado apresentado, minúsculo e sem dois-pontos. */
|
|
55
|
+
fingerprintSha256: string;
|
|
56
|
+
subjectCommonName: string | null;
|
|
57
|
+
issuerCommonName: string | null;
|
|
58
|
+
/** Últimos 8 caracteres do serial. O serial inteiro identifica sem acrescentar. */
|
|
59
|
+
serialTail: string | null;
|
|
60
|
+
validFrom: string;
|
|
61
|
+
validTo: string;
|
|
62
|
+
/** Já venceu, medido contra o relógio de quem inspeciona. */
|
|
63
|
+
expired: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* CNPJ/CPF lido DO MATERIAL — nunca o digitado no cadastro.
|
|
66
|
+
* `null` quando o certificado não o carrega em forma reconhecível.
|
|
67
|
+
*/
|
|
68
|
+
document: string | null;
|
|
69
|
+
}
|
|
70
|
+
export type CertificateInspection = {
|
|
71
|
+
status: 'ok';
|
|
72
|
+
facts: CertificateFacts;
|
|
73
|
+
} | {
|
|
74
|
+
status: 'failed';
|
|
75
|
+
reason: CertificateInspectionFailure;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Inspeciona o material, sem rede e sem disco.
|
|
79
|
+
*
|
|
80
|
+
* NUNCA lança: toda falha vira uma classe. Quem chama é uma rota de upload, e
|
|
81
|
+
* uma exceção com texto de OpenSSL atravessando até o handler é exatamente como
|
|
82
|
+
* o sujeito de um certificado acaba num log.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* Inspeciona o material, sem rede e sem disco.
|
|
86
|
+
*
|
|
87
|
+
* NUNCA lança: toda falha vira uma classe. Quem chama é uma rota de upload, e
|
|
88
|
+
* uma exceção com texto de OpenSSL atravessando até o handler é exatamente como
|
|
89
|
+
* o sujeito de um certificado acaba num log.
|
|
90
|
+
*
|
|
91
|
+
* `async` sem `await` é deliberado: o contrato é assíncrono porque a primeira
|
|
92
|
+
* implementação precisava de um handshake, e porque uma leitura de material
|
|
93
|
+
* criptográfico é o tipo de operação que pode voltar a precisar. Mudar a
|
|
94
|
+
* assinatura para síncrona obrigaria todo chamador a mudar junto, por nenhum
|
|
95
|
+
* ganho.
|
|
96
|
+
*/
|
|
97
|
+
export declare function inspectPkcs12(pfx: Buffer, passphrase: string | undefined): Promise<CertificateInspection>;
|
|
98
|
+
/**
|
|
99
|
+
* O certificado é DESTE cliente?
|
|
100
|
+
*
|
|
101
|
+
* Três respostas, e a do meio é a que impede o vínculo silencioso:
|
|
102
|
+
*
|
|
103
|
+
* `match` os dois documentos existem e são o mesmo.
|
|
104
|
+
* `mismatch` os dois existem e são diferentes. Exige assinatura humana
|
|
105
|
+
* (a restrição está na 0060, não aqui).
|
|
106
|
+
* `indeterminate` falta um dos dois. Não é aprovação.
|
|
107
|
+
*
|
|
108
|
+
* A comparação é por DÍGITOS: o cadastro do produto guarda o documento sem
|
|
109
|
+
* máscara, o certificado o traz sem máscara, e uma comparação textual passaria a
|
|
110
|
+
* reprovar no dia em que alguém colasse um CNPJ pontuado num dos dois lados.
|
|
111
|
+
*/
|
|
112
|
+
export type IdentityMatch = 'match' | 'mismatch' | 'indeterminate';
|
|
113
|
+
export declare function matchIdentity(certificateDocument: string | null, clientDocument: string | null | undefined): IdentityMatch;
|
|
114
|
+
//# sourceMappingURL=inspect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../../src/fiscal/credentials/inspect.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,4BAA4B,GACtC,gBAAgB,GAAG,oBAAoB,GAAG,sBAAsB,GAAG,mBAAmB,CAAC;AAEzF,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mFAAmF;IACnF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,GACzC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,4BAA4B,CAAA;CAAE,CAAC;AAe/D;;;;;;GAMG;AACH;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAAC,qBAAqB,CAAC,CAyBhC;AA+GD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,eAAe,CAAC;AAEnE,wBAAgB,aAAa,CAC3B,mBAAmB,EAAE,MAAM,GAAG,IAAI,EAClC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACxC,aAAa,CAKf"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createSecureContext } from 'node:tls';
|
|
2
|
+
import { X509Certificate } from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* OID 2.16.76.1.3.3 — o CNPJ da pessoa jurídica no `otherName` do
|
|
5
|
+
* `subjectAltName`, na forma que a ICP-Brasil padronizou (DOC-ICP-04).
|
|
6
|
+
*
|
|
7
|
+
* Em DER: tag `06`, comprimento `05`, e os componentes `2.16` colapsados em
|
|
8
|
+
* `0x60`. Procurar a sequência de bytes é mais confiável que ler
|
|
9
|
+
* `x509.subjectAltName`, que em várias versões do Node devolve `undefined` ou
|
|
10
|
+
* `othername:<unsupported>` justamente para os `otherName` do ICP-Brasil.
|
|
11
|
+
*/
|
|
12
|
+
const OID_CNPJ_ICP_BRASIL = Buffer.from([0x06, 0x05, 0x60, 0x4c, 0x01, 0x03, 0x03]);
|
|
13
|
+
/** Janela de busca depois do OID: o valor vem logo em seguida, dentro do mesmo `otherName`. */
|
|
14
|
+
const OID_VALUE_WINDOW = 48;
|
|
15
|
+
/**
|
|
16
|
+
* Inspeciona o material, sem rede e sem disco.
|
|
17
|
+
*
|
|
18
|
+
* NUNCA lança: toda falha vira uma classe. Quem chama é uma rota de upload, e
|
|
19
|
+
* uma exceção com texto de OpenSSL atravessando até o handler é exatamente como
|
|
20
|
+
* o sujeito de um certificado acaba num log.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Inspeciona o material, sem rede e sem disco.
|
|
24
|
+
*
|
|
25
|
+
* NUNCA lança: toda falha vira uma classe. Quem chama é uma rota de upload, e
|
|
26
|
+
* uma exceção com texto de OpenSSL atravessando até o handler é exatamente como
|
|
27
|
+
* o sujeito de um certificado acaba num log.
|
|
28
|
+
*
|
|
29
|
+
* `async` sem `await` é deliberado: o contrato é assíncrono porque a primeira
|
|
30
|
+
* implementação precisava de um handshake, e porque uma leitura de material
|
|
31
|
+
* criptográfico é o tipo de operação que pode voltar a precisar. Mudar a
|
|
32
|
+
* assinatura para síncrona obrigaria todo chamador a mudar junto, por nenhum
|
|
33
|
+
* ganho.
|
|
34
|
+
*/
|
|
35
|
+
export async function inspectPkcs12(pfx, passphrase) {
|
|
36
|
+
if (pfx.byteLength === 0)
|
|
37
|
+
return { status: 'failed', reason: 'malformed_material' };
|
|
38
|
+
// Primeiro portão: o contexto abre? Isto é a checagem de senha, e ela lança
|
|
39
|
+
// aqui — antes de existir qualquer outra coisa.
|
|
40
|
+
let context;
|
|
41
|
+
try {
|
|
42
|
+
context = createSecureContext({ pfx, passphrase, minVersion: 'TLSv1.2' });
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return { status: 'failed', reason: classify(err) };
|
|
46
|
+
}
|
|
47
|
+
let der;
|
|
48
|
+
try {
|
|
49
|
+
der = certificateDerFrom(context);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
return { status: 'failed', reason: classify(err) };
|
|
53
|
+
}
|
|
54
|
+
if (!der || der.byteLength === 0)
|
|
55
|
+
return { status: 'failed', reason: 'inspection_failed' };
|
|
56
|
+
try {
|
|
57
|
+
return { status: 'ok', facts: factsFrom(new X509Certificate(der)) };
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return { status: 'failed', reason: classify(err) };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* O DER do certificado carregado no contexto.
|
|
65
|
+
*
|
|
66
|
+
* `context.getCertificate()` é o handle NATIVO do `SecureContext`, não a API
|
|
67
|
+
* pública documentada do Node. Por isso o acesso é defensivo em vez de direto: o
|
|
68
|
+
* dia em que uma versão do Node o renomear, o produto deve responder
|
|
69
|
+
* `inspection_failed` — e a suíte, que lê um PKCS#12 sintético de verdade, vai
|
|
70
|
+
* ficar vermelha na atualização, que é quando alguém pode agir.
|
|
71
|
+
*
|
|
72
|
+
* A alternativa — depender do binário `openssl` na imagem — troca este risco por
|
|
73
|
+
* um pior: uma falha que só aparece em produção, num contêiner onde ninguém
|
|
74
|
+
* conferiu o que está instalado.
|
|
75
|
+
*/
|
|
76
|
+
function certificateDerFrom(context) {
|
|
77
|
+
const handle = context.context;
|
|
78
|
+
const der = handle?.getCertificate?.();
|
|
79
|
+
return Buffer.isBuffer(der) ? der : null;
|
|
80
|
+
}
|
|
81
|
+
function factsFrom(cert) {
|
|
82
|
+
const validTo = new Date(cert.validTo);
|
|
83
|
+
return {
|
|
84
|
+
fingerprintSha256: cert.fingerprint256.replace(/:/g, '').toLowerCase(),
|
|
85
|
+
subjectCommonName: commonName(cert.subject),
|
|
86
|
+
issuerCommonName: commonName(cert.issuer),
|
|
87
|
+
serialTail: cert.serialNumber ? cert.serialNumber.slice(-8) : null,
|
|
88
|
+
validFrom: new Date(cert.validFrom).toISOString(),
|
|
89
|
+
validTo: validTo.toISOString(),
|
|
90
|
+
expired: validTo.getTime() <= Date.now(),
|
|
91
|
+
document: documentFrom(cert),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** `CN=` do DN multilinha que o Node devolve. `null` quando não há. */
|
|
95
|
+
function commonName(dn) {
|
|
96
|
+
if (!dn)
|
|
97
|
+
return null;
|
|
98
|
+
for (const line of dn.split('\n')) {
|
|
99
|
+
const m = /^\s*CN\s*=\s*(.+?)\s*$/.exec(line);
|
|
100
|
+
if (m?.[1])
|
|
101
|
+
return m[1];
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* O documento do titular, do material.
|
|
107
|
+
*
|
|
108
|
+
* Duas fontes, nesta ordem — e a ordem importa:
|
|
109
|
+
*
|
|
110
|
+
* 1. O `otherName` do OID da ICP-Brasil, varrido no DER. É onde a norma manda
|
|
111
|
+
* escrever, é o que a Receita valida, e é o único lugar que não depende de
|
|
112
|
+
* convenção de quem emitiu.
|
|
113
|
+
* 2. O sufixo `:<14 dígitos>` do `CN`, convenção quase universal dos e-CNPJ.
|
|
114
|
+
*
|
|
115
|
+
* Não há terceira fonte. Procurar "14 dígitos em qualquer lugar do sujeito"
|
|
116
|
+
* casaria com um CEP colado a um telefone e devolveria um CNPJ inventado — que
|
|
117
|
+
* é pior que `null`, porque `null` deixa a comparação `indeterminate` e essa é
|
|
118
|
+
* a resposta honesta.
|
|
119
|
+
*/
|
|
120
|
+
function documentFrom(cert) {
|
|
121
|
+
const fromOid = documentFromDer(cert.raw);
|
|
122
|
+
if (fromOid)
|
|
123
|
+
return fromOid;
|
|
124
|
+
const cn = commonName(cert.subject);
|
|
125
|
+
const m = cn ? /:(\d{11}|\d{14})\s*$/.exec(cn) : null;
|
|
126
|
+
return m?.[1] ?? null;
|
|
127
|
+
}
|
|
128
|
+
function documentFromDer(der) {
|
|
129
|
+
let from = 0;
|
|
130
|
+
for (;;) {
|
|
131
|
+
const at = der.indexOf(OID_CNPJ_ICP_BRASIL, from);
|
|
132
|
+
if (at < 0)
|
|
133
|
+
return null;
|
|
134
|
+
const window = der
|
|
135
|
+
.subarray(at + OID_CNPJ_ICP_BRASIL.length, at + OID_CNPJ_ICP_BRASIL.length + OID_VALUE_WINDOW)
|
|
136
|
+
.toString('latin1');
|
|
137
|
+
const digits = /(\d{14})/.exec(window);
|
|
138
|
+
if (digits?.[1])
|
|
139
|
+
return digits[1];
|
|
140
|
+
from = at + 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Traduz a falha do OpenSSL em classe, olhando SÓ os fragmentos conhecidos.
|
|
145
|
+
*
|
|
146
|
+
* A mensagem inteira nunca é propagada nem registrada. `mac verify failure` é a
|
|
147
|
+
* resposta do PKCS#12 a uma senha que não abre o MAC — é o único caso em que se
|
|
148
|
+
* pode dizer ao operador "a senha está errada" com alguma confiança.
|
|
149
|
+
*/
|
|
150
|
+
function classify(err) {
|
|
151
|
+
const raw = err instanceof Error ? err.message : '';
|
|
152
|
+
const message = raw.toLowerCase();
|
|
153
|
+
if (message.includes('mac verify failure') || message.includes('invalid password')) {
|
|
154
|
+
return 'wrong_password';
|
|
155
|
+
}
|
|
156
|
+
if (message.includes('not enough data') ||
|
|
157
|
+
message.includes('wrong tag') ||
|
|
158
|
+
message.includes('header too long') ||
|
|
159
|
+
message.includes('nested asn1') ||
|
|
160
|
+
message.includes('asn1')) {
|
|
161
|
+
return 'malformed_material';
|
|
162
|
+
}
|
|
163
|
+
if (message.includes('unsupported') || message.includes('unknown cipher')) {
|
|
164
|
+
return 'unsupported_material';
|
|
165
|
+
}
|
|
166
|
+
return 'inspection_failed';
|
|
167
|
+
}
|
|
168
|
+
export function matchIdentity(certificateDocument, clientDocument) {
|
|
169
|
+
const a = digitsOnly(certificateDocument);
|
|
170
|
+
const b = digitsOnly(clientDocument);
|
|
171
|
+
if (!a || !b)
|
|
172
|
+
return 'indeterminate';
|
|
173
|
+
return a === b ? 'match' : 'mismatch';
|
|
174
|
+
}
|
|
175
|
+
function digitsOnly(v) {
|
|
176
|
+
return (v ?? '').replace(/\D/g, '');
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/inspect.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { revealWriteOnlySecretForProcess } from '../../vault/repository.js';
|
|
3
3
|
import { FiscalSecretBoundaryError, assertFiscalSecretAccess, } from '../sync/secret-boundary.js';
|
|
4
4
|
/**
|
|
5
5
|
* Constrói o material a partir do envelope decifrado.
|
|
@@ -151,7 +151,7 @@ export async function loadFiscalTlsMaterial(tx, keyring, tenantId, input) {
|
|
|
151
151
|
return { status: 'unavailable', reason: 'no_credential' };
|
|
152
152
|
let secret;
|
|
153
153
|
try {
|
|
154
|
-
({ secret } = await
|
|
154
|
+
({ secret } = await revealWriteOnlySecretForProcess(tx, keyring, tenantId, pointer));
|
|
155
155
|
}
|
|
156
156
|
catch {
|
|
157
157
|
// Ponteiro pendurado, chave de cifra ausente, linha invisível sob RLS: os
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"material.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/material.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"material.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/material.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Tx } from '../../db/client.js';
|
|
2
|
+
import type { Keyring } from '../../vault/crypto.js';
|
|
3
|
+
import { type IdentityMatch } from './inspect.js';
|
|
4
|
+
/**
|
|
5
|
+
* O caminho de entrada do certificado digital — o único.
|
|
6
|
+
*
|
|
7
|
+
* ## O que esta frente entrega, e por que ela existe
|
|
8
|
+
*
|
|
9
|
+
* Até aqui o produto sabia USAR um certificado (`material.ts`) e sabia dizer se
|
|
10
|
+
* ele estava pronto (`readiness.ts`). O que não existia era como ele ENTRA:
|
|
11
|
+
* `POST /v1/compliance/certificates` aceitava um `vaultCredentialId` já
|
|
12
|
+
* existente, e nada no produto criava esse ponteiro. Na prática, a única forma
|
|
13
|
+
* de cadastrar um A1 era alguém colar o base64 do `.pfx` — e a senha — na tela
|
|
14
|
+
* genérica do cofre, ou digitá-los num terminal.
|
|
15
|
+
*
|
|
16
|
+
* Uma senha de certificado digitada num terminal é uma senha no histórico do
|
|
17
|
+
* shell, no scrollback e, se o comando falhar, na mensagem de erro. O objetivo
|
|
18
|
+
* declarado desta fase é que ela nunca precise sair do navegador.
|
|
19
|
+
*
|
|
20
|
+
* ## Três recusas que acontecem ANTES de qualquer escrita
|
|
21
|
+
*
|
|
22
|
+
* **Senha que não abre.** Sem isso, o cadastro fica `active` e o defeito só
|
|
23
|
+
* aparece no dia em que o worker apresentar o material à Receita.
|
|
24
|
+
*
|
|
25
|
+
* **Certificado vencido.** Guardar material vencido é assumir o risco de
|
|
26
|
+
* custodiar uma chave privada sem nenhum uso possível em troca.
|
|
27
|
+
*
|
|
28
|
+
* **Certificado de outra empresa.** É o caso que a arquitetura precisa impedir
|
|
29
|
+
* em silêncio total: cliente A com o certificado da empresa B consulta a Receita
|
|
30
|
+
* em nome de quem não autorizou. Aqui ele exige assinatura — nome e motivo —, e
|
|
31
|
+
* a 0060 repete a exigência como CHECK, para o caminho que ainda não foi escrito.
|
|
32
|
+
*
|
|
33
|
+
* ## O que NUNCA sai daqui
|
|
34
|
+
*
|
|
35
|
+
* Nenhum retorno desta função carrega material nem senha. O que volta é
|
|
36
|
+
* metadata: sujeito, emissor, validade, impressão digital e estado. É essa
|
|
37
|
+
* estrutura que a tela mostra, e é por isso que ela não tem como mostrar mais.
|
|
38
|
+
*/
|
|
39
|
+
export type CertificateRejection = 'wrong_password' | 'malformed_material' | 'unsupported_material' | 'inspection_failed' | 'expired' | 'identity_mismatch' | 'client_not_found';
|
|
40
|
+
/** Metadata segura para tela, trilha e resposta de API. Nunca há material aqui. */
|
|
41
|
+
export interface FiscalCertificateSummary {
|
|
42
|
+
id: string;
|
|
43
|
+
clientId: string;
|
|
44
|
+
certType: 'A1' | 'A3';
|
|
45
|
+
status: string;
|
|
46
|
+
subjectCommonName: string | null;
|
|
47
|
+
issuerCommonName: string | null;
|
|
48
|
+
serialTail: string | null;
|
|
49
|
+
/** sha256 do certificado apresentado, truncado — identifica sem revelar. */
|
|
50
|
+
fingerprint: string;
|
|
51
|
+
certificateDocument: string | null;
|
|
52
|
+
identityMatch: IdentityMatch;
|
|
53
|
+
identityOverrideReason: string | null;
|
|
54
|
+
issuedAt: string | null;
|
|
55
|
+
expiresOn: string;
|
|
56
|
+
uploadedAt: string | null;
|
|
57
|
+
/** Já exercido contra o ambiente oficial? Preenchido por execução, nunca aqui. */
|
|
58
|
+
verifiedAt: string | null;
|
|
59
|
+
/** `true` quando esta chamada substituiu material que já existia. */
|
|
60
|
+
replaced: boolean;
|
|
61
|
+
}
|
|
62
|
+
export type CertificateOnboardingResult = {
|
|
63
|
+
status: 'stored';
|
|
64
|
+
certificate: FiscalCertificateSummary;
|
|
65
|
+
} | {
|
|
66
|
+
status: 'rejected';
|
|
67
|
+
reason: CertificateRejection;
|
|
68
|
+
identityMatch?: IdentityMatch;
|
|
69
|
+
};
|
|
70
|
+
export interface CertificateOnboardingInput {
|
|
71
|
+
clientId: string;
|
|
72
|
+
certType?: 'A1' | 'A3';
|
|
73
|
+
/** O `.pfx`/`.p12` em bytes. Nunca um caminho de arquivo: não há arquivo. */
|
|
74
|
+
material: Buffer;
|
|
75
|
+
passphrase?: string;
|
|
76
|
+
actorId: string;
|
|
77
|
+
/**
|
|
78
|
+
* A assinatura humana que destrava um titular divergente. Sem ela, divergência
|
|
79
|
+
* é recusa — e é assim que "cliente A + certificado B" deixa de ser possível
|
|
80
|
+
* por distração.
|
|
81
|
+
*/
|
|
82
|
+
identityOverrideReason?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Nome determinístico da credencial no cofre.
|
|
86
|
+
*
|
|
87
|
+
* Determinístico porque a substituição precisa ENCONTRAR o que já existe: o
|
|
88
|
+
* índice único do cofre é `(tenant, cliente, lower(nome))`, e um nome novo a
|
|
89
|
+
* cada upload deixaria uma trilha de chaves privadas órfãs, todas ainda
|
|
90
|
+
* decifráveis, nenhuma referenciada.
|
|
91
|
+
*/
|
|
92
|
+
export declare function fiscalCertificateCredentialName(clientId: string, certType: 'A1' | 'A3'): string;
|
|
93
|
+
export declare function onboardFiscalCertificate(tx: Tx, keyring: Keyring, tenantId: string, input: CertificateOnboardingInput): Promise<CertificateOnboardingResult>;
|
|
94
|
+
/** Os certificados de um cliente, só metadata. É o que a tela lê. */
|
|
95
|
+
export declare function listFiscalCertificates(tx: Tx, clientId: string): Promise<FiscalCertificateSummary[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Revoga o uso do certificado DENTRO deste produto.
|
|
98
|
+
*
|
|
99
|
+
* Não toca a ICP-Brasil, e é por isso que a função se chama assim. Revogar de
|
|
100
|
+
* verdade um certificado é um ato junto à Autoridade Certificadora que o emitiu;
|
|
101
|
+
* apagar a linha daqui apenas garante que este produto pare de apresentá-lo. A
|
|
102
|
+
* tela repete a distinção, porque um operador que acredite ter revogado o
|
|
103
|
+
* certificado do cliente não vai à AC — e o certificado segue válido no mundo.
|
|
104
|
+
*
|
|
105
|
+
* O material sai do cofre no mesmo movimento (`deleted_at`), de modo que a
|
|
106
|
+
* revogação não deixa chave privada decifrável para trás.
|
|
107
|
+
*/
|
|
108
|
+
export declare function revokeFiscalCertificate(tx: Tx, clientId: string, certificateId: string): Promise<{
|
|
109
|
+
revoked: boolean;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* Impressão digital do material recebido, antes de qualquer decisão.
|
|
113
|
+
*
|
|
114
|
+
* Serve à trilha do upload recusado: registrar "tentaram subir algo" sem
|
|
115
|
+
* registrar o quê. É o mesmo cálculo de `material.ts`, e é seguro por
|
|
116
|
+
* construção — um hash não volta a ser um certificado.
|
|
117
|
+
*/
|
|
118
|
+
export declare function materialFingerprint(material: Buffer): string;
|
|
119
|
+
//# sourceMappingURL=onboarding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../../src/fiscal/credentials/onboarding.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GAChB,oBAAoB,GACpB,sBAAsB,GACtB,mBAAmB,GACnB,SAAS,GACT,mBAAmB,GACnB,kBAAkB,CAAC;AAEvB,mFAAmF;AACnF,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kFAAkF;IAClF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,2BAA2B,GACnC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,WAAW,EAAE,wBAAwB,CAAA;CAAE,GAC3D;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAC;IAAC,aAAa,CAAC,EAAE,aAAa,CAAA;CAAE,CAAC;AAExF,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAOD;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAE/F;AAED,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,2BAA2B,CAAC,CA0HtC;AAiHD,qEAAqE;AACrE,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAQrC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAgB/B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|