PageSourceSearch

https://atendimentonet.cm-cinfaes.pt/atendimentonet/scripts/loginCrypto.js

js cm-cinfaes.pt collected 2026-10-02 08:16:16 UTC 7,062 bytes, 210 lines download raw bytes

1/**
2 * Cifra híbrida (RSA-OAEP + AES-GCM) para as passwords não aparecerem em
3 * claro no corpo do pedido HTTP (visível no separador "Rede" das DevTools,
4 * em proxies intermédios, em logs, etc.).
5 *
6 * Usa a biblioteca node-forge (criptografia pura em JavaScript) em vez da
7 * Web Crypto API (`window.crypto.subtle`), porque esta última só funciona
8 * em "contextos seguros" (HTTPS, ou localhost) — e este ficheiro pode
9 * correr em HTTP puro, inclusive por IP de rede local (ex. 192.168.x.x),
10 * onde `crypto.subtle` fica indisponível.
11 *
12 * Requisitos:
13 *  - Carregar o node-forge ANTES deste ficheiro, para existir o global
14 *    `forge`. Exemplo via CDN:
15 *      <script src="https://cdn.jsdelivr.net/npm/node-forge@1/dist/forge.min.js"></script>
16 *      <script src="loginCrypto.js"></script>
17 *  - O backend tem de expor GET /auth/publicKey devolvendo a chave pública
18 *    RSA em Base64, formato SPKI/X.509 DER, e fazer a decifragem
19 *    equivalente do lado do servidor (RSA-OAEP SHA-256 + AES-256-GCM,
20 *    concatenando ciphertext+tag tal como o Cipher do Java produz).
21 *
22 * Protocolo:
23 *  1. Pede-se ao backend a chave pública RSA (GET /auth/publicKey).
24 *  2. Gera-se uma chave AES-256-GCM efémera, só para esta cifragem.
25 *  3. Cifra-se a password com essa chave AES (o resultado inclui a tag de
26 *     autenticação de 16 bytes no fim).
27 *  4. Cifra-se a própria chave AES com a chave pública RSA (RSA-OAEP,
28 *     SHA-256).
29 *  5. Devolve-se { encryptedPassword, encryptedKey, iv }, tudo em Base64,
30 *     prontos para enviar ao backend.
31 *
32 * Uso:
33 *   LoginCrypto.encryptPassword(password).then(function (result) {
34 *     // result.encryptedPassword, result.encryptedKey, result.iv
35 *   });
36 *
37 * Nota: isto é uma camada de defesa em profundidade — não substitui o
38 * HTTPS como proteção do canal de transporte.
39 */
40
41
42
43(function (global) {
44  'use strict';
45
46  if (typeof global.forge === 'undefined') {
47    throw new Error(
48      'LoginCrypto: a biblioteca node-forge não foi encontrada. Carregue-a (ex: via CDN) antes deste ficheiro.'
49    );
50  }
51  var forge = global.forge;
52
53  // Ajustar caso o endpoint da chave pública não esteja na mesma origem
54  // (ex: 'https://outra-api.exemplo.pt/auth/publicKey').
55  //var PUBLIC_KEY_ENDPOINT = '/web/services/api/webusers/publicKey';
56  var PUBLIC_KEY_ENDPOINT = 'api/webusers/publicKey';
57  var cachedPublicKeyPromise = null;   
58  
59var baseUrlPromises = {};
60
61 window.handleBaseUrlLoadedCompany = function() {
62    handleBaseUrlLoaded(true);
63};
64
65window.handleBaseUrlLoadedPerson = function() {
66    handleBaseUrlLoaded(false);
67};
68
69function extractPath(baseUrl) {
70  var url = new URL(baseUrl);
71  return url;
72}
73
74function getContext(urlTexto) {
75  try {
76    // Cria um objeto URL a partir da string
77    var url = new URL(urlTexto);
78    
79    // Extrai o pathname (ex: "/atendimentonet-tq.ano.pt/atendimentonet" ou "/atendimentonet")
80    // e divide pelas barras '/', pegando no último elemento válido
81    var partes = url.pathname.split('/').filter(Boolean);
82    
83    return partes[partes.length - 1];
84  } catch (e) {
85    console.error("URL inválido:", e);
86    return null;
87  }
88}
89
90
91function fetchServicesBaseUrl(isCompany) {
92  var key = isCompany ? "company" : "person";
93
94  if (!baseUrlPromises[key]) {
95    baseUrlPromises[key] = new Promise(function (resolve, reject) {
96   
97      window.handleBaseUrlLoaded = function (isCompany) {
98
99        var holderId = isCompany
100          ? "loginTabs:loginCompanyForm:baseUrlHolder"
101          : "loginTabs:loginPersonForm:baseUrlHolder";
102        var holder = document.getElementById(holderId);
103        var rawValue = holder ? holder.value : null;
104
105
106        if (!rawValue || !rawValue.trim()) {
107          baseUrlPromises[key] = null;
108          reject(new Error('LoginCrypto: baseUrlHolder vazio.'));
109          return;
110        }
111
112        try {
113          resolve(extractPath(rawValue.trim()));
114        } catch (e) {
115          baseUrlPromises[key] = null;
116          reject(new Error('LoginCrypto: baseUrlHolder não é uma URL válida: ' + rawValue));
117        }
118      };
119 
120
121      if (isCompany) {
122        getServicesBaseUrlCompany();
123      } else {
124        getServicesBaseUrlPerson();
125      }
126    });
127  }
128  return baseUrlPromises[key];
129}
130
131
132  /**
133   * Obtém (e mantém em cache) a chave pública RSA do servidor, já
134   * convertida para o formato que o node-forge usa.
135   * @returns {Promise<forge.pki.rsa.PublicKey>}
136   */
137 function getServerPublicKey(isCompany) {
138  if (!cachedPublicKeyPromise) {
139    cachedPublicKeyPromise = fetchServicesBaseUrl(isCompany)
140      .then(function (path) {
141        return fetch("/" + getContext(path) + "/services/api/futuredoc/publicKey");
142      })
143      .then(function (response) {
144        if (!response.ok) {
145          throw new Error(
146            'LoginCrypto: falha ao obter a chave pública (HTTP ' + response.status + ').'
147          );
148        }
149        return response.json();
150      })
151      .then(function (body) {
152        var publicKeyBase64 =
153          (body && body.data && body.data.publicKey) || (body && body.publicKey);
154  
155        if (!publicKeyBase64) {
156          throw new Error('LoginCrypto: resposta sem publicKey.');
157        }
158        var der = forge.util.decode64(publicKeyBase64);
159        var asn1 = forge.asn1.fromDer(der);
160        return forge.pki.publicKeyFromAsn1(asn1);
161      })
162      .catch(function (err) {
163        cachedPublicKeyPromise = null;
164        throw err;
165      });
166  }
167  return cachedPublicKeyPromise;
168}
169 
170
171  /**
172   * Cifra uma password para envio seguro ao backend.
173   * @param {string} password Password em claro (aplicar trim() antes de chamar).
174   * @returns {Promise<{encryptedPassword: string, encryptedKey: string, iv: string}>}
175   */
176  function encryptPassword(password,isCompany) {
177      
178    return getServerPublicKey(isCompany).then(function (rsaPublicKey) {
179      // Chave AES-256 efémera, gerada de novo em cada cifragem.
180      var aesKeyBytes = forge.random.getBytesSync(32); // 256 bits
181      var ivBytes = forge.random.getBytesSync(12); // 96 bits, padrão para GCM
182      
183      var cipher = forge.cipher.createCipher('AES-GCM', aesKeyBytes);
184      cipher.start({ iv: ivBytes });
185      cipher.update(forge.util.createBuffer(password, 'utf8'));
186      cipher.finish();
187
188      // O Cipher do Java (AES/GCM/NoPadding) devolve ciphertext + tag
189      // concatenados no doFinal(); replica-se isso aqui para o backend
190      // poder usar exatamente a mesma lógica de decifragem.
191      var ciphertextWithTag = cipher.output.getBytes() + cipher.mode.tag.getBytes();
192
193      var wrappedKey = rsaPublicKey.encrypt(aesKeyBytes, 'RSA-OAEP', {
194        md: forge.md.sha256.create(),
195        mgf1: { md: forge.md.sha256.create() },
196      });
197        
198      return {
199        encryptedPassword: forge.util.encode64(ciphertextWithTag),
200        encryptedKey: forge.util.encode64(wrappedKey),
201        iv: forge.util.encode64(ivBytes),
202      };
203    });
204  }
205
206  global.LoginCrypto = {
207    encryptPassword: encryptPassword,
208  };
209})(typeof window !== 'undefined' ? window : this);
210 

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.