From 81e75d1509d6f841f803c9f8406fd407bc8a7daa Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Mon, 10 Jun 2024 14:41:06 +0530 Subject: [PATCH 1/4] Add crypto hash functions to auto-utils package --- packages/auto-utils/__test__/crypto.test.ts | 16 +++++++++++ packages/auto-utils/src/crypto.ts | 31 +++++++++++++++++++++ packages/auto-utils/src/index.ts | 1 + 3 files changed, 48 insertions(+) create mode 100644 packages/auto-utils/__test__/crypto.test.ts create mode 100644 packages/auto-utils/src/crypto.ts diff --git a/packages/auto-utils/__test__/crypto.test.ts b/packages/auto-utils/__test__/crypto.test.ts new file mode 100644 index 0000000..636aa13 --- /dev/null +++ b/packages/auto-utils/__test__/crypto.test.ts @@ -0,0 +1,16 @@ +import { blake2b_256, stringToUint8Array } from '../src/crypto' + +describe('Verify crypto functions', () => { + test('Check blake2b_256 return the hash of the data', async () => { + const message = 'Hello, world!' + const message_bytes = stringToUint8Array(message) + const hash = blake2b_256(message_bytes) + expect(hash).toEqual('0xb5da441cfe72ae042ef4d2b17742907f675de4da57462d4c3609c2e2ed755970') + }) + + test('Check stringToUint8Array return the byte array of the string', async () => { + const message = 'Hello, world!' + const byteArray = stringToUint8Array(message) + expect(byteArray).toBeInstanceOf(Uint8Array) + }) +}) diff --git a/packages/auto-utils/src/crypto.ts b/packages/auto-utils/src/crypto.ts new file mode 100644 index 0000000..1dce5c7 --- /dev/null +++ b/packages/auto-utils/src/crypto.ts @@ -0,0 +1,31 @@ +import { blake2AsHex } from '@polkadot/util-crypto' + +/** + * Hashes the given data using BLAKE2b-256. + * + * @param data Uint8Array - The data to be hashed. + * @returns string - The BLAKE2b-256 hash of the data as a hex string. + */ +export function blake2b_256(data: Uint8Array): string { + return blake2AsHex(data, 256) +} + +/** + * Converts a string to a Uint8Array using UTF-8 encoding. + * + * This function uses the TextEncoder API to convert a plain string into its equivalent byte array + * representation in UTF-8 format. It is useful for scenarios where string data needs to be processed + * in a binary format, such as hashing or cryptographic operations. + * + * @param text The string to be converted into a byte array. + * @returns Uint8Array - The UTF-8 encoded byte array representation of the input string. + * + * @example + * const text = "Hello, world!"; + * const byteArray = stringToUint8Array(text); + * console.log(byteArray); // Outputs the byte array of the string + */ +export function stringToUint8Array(text: string): Uint8Array { + const encoder = new TextEncoder() // Create a new TextEncoder instance + return encoder.encode(text) // Encode the string to a Uint8Array using UTF-8 encoding +} diff --git a/packages/auto-utils/src/index.ts b/packages/auto-utils/src/index.ts index d38eecf..520949a 100644 --- a/packages/auto-utils/src/index.ts +++ b/packages/auto-utils/src/index.ts @@ -1,4 +1,5 @@ export * from './api' +export * from './crypto' export * from './network' export * from './read' export * from './save' From b081b2d85cf766d77323bb6a5aea91985175798a Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Mon, 10 Jun 2024 17:23:08 +0530 Subject: [PATCH 2/4] Add the AlgorithmIdentifier, ASN encoding, decoding related functions --- packages/auto-id/package.json | 5 +- packages/auto-id/src/index.ts | 1 + packages/auto-id/src/utils.ts | 77 +++++++++++++++++++++++++ packages/auto-id/tests/issuer.cert.der | Bin 0 -> 981 bytes packages/auto-id/tests/utils.test.ts | 29 ++++++++++ yarn.lock | 77 +++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 packages/auto-id/src/utils.ts create mode 100644 packages/auto-id/tests/issuer.cert.der create mode 100644 packages/auto-id/tests/utils.test.ts diff --git a/packages/auto-id/package.json b/packages/auto-id/package.json index 558810b..4fd8eb9 100644 --- a/packages/auto-id/package.json +++ b/packages/auto-id/package.json @@ -10,7 +10,10 @@ }, "dependencies": { "@autonomys/auto-utils": "workspace:*", - "@types/node": "^20.12.12" + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/asn1-x509": "^2.3.8", + "@types/node": "^20.12.12", + "asn1-ts": "^8.0.2" }, "files": [ "dist", diff --git a/packages/auto-id/src/index.ts b/packages/auto-id/src/index.ts index 310afd4..5527667 100644 --- a/packages/auto-id/src/index.ts +++ b/packages/auto-id/src/index.ts @@ -1 +1,2 @@ export * from './keyManagement' +export * from './utils' diff --git a/packages/auto-id/src/utils.ts b/packages/auto-id/src/utils.ts new file mode 100644 index 0000000..a770215 --- /dev/null +++ b/packages/auto-id/src/utils.ts @@ -0,0 +1,77 @@ +import { + ASN1Construction, + ASN1TagClass, + ASN1UniversalType, + DERElement, + ObjectIdentifier, +} from 'asn1-ts' + +/** + * Represents an ASN.1 AlgorithmIdentifier structure commonly used in cryptographic protocols. + * This class handles the construction and DER encoding of an algorithm identifier, which typically + * consists of an algorithm OID and optional parameters. + */ +class AlgorithmIdentifier { + public algorithm: ObjectIdentifier + public parameters: null + + /** + * Creates an instance of AlgorithmIdentifier. + * + * @param algorithm The ObjectIdentifier of the algorithm. + * @param parameters The parameters of the algorithm, generally null in many cryptographic uses. + */ + constructor(algorithm: ObjectIdentifier, parameters: null = null) { + this.algorithm = algorithm + this.parameters = parameters + } + + /** + * Encodes this AlgorithmIdentifier into its DER (Distinguished Encoding Rules) format. + * + * @returns Uint8Array containing the DER encoded bytes of the AlgorithmIdentifier. + */ + public toDER(): Uint8Array { + const sequenceElement = new DERElement( + ASN1TagClass.universal, + ASN1Construction.constructed, + ASN1UniversalType.sequence, + ) + + const oidElement = new DERElement( + ASN1TagClass.universal, + ASN1Construction.primitive, + ASN1UniversalType.objectIdentifier, + ) + oidElement.objectIdentifier = this.algorithm + + const nullElement = new DERElement( + ASN1TagClass.universal, + ASN1Construction.primitive, + ASN1UniversalType.nill, + ) + + sequenceElement.sequence = [oidElement, nullElement] + + return sequenceElement.toBytes() + } +} + +/** + * Encodes a given string representation of an OID into its DER format. + * This function is specifically used to encode signature algorithm OIDs. + * + * @param oid The string representation of the ObjectIdentifier to be encoded. + * @returns Uint8Array containing the DER encoded OID. + * @example + * ```ts + * const oid = '1.2.840.113549.1.1.11' // Example OID for SHA-256 with RSA Encryption + * const derEncodedOID = derEncodeSignatureAlgorithmOID(oid) + * console.log(new Uint8Array(derEncodedOID)) // Logs the DER encoded bytes + * ``` + */ +export function derEncodeSignatureAlgorithmOID(oid: string): Uint8Array { + const numbers = oid.split('.').map((n) => parseInt(n, 10)) // Convert the string parts to numbers + const algorithmIdentifier = new AlgorithmIdentifier(new ObjectIdentifier(numbers)) + return algorithmIdentifier.toDER() +} diff --git a/packages/auto-id/tests/issuer.cert.der b/packages/auto-id/tests/issuer.cert.der new file mode 100644 index 0000000000000000000000000000000000000000..c10a4217f5ae0c9b4bf12c334e4f3f6fa3e692c6 GIT binary patch literal 981 zcmXqLV!mk5#I$n(GZP~dlL*_}uAi%nzQ>wqzJ6wRSHZJOsovOtmyJ`a&7XDOfke1@xLxzoGxJhYi!$@l4P_0aL88n&B4Md18Hr_;iNy-RK#9bn)RdG=LwN&P zxDz-T#RM`k9m-(p^iopG4CKUljZ6%TjSLKpjg3r9qJUgeB<^5zaucHxa=*0AF)}hNmJk+>Ol$YlQ+qA{zBw#rf{>bo!iSLLbsAIq_$E$~x+8o2cI7th zfU~K3Z^S>D%%AeSsBV3=hWMLrv+sZEnIC?Jy~A~th|!F1|0l@rIMq_UD?YC1O-p9` zuWq*HIm}vhr!q>O{VgFfclzwWTgAWUh5V>KZv0SeCTDTIgigbS{9l~!7j;d(Ev}!n?x}(& z!;h0G|6cbo8_(dCsi;^tFZN62V3eVyIUJyJ6Rr^W|JaZzxfzfK)>$ff1fVeAIzMvXw{s$^Q>2cwy*8f zK6sCduY`5m^!;}wHSfn)*k(U``MO}W`-&BLAH0iRW*Tk`ZTDQK{!YR_)aNY!`U4lA z6vvePyUV#NFcp h=e;QD={3uJ7oIv^X)XRgU%l!4S!3?)&F; { + test('DER encode signature algorithm OID from a certificate', () => { + // Load the certificate from a file + const certPath = 'tests/issuer.cert.der' + const certData = fs.readFileSync(certPath) + + // Load and parse the certificate + const cert = AsnParser.parse(certData, Certificate) + + // Extract the OID of the signature algorithm + const signatureAlgorithmOID = cert.signatureAlgorithm.algorithm + + // DER encode the OID + const derEncodedOID = derEncodeSignatureAlgorithmOID(signatureAlgorithmOID.toString()) + + // Expected DER encoded OID from a known good implementation (example hex string) + const fromRustImplementation = new Uint8Array( + Buffer.from('300d06092a864886f70d01010b0500', 'hex'), + ) + + // Compare the DER encoded OID with the expected result + expect(new Uint8Array(derEncodedOID)).toEqual(fromRustImplementation) + }) +}) diff --git a/yarn.lock b/yarn.lock index 8570173..b829603 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32,8 +32,11 @@ __metadata: resolution: "@autonomys/auto-id@workspace:packages/auto-id" dependencies: "@autonomys/auto-utils": "workspace:*" + "@peculiar/asn1-schema": "npm:^2.3.8" + "@peculiar/asn1-x509": "npm:^2.3.8" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^20.12.12" + asn1-ts: "npm:^8.0.2" jest: "npm:^29.7.0" ts-jest: "npm:^29.1.4" ts-node: "npm:^10.9.2" @@ -914,6 +917,30 @@ __metadata: languageName: node linkType: hard +"@peculiar/asn1-schema@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-schema@npm:2.3.8" + dependencies: + asn1js: "npm:^3.0.5" + pvtsutils: "npm:^1.3.5" + tslib: "npm:^2.6.2" + checksum: 10c0/65f16b2a7eb91365b6dac47730ffcad4617ef04b821e0a4286c379ac7283588b0a6744032ee686e0914a0886c2a055108ed945b9c4d22821a3b123640b61f3b2 + languageName: node + linkType: hard + +"@peculiar/asn1-x509@npm:^2.3.8": + version: 2.3.8 + resolution: "@peculiar/asn1-x509@npm:2.3.8" + dependencies: + "@peculiar/asn1-schema": "npm:^2.3.8" + asn1js: "npm:^3.0.5" + ipaddr.js: "npm:^2.1.0" + pvtsutils: "npm:^1.3.5" + tslib: "npm:^2.6.2" + checksum: 10c0/75d217e679cb0baa4d3e68eed0a7981f5eed1d5fe1812852987a29bab293995b694d15b3784b06fbd688162a7098915dc8fbbd167331eaaedb3f4951620af727 + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -1798,6 +1825,26 @@ __metadata: languageName: node linkType: hard +"asn1-ts@npm:^8.0.2": + version: 8.0.2 + resolution: "asn1-ts@npm:8.0.2" + dependencies: + tslib: "npm:^2.4.1" + checksum: 10c0/37ca66c319730ae7a2762642a3bb867b298a1ad721a11f859f900332b39117497fd337ba16e7d4c4f7dc9bd6f5e577d8d49c07edcc7fdebeff1d42d7a66a3112 + languageName: node + linkType: hard + +"asn1js@npm:^3.0.5": + version: 3.0.5 + resolution: "asn1js@npm:3.0.5" + dependencies: + pvtsutils: "npm:^1.3.2" + pvutils: "npm:^1.1.3" + tslib: "npm:^2.4.0" + checksum: 10c0/bb8eaf4040c8f49dd475566874986f5976b81bae65a6b5526e2208a13cdca323e69ce297bcd435fdda3eb6933defe888e71974d705b6fcb14f2734a907f8aed4 + languageName: node + linkType: hard + "babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -2937,6 +2984,13 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:^2.1.0": + version: 2.2.0 + resolution: "ipaddr.js@npm:2.2.0" + checksum: 10c0/e4ee875dc1bd92ac9d27e06cfd87cdb63ca786ff9fd7718f1d4f7a8ef27db6e5d516128f52d2c560408cbb75796ac2f83ead669e73507c86282d45f84c5abbb6 + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -4273,6 +4327,22 @@ __metadata: languageName: node linkType: hard +"pvtsutils@npm:^1.3.2, pvtsutils@npm:^1.3.5": + version: 1.3.5 + resolution: "pvtsutils@npm:1.3.5" + dependencies: + tslib: "npm:^2.6.1" + checksum: 10c0/d425aed316907e0b447a459bfb97c55d22270c3cfdba5a07ec90da0737b0e40f4f1771a444636f85bb6a453de90ff8c6b5f4f6ddba7597977166af49974b4534 + languageName: node + linkType: hard + +"pvutils@npm:^1.1.3": + version: 1.1.3 + resolution: "pvutils@npm:1.1.3" + checksum: 10c0/23489e6b3c76b6afb6964a20f891d6bef092939f401c78bba186b2bfcdc7a13904a0af0a78f7933346510f8c1228d5ab02d3c80e968fd84d3c76ff98d8ec9aac + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -4796,6 +4866,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.6.1": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a + languageName: node + linkType: hard + "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" From d76a8cfb53dd4431f06474393304a63f9572a4f8 Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Mon, 10 Jun 2024 19:10:34 +0530 Subject: [PATCH 3/4] fix suggestion --- packages/auto-id/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/auto-id/package.json b/packages/auto-id/package.json index 4fd8eb9..fee2994 100644 --- a/packages/auto-id/package.json +++ b/packages/auto-id/package.json @@ -12,7 +12,6 @@ "@autonomys/auto-utils": "workspace:*", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", - "@types/node": "^20.12.12", "asn1-ts": "^8.0.2" }, "files": [ @@ -20,10 +19,11 @@ "README.md" ], "devDependencies": { + "@types/node": "^20.12.12", "@types/jest": "^29.5.12", "jest": "^29.7.0", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "typescript": "^5.4.5" } -} +} \ No newline at end of file From 414b7ce996d83ebe5c7d23988c4f3eb16467f32d Mon Sep 17 00:00:00 2001 From: Abhijit Roy Date: Tue, 11 Jun 2024 21:18:41 +0530 Subject: [PATCH 4/4] Fixed the function considering the new library Old ASN library is replaced by the new one because it has high no. of downloads/week although the last commit is 2 years old. --- packages/auto-id/package.json | 6 +- packages/auto-id/src/utils.ts | 82 +++++----------------------- packages/auto-id/tests/utils.test.ts | 15 ++--- 3 files changed, 25 insertions(+), 78 deletions(-) diff --git a/packages/auto-id/package.json b/packages/auto-id/package.json index fee2994..c3a70e4 100644 --- a/packages/auto-id/package.json +++ b/packages/auto-id/package.json @@ -12,18 +12,18 @@ "@autonomys/auto-utils": "workspace:*", "@peculiar/asn1-schema": "^2.3.8", "@peculiar/asn1-x509": "^2.3.8", - "asn1-ts": "^8.0.2" + "asn1js": "^3.0.5" }, "files": [ "dist", "README.md" ], "devDependencies": { - "@types/node": "^20.12.12", "@types/jest": "^29.5.12", + "@types/node": "^20.12.12", "jest": "^29.7.0", "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "typescript": "^5.4.5" } -} \ No newline at end of file +} diff --git a/packages/auto-id/src/utils.ts b/packages/auto-id/src/utils.ts index a770215..62093fe 100644 --- a/packages/auto-id/src/utils.ts +++ b/packages/auto-id/src/utils.ts @@ -1,77 +1,23 @@ -import { - ASN1Construction, - ASN1TagClass, - ASN1UniversalType, - DERElement, - ObjectIdentifier, -} from 'asn1-ts' - -/** - * Represents an ASN.1 AlgorithmIdentifier structure commonly used in cryptographic protocols. - * This class handles the construction and DER encoding of an algorithm identifier, which typically - * consists of an algorithm OID and optional parameters. - */ -class AlgorithmIdentifier { - public algorithm: ObjectIdentifier - public parameters: null - - /** - * Creates an instance of AlgorithmIdentifier. - * - * @param algorithm The ObjectIdentifier of the algorithm. - * @param parameters The parameters of the algorithm, generally null in many cryptographic uses. - */ - constructor(algorithm: ObjectIdentifier, parameters: null = null) { - this.algorithm = algorithm - this.parameters = parameters - } - - /** - * Encodes this AlgorithmIdentifier into its DER (Distinguished Encoding Rules) format. - * - * @returns Uint8Array containing the DER encoded bytes of the AlgorithmIdentifier. - */ - public toDER(): Uint8Array { - const sequenceElement = new DERElement( - ASN1TagClass.universal, - ASN1Construction.constructed, - ASN1UniversalType.sequence, - ) - - const oidElement = new DERElement( - ASN1TagClass.universal, - ASN1Construction.primitive, - ASN1UniversalType.objectIdentifier, - ) - oidElement.objectIdentifier = this.algorithm - - const nullElement = new DERElement( - ASN1TagClass.universal, - ASN1Construction.primitive, - ASN1UniversalType.nill, - ) - - sequenceElement.sequence = [oidElement, nullElement] - - return sequenceElement.toBytes() - } -} +import { ObjectIdentifier } from 'asn1js' /** * Encodes a given string representation of an OID into its DER format. * This function is specifically used to encode signature algorithm OIDs. * * @param oid The string representation of the ObjectIdentifier to be encoded. - * @returns Uint8Array containing the DER encoded OID. - * @example - * ```ts - * const oid = '1.2.840.113549.1.1.11' // Example OID for SHA-256 with RSA Encryption - * const derEncodedOID = derEncodeSignatureAlgorithmOID(oid) - * console.log(new Uint8Array(derEncodedOID)) // Logs the DER encoded bytes - * ``` + * @returns Uint8Array containing the DER encoded OID along with NULL params of X.509 signature algorithm. */ export function derEncodeSignatureAlgorithmOID(oid: string): Uint8Array { - const numbers = oid.split('.').map((n) => parseInt(n, 10)) // Convert the string parts to numbers - const algorithmIdentifier = new AlgorithmIdentifier(new ObjectIdentifier(numbers)) - return algorithmIdentifier.toDER() + const objectIdentifier = new ObjectIdentifier({ value: oid }) + const berArrayBuffer = objectIdentifier.toBER(false) + + // Typically, in X.509, the algorithm identifier is followed by parameters; for many algorithms, this is just NULL. + const nullParameter = [0x05, 0x00] // DER encoding for NULL + + // Calculate the total length including OID and NULL parameter + const totalLength = berArrayBuffer.byteLength + nullParameter.length + + const sequenceHeader = [0x30, totalLength] // 0x30 is the DER tag for SEQUENCE + + return new Uint8Array([...sequenceHeader, ...new Uint8Array(berArrayBuffer), ...nullParameter]) } diff --git a/packages/auto-id/tests/utils.test.ts b/packages/auto-id/tests/utils.test.ts index 67ee670..1b58b8d 100644 --- a/packages/auto-id/tests/utils.test.ts +++ b/packages/auto-id/tests/utils.test.ts @@ -1,4 +1,5 @@ import { AsnParser } from '@peculiar/asn1-schema' // A library to parse ASN.1 +// TODO: See why X509Certificate (from crypto) is not compatible argument. import { Certificate } from '@peculiar/asn1-x509' // Assuming X.509 certificate handling import fs from 'fs' import { derEncodeSignatureAlgorithmOID } from '../src/utils' @@ -16,14 +17,14 @@ describe('Verify crypto functions', () => { const signatureAlgorithmOID = cert.signatureAlgorithm.algorithm // DER encode the OID - const derEncodedOID = derEncodeSignatureAlgorithmOID(signatureAlgorithmOID.toString()) + const derEncodedOID = derEncodeSignatureAlgorithmOID(signatureAlgorithmOID) + // + // console.log(Buffer.from(derEncodedOID)) - // Expected DER encoded OID from a known good implementation (example hex string) - const fromRustImplementation = new Uint8Array( - Buffer.from('300d06092a864886f70d01010b0500', 'hex'), - ) + // Convert derEncodedOID to hex string for comparison + const derEncodedOIDHex = Buffer.from(derEncodedOID).toString('hex') - // Compare the DER encoded OID with the expected result - expect(new Uint8Array(derEncodedOID)).toEqual(fromRustImplementation) + // Expected DER encoded OID from the result of tests in https://github.com/subspace/subspace/blob/d875a5aac35c1732eec61ce4359782eff58ff6fc/domains/pallets/auto-id/src/tests.rs#L127 + expect(derEncodedOIDHex).toEqual('300d06092a864886f70d01010b0500') }) })