diff --git a/src/index.ts b/src/index.ts index 3b2a832d6..2e6888783 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,6 +106,6 @@ export { PrivateInternetGatewayChannel } from './lib/nodes/channels/PrivateInter //endregion export { NodeCryptoOptions } from './lib/nodes/NodeCryptoOptions'; -export { PublicNodeConnectionParams } from './lib/nodes/PublicNodeConnectionParams'; +export { NodeConnectionParams } from './lib/nodes/NodeConnectionParams'; export * from './lib/nodes/errors'; export * from './lib/internetAddressing'; diff --git a/src/lib/crypto_wrappers/x509/Certificate.spec.ts b/src/lib/crypto_wrappers/x509/Certificate.spec.ts index 5a599ddd5..fa2a264d6 100644 --- a/src/lib/crypto_wrappers/x509/Certificate.spec.ts +++ b/src/lib/crypto_wrappers/x509/Certificate.spec.ts @@ -128,27 +128,17 @@ describe('issue()', () => { }); test('should generate a positive serial number', async () => { - let anySignFlipped = false; for (let index = 0; index < 10; index++) { const cert = await Certificate.issue({ ...baseCertificateOptions, issuerPrivateKey: subjectKeyPair.privateKey, subjectPublicKey: subjectKeyPair.publicKey, }); - const serialNumberSerialized = new Uint8Array( - cert.pkijsCertificate.serialNumber.valueBlock.valueHex, - ); - if (serialNumberSerialized.length === 9) { - expect(serialNumberSerialized[0]).toEqual(0); - anySignFlipped = true; - } else { - expect(serialNumberSerialized).toHaveLength(8); - expect(serialNumberSerialized[0]).toBeGreaterThanOrEqual(0); - expect(serialNumberSerialized[0]).toBeLessThanOrEqual(127); - } + const serialNumberSerialized = cert.pkijsCertificate.serialNumber.valueBlock.valueHexView; + expect(serialNumberSerialized).toHaveLength(8); + expect(serialNumberSerialized[0]).toBeGreaterThanOrEqual(0); + expect(serialNumberSerialized[0]).toBeLessThanOrEqual(127); } - - expect(anySignFlipped).toBeTrue(); }); test('should create a certificate valid from now by default', async () => { diff --git a/src/lib/crypto_wrappers/x509/Certificate.ts b/src/lib/crypto_wrappers/x509/Certificate.ts index 58fa6a7db..d031a268e 100644 --- a/src/lib/crypto_wrappers/x509/Certificate.ts +++ b/src/lib/crypto_wrappers/x509/Certificate.ts @@ -280,19 +280,14 @@ export default class Certificate { } function generatePositiveASN1Integer(): Integer { - const signedInteger = new Uint8Array(generateRandom64BitValue()); - - let unsignedInteger = signedInteger; - if (127 < signedInteger[0]) { - // The integer is negative, so let's flip the sign by prepending a 0x00 octet. See: - // https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-integer - unsignedInteger = new Uint8Array(signedInteger.byteLength + 1); - unsignedInteger.set(signedInteger, 1); // Skip the first octet, leaving it as 0x00 - } + const potentiallySignedInteger = new Uint8Array(generateRandom64BitValue()); + + // ASN.1 BER/DER INTEGER uses two's complement with big endian, so we ensure the integer is + // positive by keeping the leftmost octet below 128. + const positiveInteger = new Uint8Array(potentiallySignedInteger); + positiveInteger.set([Math.min(potentiallySignedInteger[0], 127)], 0); - return new Integer({ - valueHex: unsignedInteger, - } as any); + return new Integer({ valueHex: positiveInteger }); } //region Extensions diff --git a/src/lib/nodes/PublicNodeConnectionParams.spec.ts b/src/lib/nodes/NodeConnectionParams.spec.ts similarity index 74% rename from src/lib/nodes/PublicNodeConnectionParams.spec.ts rename to src/lib/nodes/NodeConnectionParams.spec.ts index ca723fc2e..747f732da 100644 --- a/src/lib/nodes/PublicNodeConnectionParams.spec.ts +++ b/src/lib/nodes/NodeConnectionParams.spec.ts @@ -10,10 +10,10 @@ import { generateRSAKeyPair, } from '../crypto_wrappers/keys'; import { SessionKey } from '../SessionKey'; -import { InvalidPublicNodeConnectionParams } from './errors'; -import { PublicNodeConnectionParams } from './PublicNodeConnectionParams'; +import { InvalidNodeConnectionParams } from './errors'; +import { NodeConnectionParams } from './NodeConnectionParams'; -const PUBLIC_ADDRESS = 'example.com'; +const INTERNET_ADDRESS = 'example.com'; let identityKey: CryptoKey; let sessionKey: SessionKey; @@ -30,7 +30,7 @@ beforeAll(async () => { describe('serialize', () => { test('Internet address should be serialized', async () => { - const params = new PublicNodeConnectionParams(PUBLIC_ADDRESS, identityKey, sessionKey); + const params = new NodeConnectionParams(INTERNET_ADDRESS, identityKey, sessionKey); const serialization = await params.serialize(); @@ -38,12 +38,12 @@ describe('serialize', () => { expect(sequence).toBeInstanceOf(Sequence); expect((sequence as Sequence).valueBlock.value[0]).toHaveProperty( 'valueBlock.valueHex', - arrayBufferFrom(PUBLIC_ADDRESS), + arrayBufferFrom(INTERNET_ADDRESS), ); }); test('Identity key should be serialized', async () => { - const params = new PublicNodeConnectionParams(PUBLIC_ADDRESS, identityKey, sessionKey); + const params = new NodeConnectionParams(INTERNET_ADDRESS, identityKey, sessionKey); const serialization = await params.serialize(); @@ -57,7 +57,7 @@ describe('serialize', () => { describe('Session key', () => { test('Session key should be a CONSTRUCTED value', async () => { - const params = new PublicNodeConnectionParams(PUBLIC_ADDRESS, identityKey, sessionKey); + const params = new NodeConnectionParams(INTERNET_ADDRESS, identityKey, sessionKey); const serialization = await params.serialize(); @@ -67,7 +67,7 @@ describe('serialize', () => { }); test('Id should be serialized', async () => { - const params = new PublicNodeConnectionParams(PUBLIC_ADDRESS, identityKey, sessionKey); + const params = new NodeConnectionParams(INTERNET_ADDRESS, identityKey, sessionKey); const serialization = await params.serialize(); @@ -78,7 +78,7 @@ describe('serialize', () => { }); test('Public key should be serialized', async () => { - const params = new PublicNodeConnectionParams(PUBLIC_ADDRESS, identityKey, sessionKey); + const params = new NodeConnectionParams(INTERNET_ADDRESS, identityKey, sessionKey); const serialization = await params.serialize(); @@ -109,14 +109,15 @@ describe('deserialize', () => { ); }); - const malformedErrorMessage = 'Serialization is not a valid PublicNodeConnectionParams'; + const malformedErrorMessage = 'Serialization is not a valid NodeConnectionParams'; test('Serialization should be DER sequence', async () => { const invalidSerialization = arrayBufferFrom('nope.jpg'); - await expect( - PublicNodeConnectionParams.deserialize(invalidSerialization), - ).rejects.toThrowWithMessage(InvalidPublicNodeConnectionParams, malformedErrorMessage); + await expect(NodeConnectionParams.deserialize(invalidSerialization)).rejects.toThrowWithMessage( + InvalidNodeConnectionParams, + malformedErrorMessage, + ); }); test('Sequence should have at least three items', async () => { @@ -125,9 +126,10 @@ describe('deserialize', () => { new OctetString({ valueHex: arrayBufferFrom('whoops.jpg') }), ).toBER(); - await expect( - PublicNodeConnectionParams.deserialize(invalidSerialization), - ).rejects.toThrowWithMessage(InvalidPublicNodeConnectionParams, malformedErrorMessage); + await expect(NodeConnectionParams.deserialize(invalidSerialization)).rejects.toThrowWithMessage( + InvalidNodeConnectionParams, + malformedErrorMessage, + ); }); test('Internet address should be syntactically valid', async () => { @@ -138,8 +140,8 @@ describe('deserialize', () => { sessionKeySequence, ).toBER(); - await expect(PublicNodeConnectionParams.deserialize(invalidSerialization)).rejects.toThrow( - new InvalidPublicNodeConnectionParams( + await expect(NodeConnectionParams.deserialize(invalidSerialization)).rejects.toThrow( + new InvalidNodeConnectionParams( `Internet address is syntactically invalid (${invalidInternetAddress})`, ), ); @@ -147,17 +149,15 @@ describe('deserialize', () => { test('Identity key should be a valid RSA public key', async () => { const invalidSerialization = makeImplicitlyTaggedSequence( - new VisibleString({ value: PUBLIC_ADDRESS }), + new VisibleString({ value: INTERNET_ADDRESS }), new OctetString({ valueHex: sessionKeySerialized, // Wrong type of key }), sessionKeySequence, ).toBER(); - await expect( - PublicNodeConnectionParams.deserialize(invalidSerialization), - ).rejects.toThrowWithMessage( - InvalidPublicNodeConnectionParams, + await expect(NodeConnectionParams.deserialize(invalidSerialization)).rejects.toThrowWithMessage( + InvalidNodeConnectionParams, /^Identity key is not a valid RSA public key/, ); }); @@ -165,7 +165,7 @@ describe('deserialize', () => { describe('Session key', () => { test('SEQUENCE should contain at least two items', async () => { const invalidSerialization = makeImplicitlyTaggedSequence( - new VisibleString({ value: PUBLIC_ADDRESS }), + new VisibleString({ value: INTERNET_ADDRESS }), new OctetString({ valueHex: identityKeySerialized }), makeImplicitlyTaggedSequence( new OctetString({ valueHex: bufferToArray(sessionKey.keyId) }), @@ -173,16 +173,16 @@ describe('deserialize', () => { ).toBER(); await expect( - PublicNodeConnectionParams.deserialize(invalidSerialization), + NodeConnectionParams.deserialize(invalidSerialization), ).rejects.toThrowWithMessage( - InvalidPublicNodeConnectionParams, + InvalidNodeConnectionParams, 'Session key should have at least two items', ); }); test('Session key should be a valid ECDH public key', async () => { const invalidSerialization = makeImplicitlyTaggedSequence( - new VisibleString({ value: PUBLIC_ADDRESS }), + new VisibleString({ value: INTERNET_ADDRESS }), new OctetString({ valueHex: identityKeySerialized }), makeImplicitlyTaggedSequence( new OctetString({ valueHex: bufferToArray(sessionKey.keyId) }), @@ -193,21 +193,21 @@ describe('deserialize', () => { ).toBER(); await expect( - PublicNodeConnectionParams.deserialize(invalidSerialization), + NodeConnectionParams.deserialize(invalidSerialization), ).rejects.toThrowWithMessage( - InvalidPublicNodeConnectionParams, + InvalidNodeConnectionParams, /^Session key is not a valid ECDH public key/, ); }); }); test('Valid serialization should be deserialized', async () => { - const params = new PublicNodeConnectionParams(PUBLIC_ADDRESS, identityKey, sessionKey); + const params = new NodeConnectionParams(INTERNET_ADDRESS, identityKey, sessionKey); const serialization = await params.serialize(); - const paramsDeserialized = await PublicNodeConnectionParams.deserialize(serialization); + const paramsDeserialized = await NodeConnectionParams.deserialize(serialization); - expect(paramsDeserialized.internetAddress).toEqual(PUBLIC_ADDRESS); + expect(paramsDeserialized.internetAddress).toEqual(INTERNET_ADDRESS); await expect(derSerializePublicKey(paramsDeserialized.identityKey)).resolves.toEqual( Buffer.from(identityKeySerialized), ); diff --git a/src/lib/nodes/PublicNodeConnectionParams.ts b/src/lib/nodes/NodeConnectionParams.ts similarity index 79% rename from src/lib/nodes/PublicNodeConnectionParams.ts rename to src/lib/nodes/NodeConnectionParams.ts index d0e719224..0d60a507e 100644 --- a/src/lib/nodes/PublicNodeConnectionParams.ts +++ b/src/lib/nodes/NodeConnectionParams.ts @@ -10,23 +10,21 @@ import { derSerializePublicKey, } from '../crypto_wrappers/keys'; import { SessionKey } from '../SessionKey'; -import { InvalidPublicNodeConnectionParams } from './errors'; +import { InvalidNodeConnectionParams } from './errors'; -export class PublicNodeConnectionParams { - public static async deserialize(serialization: ArrayBuffer): Promise { - const result = verifySchema(serialization, PublicNodeConnectionParams.SCHEMA); +export class NodeConnectionParams { + public static async deserialize(serialization: ArrayBuffer): Promise { + const result = verifySchema(serialization, NodeConnectionParams.SCHEMA); if (!result.verified) { - throw new InvalidPublicNodeConnectionParams( - 'Serialization is not a valid PublicNodeConnectionParams', - ); + throw new InvalidNodeConnectionParams('Serialization is not a valid NodeConnectionParams'); } - const paramsASN1 = (result.result as any).PublicNodeConnectionParams; + const paramsASN1 = (result.result as any).NodeConnectionParams; const textDecoder = new TextDecoder(); const internetAddress = textDecoder.decode(paramsASN1.internetAddress.valueBlock.valueHex); if (!isValidDomain(internetAddress)) { - throw new InvalidPublicNodeConnectionParams( + throw new InvalidNodeConnectionParams( `Internet address is syntactically invalid (${internetAddress})`, ); } @@ -35,7 +33,7 @@ export class PublicNodeConnectionParams { try { identityKey = await derDeserializeRSAPublicKey(paramsASN1.identityKey.valueBlock.valueHex); } catch (err: any) { - throw new InvalidPublicNodeConnectionParams( + throw new InvalidNodeConnectionParams( new Error(err), // The original error could be a string 🤦 'Identity key is not a valid RSA public key', ); @@ -43,7 +41,7 @@ export class PublicNodeConnectionParams { const sessionKeySequence = paramsASN1.sessionKey as Sequence; if (sessionKeySequence.valueBlock.value.length < 2) { - throw new InvalidPublicNodeConnectionParams('Session key should have at least two items'); + throw new InvalidNodeConnectionParams('Session key should have at least two items'); } const sessionKeyId = (sessionKeySequence.valueBlock.value[0] as Primitive).valueBlock.valueHex; const sessionPublicKeyASN1 = sessionKeySequence.valueBlock.value[1] as Primitive; @@ -53,19 +51,19 @@ export class PublicNodeConnectionParams { sessionPublicKeyASN1.valueBlock.valueHex, ); } catch (err: any) { - throw new InvalidPublicNodeConnectionParams( + throw new InvalidNodeConnectionParams( new Error(err), // The original error could be a string 🤦 'Session key is not a valid ECDH public key', ); } - return new PublicNodeConnectionParams(internetAddress, identityKey, { + return new NodeConnectionParams(internetAddress, identityKey, { keyId: Buffer.from(sessionKeyId), publicKey: sessionPublicKey, }); } - private static readonly SCHEMA = makeHeterogeneousSequenceSchema('PublicNodeConnectionParams', [ + private static readonly SCHEMA = makeHeterogeneousSequenceSchema('NodeConnectionParams', [ new Primitive({ name: 'internetAddress' }), new Primitive({ name: 'identityKey' }), new Constructed({ diff --git a/src/lib/nodes/errors.ts b/src/lib/nodes/errors.ts index 87209948a..ff2b1af7d 100644 --- a/src/lib/nodes/errors.ts +++ b/src/lib/nodes/errors.ts @@ -4,4 +4,4 @@ import RelaynetError from '../RelaynetError'; export class NodeError extends RelaynetError {} -export class InvalidPublicNodeConnectionParams extends NodeError {} +export class InvalidNodeConnectionParams extends NodeError {}