[INJIMOB-3015] add noble ecr1 signature and remove jose signing for ios (#1930)

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>
Co-authored-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>
This commit is contained in:
abhip2565
2025-05-12 21:27:28 +05:30
committed by GitHub
parent 1df2d8f26a
commit 47bab54769
5 changed files with 71 additions and 63 deletions

View File

@@ -108,11 +108,8 @@ export const openID4VPServices = () => {
const key = await fetchKeyPair(mdocAuthenticationAlgorithm);
const signature = await createSignature(
key.privateKey,
'',
payload,
mdocAuthenticationAlgorithm,
'',
payload,
);
if (signature) {

36
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"@expo/metro-config": "~0.18.11",
"@invertase/react-native-apple-authentication": "^2.3.0",
"@iriscan/biometric-sdk-react-native": "0.2.6",
"@noble/curves": "^1.9.0",
"@noble/ed25519": "^2.1.0",
"@noble/hashes": "^1.5.0",
"@noble/secp256k1": "2.0.0",
@@ -6069,6 +6070,21 @@
"eslint-scope": "5.1.1"
}
},
"node_modules/@noble/curves": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz",
"integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.8.0"
},
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/ed25519": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.1.0.tgz",
@@ -6079,9 +6095,9 @@
}
},
"node_modules/@noble/hashes": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz",
"integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
@@ -34899,15 +34915,23 @@
"eslint-scope": "5.1.1"
}
},
"@noble/curves": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz",
"integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==",
"requires": {
"@noble/hashes": "1.8.0"
}
},
"@noble/ed25519": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.1.0.tgz",
"integrity": "sha512-KM4qTyXPinyCgMzeYJH/UudpdL+paJXtY3CHtHYZQtBkS8MZoPr4rOikZllIutJe0d06QDQKisyn02gxZ8TcQA=="
},
"@noble/hashes": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz",
"integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA=="
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="
},
"@noble/secp256k1": {
"version": "2.0.0",

View File

@@ -23,6 +23,7 @@
"@expo/metro-config": "~0.18.11",
"@invertase/react-native-apple-authentication": "^2.3.0",
"@iriscan/biometric-sdk-react-native": "0.2.6",
"@noble/curves": "^1.9.0",
"@noble/ed25519": "^2.1.0",
"@noble/hashes": "^1.5.0",
"@noble/secp256k1": "2.0.0",

View File

@@ -1,6 +1,6 @@
import 'react-native-get-random-values';
import {RSA} from 'react-native-rsa-native';
import forge from 'node-forge';
import jose from 'node-jose';
import {
BIOMETRIC_CANCELLED,
DEBUG_MODE_ENABLED,
@@ -16,8 +16,8 @@ import base64url from 'base64url';
import {hmac} from '@noble/hashes/hmac';
import {sha256} from '@noble/hashes/sha256';
import {sha512} from '@noble/hashes/sha512';
import 'react-native-get-random-values';
import * as secp from '@noble/secp256k1';
import {p256} from '@noble/curves/p256';
import * as ed from '@noble/ed25519';
import base64 from 'react-native-base64';
import {KeyTypes} from './KeyTypes';
@@ -89,13 +89,11 @@ export async function generateKeyPairECR1() {
privateKey: '',
};
}
const keystore = jose.JWK.createKeyStore();
const key = await keystore.generate('EC', 'P-256');
const jwkPublicKey = key.toJSON(); // Public key JWK
const jwkPrivateKey = key.toJSON(true); // Private key JWK (include private part)
const privKey = p256.utils.randomPrivateKey();
const pubKey = p256.getPublicKey(privKey, false);
return {
publicKey: JSON.stringify(jwkPublicKey),
privateKey: JSON.stringify(jwkPrivateKey),
publicKey: Buffer.from(pubKey).toString('base64'),
privateKey: Buffer.from(privKey).toString('base64'),
};
}
@@ -192,11 +190,8 @@ export async function getJWT(
const preHash = header64 + '.' + payLoad64;
const signature64 = await createSignature(
privateKey,
alias,
preHash,
keyType,
header,
payLoad,
);
return header64 + '.' + payLoad64 + '.' + signature64;
} catch (error) {
@@ -208,29 +203,22 @@ export async function getJWT(
}
}
export async function createSignature(
privateKey,
alias,
preHash,
keyType: string,
header,
payload,
) {
export async function createSignature(privateKey, payload, keyType: string) {
switch (keyType) {
case KeyTypes.RS256:
return createSignatureRSA(privateKey, preHash);
return createSignatureRSA(privateKey, payload);
case KeyTypes.ES256:
return createSignatureECR1(privateKey, header, payload, preHash);
return createSignatureECR1(privateKey, payload);
case KeyTypes.ES256K:
return createSignatureECK1(privateKey, preHash);
return createSignatureECK1(privateKey, payload);
case KeyTypes.ED25519:
return createSignatureED(privateKey, preHash);
return createSignatureED(privateKey, payload);
default:
break;
}
}
export async function createSignatureRSA(privateKey: string, preHash: string) {
export async function createSignatureRSA(privateKey: string, payload: string) {
let signature64;
if (!isHardwareKeystoreExists) {
@@ -240,12 +228,12 @@ export async function createSignatureRSA(privateKey: string, preHash: string) {
signature64 = await RNSecureKeystoreModule.sign(
KeyTypes.RS256,
KeyTypes.RS256,
preHash,
payload,
);
else {
const key = forge.pki.privateKeyFromPem(privateKey);
const md = forge.md.sha256.create();
md.update(preHash, 'utf8');
md.update(payload, 'utf8');
const signature = key.sign(md);
signature64 = encodeB64(signature);
@@ -254,24 +242,19 @@ export async function createSignatureRSA(privateKey: string, preHash: string) {
return replaceCharactersInB64(signature64);
}
export async function createSignatureECK1(privateKey, prehash) {
const sha = sha256(prehash);
export async function createSignatureECK1(privateKey, payload) {
const sha = sha256(payload);
const sign = await secp.signAsync(sha, privateKey, {lowS: false});
return base64url(Buffer.from(sign.toCompactRawBytes()));
}
export async function createSignatureED(privateKey, prehash) {
const messageBytes = new TextEncoder().encode(prehash);
export async function createSignatureED(privateKey, payload) {
const messageBytes = new TextEncoder().encode(payload);
const privateKeyUint8 = Uint8Array.from(privateKey);
const sign = await ed.signAsync(messageBytes, privateKeyUint8);
return replaceCharactersInB64(Buffer.from(sign).toString('base64'));
}
export async function createSignatureECR1(
privateKey,
header,
payload,
preHash,
) {
export async function createSignatureECR1(privateKey, payload) {
if (!isHardwareKeystoreExists) {
throw Error;
} else {
@@ -279,7 +262,7 @@ export async function createSignatureECR1(
let signature64 = await RNSecureKeystoreModule.sign(
KeyTypes.ES256,
KeyTypes.ES256,
preHash,
payload,
);
const base64DeodedSignature = base64.decode(
signature64.replace(/\n/g, ''),
@@ -291,19 +274,12 @@ export async function createSignatureECR1(
return replaceCharactersInB64(signature64);
}
}
const sha = sha256(payload);
const key = await jose.JWK.asKey(JSON.parse(privateKey));
const signer = await jose.JWS.createSign(
{format: 'compact', fields: header},
{key, reference: false},
);
const jws = await signer.update(JSON.stringify(payload), 'base64').final();
const jwsParts = jws.split('.');
if (jwsParts.length !== 3) {
throw new Error('Invalid JWS format');
}
return jwsParts[2];
const sign = await p256.sign(sha, Buffer.from(privateKey, 'base64'), {
lowS: false,
});
return base64url(Buffer.from(sign.toCompactRawBytes()));
}
export function replaceCharactersInB64(encodedB64: string) {
@@ -438,8 +414,12 @@ export async function fetchKeyPair(keyType: any) {
const keyPair = await RNSecureKeystoreModule.retrieveGenericKey(
keyType,
);
const publicKey = keyPair[1];
const privateKey = keyPair[0];
let publicKey = keyPair[1];
let privateKey = keyPair[0];
if (keyType == KeyTypes.ES256) {
publicKey = Buffer.from(publicKey, 'base64');
privateKey = Buffer.from(privateKey, 'base64');
}
return {
publicKey: publicKey,
privateKey: privateKey,

View File

@@ -329,9 +329,15 @@ async function getJWKRSA(publicKey): Promise<any> {
return publicKeyJWKString.toJSON();
}
async function getJWKECR1(publicKey): Promise<any> {
if (isIOS()) return JSON.parse(publicKey);
const publicKeyJWKString = await jose.JWK.asKey(publicKey, 'pem');
return publicKeyJWKString.toJSON();
const x = base64url(Buffer.from(publicKey.slice(1, 33))); // Skip the first byte (0x04) in the uncompressed public key
const y = base64url(Buffer.from(publicKey.slice(33,65)));
const jwk = {
kty: 'EC',
crv: 'P-256',
x: x,
y: y,
};
return jwk;
}
function getJWKECK1(publicKey): any {
const x = base64url(Buffer.from(publicKey.slice(1, 33))); // Skip the first byte (0x04) in the uncompressed public key