Skip to content

Commit

Permalink
wip: claimNetworkNode payload
Browse files Browse the repository at this point in the history
  • Loading branch information
amydevs committed Jul 25, 2024
1 parent 58d4f27 commit c69c560
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 3 deletions.
71 changes: 71 additions & 0 deletions src/claims/payloads/claimNetworkNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { Claim, SignedClaim } from '../types';
import type { NodeIdEncoded } from '../../ids/types';
import * as ids from '../../ids';
import * as claimsUtils from '../utils';
import * as tokensUtils from '../../tokens/utils';
import * as validationErrors from '../../validation/errors';
import * as utils from '../../utils';

/**
* Asserts that a node is apart of a network
*/
interface ClaimNetworkNode extends Claim {
typ: 'ClaimNetworkNode';
iss: NodeIdEncoded;
sub: NodeIdEncoded;
}

function assertClaimNetworkNode(
claimNetworkNode: unknown,
): asserts claimNetworkNode is ClaimNetworkNode {
if (!utils.isObject(claimNetworkNode)) {
throw new validationErrors.ErrorParse('must be POJO');
}
if (claimNetworkNode['typ'] !== 'ClaimNetworkNode') {
throw new validationErrors.ErrorParse(
'`typ` property must be `ClaimNetworkNode`',
);
}
if (
claimNetworkNode['iss'] == null ||
ids.decodeNodeId(claimNetworkNode['iss']) == null
) {
throw new validationErrors.ErrorParse(
'`iss` property must be an encoded node ID',
);
}
if (
claimNetworkNode['sub'] == null ||
ids.decodeNodeId(claimNetworkNode['sub']) == null
) {
throw new validationErrors.ErrorParse(
'`sub` property must be an encoded node ID',
);
}
}

function parseClaimNetworkNode(
claimNetworkNodeEncoded: unknown,
): ClaimNetworkNode {
const claimNetworkNode = claimsUtils.parseClaim(claimNetworkNodeEncoded);
assertClaimNetworkNode(claimNetworkNode);
return claimNetworkNode;
}

function parseSignedClaimNetworkNode(
signedClaimNetworkNodeEncoded: unknown,
): SignedClaim<ClaimNetworkNode> {
const signedClaim = tokensUtils.parseSignedToken(
signedClaimNetworkNodeEncoded,
);
assertClaimNetworkNode(signedClaim.payload);
return signedClaim as SignedClaim<ClaimNetworkNode>;
}

export {
assertClaimNetworkNode,
parseClaimNetworkNode,
parseSignedClaimNetworkNode,
};

export type { ClaimNetworkNode };
1 change: 1 addition & 0 deletions src/claims/payloads/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './claimLinkIdentity';
export * from './claimLinkNode';
export * from './claimNetworkNode';
1 change: 1 addition & 0 deletions src/nodes/NodeConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ 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;

Expand Down
98 changes: 95 additions & 3 deletions src/nodes/NodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {
ClaimIdEncoded,
SignedClaim,
} from '../claims/types';
import type { ClaimLinkNode } from '../claims/payloads';
import type { ClaimLinkNode, ClaimNetworkNode } from '../claims/payloads';
import type NodeConnection from '../nodes/NodeConnection';
import type {
AgentRPCRequestParams,
Expand Down Expand Up @@ -247,8 +247,8 @@ class NodeManager {
);
const successfulConnections = connectionResults.filter(
(r) => r.status === 'fulfilled',
).length;
if (successfulConnections === 0) {
) as Array<PromiseFulfilledResult<NodeConnection>>;
if (successfulConnections.length === 0) {
const failedConnectionErrors = connectionResults
.filter((r) => r.status === 'rejected')
.map((v) => {
Expand All @@ -260,6 +260,43 @@ 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.nodesNetworkSignClaim({
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;

Expand Down Expand Up @@ -1478,6 +1515,61 @@ class NodeManager {
});
}

public async handleClaimNetworkNode(
requestingNodeId: NodeId,
input: AgentRPCRequestParams<AgentClaimMessage>,
tran?: DBTransaction,
): Promise<AgentRPCResponseResult<AgentClaimMessage>> {
if (tran == null) {
return this.db.withTransactionF((tran) =>
this.handleClaimNetworkNode(requestingNodeId, input, tran),
);
}
const signedClaim = claimsUtils.parseSignedClaim(input.signedTokenEncoded);
const token = Token.fromSigned(signedClaim);
// Verify if the token is signed
if (
!token.verifyWithPublicKey(
keysUtils.publicKeyFromNodeId(requestingNodeId),
)
) {
throw new claimsErrors.ErrorSinglySignedClaimVerificationFailed();
}
// If verified, add your own signature to the received claim
token.signWithPrivateKey(this.keyRing.keyPair);
// Return the signed claim
const doublySignedClaim = token.toSigned();
const halfSignedClaimEncoded =
claimsUtils.generateSignedClaim(doublySignedClaim);
return {
signedTokenEncoded: halfSignedClaimEncoded,
};
}

public async handleVerifyClaimNetworkNode(
requestingNodeId: NodeId,
input: AgentRPCRequestParams<AgentClaimMessage>,
tran?: DBTransaction,
): Promise<AgentRPCResponseResult<AgentClaimMessage>> {
if (tran == null) {
return this.db.withTransactionF((tran) =>
this.handleVerifyClaimNetworkNode(requestingNodeId, input, tran),
);
}
const signedClaim = claimsUtils.parseSignedClaim(input.signedTokenEncoded);
const token = Token.fromSigned(signedClaim);
// Verify if the token is signed
if (
!token.verifyWithPublicKey(
keysUtils.publicKeyFromNodeId(requestingNodeId),
)
) {
throw new claimsErrors.ErrorSinglySignedClaimVerificationFailed();
}
// Need to get the seednode and test public keys against the claim
throw new Error();
}

/**
* Adds a node to the node graph. This assumes that you have already authenticated the node
* Updates the node if the node already exists
Expand Down
3 changes: 3 additions & 0 deletions src/nodes/agent/callers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import nodesClosestLocalNodesGet from './nodesClosestLocalNodesGet';
import nodesConnectionSignalFinal from './nodesConnectionSignalFinal';
import nodesConnectionSignalInitial from './nodesConnectionSignalInitial';
import nodesCrossSignClaim from './nodesCrossSignClaim';
import nodesNetworkSignClaim from './nodesNetworkSignClaim';
import notificationsSend from './notificationsSend';
import vaultsGitInfoGet from './vaultsGitInfoGet';
import vaultsGitPackGet from './vaultsGitPackGet';
Expand All @@ -19,6 +20,7 @@ const manifestClient = {
nodesConnectionSignalFinal,
nodesConnectionSignalInitial,
nodesCrossSignClaim,
nodesNetworkSignClaim,
notificationsSend,
vaultsGitInfoGet,
vaultsGitPackGet,
Expand All @@ -36,6 +38,7 @@ export {
nodesConnectionSignalFinal,
nodesConnectionSignalInitial,
nodesCrossSignClaim,
nodesNetworkSignClaim,
notificationsSend,
vaultsGitInfoGet,
vaultsGitPackGet,
Expand Down
12 changes: 12 additions & 0 deletions src/nodes/agent/callers/nodesNetworkSignClaim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { HandlerTypes } from '@matrixai/rpc';
import type NodesNetworkSignClaim from '../handlers/NodesNetworkSignClaim';
import { UnaryCaller } from '@matrixai/rpc';

type CallerTypes = HandlerTypes<NodesNetworkSignClaim>;

const nodesNetworkSignClaim = new UnaryCaller<
CallerTypes['input'],
CallerTypes['output']
>();

export default nodesNetworkSignClaim;
34 changes: 34 additions & 0 deletions src/nodes/agent/handlers/NodesNetworkSignClaim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type {
AgentRPCRequestParams,
AgentRPCResponseResult,
AgentClaimMessage,
} from '../types';
import type NodeManager from '../../../nodes/NodeManager';
import type { JSONValue } from '../../../types';
import { UnaryHandler } from '@matrixai/rpc';
import * as agentErrors from '../errors';
import * as agentUtils from '../utils';

class NodesNetworkSignClaim extends UnaryHandler<
{
nodeManager: NodeManager;
},
AgentRPCRequestParams<AgentClaimMessage>,
AgentRPCResponseResult<AgentClaimMessage>
> {
public handle = async (
input: AgentRPCRequestParams<AgentClaimMessage>,
_cancel,
meta: Record<string, JSONValue> | undefined,
): Promise<AgentRPCResponseResult<AgentClaimMessage>> => {
const { nodeManager } = this.container;
// Connections should always be validated
const requestingNodeId = agentUtils.nodeIdFromMeta(meta);
if (requestingNodeId == null) {
throw new agentErrors.ErrorAgentNodeIdMissing();
}
return nodeManager.handleClaimNetworkNode(requestingNodeId, input);
};
}

export default NodesNetworkSignClaim;
32 changes: 32 additions & 0 deletions src/nodes/agent/handlers/NodesNetworkVerifyClaim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type {
AgentRPCRequestParams,
AgentRPCResponseResult,
} from '../types';
import type NodeConnectionManager from '../../../nodes/NodeConnectionManager';
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 NodesNetworkAuthenticate extends UnaryHandler<
{
nodeConnectionManager: NodeConnectionManager;
},
AgentRPCRequestParams<{}>,
AgentRPCResponseResult<{}>
> {
public handle = async (
input: AgentRPCRequestParams<{}>,
_cancel,
meta: Record<string, JSONValue> | undefined,
): Promise<AgentRPCResponseResult<{}>> => {
return {};
};
}

export default NodesNetworkAuthenticate;
3 changes: 3 additions & 0 deletions src/nodes/agent/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import NodesClosestLocalNodesGet from './NodesClosestLocalNodesGet';
import NodesConnectionSignalFinal from './NodesConnectionSignalFinal';
import NodesConnectionSignalInitial from './NodesConnectionSignalInitial';
import NodesCrossSignClaim from './NodesCrossSignClaim';
import NodesNetworkSignClaim from './NodesNetworkSignClaim';
import NotificationsSend from './NotificationsSend';
import VaultsGitInfoGet from './VaultsGitInfoGet';
import VaultsGitPackGet from './VaultsGitPackGet';
Expand Down Expand Up @@ -43,6 +44,7 @@ const manifestServer = (container: {
nodesConnectionSignalFinal: new NodesConnectionSignalFinal(container),
nodesConnectionSignalInitial: new NodesConnectionSignalInitial(container),
nodesCrossSignClaim: new NodesCrossSignClaim(container),
nodesNetworkSignClaim: new NodesNetworkSignClaim(container),
notificationsSend: new NotificationsSend(container),
vaultsGitInfoGet: new VaultsGitInfoGet(container),
vaultsGitPackGet: new VaultsGitPackGet(container),
Expand All @@ -61,6 +63,7 @@ export {
NodesConnectionSignalFinal,
NodesConnectionSignalInitial,
NodesCrossSignClaim,
NodesNetworkSignClaim,
NotificationsSend,
VaultsGitInfoGet,
VaultsGitPackGet,
Expand Down

0 comments on commit c69c560

Please sign in to comment.