mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
clean certificate parsing library
This commit is contained in:
@@ -10,7 +10,7 @@ export interface StandardCurve {
|
||||
|
||||
export const standardCurves: StandardCurve[] = [
|
||||
{
|
||||
name: "ECDSA_P256",
|
||||
name: "secp256r1",
|
||||
p: "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
|
||||
a: "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
|
||||
b: "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B",
|
||||
@@ -19,7 +19,7 @@ export const standardCurves: StandardCurve[] = [
|
||||
h: "01"
|
||||
},
|
||||
{
|
||||
name: "ECDSA_P384",
|
||||
name: "secp384r1",
|
||||
p: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
|
||||
a: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
|
||||
b: "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
|
||||
@@ -28,7 +28,7 @@ export const standardCurves: StandardCurve[] = [
|
||||
h: "01"
|
||||
},
|
||||
{
|
||||
name: "ECDSA_P521",
|
||||
name: "secp521r1",
|
||||
p: "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
||||
a: "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC",
|
||||
b: "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00",
|
||||
@@ -106,9 +106,10 @@ export function identifyCurve(params: any): string {
|
||||
|
||||
export function getECDSACurveBits(curveName: string): string {
|
||||
const curveBits: { [key: string]: number } = {
|
||||
'ECDSA_P256': 256,
|
||||
'ECDSA_P384': 384,
|
||||
'ECDSA_P521': 521,
|
||||
'secp224r1': 224,
|
||||
'secp256r1': 256,
|
||||
'secp384r1': 384,
|
||||
'secp521r1': 521,
|
||||
'brainpoolP224r1': 224,
|
||||
'brainpoolP256r1': 256,
|
||||
'brainpoolP384r1': 384,
|
||||
@@ -122,12 +123,14 @@ export function getECDSACurveBits(curveName: string): string {
|
||||
}
|
||||
export function getCurveForElliptic(curveName: string): string {
|
||||
const curves = {
|
||||
ECDSA_P256: 'p256',
|
||||
ECDSA_P384: 'p384',
|
||||
ECDSA_P521: 'p521',
|
||||
secp224r1: 'p224',
|
||||
secp256r1: 'p256',
|
||||
secp384r1: 'p384',
|
||||
secp521r1: 'p521',
|
||||
brainpoolP224r1: 'brainpoolP224r1',
|
||||
brainpoolP256r1: 'brainpoolP256r1',
|
||||
brainpoolP384r1: 'brainpoolP384r1',
|
||||
brainpoolP512r1: 'brainpoolP512r1',
|
||||
};
|
||||
|
||||
if (!curves[curveName]) {
|
||||
|
||||
@@ -29,6 +29,8 @@ export interface PublicKeyDetailsRSAPSS extends PublicKeyDetailsRSA {
|
||||
}
|
||||
|
||||
export interface PublicKeyDetailsECDSA {
|
||||
x: string;
|
||||
y: string;
|
||||
curve: string;
|
||||
params: StandardCurve;
|
||||
bits: string;
|
||||
|
||||
@@ -104,8 +104,19 @@ export const oidMap: { [key: string]: string } = {
|
||||
"1.2.840.10045.3.1.6": "x962P239v3",
|
||||
};
|
||||
|
||||
export const mapSecpCurves: { [key: string]: string } = {
|
||||
"ECDSA_224": "secp224r1",
|
||||
"ECDSA_P256": "secp256r1",
|
||||
"ECDSA_P384": "secp384r1",
|
||||
"ECDSA_P521": "secp521r1",
|
||||
}
|
||||
|
||||
function getFriendlyNameSecpCurves(friendlyName: string): string {
|
||||
return mapSecpCurves[friendlyName] || friendlyName;
|
||||
}
|
||||
|
||||
export function getFriendlyName(oid: string): string {
|
||||
return oidMap[oid] || "Unknown Algorithm";
|
||||
return getFriendlyNameSecpCurves(oidMap[oid]) || "Unknown Algorithm";
|
||||
}
|
||||
|
||||
export function extractHashFunction(friendlyName: string): string {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getECDSACurveBits, identifyCurve, StandardCurve } from "./curves";
|
||||
import { getIssuerCountryCode, getSubjectKeyIdentifier } from "./utils";
|
||||
import fs from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
import { getAuthorityKeyIdentifier } from "../certificates/handleCertificate";
|
||||
import { parseCertificateSimple } from "./parseCertificateSimple";
|
||||
export function parseCertificate(pem: string, fileName: string): any {
|
||||
let certificateData: CertificateData = {
|
||||
|
||||
@@ -2,9 +2,11 @@ import * as asn1js from "asn1js";
|
||||
import { Certificate, RSAPublicKey, RSASSAPSSParams } from "pkijs";
|
||||
import { extractHashFunction, getFriendlyName } from "./oids";
|
||||
import { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, PublicKeyDetailsRSAPSS } from "./dataStructure";
|
||||
import { getECDSACurveBits, identifyCurve, StandardCurve } from "./curves";
|
||||
import { getCurveForElliptic, getECDSACurveBits, identifyCurve, StandardCurve } from "./curves";
|
||||
import { getIssuerCountryCode, getSubjectKeyIdentifier } from "./utils";
|
||||
import { getAuthorityKeyIdentifier } from "../certificates/handleCertificate";
|
||||
import elliptic from 'elliptic';
|
||||
import { circuitNameFromMode } from "../../constants/constants";
|
||||
import { Mode } from "../appType";
|
||||
|
||||
|
||||
export function parseCertificateSimple(pem: string): CertificateData {
|
||||
@@ -170,84 +172,160 @@ function getParamsRSAPSS2(cert: Certificate): PublicKeyDetailsRSAPSS {
|
||||
|
||||
export function getParamsECDSA(cert: Certificate): PublicKeyDetailsECDSA {
|
||||
try {
|
||||
|
||||
const algorithmParams = cert.subjectPublicKeyInfo.algorithm.algorithmParams;
|
||||
|
||||
if (!algorithmParams) {
|
||||
console.log('No algorithm params found');
|
||||
return { curve: 'Unknown', params: {} as StandardCurve, bits: 'Unknown' };
|
||||
return { curve: 'Unknown', params: {} as StandardCurve, bits: 'Unknown', x: 'Unknown', y: 'Unknown' };
|
||||
}
|
||||
|
||||
// Add this check for named curves
|
||||
let curveName, bits, x, y = 'Unknown';
|
||||
let curveParams: StandardCurve = {} as StandardCurve;
|
||||
|
||||
// Try to get the curve name from the OID
|
||||
if (algorithmParams instanceof asn1js.ObjectIdentifier) {
|
||||
// Get the curve name from the OID
|
||||
const curveOid = algorithmParams.valueBlock.toString();
|
||||
// You might want to add a mapping of OIDs to curve names
|
||||
const curveName = getFriendlyName(curveOid) || 'secp256k1'; // Default to secp256k1 if unknown
|
||||
return {
|
||||
curve: curveName,
|
||||
params: {} as StandardCurve, // Empty params since we're using a named curve
|
||||
bits: getECDSACurveBits(curveName)
|
||||
};
|
||||
curveName = getFriendlyName(curveOid) || 'Unknown';
|
||||
bits = getECDSACurveBits(curveName);
|
||||
}
|
||||
|
||||
// Original code for explicit parameters
|
||||
const params = asn1js.fromBER(algorithmParams.valueBeforeDecodeView).result;
|
||||
const valueBlock: any = params.valueBlock;
|
||||
// If the OID of the curve is not present, we try to get the curve parameters and identify the curve from them
|
||||
else {
|
||||
const params = asn1js.fromBER(algorithmParams.valueBeforeDecodeView).result;
|
||||
const valueBlock: any = params.valueBlock;
|
||||
if (valueBlock.value && valueBlock.value.length >= 5) {
|
||||
const curveParams: StandardCurve = {} as StandardCurve;
|
||||
// Field ID (index 1)
|
||||
const fieldId = valueBlock.value[1];
|
||||
if (fieldId && fieldId.valueBlock && fieldId.valueBlock.value) {
|
||||
const fieldType = fieldId.valueBlock.value[0];
|
||||
const prime = fieldId.valueBlock.value[1];
|
||||
//curveParams.fieldType = fieldType.valueBlock.toString();
|
||||
curveParams.p = Buffer.from(prime.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
|
||||
if (valueBlock.value && valueBlock.value.length >= 5) {
|
||||
const curveParams: StandardCurve = {} as StandardCurve;
|
||||
// Field ID (index 1)
|
||||
const fieldId = valueBlock.value[1];
|
||||
if (fieldId && fieldId.valueBlock && fieldId.valueBlock.value) {
|
||||
const fieldType = fieldId.valueBlock.value[0];
|
||||
const prime = fieldId.valueBlock.value[1];
|
||||
//curveParams.fieldType = fieldType.valueBlock.toString();
|
||||
curveParams.p = Buffer.from(prime.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
// Curve Coefficients (index 2)
|
||||
const curveCoefficients = valueBlock.value[2];
|
||||
if (curveCoefficients && curveCoefficients.valueBlock && curveCoefficients.valueBlock.value) {
|
||||
const a = curveCoefficients.valueBlock.value[0];
|
||||
const b = curveCoefficients.valueBlock.value[1];
|
||||
curveParams.a = Buffer.from(a.valueBlock.valueHexView).toString('hex');
|
||||
curveParams.b = Buffer.from(b.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
|
||||
// Curve Coefficients (index 2)
|
||||
const curveCoefficients = valueBlock.value[2];
|
||||
if (curveCoefficients && curveCoefficients.valueBlock && curveCoefficients.valueBlock.value) {
|
||||
const a = curveCoefficients.valueBlock.value[0];
|
||||
const b = curveCoefficients.valueBlock.value[1];
|
||||
curveParams.a = Buffer.from(a.valueBlock.valueHexView).toString('hex');
|
||||
curveParams.b = Buffer.from(b.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
// Base Point G (index 3)
|
||||
const basePoint = valueBlock.value[3];
|
||||
if (basePoint && basePoint.valueBlock) {
|
||||
curveParams.G = Buffer.from(basePoint.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
|
||||
// Base Point G (index 3)
|
||||
const basePoint = valueBlock.value[3];
|
||||
if (basePoint && basePoint.valueBlock) {
|
||||
curveParams.G = Buffer.from(basePoint.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
// Order n (index 4)
|
||||
const order = valueBlock.value[4];
|
||||
if (order && order.valueBlock) {
|
||||
curveParams.n = Buffer.from(order.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
|
||||
// Order n (index 4)
|
||||
const order = valueBlock.value[4];
|
||||
if (order && order.valueBlock) {
|
||||
curveParams.n = Buffer.from(order.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
|
||||
if (valueBlock.value.length >= 6) {
|
||||
// Cofactor h (index 5)
|
||||
const cofactor = valueBlock.value[5];
|
||||
if (cofactor && cofactor.valueBlock) {
|
||||
curveParams.h = Buffer.from(cofactor.valueBlock.valueHexView).toString('hex');
|
||||
if (valueBlock.value.length >= 6) {
|
||||
// Cofactor h (index 5)
|
||||
const cofactor = valueBlock.value[5];
|
||||
if (cofactor && cofactor.valueBlock) {
|
||||
curveParams.h = Buffer.from(cofactor.valueBlock.valueHexView).toString('hex');
|
||||
}
|
||||
}
|
||||
else {
|
||||
curveParams.h = '01';
|
||||
}
|
||||
const identifiedCurve = identifyCurve(curveParams);
|
||||
curveName = identifiedCurve;
|
||||
bits = getECDSACurveBits(curveName);
|
||||
} else {
|
||||
if (valueBlock.value) {
|
||||
console.log(valueBlock.value);
|
||||
}
|
||||
else {
|
||||
console.log('No value block found');
|
||||
}
|
||||
}
|
||||
else {
|
||||
curveParams.h = '01';
|
||||
}
|
||||
console.log(cert);
|
||||
const identifiedCurve = identifyCurve(curveParams);
|
||||
return { curve: identifiedCurve, params: curveParams, bits: getECDSACurveBits(identifiedCurve) };
|
||||
} else {
|
||||
if (valueBlock.value) {
|
||||
console.log(valueBlock.value);
|
||||
}
|
||||
else {
|
||||
console.log('No value block found');
|
||||
}
|
||||
}
|
||||
|
||||
// Get the public key x and y parameters
|
||||
const publicKeyBuffer = cert.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView;
|
||||
if (publicKeyBuffer && curveName !== 'Unknown') {
|
||||
const ec = new elliptic.ec(getCurveForElliptic(curveName));
|
||||
const key = ec.keyFromPublic(publicKeyBuffer);
|
||||
x = key.getPublic().getX().toString('hex');
|
||||
y = key.getPublic().getY().toString('hex');
|
||||
}
|
||||
|
||||
return { curve: curveName, params: curveParams, bits: bits, x: x, y: y };
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error parsing EC parameters:', error);
|
||||
return { curve: 'Error', params: {} as StandardCurve, bits: 'Unknown' };
|
||||
return { curve: 'Error', params: {} as StandardCurve, bits: 'Unknown', x: 'Unknown', y: 'Unknown' };
|
||||
}
|
||||
}
|
||||
|
||||
export const getAuthorityKeyIdentifier = (cert: Certificate): string => {
|
||||
const authorityKeyIdentifier = cert.extensions.find((ext) => ext.extnID === '2.5.29.35');
|
||||
if (authorityKeyIdentifier) {
|
||||
let akiValue = Buffer.from(authorityKeyIdentifier.extnValue.valueBlock.valueHexView).toString(
|
||||
'hex'
|
||||
);
|
||||
akiValue = akiValue.replace(/^(?:3016)?(?:0414)?/, '');
|
||||
// cur off the first 2 bytes
|
||||
akiValue = akiValue.slice(4);
|
||||
return akiValue;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getCircuitName = (
|
||||
circuitMode: "prove" | "dsc" | "vc_and_disclose",
|
||||
signatureAlgorithm: string,
|
||||
hashFunction: string,
|
||||
domainParameter: string,
|
||||
keyLength: string
|
||||
) => {
|
||||
const circuit = circuitNameFromMode[circuitMode];
|
||||
if (circuit == 'vc_and_disclose') {
|
||||
return 'vc_and_disclose';
|
||||
}
|
||||
if (circuit == 'dsc') {
|
||||
return (
|
||||
circuit +
|
||||
'_' +
|
||||
signatureAlgorithm +
|
||||
'_' +
|
||||
hashFunction +
|
||||
'_' +
|
||||
domainParameter +
|
||||
'_' +
|
||||
keyLength
|
||||
);
|
||||
}
|
||||
return (
|
||||
circuit +
|
||||
'_' +
|
||||
signatureAlgorithm +
|
||||
'_' +
|
||||
hashFunction +
|
||||
'_' +
|
||||
domainParameter +
|
||||
'_' +
|
||||
keyLength
|
||||
);
|
||||
};
|
||||
export const getCircuitNameOld = (circuitMode: Mode, signatureAlgorithm: string, hashFunction: string) => {
|
||||
const circuit = circuitNameFromMode[circuitMode];
|
||||
if (circuit == 'vc_and_disclose') {
|
||||
return 'vc_and_disclose';
|
||||
}
|
||||
else if (signatureAlgorithm === 'ecdsa') {
|
||||
return circuit + "_" + signatureAlgorithm + "_secp256r1_" + hashFunction;
|
||||
}
|
||||
else {
|
||||
return circuit + "_" + signatureAlgorithm + "_65537_" + hashFunction;
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import * as path from 'path';
|
||||
import jsrsasign from 'jsrsasign';
|
||||
import * as asn1 from 'asn1.js';
|
||||
import fs from 'fs';
|
||||
|
||||
export const RSAPublicKey = asn1.define('RSAPublicKey', function () {
|
||||
this.seq().obj(this.key('n').int(), this.key('e').int());
|
||||
});
|
||||
|
||||
export function isRsaPublicKey(key) {
|
||||
return key.type === 'RSA' || key.type === 'RSA-PSS';
|
||||
}
|
||||
|
||||
export function getPublicKey(certificate) {
|
||||
const publicKeyInfo = certificate.getPublicKeyHex();
|
||||
|
||||
try {
|
||||
// Try to parse the public key as ASN.1
|
||||
const publicKeyAsn1 = asn1.define('PublicKey', function () {
|
||||
this.seq().obj(
|
||||
this.key('algorithm')
|
||||
.seq()
|
||||
.obj(this.key('algorithmId').objid(), this.key('parameters').optional().any()),
|
||||
this.key('publicKey').bitstr()
|
||||
);
|
||||
});
|
||||
|
||||
const parsed = publicKeyAsn1.decode(Buffer.from(publicKeyInfo, 'hex'), 'der');
|
||||
const publicKeyBuffer = parsed.publicKey.data;
|
||||
|
||||
// Parse the RSA public key
|
||||
const rsaPublicKey = RSAPublicKey.decode(publicKeyBuffer, 'der');
|
||||
|
||||
return {
|
||||
n: new jsrsasign.BigInteger(rsaPublicKey.n.toString('hex'), 16),
|
||||
e: new jsrsasign.BigInteger(rsaPublicKey.e.toString('hex'), 16),
|
||||
type: 'RSA',
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error parsing public key:', e);
|
||||
}
|
||||
|
||||
// If parsing fails, fall back to manual extraction
|
||||
const modulus = extractModulus(publicKeyInfo);
|
||||
if (modulus) {
|
||||
return { n: new jsrsasign.BigInteger(modulus, 16), type: 'RSA' };
|
||||
}
|
||||
|
||||
throw new Error('Unable to extract public key');
|
||||
}
|
||||
|
||||
function extractModulus(publicKeyInfo: string): string | null {
|
||||
// RSA OID
|
||||
const rsaOid = '2a864886f70d010101';
|
||||
// RSA-PSS OID
|
||||
const rsaPssOid = '2a864886f70d01010a';
|
||||
|
||||
let offset = publicKeyInfo.indexOf(rsaOid);
|
||||
if (offset === -1) {
|
||||
offset = publicKeyInfo.indexOf(rsaPssOid);
|
||||
}
|
||||
|
||||
if (offset === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Skip OID and move to the bit string
|
||||
offset = publicKeyInfo.indexOf('03', offset);
|
||||
if (offset === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Skip bit string tag and length
|
||||
offset += 4;
|
||||
|
||||
// Extract modulus
|
||||
const modulusStart = publicKeyInfo.indexOf('02', offset) + 2;
|
||||
const modulusLength = parseInt(publicKeyInfo.substr(modulusStart, 2), 16) * 2;
|
||||
const modulus = publicKeyInfo.substr(modulusStart + 2, modulusLength);
|
||||
|
||||
return modulus;
|
||||
}
|
||||
|
||||
export function readCertificate(filePath: string): jsrsasign.X509 {
|
||||
const certPem = fs.readFileSync(filePath, 'utf8');
|
||||
const certificate = new jsrsasign.X509();
|
||||
certificate.readCertPEM(certPem);
|
||||
return certificate;
|
||||
}
|
||||
|
||||
export function getTBSCertificate(certificate: jsrsasign.X509): Buffer {
|
||||
// console.log("Certificate:", certificate);
|
||||
|
||||
const certASN1 = certificate.getParam();
|
||||
// console.log("certASN1:", certASN1);
|
||||
|
||||
if (!certASN1) {
|
||||
console.error('Failed to get certificate parameters');
|
||||
throw new Error('Invalid certificate structure');
|
||||
}
|
||||
|
||||
// Extract the TBS part directly from the certificate's hex representation
|
||||
const certHex = certificate.hex;
|
||||
const tbsStartIndex = certHex.indexOf('30') + 2; // Start after the first sequence tag
|
||||
const tbsLength = parseInt(certHex.substr(tbsStartIndex, 2), 16) * 2 + 2; // Length in bytes * 2 for hex + 2 for length field
|
||||
const tbsHex = certHex.substr(tbsStartIndex - 2, tbsLength); // Include the sequence tag
|
||||
|
||||
// console.log("TBS Hex:", tbsHex);
|
||||
|
||||
return Buffer.from(tbsHex, 'hex');
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
export interface StandardCurve {
|
||||
name: string;
|
||||
p: string;
|
||||
a: string;
|
||||
b: string;
|
||||
G: string;
|
||||
n: string;
|
||||
h: string;
|
||||
}
|
||||
|
||||
export const standardCurves: StandardCurve[] = [
|
||||
{
|
||||
name: 'secp256r1',
|
||||
p: 'FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF',
|
||||
a: 'FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC',
|
||||
b: '5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B',
|
||||
G: '046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5',
|
||||
n: 'FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551',
|
||||
h: '01',
|
||||
},
|
||||
{
|
||||
name: 'secp384r1',
|
||||
p: 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF',
|
||||
a: 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC',
|
||||
b: 'B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF',
|
||||
G: '04AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB73617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F',
|
||||
n: 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973',
|
||||
h: '01',
|
||||
},
|
||||
{
|
||||
name: 'secp521r1',
|
||||
p: '01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
|
||||
a: '01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC',
|
||||
b: '0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00',
|
||||
G: '0400C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650',
|
||||
n: '01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409',
|
||||
h: '01',
|
||||
},
|
||||
{
|
||||
name: 'brainpoolP256r1',
|
||||
p: 'A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377',
|
||||
a: '7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9',
|
||||
b: '26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6',
|
||||
G: '048BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997',
|
||||
n: 'A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7',
|
||||
h: '01',
|
||||
},
|
||||
{
|
||||
name: 'brainpoolP384r1',
|
||||
p: '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53',
|
||||
a: '7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826',
|
||||
b: '04A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DBC9943AB78696FA504C11',
|
||||
G: '041D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D646AAEF87B2E247D4AF1E8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315',
|
||||
n: '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565',
|
||||
h: '01',
|
||||
},
|
||||
{
|
||||
name: 'brainpoolP512r1',
|
||||
p: 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3',
|
||||
a: '7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA',
|
||||
b: '3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723',
|
||||
G: '0481AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F8227DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892',
|
||||
n: 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069',
|
||||
h: '01',
|
||||
},
|
||||
];
|
||||
|
||||
export function normalizeHex(hex: string): string {
|
||||
return hex.toLowerCase().replace(/^0x/, '').replace(/^00/, '');
|
||||
}
|
||||
|
||||
export function identifyCurve(params: any): string {
|
||||
const normalizedParams = {
|
||||
p: normalizeHex(params.p),
|
||||
a: normalizeHex(params.a),
|
||||
b: normalizeHex(params.b),
|
||||
G: normalizeHex(params.G),
|
||||
n: normalizeHex(params.n),
|
||||
h: normalizeHex(params.h),
|
||||
};
|
||||
|
||||
for (const curve of standardCurves) {
|
||||
if (
|
||||
normalizedParams.p === normalizeHex(curve.p) &&
|
||||
normalizedParams.a === normalizeHex(curve.a) &&
|
||||
normalizedParams.b === normalizeHex(curve.b) &&
|
||||
normalizedParams.G === normalizeHex(curve.G) &&
|
||||
normalizedParams.n === normalizeHex(curve.n) &&
|
||||
normalizedParams.h === normalizeHex(curve.h)
|
||||
) {
|
||||
return curve.name;
|
||||
}
|
||||
}
|
||||
return 'Unknown curve';
|
||||
}
|
||||
|
||||
export function getNamedCurve(oid: string): string {
|
||||
const curves = {
|
||||
'1.2.840.10045.3.1.7': 'secp256r1',
|
||||
'1.3.132.0.34': 'secp384r1',
|
||||
'1.3.132.0.35': 'secp521r1',
|
||||
// Add more curve OIDs as needed
|
||||
};
|
||||
return curves[oid] || `Unknown (${oid})`;
|
||||
}
|
||||
export function getECDSACurveBits(curveName: string): string {
|
||||
const curveBits: { [key: string]: number } = {
|
||||
secp256r1: 256,
|
||||
secp384r1: 384,
|
||||
secp521r1: 521,
|
||||
brainpoolP256r1: 256,
|
||||
brainpoolP384r1: 384,
|
||||
brainpoolP512r1: 512,
|
||||
'secp256r1 (NIST P-256)': 256,
|
||||
'secp384r1 (NIST P-384)': 384,
|
||||
'secp521r1 (NIST P-521)': 521,
|
||||
};
|
||||
if (curveName in curveBits) {
|
||||
return curveBits[curveName].toString();
|
||||
}
|
||||
console.log('\x1b[31m%s\x1b[0m', `curve name ${curveName} not found in curveBits`);
|
||||
return 'unknown';
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { StandardCurve } from './curves';
|
||||
|
||||
export interface CertificateData {
|
||||
id: string;
|
||||
issuer: string;
|
||||
validity: {
|
||||
notBefore: string;
|
||||
notAfter: string;
|
||||
};
|
||||
subjectKeyIdentifier: string;
|
||||
signatureAlgorithm: string;
|
||||
hashFunction: string;
|
||||
publicKeyDetails:
|
||||
| PublicKeyDetailsRSA
|
||||
| PublicKeyDetailsECDSA
|
||||
| PublicKeyDetailsRSAPSS
|
||||
| undefined;
|
||||
rawPem: string;
|
||||
rawTxt: string;
|
||||
}
|
||||
|
||||
export interface PublicKeyDetailsRSA {
|
||||
modulus: string;
|
||||
exponent: string;
|
||||
bits: string;
|
||||
}
|
||||
|
||||
export interface PublicKeyDetailsRSAPSS extends PublicKeyDetailsRSA {
|
||||
hashFunction: string;
|
||||
mgf: string;
|
||||
saltLength: string;
|
||||
}
|
||||
|
||||
export interface PublicKeyDetailsECDSA {
|
||||
curve: string;
|
||||
params: StandardCurve;
|
||||
bits: string;
|
||||
x: string;
|
||||
y: string;
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
import * as asn1 from 'asn1js';
|
||||
import { Certificate } from 'pkijs';
|
||||
import { getHashLen } from '../utils';
|
||||
import elliptic from 'elliptic';
|
||||
import { parseRsaPublicKey, parseRsaPssPublicKey, parseECParameters } from './publicKeyDetails';
|
||||
import { PublicKeyDetailsRSAPSS } from './dataStructure';
|
||||
import { getNamedCurve } from './curves';
|
||||
import { circuitNameFromMode } from '../../constants/constants';
|
||||
import { Mode } from '../appType';
|
||||
|
||||
if (typeof global.Buffer === 'undefined') {
|
||||
global.Buffer = require('buffer').Buffer;
|
||||
}
|
||||
|
||||
export function parseCertificate(pem: string) {
|
||||
const cert = getCertificateFromPem(pem);
|
||||
let { signatureAlgorithm, hashFunction } = getSignatureAlgorithmDetails(
|
||||
cert.signatureAlgorithm.algorithmId
|
||||
);
|
||||
const subjectPublicKeyInfo = cert.subjectPublicKeyInfo;
|
||||
const subjectKeyIdentifier = getSubjectKeyIdentifier(cert);
|
||||
const authorityKeyIdentifier = getAuthorityKeyIdentifier(cert);
|
||||
let publicKeyDetails: any;
|
||||
switch (signatureAlgorithm) {
|
||||
case 'rsa':
|
||||
publicKeyDetails = parseRsaPublicKey(subjectPublicKeyInfo);
|
||||
if (!publicKeyDetails) {
|
||||
console.log('\x1b[33mRSA public key not found, probably ECDSA certificate\x1b[0m');
|
||||
}
|
||||
break;
|
||||
case 'rsapss':
|
||||
const rsaPssParams = cert.signatureAlgorithm.algorithmParams;
|
||||
publicKeyDetails = parseRsaPssPublicKey(subjectPublicKeyInfo, rsaPssParams);
|
||||
if (publicKeyDetails) {
|
||||
hashFunction = (publicKeyDetails as PublicKeyDetailsRSAPSS).hashFunction;
|
||||
}
|
||||
if (!publicKeyDetails) {
|
||||
console.log('\x1b[33mRSA-PSS public key not found\x1b[0m');
|
||||
}
|
||||
break;
|
||||
case 'ecdsa':
|
||||
publicKeyDetails = parseECParameters(subjectPublicKeyInfo);
|
||||
if (!publicKeyDetails) {
|
||||
console.log('\x1b[33mECDSA public key not found\x1b[0m');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log('\x1b[33mUnknown signature algorithm: \x1b[0m', signatureAlgorithm);
|
||||
}
|
||||
const hashLen = getHashLen(hashFunction);
|
||||
return {
|
||||
signatureAlgorithm,
|
||||
hashFunction,
|
||||
hashLen,
|
||||
subjectKeyIdentifier,
|
||||
authorityKeyIdentifier,
|
||||
...publicKeyDetails,
|
||||
};
|
||||
}
|
||||
|
||||
export const getCircuitName = (
|
||||
circuitMode: "prove" | "dsc" | "vc_and_disclose",
|
||||
signatureAlgorithm: string,
|
||||
hashFunction: string,
|
||||
domainParameter: string,
|
||||
keyLength: string
|
||||
) => {
|
||||
const circuit = circuitNameFromMode[circuitMode];
|
||||
if (circuit == 'vc_and_disclose') {
|
||||
return 'vc_and_disclose';
|
||||
}
|
||||
if (circuit == 'dsc') {
|
||||
return (
|
||||
circuit +
|
||||
'_' +
|
||||
signatureAlgorithm +
|
||||
'_' +
|
||||
hashFunction +
|
||||
'_' +
|
||||
domainParameter +
|
||||
'_' +
|
||||
keyLength
|
||||
);
|
||||
}
|
||||
return (
|
||||
circuit +
|
||||
'_' +
|
||||
signatureAlgorithm +
|
||||
'_' +
|
||||
hashFunction +
|
||||
'_' +
|
||||
domainParameter +
|
||||
'_' +
|
||||
keyLength
|
||||
);
|
||||
};
|
||||
export const getCircuitNameOld = (circuitMode: Mode, signatureAlgorithm: string, hashFunction: string) => {
|
||||
const circuit = circuitNameFromMode[circuitMode];
|
||||
if (circuit == 'vc_and_disclose') {
|
||||
return 'vc_and_disclose';
|
||||
}
|
||||
else if (signatureAlgorithm === 'ecdsa') {
|
||||
return circuit + "_" + signatureAlgorithm + "_secp256r1_" + hashFunction;
|
||||
}
|
||||
else {
|
||||
return circuit + "_" + signatureAlgorithm + "_65537_" + hashFunction;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getSignatureAlgorithmDetails(oid: string): {
|
||||
signatureAlgorithm: string;
|
||||
hashFunction: string;
|
||||
} {
|
||||
const details = {
|
||||
'1.2.840.113549.1.1.5': {
|
||||
signatureAlgorithm: 'rsa',
|
||||
hashFunction: 'sha1',
|
||||
domainParameter: '65537',
|
||||
keyLength: '2048',
|
||||
},
|
||||
'1.2.840.113549.1.1.11': {
|
||||
signatureAlgorithm: 'rsa',
|
||||
hashFunction: 'sha256',
|
||||
domainParameter: '65537',
|
||||
keyLength: '2048',
|
||||
},
|
||||
'1.2.840.113549.1.1.12': {
|
||||
signatureAlgorithm: 'rsa',
|
||||
hashFunction: 'sha384',
|
||||
domainParameter: '65537',
|
||||
keyLength: '2048',
|
||||
},
|
||||
'1.2.840.113549.1.1.13': {
|
||||
signatureAlgorithm: 'rsa',
|
||||
hashFunction: 'sha512',
|
||||
domainParameter: '65537',
|
||||
keyLength: '2048',
|
||||
},
|
||||
// rsapss
|
||||
'1.2.840.113549.1.1.10': {
|
||||
signatureAlgorithm: 'rsapss',
|
||||
hashFunction: 'sha256',
|
||||
domainParameter: '65537',
|
||||
keyLength: '2048',
|
||||
}, // TODO: detect which hash function is used (not always sha256)
|
||||
// ecdsa
|
||||
'1.2.840.10045.4.1': {
|
||||
signatureAlgorithm: 'ecdsa',
|
||||
hashFunction: 'sha1',
|
||||
domainParameter: 'secp256r1',
|
||||
keyLength: '256',
|
||||
},
|
||||
'1.2.840.10045.4.3.1': {
|
||||
signatureAlgorithm: 'ecdsa',
|
||||
hashFunction: 'sha224',
|
||||
domainParameter: 'secp256r1',
|
||||
keyLength: '256',
|
||||
},
|
||||
'1.2.840.10045.4.3.2': {
|
||||
signatureAlgorithm: 'ecdsa',
|
||||
hashFunction: 'sha256',
|
||||
domainParameter: 'secp256r1',
|
||||
keyLength: '256',
|
||||
},
|
||||
'1.2.840.10045.4.3.3': {
|
||||
signatureAlgorithm: 'ecdsa',
|
||||
hashFunction: 'sha384',
|
||||
domainParameter: 'secp384r1',
|
||||
keyLength: '384',
|
||||
},
|
||||
'1.2.840.10045.4.3.4': {
|
||||
signatureAlgorithm: 'ecdsa',
|
||||
hashFunction: 'sha512',
|
||||
domainParameter: 'secp521r1',
|
||||
keyLength: '521',
|
||||
},
|
||||
};
|
||||
return details[oid] || { signatureAlgorithm: `Unknown (${oid})`, hashFunction: 'Unknown' };
|
||||
}
|
||||
|
||||
export function gethashFunctionName(oid: string): string {
|
||||
const hashFunctions = {
|
||||
'1.3.14.3.2.26': 'sha1',
|
||||
'2.16.840.1.101.3.4.2.1': 'sha256',
|
||||
'2.16.840.1.101.3.4.2.2': 'sha384',
|
||||
'2.16.840.1.101.3.4.2.3': 'sha512',
|
||||
};
|
||||
return hashFunctions[oid] || `Unknown (${oid})`;
|
||||
}
|
||||
|
||||
export function getCertificateFromPem(pemContent: string): Certificate {
|
||||
const certBuffer = Buffer.from(
|
||||
pemContent.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ''),
|
||||
'base64'
|
||||
);
|
||||
const asn1Data = asn1.fromBER(certBuffer);
|
||||
return new Certificate({ schema: asn1Data.result });
|
||||
}
|
||||
|
||||
export const getSubjectKeyIdentifier = (cert: Certificate): string => {
|
||||
const subjectKeyIdentifier = cert.extensions.find(
|
||||
(ext) => ext.extnID === '2.5.29.14' // OID for Subject Key Identifier
|
||||
);
|
||||
if (subjectKeyIdentifier) {
|
||||
let skiValue = Buffer.from(subjectKeyIdentifier.extnValue.valueBlock.valueHexView).toString(
|
||||
'hex'
|
||||
);
|
||||
|
||||
skiValue = skiValue.replace(/^(?:3016)?(?:0414)?/, '');
|
||||
return skiValue;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getAuthorityKeyIdentifier = (cert: Certificate): string => {
|
||||
const authorityKeyIdentifier = cert.extensions.find((ext) => ext.extnID === '2.5.29.35');
|
||||
if (authorityKeyIdentifier) {
|
||||
let akiValue = Buffer.from(authorityKeyIdentifier.extnValue.valueBlock.valueHexView).toString(
|
||||
'hex'
|
||||
);
|
||||
akiValue = akiValue.replace(/^(?:3016)?(?:0414)?/, '');
|
||||
// cur off the first 2 bytes
|
||||
akiValue = akiValue.slice(4);
|
||||
return akiValue;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export function getIssuerCountryCode(cert: Certificate): string {
|
||||
const issuerRDN = cert.issuer.typesAndValues;
|
||||
let issuerCountryCode = '';
|
||||
for (const rdn of issuerRDN) {
|
||||
if (rdn.type === '2.5.4.6') {
|
||||
// OID for Country Name
|
||||
issuerCountryCode = rdn.value.valueBlock.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return issuerCountryCode.toUpperCase();
|
||||
}
|
||||
|
||||
export const parseDSC = (pemContent: string) => {
|
||||
const certBuffer = Buffer.from(
|
||||
pemContent.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ''),
|
||||
'base64'
|
||||
);
|
||||
const asn1Data = asn1.fromBER(certBuffer);
|
||||
const cert = new Certificate({ schema: asn1Data.result });
|
||||
const signatureAlgorithmOid = cert.signatureAlgorithm.algorithmId;
|
||||
const { signatureAlgorithm, hashFunction } = getSignatureAlgorithmDetails(signatureAlgorithmOid);
|
||||
const hashLen = getHashLen(hashFunction);
|
||||
|
||||
let publicKeyDetails;
|
||||
if (signatureAlgorithm === 'ecdsa') {
|
||||
const subjectPublicKeyInfo = cert.subjectPublicKeyInfo;
|
||||
const algorithmParams = subjectPublicKeyInfo.algorithm.algorithmParams;
|
||||
const curveOid = asn1.fromBER(algorithmParams.valueBeforeDecode).result.valueBlock.toString();
|
||||
const curve = getNamedCurve(curveOid);
|
||||
|
||||
const publicKeyBuffer = subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView;
|
||||
const curveForElliptic = curve === 'secp256r1' ? 'p256' : 'p384';
|
||||
const ec = new elliptic.ec(curveForElliptic);
|
||||
const key = ec.keyFromPublic(publicKeyBuffer);
|
||||
const x = key.getPublic().getX().toString('hex');
|
||||
const y = key.getPublic().getY().toString('hex');
|
||||
|
||||
const fieldSizeMap: { [key: string]: number } = {
|
||||
secp256r1: 256,
|
||||
secp384r1: 384,
|
||||
};
|
||||
const bits = fieldSizeMap[curve];
|
||||
|
||||
publicKeyDetails = { curve, x, y, bits };
|
||||
} else {
|
||||
const publicKey = cert.subjectPublicKeyInfo.subjectPublicKey;
|
||||
const asn1PublicKey = asn1.fromBER(publicKey.valueBlock.valueHexView);
|
||||
const rsaPublicKey = asn1PublicKey.result.valueBlock;
|
||||
const modulus = Buffer.from((rsaPublicKey as any).value[0].valueBlock.valueHexView).toString(
|
||||
'hex'
|
||||
);
|
||||
const exponent = Buffer.from((rsaPublicKey as any).value[1].valueBlock.valueHexView).toString(
|
||||
'hex'
|
||||
);
|
||||
const bits = Buffer.from(modulus, 'hex').length * 8;
|
||||
publicKeyDetails = { modulus, exponent, bits };
|
||||
}
|
||||
return { signatureAlgorithm, hashFunction, hashLen, ...publicKeyDetails };
|
||||
};
|
||||
@@ -1,252 +0,0 @@
|
||||
import { fromBER, BitString } from 'asn1js';
|
||||
import * as asn1 from 'asn1js';
|
||||
import * as forge from 'node-forge';
|
||||
import {
|
||||
PublicKeyDetailsECDSA,
|
||||
PublicKeyDetailsRSA,
|
||||
PublicKeyDetailsRSAPSS,
|
||||
} from './dataStructure';
|
||||
import { identifyCurve, StandardCurve, getNamedCurve, getECDSACurveBits } from './curves';
|
||||
import { gethashFunctionName } from './handleCertificate';
|
||||
import elliptic from 'elliptic';
|
||||
|
||||
export function parseRsaPublicKey(subjectPublicKeyInfo: any): PublicKeyDetailsRSA {
|
||||
const publicKey = subjectPublicKeyInfo.subjectPublicKey;
|
||||
const asn1PublicKey = fromBER(publicKey.valueBlock.valueHexView);
|
||||
const rsaPublicKey = asn1PublicKey.result.valueBlock;
|
||||
|
||||
if (
|
||||
rsaPublicKey &&
|
||||
(rsaPublicKey as any).value &&
|
||||
(rsaPublicKey as any).value[0] &&
|
||||
(rsaPublicKey as any).value[1]
|
||||
) {
|
||||
const modulusAsn1 = (rsaPublicKey as any).value[0];
|
||||
const exponentAsn1 = (rsaPublicKey as any).value[1];
|
||||
const modulusHex = Buffer.from(modulusAsn1.valueBlock.valueHexView).toString('hex');
|
||||
const exponentHex = Buffer.from(exponentAsn1.valueBlock.valueHexView).toString('hex');
|
||||
|
||||
const publicKeyForge = forge.pki.rsa.setPublicKey(
|
||||
new forge.jsbn.BigInteger(modulusHex, 16),
|
||||
new forge.jsbn.BigInteger(exponentHex, 16)
|
||||
);
|
||||
const publicKeyDetailsRSA: PublicKeyDetailsRSA = {
|
||||
modulus: publicKeyForge.n.toString(16),
|
||||
exponent: publicKeyForge.e.toString(10),
|
||||
bits: publicKeyForge.n.bitLength().toString(),
|
||||
};
|
||||
return publicKeyDetailsRSA;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseECParameters(publicKeyInfo: any): PublicKeyDetailsECDSA {
|
||||
try {
|
||||
const algorithmParams = publicKeyInfo.algorithm.algorithmParams;
|
||||
if (!algorithmParams) {
|
||||
console.error('\x1b[31mNo algorithm params found\x1b[0m');
|
||||
return null;
|
||||
}
|
||||
// get x and y;
|
||||
const curveOid = asn1.fromBER(algorithmParams.valueBeforeDecode).result.valueBlock.toString();
|
||||
const curve = getNamedCurve(curveOid);
|
||||
|
||||
const publicKeyBuffer = publicKeyInfo.subjectPublicKey.valueBlock.valueHexView;
|
||||
const curveForElliptic = curve === 'secp256r1' ? 'p256' : 'p384';
|
||||
const ec = new elliptic.ec(curveForElliptic);
|
||||
const key = ec.keyFromPublic(publicKeyBuffer);
|
||||
const x = key.getPublic().getX().toString('hex');
|
||||
const y = key.getPublic().getY().toString('hex');
|
||||
const fieldSizeMap: { [key: string]: number } = {
|
||||
secp256r1: 256,
|
||||
secp384r1: 384,
|
||||
};
|
||||
const bits = fieldSizeMap[curve];
|
||||
|
||||
const params = asn1.fromBER(algorithmParams.valueBeforeDecodeView).result;
|
||||
const valueBlock: any = params.valueBlock;
|
||||
let curveParams: StandardCurve = {} as StandardCurve;
|
||||
|
||||
// if (valueBlock.value && valueBlock.value.length >= 6) {
|
||||
// // Field ID (index 1)
|
||||
// const curveParams = {} as StandardCurve;
|
||||
// const fieldId = valueBlock.value[1];
|
||||
// if (fieldId && fieldId.valueBlock && fieldId.valueBlock.value) {
|
||||
// const fieldType = fieldId.valueBlock.value[0];
|
||||
// const prime = fieldId.valueBlock.value[1];
|
||||
// //curveParams.fieldType = fieldType.valueBlock.toString();
|
||||
// curveParams.p = Buffer.from(prime.valueBlock.valueHexView).toString('hex');
|
||||
// }
|
||||
|
||||
// // Curve Coefficients (index 2)
|
||||
// const curveCoefficients = valueBlock.value[2];
|
||||
// if (curveCoefficients && curveCoefficients.valueBlock && curveCoefficients.valueBlock.value) {
|
||||
// const a = curveCoefficients.valueBlock.value[0];
|
||||
// const b = curveCoefficients.valueBlock.value[1];
|
||||
// curveParams.a = Buffer.from(a.valueBlock.valueHexView).toString('hex');
|
||||
// curveParams.b = Buffer.from(b.valueBlock.valueHexView).toString('hex');
|
||||
// }
|
||||
|
||||
// // Base Point G (index 3)
|
||||
// const basePoint = valueBlock.value[3];
|
||||
// if (basePoint && basePoint.valueBlock) {
|
||||
// curveParams.G = Buffer.from(basePoint.valueBlock.valueHexView).toString('hex');
|
||||
// }
|
||||
|
||||
// // Order n (index 4)
|
||||
// const order = valueBlock.value[4];
|
||||
// if (order && order.valueBlock) {
|
||||
// curveParams.n = Buffer.from(order.valueBlock.valueHexView).toString('hex');
|
||||
// }
|
||||
|
||||
// // Cofactor h (index 5)
|
||||
// const cofactor = valueBlock.value[5];
|
||||
// if (cofactor && cofactor.valueBlock) {
|
||||
// curveParams.h = Buffer.from(cofactor.valueBlock.valueHexView).toString('hex');
|
||||
// }
|
||||
// if (curveParams.p && curveParams.a && curveParams.b && curveParams.G && curveParams.n && curveParams.h) {
|
||||
// const identifiedCurve = identifyCurve(curveParams);
|
||||
// }
|
||||
// } else {
|
||||
// if (valueBlock.value) {
|
||||
|
||||
// if (algorithmParams.idBlock.tagNumber === 6) {
|
||||
// console.log('\x1b[33malgorithmParams.idBlock.tagNumber === 6, looking for algorithmParams.valueBlock\x1b[0m');
|
||||
|
||||
// const curveOid = algorithmParams.valueBlock.toString();
|
||||
// const curveName = getNamedCurve(curveOid);
|
||||
// // console.error('\x1b[33mCurve OID:', curveName, '\x1b[0m');
|
||||
// return { curve: curveName, params: {} as StandardCurve, bits: getECDSACurveBits(curveName) };
|
||||
// }
|
||||
// else {
|
||||
// console.log('\x1b[31malgorithmParams.idBlock.tagNumber !== 6\x1b[0m');
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// console.log('\x1b[31mvalue block is not defined\x1b[0m');
|
||||
// }
|
||||
// }
|
||||
const publicKeyDetailsECDSA: PublicKeyDetailsECDSA = {
|
||||
curve: curve,
|
||||
params: curveParams,
|
||||
bits: bits.toString(),
|
||||
x: x,
|
||||
y: y,
|
||||
};
|
||||
return publicKeyDetailsECDSA;
|
||||
} catch (error) {
|
||||
console.error('Error parsing EC parameters:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRsaPssParams(params: any): {
|
||||
hashFunction: string;
|
||||
mgf: string;
|
||||
saltLength: string;
|
||||
} {
|
||||
try {
|
||||
const algorithmParams = asn1.fromBER(params.valueBeforeDecodeView);
|
||||
const sequence = algorithmParams.result;
|
||||
|
||||
let hashFunction = 'Unknown';
|
||||
let mgf = 'Unknown';
|
||||
let saltLength = 'Unknown';
|
||||
|
||||
// Parse hash algorithm
|
||||
if ((sequence.valueBlock as any).value && (sequence.valueBlock as any).value[0]) {
|
||||
const hashFunctionSequence = (sequence.valueBlock as any).value[0].valueBlock.value[0];
|
||||
const hashFunctionOid = hashFunctionSequence.valueBlock.value[0].valueBlock.toString();
|
||||
hashFunction = gethashFunctionName(hashFunctionOid);
|
||||
}
|
||||
|
||||
// Parse MGF
|
||||
if ((sequence.valueBlock as any).value && (sequence.valueBlock as any).value[1]) {
|
||||
const mgfSequence = (sequence.valueBlock as any).value[1].valueBlock.value[0];
|
||||
const mgfOid = mgfSequence.valueBlock.value[0].valueBlock.toString();
|
||||
mgf = mgfOid === '1.2.840.113549.1.1.8' ? 'MGF1' : `Unknown (${mgfOid})`;
|
||||
}
|
||||
// console.log((sequence.valueBlock as any).value[0].valueBlock);
|
||||
// console.log((sequence.valueBlock as any).value[1].valueBlock);
|
||||
// console.log((sequence.valueBlock as any).value[2].valueBlock);
|
||||
|
||||
// Parse salt length
|
||||
if ((sequence.valueBlock as any).value && (sequence.valueBlock as any).value[2]) {
|
||||
const saltLengthContainer = (sequence.valueBlock as any).value[2];
|
||||
|
||||
if (saltLengthContainer.valueBlock && saltLengthContainer.valueBlock.value) {
|
||||
const rawSaltLength = saltLengthContainer.valueBlock.value[0];
|
||||
if (typeof rawSaltLength === 'number') {
|
||||
saltLength = rawSaltLength.toString();
|
||||
} else if (
|
||||
rawSaltLength &&
|
||||
rawSaltLength.valueBlock &&
|
||||
rawSaltLength.valueBlock.valueHexView
|
||||
) {
|
||||
const saltLengthValue = rawSaltLength.valueBlock.valueHexView[0];
|
||||
saltLength = saltLengthValue.toString();
|
||||
} else {
|
||||
console.error('\x1b[31mUnable to parse salt length\x1b[0m');
|
||||
}
|
||||
} else {
|
||||
console.log('\x1b[31mSalt length not found\x1b[0m');
|
||||
}
|
||||
}
|
||||
|
||||
return { hashFunction, mgf, saltLength };
|
||||
} catch (error) {
|
||||
console.error('Error parsing RSA-PSS parameters:', error);
|
||||
return { hashFunction: 'Unknown', mgf: 'Unknown', saltLength: 'Unknown' };
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRsaPssPublicKey(
|
||||
subjectPublicKeyInfo: any,
|
||||
rsaPssParams: any
|
||||
): PublicKeyDetailsRSAPSS {
|
||||
let hashFunction = 'Unknown';
|
||||
let mgf = 'Unknown';
|
||||
let saltLength = 'Unknown';
|
||||
|
||||
if (rsaPssParams) {
|
||||
const parsedParams = parseRsaPssParams(rsaPssParams);
|
||||
hashFunction = parsedParams.hashFunction;
|
||||
mgf = parsedParams.mgf;
|
||||
saltLength = parsedParams.saltLength;
|
||||
} else {
|
||||
console.log('\x1b[31mRSA-PSS parameters not found\x1b[0m');
|
||||
}
|
||||
|
||||
// Add PublicKeyDetails for RSA-PSS
|
||||
const publicKey = subjectPublicKeyInfo.subjectPublicKey;
|
||||
const asn1PublicKey = fromBER(publicKey.valueBlock.valueHexView);
|
||||
const rsaPublicKey = asn1PublicKey.result.valueBlock;
|
||||
|
||||
if (
|
||||
rsaPublicKey &&
|
||||
(rsaPublicKey as any).value &&
|
||||
(rsaPublicKey as any).value[0] &&
|
||||
(rsaPublicKey as any).value[1]
|
||||
) {
|
||||
const modulusAsn1 = (rsaPublicKey as any).value[0];
|
||||
const exponentAsn1 = (rsaPublicKey as any).value[1];
|
||||
const modulusHex = Buffer.from(modulusAsn1.valueBlock.valueHexView).toString('hex');
|
||||
const exponentHex = Buffer.from(exponentAsn1.valueBlock.valueHexView).toString('hex');
|
||||
|
||||
const publicKeyForge = forge.pki.rsa.setPublicKey(
|
||||
new forge.jsbn.BigInteger(modulusHex, 16),
|
||||
new forge.jsbn.BigInteger(exponentHex, 16)
|
||||
);
|
||||
const PublicKeyDetailsRSAPSS: PublicKeyDetailsRSAPSS = {
|
||||
modulus: publicKeyForge.n.toString(16),
|
||||
exponent: publicKeyForge.e.toString(10),
|
||||
bits: publicKeyForge.n.bitLength().toString(),
|
||||
hashFunction,
|
||||
mgf,
|
||||
saltLength,
|
||||
};
|
||||
return PublicKeyDetailsRSAPSS;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
} from '../constants/mockCertificates';
|
||||
import { sampleDataHashes_small, sampleDataHashes_large } from '../constants/sampleDataHashes';
|
||||
import { countryCodes } from '../constants/constants';
|
||||
// import { parseCertificate } from './certificates/handleCertificate';
|
||||
import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple';
|
||||
import { SignatureAlgorithm } from './types';
|
||||
import { PublicKeyDetailsECDSA } from './certificate_parsing/dataStructure';
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
MAX_PADDED_SIGNED_ATTR_LEN,
|
||||
} from '../constants/constants';
|
||||
import { assert, shaPad } from './shaPad';
|
||||
import { PassportData } from './types';
|
||||
import { PassportData, SignatureAlgorithm } from './types';
|
||||
import {
|
||||
bytesToBigDecimal,
|
||||
formatMrz,
|
||||
@@ -30,7 +30,8 @@ import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
import { getCountryLeaf, getNameLeaf, getNameDobLeaf, getPassportNumberLeaf } from './smtTree';
|
||||
import { packBytes } from '../utils/utils';
|
||||
import { SMT } from '@openpassport/zk-kit-smt';
|
||||
import { parseCertificate } from './certificates/handleCertificate';
|
||||
import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple';
|
||||
import { PublicKeyDetailsECDSA, PublicKeyDetailsRSA } from './certificate_parsing/dataStructure';
|
||||
|
||||
export function generateCircuitInputsDisclose(
|
||||
secret: string,
|
||||
@@ -181,16 +182,20 @@ export function generateCircuitInputsProve(
|
||||
user_identifier_type: 'uuid' | 'hex' | 'ascii' = DEFAULT_USER_ID_TYPE
|
||||
) {
|
||||
const { mrz, eContent, signedAttr, encryptedDigest, dsc, dg2Hash } = passportData;
|
||||
const { signatureAlgorithm, hashFunction, hashLen, x, y, modulus, curve, exponent, bits } =
|
||||
parseCertificate(passportData.dsc);
|
||||
|
||||
const signatureAlgorithmFullName = `${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}`;
|
||||
const { signatureAlgorithm, hashAlgorithm, publicKeyDetails } = parseCertificateSimple(passportData.dsc);
|
||||
let pubKey: any;
|
||||
let signature: any;
|
||||
let signatureAlgorithmFullName: string;
|
||||
let n, k;
|
||||
// const
|
||||
|
||||
const { n, k } = getNAndK(`${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}` as any);
|
||||
|
||||
// const { n, k } = getNAndK(`${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}` as any);
|
||||
|
||||
if (signatureAlgorithm === 'ecdsa') {
|
||||
signatureAlgorithmFullName = `${signatureAlgorithm}_${hashAlgorithm}_${(publicKeyDetails as PublicKeyDetailsECDSA).curve}_${publicKeyDetails.bits}`;
|
||||
({ n, k } = getNAndK(signatureAlgorithmFullName as SignatureAlgorithm));
|
||||
const { x, y } = publicKeyDetails as PublicKeyDetailsECDSA;
|
||||
const { r, s } = extractRSFromSignature(encryptedDigest);
|
||||
const signature_r = splitToWords(BigInt(hexToDecimal(r)), n, k);
|
||||
const signature_s = splitToWords(BigInt(hexToDecimal(s)), n, k);
|
||||
@@ -199,18 +204,22 @@ export function generateCircuitInputsProve(
|
||||
const dsc_modulus_y = splitToWords(BigInt(hexToDecimal(y)), n, k);
|
||||
pubKey = [...dsc_modulus_x, ...dsc_modulus_y];
|
||||
} else {
|
||||
const modulus = (publicKeyDetails as PublicKeyDetailsRSA).modulus;
|
||||
signatureAlgorithmFullName = `${signatureAlgorithm}_${hashAlgorithm}_${modulus}_${publicKeyDetails.bits}`;
|
||||
({ n, k } = getNAndK(signatureAlgorithmFullName as SignatureAlgorithm));
|
||||
signature = splitToWords(BigInt(bytesToBigDecimal(encryptedDigest)), n, k);
|
||||
|
||||
pubKey = splitToWords(BigInt(hexToDecimal(modulus)), n, k);
|
||||
}
|
||||
|
||||
console.log('signatureAlgorithmFullName', signatureAlgorithmFullName);
|
||||
|
||||
const formattedMrz = formatMrz(mrz);
|
||||
const dg1Hash = hash(hashFunction, formattedMrz);
|
||||
const dg1Hash = hash(hashAlgorithm, formattedMrz);
|
||||
const dg1HashOffset = findSubarrayIndex(eContent, dg1Hash);
|
||||
console.log('\x1b[90m%s\x1b[0m', 'dg1HashOffset', dg1HashOffset);
|
||||
assert(dg1HashOffset !== -1, `DG1 hash ${dg1Hash} not found in eContent`);
|
||||
|
||||
const eContentHash = hash(hashFunction, eContent);
|
||||
const eContentHash = hash(hashAlgorithm, eContent);
|
||||
const eContentHashOffset = findSubarrayIndex(signedAttr, eContentHash);
|
||||
console.log('\x1b[90m%s\x1b[0m', 'eContentHashOffset', eContentHashOffset);
|
||||
assert(eContentHashOffset !== -1, `eContent hash ${eContentHash} not found in signedAttr`);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ECDSA_K_LENGTH_FACTOR, k_dsc, k_dsc_ecdsa } from '../constants/constants';
|
||||
import { parseDSC } from './certificates/handleCertificate';
|
||||
import {
|
||||
bigIntToHex,
|
||||
castToScope,
|
||||
|
||||
@@ -7,8 +7,10 @@ import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
import axios from 'axios';
|
||||
import { poseidon16, poseidon2, poseidon6, poseidon7 } from 'poseidon-lite';
|
||||
import { formatDg2Hash, getNAndK, getNAndKCSCA, hexToDecimal, splitToWords } from './utils';
|
||||
import { parseCertificate } from './certificates/handleCertificate';
|
||||
import { flexiblePoseidon } from './poseidon';
|
||||
import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple';
|
||||
import { PublicKeyDetailsECDSA, PublicKeyDetailsRSA } from './certificate_parsing/dataStructure';
|
||||
import { SignatureAlgorithm } from './types';
|
||||
|
||||
export function customHasher(pubKeyFormatted: string[]) {
|
||||
const rounds = Math.ceil(pubKeyFormatted.length / 16);
|
||||
@@ -28,45 +30,59 @@ export function customHasher(pubKeyFormatted: string[]) {
|
||||
}
|
||||
|
||||
export function getLeaf(dsc: string): string {
|
||||
const { signatureAlgorithm, hashFunction, modulus, x, y, bits, curve, exponent } =
|
||||
parseCertificate(dsc);
|
||||
const { n, k } = getNAndK(signatureAlgorithm);
|
||||
console.log(`${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}`);
|
||||
const sigAlgKey = `${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}`;
|
||||
const sigAlgIndex = SignatureAlgorithmIndex[sigAlgKey];
|
||||
const { signatureAlgorithm, hashAlgorithm, publicKeyDetails } = parseCertificateSimple(dsc);
|
||||
|
||||
|
||||
|
||||
if (sigAlgIndex == undefined) {
|
||||
console.error(`\x1b[31mInvalid signature algorithm: ${sigAlgKey}\x1b[0m`);
|
||||
throw new Error(`Invalid signature algorithm: ${sigAlgKey}`);
|
||||
}
|
||||
if (signatureAlgorithm === 'ecdsa') {
|
||||
const { x, y, curve, bits } = publicKeyDetails as PublicKeyDetailsECDSA;
|
||||
const sigAlgKey = `${signatureAlgorithm}_${hashAlgorithm}_${curve}_${bits}`;
|
||||
const { n, k } = getNAndK(sigAlgKey as SignatureAlgorithm);
|
||||
const sigAlgIndex = SignatureAlgorithmIndex[sigAlgKey];
|
||||
|
||||
if (sigAlgIndex == undefined) {
|
||||
console.error(`\x1b[31mInvalid signature algorithm: ${sigAlgKey}\x1b[0m`);
|
||||
throw new Error(`Invalid signature algorithm: ${sigAlgKey}`);
|
||||
}
|
||||
let qx = splitToWords(BigInt(hexToDecimal(x)), n, k);
|
||||
let qy = splitToWords(BigInt(hexToDecimal(y)), n, k);
|
||||
return customHasher([sigAlgIndex, ...qx, ...qy]);
|
||||
} else {
|
||||
const { modulus, bits } = publicKeyDetails as PublicKeyDetailsRSA;
|
||||
const sigAlgKey = `${signatureAlgorithm}_${hashAlgorithm}_${modulus}_${bits}`;
|
||||
const { n, k } = getNAndK(sigAlgKey as SignatureAlgorithm);
|
||||
const pubkeyChunked = splitToWords(BigInt(hexToDecimal(modulus)), n, k);
|
||||
|
||||
const sigAlgIndex = SignatureAlgorithmIndex[sigAlgKey];
|
||||
if (sigAlgIndex == undefined) {
|
||||
console.error(`\x1b[31mInvalid signature algorithm: ${sigAlgKey}\x1b[0m`);
|
||||
throw new Error(`Invalid signature algorithm: ${sigAlgKey}`);
|
||||
}
|
||||
return customHasher([sigAlgIndex, ...pubkeyChunked]);
|
||||
}
|
||||
}
|
||||
export function getLeafCSCA(dsc: string): string {
|
||||
const { signatureAlgorithm, hashFunction, modulus, x, y, bits, curve, exponent } =
|
||||
parseCertificate(dsc);
|
||||
const { n, k } = getNAndKCSCA(signatureAlgorithm);
|
||||
console.log(`${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}`);
|
||||
const sigAlgKey = `${signatureAlgorithm}_${hashFunction}_${curve || exponent}_${bits}`;
|
||||
console.log('sigAlgKey', sigAlgKey);
|
||||
const sigAlgIndex = SignatureAlgorithmIndex[sigAlgKey];
|
||||
console.log('sigAlgIndex', sigAlgIndex);
|
||||
const { signatureAlgorithm, hashAlgorithm, publicKeyDetails } = parseCertificateSimple(dsc);
|
||||
const { n, k } = getNAndKCSCA(signatureAlgorithm as any);
|
||||
|
||||
|
||||
if (sigAlgIndex == undefined) {
|
||||
console.error(`\x1b[31mInvalid signature algorithm: ${sigAlgKey}\x1b[0m`);
|
||||
throw new Error(`Invalid signature algorithm: ${sigAlgKey}`);
|
||||
}
|
||||
if (signatureAlgorithm === 'ecdsa') {
|
||||
const { x, y, curve, bits } = publicKeyDetails as PublicKeyDetailsECDSA;
|
||||
const sigAlgKey = `${signatureAlgorithm}_${hashAlgorithm}_${curve}_${bits}`;
|
||||
const sigAlgIndex = SignatureAlgorithmIndex[sigAlgKey];
|
||||
let qx = splitToWords(BigInt(hexToDecimal(x)), n, k);
|
||||
let qy = splitToWords(BigInt(hexToDecimal(y)), n, k);
|
||||
return customHasher([sigAlgIndex, ...qx, ...qy]);
|
||||
} else {
|
||||
const { modulus, bits } = publicKeyDetails as PublicKeyDetailsRSA;
|
||||
const sigAlgKey = `${signatureAlgorithm}_${hashAlgorithm}_${modulus}_${bits}`;
|
||||
const sigAlgIndex = SignatureAlgorithmIndex[sigAlgKey];
|
||||
|
||||
if (sigAlgIndex == undefined) {
|
||||
console.error(`\x1b[31mInvalid signature algorithm: ${sigAlgKey}\x1b[0m`);
|
||||
throw new Error(`Invalid signature algorithm: ${sigAlgKey}`);
|
||||
}
|
||||
|
||||
const pubkeyChunked = splitToWords(BigInt(hexToDecimal(modulus)), n, k);
|
||||
return customHasher([sigAlgIndex, ...pubkeyChunked]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user