From b37307a45f2439780c4e0990f103385f0c268813 Mon Sep 17 00:00:00 2001 From: Amy Yan Date: Mon, 2 Sep 2024 14:50:11 +1000 Subject: [PATCH] feat: implemented ClaimNetworkAccess and ClaimNetworkAuthority in preparation for network segregation wip --- src/PolykeyAgent.ts | 5 ++ src/bootstrap/utils.ts | 2 +- src/claims/payloads/claimNetworkAccess.ts | 17 ++++- src/claims/payloads/index.ts | 2 +- src/config.ts | 6 ++ src/nodes/NodeConnection.ts | 1 - src/nodes/NodeManager.ts | 73 +++++++------------ src/nodes/agent/errors.ts | 2 +- .../agent/handlers/NodesClaimNetworkVerify.ts | 10 +-- .../handlers/nodesClaimNetworkVerify.test.ts | 58 ++++++++------- 10 files changed, 90 insertions(+), 86 deletions(-) diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index 702887144..337434b82 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -52,6 +52,7 @@ import * as workersUtils from './workers/utils'; import * as clientMiddleware from './client/middleware'; import clientServerManifest from './client/handlers'; import agentServerManifest from './nodes/agent/handlers'; + /** * Optional configuration for `PolykeyAgent`. */ @@ -61,6 +62,7 @@ type PolykeyAgentOptions = { clientServicePort: number; agentServiceHost: string; agentServicePort: number; + network: string; seedNodes: SeedNodes; workers: number; ipv6Only: boolean; @@ -160,6 +162,7 @@ class PolykeyAgent { agentServiceHost: config.defaultsUser.agentServiceHost, agentServicePort: config.defaultsUser.agentServicePort, seedNodes: config.defaultsUser.seedNodes, + network: config.defaultsUser.network, workers: config.defaultsUser.workers, ipv6Only: config.defaultsUser.ipv6Only, keys: { @@ -687,6 +690,7 @@ class PolykeyAgent { groups: Array; port: number; }; + network: string; seedNodes: SeedNodes; }>; workers?: number; @@ -705,6 +709,7 @@ class PolykeyAgent { groups: config.defaultsSystem.mdnsGroups, port: config.defaultsSystem.mdnsPort, }, + network: config.defaultsUser.network, seedNodes: config.defaultsUser.seedNodes, }); // Register event handlers diff --git a/src/bootstrap/utils.ts b/src/bootstrap/utils.ts index dd4d2b4ed..d6cd2e66a 100644 --- a/src/bootstrap/utils.ts +++ b/src/bootstrap/utils.ts @@ -30,7 +30,7 @@ import * as utils from '../utils'; import * as errors from '../errors'; /** - * Bootstraps the Node Path + * Bootstraps the Node Path` */ async function bootstrapState({ // Required parameters diff --git a/src/claims/payloads/claimNetworkAccess.ts b/src/claims/payloads/claimNetworkAccess.ts index 2af7f1400..0be8a340e 100644 --- a/src/claims/payloads/claimNetworkAccess.ts +++ b/src/claims/payloads/claimNetworkAccess.ts @@ -1,6 +1,7 @@ import type { Claim, SignedClaim } from '../types'; import type { NodeIdEncoded } from '../../ids/types'; import type { SignedTokenEncoded } from '../../tokens/types'; +import * as tokensSchema from '../../tokens/schemas'; import * as ids from '../../ids'; import * as claimsUtils from '../utils'; import * as tokensUtils from '../../tokens/utils'; @@ -14,7 +15,8 @@ interface ClaimNetworkAccess extends Claim { typ: 'ClaimNetworkAccess'; iss: NodeIdEncoded; sub: NodeIdEncoded; - signedClaimNetworkAuthorityEncoded: SignedTokenEncoded; + network: string; + signedClaimNetworkAuthorityEncoded?: SignedTokenEncoded; } function assertClaimNetworkAccess( @@ -45,7 +47,18 @@ function assertClaimNetworkAccess( ); } if ( - claimNetworkAccess['signedClaimNetworkAuthorityEncoded'] == null + claimNetworkAccess['network'] == null || + typeof claimNetworkAccess['network'] !== 'string' + ) { + throw new validationErrors.ErrorParse( + '`network` property must be a string', + ); + } + if ( + claimNetworkAccess['signedClaimNetworkAuthorityEncoded'] != null && + !tokensSchema.validateSignedTokenEncoded( + claimNetworkAccess['signedClaimNetworkAuthorityEncoded'], + ) ) { throw new validationErrors.ErrorParse( '`signedClaimNetworkAuthorityEncoded` property must be an encoded signed token', diff --git a/src/claims/payloads/index.ts b/src/claims/payloads/index.ts index 39097b48d..52c955be6 100644 --- a/src/claims/payloads/index.ts +++ b/src/claims/payloads/index.ts @@ -1,3 +1,3 @@ export * from './claimLinkIdentity'; export * from './claimLinkNode'; -export * from './claimNetworkNode'; +export * from './claimNetworkAccess'; diff --git a/src/config.ts b/src/config.ts index 393ce8117..b5fbac37d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -383,6 +383,12 @@ const config = { */ agentServiceHost: '::', agentServicePort: 0, + /** + * Hostname of network to connect to. + * + * This is defaulted to 'mainnet.polykey.com'. + */ + network: 'mainnet.polykey.com', /** * Seed nodes. * diff --git a/src/nodes/NodeConnection.ts b/src/nodes/NodeConnection.ts index 8b059f511..095379c06 100644 --- a/src/nodes/NodeConnection.ts +++ b/src/nodes/NodeConnection.ts @@ -25,7 +25,6 @@ import * as nodesUtils from '../nodes/utils'; import { never } from '../utils'; import config from '../config'; import * as networkUtils from '../network/utils'; -import * as keysUtils from '../keys/utils'; type AgentClientManifest = typeof agentClientManifest; diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index 06d41bce3..4f0b3747f 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -50,6 +50,8 @@ import * as nodesEvents from './events'; import * as nodesErrors from './errors'; import * as agentErrors from './agent/errors'; import NodeConnectionQueue from './NodeConnectionQueue'; +import { assertClaimNetworkAuthority } from '../claims/payloads/claimNetworkAuthority'; +import { assertClaimNetworkAccess } from '../claims/payloads/claimNetworkAccess'; import Token from '../tokens/Token'; import * as keysUtils from '../keys/utils'; import * as tasksErrors from '../tasks/errors'; @@ -58,8 +60,6 @@ import * as claimsErrors from '../claims/errors'; import * as utils from '../utils/utils'; import config from '../config'; import * as networkUtils from '../network/utils'; -import { ClaimNetworkAccess, assertClaimNetworkAccess } from '../claims/payloads/claimNetworkAccess'; -import { ClaimNetworkAuthority, assertClaimNetworkAuthority } from '@/claims/payloads/claimNetworkAuthority'; const abortEphemeralTaskReason = Symbol('abort ephemeral task reason'); const abortSingletonTaskReason = Symbol('abort singleton task reason'); @@ -263,43 +263,6 @@ class NodeManager { cause: new AggregateError(failedConnectionErrors), }, ); - } else { - // Wip: We should ideally take the fastest connection and use it here for node signing. - const conn = successfulConnections[0].value; - await this.sigchain.addClaim( - { - typ: 'ClaimNetworkNode', - iss: nodesUtils.encodeNodeId(conn.nodeId), - sub: nodesUtils.encodeNodeId(this.keyRing.getNodeId()), - }, - undefined, - async (token) => { - const halfSignedClaim = token.toSigned(); - const halfSignedClaimEncoded = - claimsUtils.generateSignedClaim(halfSignedClaim); - const receivedClaim = - await conn.rpcClient.methods.nodesClaimNetworkSign({ - signedTokenEncoded: halfSignedClaimEncoded, - }); - const signedClaim = claimsUtils.parseSignedClaim( - receivedClaim.signedTokenEncoded, - ); - const fullySignedToken = Token.fromSigned(signedClaim); - // Check that the signatures are correct - const targetNodePublicKey = keysUtils.publicKeyFromNodeId( - conn.nodeId, - ); - if ( - !fullySignedToken.verifyWithPublicKey( - this.keyRing.keyPair.publicKey, - ) || - !fullySignedToken.verifyWithPublicKey(targetNodePublicKey) - ) { - throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); - } - return fullySignedToken; - }, - ); } if (ctx.signal.aborted) return; @@ -1575,7 +1538,18 @@ class NodeManager { ) { throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); } - const authorityToken = Token.fromEncoded(token.payload.signedClaimNetworkAuthorityEncoded); + if ( + token.payload.network === 'testnet.polykey.com' || + token.payload.network === 'mainnet.polykey.com' + ) { + return { success: true }; + } + if (token.payload.signedClaimNetworkAuthorityEncoded == null) { + throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); + } + const authorityToken = Token.fromEncoded( + token.payload.signedClaimNetworkAuthorityEncoded, + ); // Verify if the token is signed if ( token.payload.iss !== authorityToken.payload.sub || @@ -1597,15 +1571,18 @@ class NodeManager { for await (const [_, claim] of this.sigchain.getSignedClaims({})) { try { assertClaimNetworkAccess(claim.payload); - } - catch { + } catch { continue; } - const tokenNetworkAuthority = Token.fromEncoded(claim.payload.signedClaimNetworkAuthorityEncoded); + if (claim.payload.signedClaimNetworkAuthorityEncoded == null) { + throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); + } + const tokenNetworkAuthority = Token.fromEncoded( + claim.payload.signedClaimNetworkAuthorityEncoded, + ); try { assertClaimNetworkAuthority(tokenNetworkAuthority.payload); - } - catch { + } catch { continue; } // No need to check if local claims are correctly signed by an Network Authority. @@ -1613,7 +1590,7 @@ class NodeManager { authorityToken.verifyWithPublicKey( keysUtils.publicKeyFromNodeId( nodesUtils.decodeNodeId(claim.payload.iss)!, - ) + ), ) ) { success = true; @@ -1627,7 +1604,7 @@ class NodeManager { return { success: true, - } + }; } /** @@ -1687,7 +1664,7 @@ class NodeManager { ); } - // need to await node connection verification, if fail, need to reject connection. + // Need to await node connection verification, if fail, need to reject connection. // When adding a node we need to handle 3 cases // 1. The node already exists. We need to update it's last updated field diff --git a/src/nodes/agent/errors.ts b/src/nodes/agent/errors.ts index 9a97f8fbb..7ff9bba44 100644 --- a/src/nodes/agent/errors.ts +++ b/src/nodes/agent/errors.ts @@ -24,7 +24,7 @@ class ErrorNodesConnectionSignalRelayVerificationFailed< class ErrorNodesClaimNetworkVerificationFailed extends ErrorAgent { static description = 'Failed to verify claim network message'; - exitCode = sysexits.UNAVAILABLE + exitCode = sysexits.UNAVAILABLE; } export { diff --git a/src/nodes/agent/handlers/NodesClaimNetworkVerify.ts b/src/nodes/agent/handlers/NodesClaimNetworkVerify.ts index d73bc5ef8..f5eaa9886 100644 --- a/src/nodes/agent/handlers/NodesClaimNetworkVerify.ts +++ b/src/nodes/agent/handlers/NodesClaimNetworkVerify.ts @@ -4,15 +4,10 @@ import type { AgentRPCResponseResult, } from '../types'; import type NodeManager from '../../../nodes/NodeManager'; -import type { Host, Port } from '../../../network/types'; import type { JSONValue } from '../../../types'; import { UnaryHandler } from '@matrixai/rpc'; -import * as x509 from '@peculiar/x509'; import * as agentErrors from '../errors'; import * as agentUtils from '../utils'; -import { never } from '../../../utils'; -import * as keysUtils from '../../../keys/utils'; -import * as ids from '../../../ids'; class NodesClaimNetworkVerify extends UnaryHandler< { @@ -30,7 +25,10 @@ class NodesClaimNetworkVerify extends UnaryHandler< if (requestingNodeId == null) { throw new agentErrors.ErrorAgentNodeIdMissing(); } - return this.container.nodeManager.handleVerifyClaimNetwork(requestingNodeId, input); + return this.container.nodeManager.handleVerifyClaimNetwork( + requestingNodeId, + input, + ); }; } diff --git a/tests/nodes/agent/handlers/nodesClaimNetworkVerify.test.ts b/tests/nodes/agent/handlers/nodesClaimNetworkVerify.test.ts index a7b14ba24..4859ed36f 100644 --- a/tests/nodes/agent/handlers/nodesClaimNetworkVerify.test.ts +++ b/tests/nodes/agent/handlers/nodesClaimNetworkVerify.test.ts @@ -1,8 +1,9 @@ import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type { AgentClaimMessage } from '@/nodes/agent/types'; import type { NodeId } from '@/ids'; -import type { ClaimLinkNode } from '@/claims/payloads'; import type { KeyPair } from '@/keys/types'; +import type { SignedTokenEncoded } from '@/tokens/types'; +import type { ClaimNetworkAuthority } from '@/claims/payloads/claimNetworkAuthority'; +import type { ClaimNetworkAccess } from '@/claims/payloads/claimNetworkAccess'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -10,10 +11,11 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { QUICClient, QUICServer, events as quicEvents } from '@matrixai/quic'; import { DB } from '@matrixai/db'; import { RPCClient, RPCServer } from '@matrixai/rpc'; +import { nodesClaimNetworkVerify } from '@/nodes/agent/callers'; +import { Token } from '@/tokens'; import Sigchain from '@/sigchain/Sigchain'; import KeyRing from '@/keys/KeyRing'; import NodeGraph from '@/nodes/NodeGraph'; -import { nodesClaimNetworkVerify } from '@/nodes/agent/callers'; import NodesClaimNetworkVerify from '@/nodes/agent/handlers/NodesClaimNetworkVerify'; import ACL from '@/acl/ACL'; import NodeManager from '@/nodes/NodeManager'; @@ -21,14 +23,9 @@ import GestaltGraph from '@/gestalts/GestaltGraph'; import TaskManager from '@/tasks/TaskManager'; import * as keysUtils from '@/keys/utils'; import * as claimsUtils from '@/claims/utils'; -import { Token } from '@/tokens'; import * as nodesUtils from '@/nodes/utils'; -import { generateKeyPair } from '@/keys/utils/generate'; import * as networkUtils from '@/network/utils'; import * as tlsTestsUtils from '../../../utils/tls'; -import { SignedTokenEncoded } from '@/tokens/types'; -import { ClaimNetworkAuthority } from '@/claims/payloads/claimNetworkAuthority'; -import { ClaimNetworkAccess } from '@/claims/payloads/claimNetworkAccess'; describe('nodesClaimNetworkVerify', () => { const logger = new Logger('nodesClaimNetworkVerify test', LogLevel.WARN, [ @@ -203,7 +200,7 @@ describe('nodesClaimNetworkVerify', () => { logger, }); - clientKeyPair = generateKeyPair(); + clientKeyPair = keysUtils.generateKeyPair(); localNodeId = keysUtils.publicKeyToNodeId(clientKeyPair.publicKey); const tlsConfigClient = await tlsTestsUtils.createTLSConfig(clientKeyPair); @@ -223,13 +220,14 @@ describe('nodesClaimNetworkVerify', () => { logger, }); - authorityKeyPair = generateKeyPair(); + authorityKeyPair = keysUtils.generateKeyPair(); authorityNodeId = keysUtils.publicKeyToNodeId(authorityKeyPair.publicKey); - seedKeyPair = generateKeyPair(); + seedKeyPair = keysUtils.generateKeyPair(); seedNodeId = keysUtils.publicKeyToNodeId(seedKeyPair.publicKey); - const authorityClaimId = claimsUtils.createClaimIdGenerator(authorityNodeId)(); + const authorityClaimId = + claimsUtils.createClaimIdGenerator(authorityNodeId)(); const authorityClaim: ClaimNetworkAuthority = { - typ: "ClaimNetworkAuthority", + typ: 'ClaimNetworkAuthority', iss: nodesUtils.encodeNodeId(authorityNodeId), sub: nodesUtils.encodeNodeId(seedNodeId), jti: claimsUtils.encodeClaimId(authorityClaimId), @@ -238,20 +236,27 @@ describe('nodesClaimNetworkVerify', () => { seq: 0, prevDigest: null, prevClaimId: null, - } + }; const authorityToken = Token.fromPayload(authorityClaim); authorityToken.signWithPrivateKey(authorityKeyPair.privateKey); authorityToken.signWithPrivateKey(seedKeyPair.privateKey); - signedClaimNetworkAuthorityEncoded = claimsUtils.generateSignedClaim(authorityToken.toSigned()) - await sigchain.addClaim({ - typ: "ClaimNetworkAccess", - iss: nodesUtils.encodeNodeId(seedNodeId), - sub: nodesUtils.encodeNodeId(remoteNodeId), - signedClaimNetworkAuthorityEncoded, - }, new Date(), async (token) => { - token.signWithPrivateKey(seedKeyPair.privateKey); - return token; - }); + signedClaimNetworkAuthorityEncoded = claimsUtils.generateSignedClaim( + authorityToken.toSigned(), + ); + await sigchain.addClaim( + { + typ: 'ClaimNetworkAccess', + iss: nodesUtils.encodeNodeId(seedNodeId), + sub: nodesUtils.encodeNodeId(remoteNodeId), + signedClaimNetworkAuthorityEncoded, + network: '', + }, + new Date(), + async (token) => { + token.signWithPrivateKey(seedKeyPair.privateKey); + return token; + }, + ); }); afterEach(async () => { await taskManager.stop(); @@ -276,7 +281,7 @@ describe('nodesClaimNetworkVerify', () => { }); const accessClaimId = claimsUtils.createClaimIdGenerator(authorityNodeId)(); const accessClaim: ClaimNetworkAccess = { - typ: "ClaimNetworkAccess", + typ: 'ClaimNetworkAccess', iss: nodesUtils.encodeNodeId(seedNodeId), sub: nodesUtils.encodeNodeId(localNodeId), jti: claimsUtils.encodeClaimId(accessClaimId), @@ -286,7 +291,8 @@ describe('nodesClaimNetworkVerify', () => { prevDigest: null, prevClaimId: null, signedClaimNetworkAuthorityEncoded, - } + network: '', + }; const accessToken = Token.fromPayload(accessClaim); accessToken.signWithPrivateKey(seedKeyPair.privateKey); accessToken.signWithPrivateKey(clientKeyPair.privateKey);