diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index 4b14306bfbdc4a3ae61d76e24bbdf599400c248a..bdda49c6ceb976d13893df6e7992d7b7cbbc1cb1 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -9,12 +9,15 @@ et ce projet adhère au [versionnage sémantique](https://semver.org/spec/v2.0.0 ## Evolutions probable / Roadmap : - GraphQL stuff - @@@@ comme séparateur entre identifiant secret et mdp pour la génération de combinaison à tester (usage principal Gsper) -- supprimer automatiquement le code inutile dans les lib (Tree Shaking) -- chiffrer déchiffrer des messages -- lire et écrire des messages au format cesium+ + ## [Non-publié/Non-Stabilisé] (par [1000i100]) +## [Version 3.4.0] - 2022-11-15 (par [1000i100]) +### Ajouté +- crypto.textEncrypt(jsonMessage, senderPrivateKey, receiverPubKey) retourne un json format cesium+ avec `jsonMessage.title` et `jsonMessage.content` chiffrés. +- textDecrypt(jsonMessage, receiverPrivateKey)) retourne en json les champs `title` et `content` déchiffrés à partir d'un message chiffré format cesium+. + ## [Version 3.3.3] - 2022-11-15 (par [1000i100]) ### Corrections - les versions 3.3.x antérieur à celle-ci, cherchaient à importer la lib crypto de node depuis le navigateur c'est corrigé. @@ -98,8 +101,9 @@ et ce projet adhère au [versionnage sémantique](https://semver.org/spec/v2.0.0 - intégration des librairies de crypto nécessaires - calcul de la clef publique correspondant à chaque combinaison de secrets saisie, et comparaison à la clef publique de référence. -[Non-publié/Non-Stabilisé]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.3.3...master +[Non-publié/Non-Stabilisé]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.4.0...main +[Version 3.4.0]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.3.3...v3.4.0 [Version 3.3.3]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.3.2...v3.3.3 [Version 3.3.2]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.3.1...v3.3.2 [Version 3.3.1]: https://git.duniter.org/libs/g1lib.js/-/compare/v3.3.0...v3.3.1 diff --git a/npm/package.json b/npm/package.json index 3dec726107294b9e8134d8de184b1a0de4aa7887..df79b1f275a7c7fba6b44887fcd9253987357f0a 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "g1lib", - "version": "3.3.3", + "version": "3.4.0", "description": "An ubiquitous static javascript toolbox lib for Ǧ1 / Duniter ecosystem with reliability in mind.", "main": "nodejs/all.mjs", "browser": "browser/all.mjs", diff --git a/src/crypto.mjs b/src/crypto.mjs index 32067a98bdf11a6e67d75d487d40908827e2b56e..cac959e77e4e88e45542717ed928ed6a2861b609 100644 --- a/src/crypto.mjs +++ b/src/crypto.mjs @@ -5,7 +5,7 @@ import sha256 from '../node_modules/js-sha256/src/sha256.mjs'; import nacl from '../generated/vendors/nacl.mjs'; import {convertPublicKey, convertSecretKey} from '../node_modules/ed2curve/src/index.mjs'; //import {convertPublicKey} from '../node_modules/ed2curve-esm/dist-src/index.mjs'; -import {b58, b64} from './basex.mjs'; +import {b16, b58, b64} from './basex.mjs'; import {random, ed25519} from './context-dependant/generics.mjs'; nacl.setPRNG(random); export const mockRandom = nacl.setPRNG; @@ -157,9 +157,9 @@ export async function signDocument(unsignedDocument, secretKey) { return `${unsignedDocument.trim()}\n${signHash}`; } -export async function sign(str, secretKey, outputFormat = 'b64') { +export function sign(str, secretKey, outputFormat = 'b64') { const encoder = new TextEncoder(); - const raw = await rawSign(encoder.encode(str), b58secretKey2bin(secretKey).slice(0, 32)); + const raw = rawSign(encoder.encode(str), b58secretKey2bin(secretKey).slice(0, 32)); switch (outputFormat.toLocaleLowerCase()) { case 'raw': case 'array': @@ -174,7 +174,58 @@ export async function sign(str, secretKey, outputFormat = 'b64') { } } -export async function rawSign(uint8Array, rawSeed) { - const keys = await seed2keyPair(rawSeed); +export function rawSign(uint8Array, rawSeed) { + const keys = seed2keyPair(rawSeed); return nacl.sign.detached(uint8Array, keys.secretKey); } + +const NONCE_BYTES = 24; +const PUBKEY_BYTES = 32; + +export function typedStrOrBin2Bin(tStrOrBin,type2binFunc){ + return (typeof tStrOrBin === 'string')?type2binFunc(tStrOrBin):tStrOrBin; +} +export function strOrBin2bin(strOrBin){ + const encoder = new TextEncoder(); + return (typeof strOrBin === 'string')?encoder.encode(strOrBin):strOrBin; +} +export function b64orBin2bin(b64orBin){ + return typedStrOrBin2Bin(b64orBin,b64.decode); +} +export function b58orBin2bin(b58orBin){ + return typedStrOrBin2Bin(b58orBin,b58.decode); +} +export function bin2str(bin){ + const decoder = new TextDecoder(); + return decoder.decode(bin); +} +function secKey2bin(secretKey){ + return b58orBin2bin(secretKey).slice(0,PUBKEY_BYTES); +} + + +export function textEncrypt(jsonMessage, senderPrivateKey, receiverPubKey, nowInSecond=0, b58nonce=''){ + const bNonce = b58nonce?b58.decode(b58nonce):nacl.randomBytes(NONCE_BYTES); + console.log("Défini : ", receiverPubKey) + const bReceiverPubKey = convertPublicKey(pubKey2bin(receiverPubKey)); + const bSenderPrivateKey = convertSecretKey(secKey2bin(senderPrivateKey)); + const encrypt = utf8text => b64.encode(nacl.box(strOrBin2bin(utf8text),bNonce,bReceiverPubKey,bSenderPrivateKey)); + const b58sendPubKey = b58.encode(seed2keyPair(secKey2bin(senderPrivateKey)).publicKey); + const sTimestamp = nowInSecond || Math.trunc(Date.now()/1000); + const res = {nonce:b58.encode(bNonce),issuer:b58sendPubKey,recipient:receiverPubKey,time:sTimestamp,version:2}; + if(jsonMessage.title) res.title = encrypt(jsonMessage.title); + if(jsonMessage.content) res.content = encrypt(jsonMessage.content); + res.hash = b16.encode(sha256Instance.digest(JSON.stringify(res))).toUpperCase(); + res.signature = sign(res.hash,senderPrivateKey) + return res; +} +export function textDecrypt(jsonMessage, receiverPrivateKey){ + const bNonce = b58.decode(jsonMessage.nonce); + const bReceiverPrivateKey = convertSecretKey(secKey2bin(receiverPrivateKey)); + const bSenderPubKey = convertPublicKey(pubKey2bin(jsonMessage.issuer)); + const decrypt = b64text => bin2str(nacl.box.open(b64.decode(b64text),bNonce,bSenderPubKey,bReceiverPrivateKey)); + const res = {}; + if(jsonMessage.content) res.content = decrypt(jsonMessage.content); + if(jsonMessage.title) res.title = decrypt(jsonMessage.title); + return res; +} diff --git a/src/crypto.test.mjs b/src/crypto.test.mjs index 65e87ef8bc41d127d0f6415fa09820a3eaff540a..2c45600463d3c1169e7371f44ccf5de969b144dd 100644 --- a/src/crypto.test.mjs +++ b/src/crypto.test.mjs @@ -28,7 +28,15 @@ test('b58 sign string', async t => t.is(await app.sign(unsignedDocument, secretK test('raw sign string', async t => t.is((await app.sign(unsignedDocument, secretKey, 'raw'))[0], 27)); test('array sign string', async t => t.is((await app.sign(unsignedDocument, secretKey, 'Array'))[0], 27)); test('uint8array sign string', async t => t.is((await app.sign(unsignedDocument, secretKey, 'uint8array'))[0], 27)); -test('sign throw for bad output format', async t => t.throwsAsync(() => app.sign(unsignedDocument, secretKey, 'whattt ?'))); +test('sign throw for bad output format', t => t.throws(() => app.sign(unsignedDocument, secretKey, 'whattt ?'))); + +//test('signOnly(message, privateKey)', t => t.is(app.signOnly('a message', secretKey), 'signature')); +//test('signOnly(message, privateKey, returnFormat=uint8array)', t => t.is(app.signOnly([0,1,2,3], secretKey), [0,1,2,3])); +//test('signDocument or signMessage(message, privateKey)', t => t.is(app.sign('a message', secretKey), 'message avec sa signature')); +//test('sign(uint8array, privateKey, returnFormat=uint8array)', async t => t.is(await app.sign([0,1,2,3], secretKey,'uint8array'), [0,0,0,0])); +//test('checkSign(message, issuerPubKey) succeed', t => t.is(app.checkSign('a message', secretKey), true)); +//test('checkSign(message, issuerPubKey) fail', t => t.is(app.checkSign([0,1,2,3], secretKey,'uint8array'), false)); + test('b58 should decode/encode well', t => t.is(app.b58.encode(app.b58.decode(pubKey)), pubKey)); test('b58 on pubKey with leading 1', t => t.is(app.b58.encode(app.b58.decode('12BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx')), '12BjyvjoAf5qik7R8TKDJAHJugsX23YgJGi2LmBUv2nx')); @@ -127,3 +135,34 @@ test('checkKey accept valid binary pubKey', t => t.true(app.checkKey(app.b58.dec test("isPubKey return true when checkKey return true ", (t) => t.true(app.isPubKey(pubKey))); test("isPubKey return false when checkKey throw an error", (t) => t.false(app.isPubKey(pubKey.replace(/6/,'9')))); + +app.mockRandom((u8a, n)=>{ + if(!u8a && !n) return 0.5; + if(!n && typeof u8a === "number"){n = u8a;u8a = new Uint8Array(n);} + for (let i = 0; i < n; i++) u8a[i] = 5; + return u8a; +}); + +const user1 = await app.idSecPass2cleanKeys('1','1'); +const user2 = await app.idSecPass2cleanKeys('2','2'); +const timestampInSeconds = 1222111000; +const sampleMessage = {title:"Mon Titre",content:"Mon message"}; +const cryptedMessage = { + content: 'G0ZAirsaoeAt/pOcsjg0milDkeBu8Uo3BgrZ', + hash: '65EC1622D2C38422EA21124397AB7A9F36A94BB6FC2C474C706ECAC33658114C', + issuer: 'BUhLyJT17bzDVXW66xxfk1F7947vytmwJVadTaWb8sJS', + nonce: 'TYPjCiGbKiwP6r12cdkmVjySbQpSHavp', + recipient: '7nge6q7F4k7FQ2q4FRMMPvt2tK7AEx8gNNRLr6LwZN38', + signature: 'u4m2YHp5nVxcXxMjQvv9H4kfSV8Q3/Jh68jHplbCjizXlA9XXksu+YuFrS9B5KuQ2HX68g9+kRdLoCspMuWtBg==', + time: timestampInSeconds, + title: 'yV5cDZCuJWqMBXsFmEm1FSlDkeBX/U02Ag==', + version: 2 +}; + +test('textEncrypt(jsonMessage, senderPrivateKey, receiverPubKey) encrypt to cesium+ message format', + t => t.deepEqual( + app.textEncrypt(sampleMessage, user1.secretKey, user2.publicKey,timestampInSeconds), + cryptedMessage) +); +test('textDecrypt(jsonMessage, receiverPrivateKey)', t => t.deepEqual(app.textDecrypt(cryptedMessage, user2.secretKey), + {title:"Mon Titre",content:"Mon message"}));