Skip to content

Commit

Permalink
fix: Rename PublicNodeConnectionParams to NodeConnectionParams (#504
Browse files Browse the repository at this point in the history
  • Loading branch information
gnarea authored Aug 22, 2022
1 parent eb268d3 commit bef264d
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 74 deletions.
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
18 changes: 4 additions & 14 deletions src/lib/crypto_wrappers/x509/Certificate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
19 changes: 7 additions & 12 deletions src/lib/crypto_wrappers/x509/Certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,20 +30,20 @@ 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();

const sequence = derDeserialize(serialization);
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();

Expand All @@ -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();

Expand All @@ -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();

Expand All @@ -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();

Expand Down Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -138,51 +140,49 @@ 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})`,
),
);
});

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/,
);
});

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) }),
),
).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) }),
Expand All @@ -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),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PublicNodeConnectionParams> {
const result = verifySchema(serialization, PublicNodeConnectionParams.SCHEMA);
export class NodeConnectionParams {
public static async deserialize(serialization: ArrayBuffer): Promise<NodeConnectionParams> {
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})`,
);
}
Expand All @@ -35,15 +33,15 @@ 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',
);
}

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;
Expand All @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion src/lib/nodes/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import RelaynetError from '../RelaynetError';

export class NodeError extends RelaynetError {}

export class InvalidPublicNodeConnectionParams extends NodeError {}
export class InvalidNodeConnectionParams extends NodeError {}

0 comments on commit bef264d

Please sign in to comment.