@kya-brasil/core 0.8.0 → 0.9.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/README.md CHANGED
@@ -87,6 +87,22 @@ const sig = await a3.sign(readSigningLink(link).text); // the text from a
87
87
  `openToken({ library, pin })` in `@kya-brasil/agent-tools` (and the `kya-sign --pkcs11` command) already wires
88
88
  `C_Sign` into `ecnpjSigner`.
89
89
 
90
+ ### Cloud certificates (PSC)
91
+
92
+ `CloudSignatureProvider` is the interface for an e-CNPJ held by an ICP-Brasil PSC: `authorizationUrl` (OAuth with
93
+ PKCE), `exchangeCode` returning a session with `certificates()` and `signHash(sha256)`. No PSC is implemented here
94
+ (each has its own contract); implement it for yours from
95
+ [examples/cloud-provider](https://github.com/celfons/kya-brasil/tree/main/examples/cloud-provider) and then:
96
+
97
+ ```ts
98
+ import { cloudSigner, pkcePair, newState } from "@kya-brasil/core";
99
+ const { verifier, challenge } = await pkcePair();
100
+ const url = await provider.authorizationUrl({ redirectUri, state: newState(), codeChallenge: challenge });
101
+ // … the person approves, your redirect URI gets ?code=…&state=…
102
+ const signer = await cloudSigner(await provider.exchangeCode({ code, redirectUri, codeVerifier: verifier }));
103
+ const ecnpj_signature = await signer.sign(challengeText);
104
+ ```
105
+
90
106
  ## Run your own issuer
91
107
 
92
108
  ```ts
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Cloud e-CNPJ (D-28): the certificate lives at an ICP-Brasil PSC (prestador de serviço de confiança) and signs
3
+ * after the holder approves, usually on the phone. Each PSC has its own contract, registration and API details,
4
+ * so this project ships the interface only: whoever has an agreement with a PSC implements CloudSignatureProvider
5
+ * for it and plugs it into the three entry points (this library, `kya-sign --cloud`, the /sign page of a
6
+ * self-hosted issuer). The KYA side (PKCE, single-use state, the CMS, what is signed) is done here.
7
+ *
8
+ * Typical PSC flow (OAuth 2.0 authorization code with PKCE): send the person to authorizationUrl, get a code at
9
+ * the redirect URI, exchange it for a session, read the certificate, sign hashes. Ask the PSC for the narrowest
10
+ * session it offers (one signature, minutes of lifetime).
11
+ */
12
+ import { type ECnpjSigner } from "./ecnpj-sign.js";
13
+ /** An authorized session at the PSC: the certificate and a way to sign one SHA-256 hash. */
14
+ export interface CloudSession {
15
+ /** Signer certificate first, then the chain if the PSC returns it (DER). */
16
+ certificates(): Promise<Uint8Array[]>;
17
+ /**
18
+ * RSA PKCS#1 v1.5 signature over a SHA-256 hash (32 bytes) computed by the caller: what PSC APIs usually
19
+ * call a "raw" or "PKCS1" signature of a hash. Return the signature bytes.
20
+ */
21
+ signHash(sha256: Uint8Array): Promise<Uint8Array>;
22
+ /** Revoke or forget the session at the PSC, when it supports it. */
23
+ close?(): Promise<void>;
24
+ }
25
+ /** One PSC. Implement it with your own registration (client id/secret, redirect URIs) and the PSC's API. */
26
+ export interface CloudSignatureProvider {
27
+ /** Short id used in URLs and on the command line, e.g. "my-psc". Lowercase letters, digits, "-". */
28
+ readonly id: string;
29
+ /** Name shown to the person, e.g. "BirdID", "VIDaaS". */
30
+ readonly name: string;
31
+ /**
32
+ * Where to send the person to log in and approve. Must carry `state` and the PKCE `codeChallenge` (S256) so the
33
+ * code comes back to `redirectUri` bound to this request. `loginHint` may carry a CNPJ/CPF when the PSC accepts it.
34
+ */
35
+ authorizationUrl(p: {
36
+ redirectUri: string;
37
+ state: string;
38
+ codeChallenge: string;
39
+ loginHint?: string;
40
+ }): string | Promise<string>;
41
+ /** Exchanges the code for a session (server or CLI side: this is where a client secret would be used). */
42
+ exchangeCode(p: {
43
+ code: string;
44
+ redirectUri: string;
45
+ codeVerifier: string;
46
+ }): Promise<CloudSession>;
47
+ }
48
+ /** PKCE (RFC 7636, S256): a random verifier kept by us and the challenge sent to the PSC. */
49
+ export declare function pkcePair(): Promise<{
50
+ verifier: string;
51
+ challenge: string;
52
+ }>;
53
+ /** A random, single-use OAuth state value. */
54
+ export declare const newState: () => string;
55
+ /** Valid provider ids: what can safely go in a URL path or a CLI flag. */
56
+ export declare const isProviderId: (id: string) => boolean;
57
+ /**
58
+ * An e-CNPJ signer over a PSC session: the CMS is built here, the PSC only signs the SHA-256 of the signed
59
+ * attributes. Same interface as openPfx (A1) and openToken (A3), so everything downstream is unchanged.
60
+ */
61
+ export declare function cloudSigner(session: CloudSession): Promise<ECnpjSigner>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Cloud e-CNPJ (D-28): the certificate lives at an ICP-Brasil PSC (prestador de serviço de confiança) and signs
3
+ * after the holder approves, usually on the phone. Each PSC has its own contract, registration and API details,
4
+ * so this project ships the interface only: whoever has an agreement with a PSC implements CloudSignatureProvider
5
+ * for it and plugs it into the three entry points (this library, `kya-sign --cloud`, the /sign page of a
6
+ * self-hosted issuer). The KYA side (PKCE, single-use state, the CMS, what is signed) is done here.
7
+ *
8
+ * Typical PSC flow (OAuth 2.0 authorization code with PKCE): send the person to authorizationUrl, get a code at
9
+ * the redirect URI, exchange it for a session, read the certificate, sign hashes. Ask the PSC for the narrowest
10
+ * session it offers (one signature, minutes of lifetime).
11
+ */
12
+ import { ecnpjSigner } from "./ecnpj-sign.js";
13
+ import { KyaError } from "./types.js";
14
+ const toArrayBuffer = (u) => u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength);
15
+ const b64url = (u) => {
16
+ let s = "";
17
+ for (const b of u)
18
+ s += String.fromCharCode(b);
19
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
20
+ };
21
+ /** PKCE (RFC 7636, S256): a random verifier kept by us and the challenge sent to the PSC. */
22
+ export async function pkcePair() {
23
+ const verifier = b64url(crypto.getRandomValues(new Uint8Array(32)));
24
+ const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
25
+ return { verifier, challenge };
26
+ }
27
+ /** A random, single-use OAuth state value. */
28
+ export const newState = () => b64url(crypto.getRandomValues(new Uint8Array(24)));
29
+ /** Valid provider ids: what can safely go in a URL path or a CLI flag. */
30
+ export const isProviderId = (id) => /^[a-z0-9][a-z0-9-]{0,31}$/.test(id);
31
+ /**
32
+ * An e-CNPJ signer over a PSC session: the CMS is built here, the PSC only signs the SHA-256 of the signed
33
+ * attributes. Same interface as openPfx (A1) and openToken (A3), so everything downstream is unchanged.
34
+ */
35
+ export async function cloudSigner(session) {
36
+ const certificates = await session.certificates();
37
+ if (!certificates.length)
38
+ throw new KyaError("CERTIFICATE_INVALID", "the cloud provider returned no certificate");
39
+ return ecnpjSigner({
40
+ certificates,
41
+ sign: async (data) => {
42
+ const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", toArrayBuffer(data)));
43
+ const sig = await session.signHash(hash);
44
+ if (!sig?.length)
45
+ throw new KyaError("UPSTREAM_ERROR", "the cloud provider returned an empty signature");
46
+ return sig;
47
+ }
48
+ });
49
+ }
package/dist/index.d.ts CHANGED
@@ -8,3 +8,4 @@ export * from "./icp-brasil.js";
8
8
  export * from "./revocation.js";
9
9
  export * from "./erc8004.js";
10
10
  export * from "./ecnpj-sign.js";
11
+ export * from "./cloud-sign.js";
package/dist/index.js CHANGED
@@ -8,3 +8,4 @@ export * from "./icp-brasil.js";
8
8
  export * from "./revocation.js";
9
9
  export * from "./erc8004.js";
10
10
  export * from "./ecnpj-sign.js";
11
+ export * from "./cloud-sign.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kya-brasil/core",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "KYA Brasil as a library: verify and issue know-your-agent credentials for Brazilian companies without any hosted service. e-CNPJ (ICP-Brasil) CMS signature and chain validation, CRL revocation, CNPJ, agent key challenges (EVM, Ed25519 / Web Bot Auth, ERC-8004), mandate schema and evaluation (including payment instruments for card networks and acquirers), credential build, Ed25519 signing and verification, and e-CNPJ signing for the holder (A1 .pfx, or any token/HSM/cloud signer through a raw-sign callback). Pure functions: storage and network are yours.",
5
5
  "license": "MIT",
6
6
  "type": "module",