Skip to content

Commit 820cf34

Browse files
committed
Closing in on signing support
1 parent 3324562 commit 820cf34

File tree

7 files changed

+150
-178
lines changed

7 files changed

+150
-178
lines changed

packages/fdc3-security/samples/signing-example.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { JosePublicFDC3Security, provisionJWKS } from '../src/impl/JosePublicFDC3Security';
22
import { connectRemoteHandlers } from '../src/secure-boundary/ClientSideHandlersImpl';
3-
import { Channel, DesktopAgent } from '@finos/fdc3-standard';
3+
import { Channel, ContextMetadata, DesktopAgent } from '@finos/fdc3-standard';
44
import { Context } from '@finos/fdc3-context';
55
import { ExchangeDataMessage } from '../src/secure-boundary/MessageTypes';
66
import { MockDesktopAgent } from '../test/mocks/MockDesktopAgent';
77
import { startAppBackEnd } from '../test/mocks/AppBackEnd';
88
import { JosePrivateFDC3Security } from '../src/impl/JosePrivateFDC3Security';
99
import { SigningChannelDelegate } from '../src/signing/SigningChannelDelegate';
10-
import { ContextMetadataWithAuthenticity } from '../src/signing/SigningSupport';
1110

1211
import { DefaultFDC3Handlers } from '../src/secure-boundary/FDC3Handlers';
1312

@@ -26,7 +25,7 @@ class AppABackendHandlers extends DefaultFDC3Handlers {
2625
async handleRemoteChannel(purpose: string, channel: Channel): Promise<void> {
2726
console.log(`[App A Backend] Received remote channel for purpose: ${purpose}`);
2827
// Wrap with SigningChannelDelegate to handle signing automatically
29-
this.channel = new SigningChannelDelegate(channel, this.security);
28+
this.channel = new SigningChannelDelegate(channel, this.security, false);
3029
// Wait for listener to be ready
3130
setTimeout(() => this.broadcast(), 1000);
3231
}
@@ -74,7 +73,7 @@ async function runExample() {
7473
);
7574

7675
// Wrap the channel with SigningChannelDelegate for automatic verification
77-
const signedChanB = new SigningChannelDelegate(chanB, securityB_Public);
76+
const signedChanB = new SigningChannelDelegate(chanB, securityB_Public, false);
7877

7978
// 1. Listen to the raw channel to show what's "on the wire"
8079
await chanB.addContextListener('fdc3.instrument', async ctx => {
@@ -83,32 +82,29 @@ async function runExample() {
8382
});
8483

8584
// 2. Listen via the Delegate to show the processed/verified version
86-
await signedChanB.addContextListener(
87-
'fdc3.instrument',
88-
async (ctx: Context, meta: ContextMetadataWithAuthenticity | undefined) => {
89-
console.log('[App B Front-end] <<< [VERIFIED] Context Received:');
90-
console.log(JSON.stringify(ctx, null, 2));
91-
console.log('[App B Front-end] <<< [VERIFIED] Metadata Received:');
92-
console.log(JSON.stringify(meta, null, 2));
93-
94-
if (meta?.authenticity) {
95-
const auth = meta.authenticity;
96-
if (auth.signed) {
97-
console.log('[App B Front-end] Verification Result: ✅ VALID');
98-
console.log(`[App B Front-end] Trusted Provider: ${auth.jku}`);
99-
} else {
100-
console.log('[App B Front-end] Context was not signed.');
101-
if (auth.errors) {
102-
console.log('[App B Front-end] Errors:', auth.errors);
103-
}
85+
await signedChanB.addContextListener('fdc3.instrument', async (ctx: Context, meta: ContextMetadata | undefined) => {
86+
console.log('[App B Front-end] <<< [VERIFIED] Context Received:');
87+
console.log(JSON.stringify(ctx, null, 2));
88+
console.log('[App B Front-end] <<< [VERIFIED] Metadata Received:');
89+
console.log(JSON.stringify(meta, null, 2));
90+
91+
if (meta?.authenticity) {
92+
const auth = meta.authenticity;
93+
if (auth.signed) {
94+
console.log('[App B Front-end] Verification Result: ✅ VALID');
95+
console.log(`[App B Front-end] Trusted Provider: ${auth.jku}`);
96+
} else {
97+
console.log('[App B Front-end] Context was not signed.');
98+
if (auth.errors) {
99+
console.log('[App B Front-end] Errors:', auth.errors);
104100
}
105101
}
106-
107-
console.log('--- FDC3 Signing Example End ---');
108-
httpServer.close();
109-
process.exit(0);
110102
}
111-
);
103+
104+
console.log('--- FDC3 Signing Example End ---');
105+
httpServer.close();
106+
process.exit(0);
107+
});
112108

113109
// 3. App A Front-end Execution
114110
console.log('[App A Front-end] Connecting to remote handlers...');

packages/fdc3-security/src/delegates/AbstractChannelDelegate.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,32 @@ import {
44
EventHandler,
55
Listener,
66
PrivateChannel,
7-
AppProvidableContextMetadata,
87
ContextMetadata,
98
} from '@finos/fdc3-standard';
109
import { Context } from '@finos/fdc3-context';
1110

11+
export const APP_METADATA_KEY = '__appMeta';
12+
1213
/**
1314
* Wraps a standard FDC3 Channel or PrivateChannel so we can give it extra behaviour.
1415
*/
1516
export abstract class AbstractChannelDelegate implements PrivateChannel {
1617
protected readonly delegate: PrivateChannel;
18+
protected readonly metadataAvailable: boolean;
1719
id: string;
1820
type: 'user' | 'app' | 'private';
1921
displayMetadata?: DisplayMetadata | undefined;
2022

21-
constructor(c: Channel | PrivateChannel) {
23+
/**
24+
* @param c Channel to wrap
25+
* @param metadataAvailable Pass in true if running in FDC3 3.0 where we can broadcast our own metadata, otherwise false.
26+
*/
27+
constructor(c: Channel | PrivateChannel, metadataAvailable: boolean) {
2228
this.delegate = c as PrivateChannel;
2329
this.id = c.id;
2430
this.type = c.type;
2531
this.displayMetadata = c.displayMetadata;
32+
this.metadataAvailable = metadataAvailable;
2633
}
2734

2835
async clearContext(contextType?: string): Promise<void> {
@@ -56,7 +63,15 @@ export abstract class AbstractChannelDelegate implements PrivateChannel {
5663

5764
async broadcast(context: Context, metadata?: ContextMetadata): Promise<void> {
5865
const wrapped = await this.wrapContext(context, metadata);
59-
return this.delegate.broadcast(wrapped.ctx, wrapped.meta);
66+
if (this.metadataAvailable) {
67+
return this.delegate.broadcast(wrapped.ctx, wrapped.meta);
68+
} else {
69+
const contextWithMeta = {
70+
...context,
71+
__appMeta: wrapped.meta,
72+
};
73+
return this.delegate.broadcast(contextWithMeta);
74+
}
6075
}
6176

6277
getCurrentContext(contextType?: string | undefined): Promise<Context | null> {

packages/fdc3-security/src/secure-boundary/ClientSideHandlersImpl.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Channel, DesktopAgent, IntentHandler, Listener, PrivateChannel } from '@finos/fdc3-standard';
1+
import { Channel, DesktopAgent, IntentHandler, Listener, PrivateChannel, ContextMetadata } from '@finos/fdc3-standard';
22
import { Context } from '@finos/fdc3-context';
33
import {
44
REMOTE_INTENT_HANDLER,
@@ -112,7 +112,7 @@ export class ClientSideHandlersImpl implements FDC3Handlers {
112112

113113
private async handleBroadcast(br: BroadcastRequest): Promise<BroadcastResponse> {
114114
const channel = this.channels.get(br.payload.channelId)!!;
115-
await channel.broadcast(br.payload.context);
115+
await channel.broadcast(br.payload.context, br.payload.metadata as any);
116116
return {
117117
type: 'broadcastResponse',
118118
meta: {
@@ -128,18 +128,21 @@ export class ClientSideHandlersImpl implements FDC3Handlers {
128128
const channel = this.channels.get(acl.payload.channelId!);
129129
const id = this.messaging.createUUID();
130130
if (channel) {
131-
const cl = await channel.addContextListener(acl.payload.contextType, async (ctx: Context) => {
132-
const msg: BroadcastEvent = {
133-
type: 'broadcastEvent',
134-
meta: {
135-
requestUuid: this.messaging.createUUID(),
136-
timestamp: new Date(),
137-
eventUuid: this.messaging.createUUID(),
138-
} as any,
139-
payload: { context: ctx, channelId: channel.id },
140-
};
141-
this.messaging.post(msg);
142-
});
131+
const cl = await channel.addContextListener(
132+
acl.payload.contextType,
133+
async (ctx: Context, meta?: ContextMetadata) => {
134+
const msg: BroadcastEvent = {
135+
type: 'broadcastEvent',
136+
meta: {
137+
requestUuid: this.messaging.createUUID(),
138+
timestamp: new Date(),
139+
eventUuid: this.messaging.createUUID(),
140+
} as any,
141+
payload: { context: ctx, channelId: channel.id, metadata: meta } as any,
142+
};
143+
this.messaging.post(msg);
144+
}
145+
);
143146
this.contextListeners.set(id, cl);
144147
return {
145148
type: 'addContextListenerResponse',
Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Channel, ContextHandler, ContextMetadata, Listener, PrivateChannel } from '@finos/fdc3-standard';
22
import { Context } from '@finos/fdc3-context';
3-
import { signingContextHandler } from './SigningSupport';
3+
import { signatureCheckingContextHandler, signContext } from './SigningSupport';
44
import { AbstractChannelDelegate } from '../delegates/AbstractChannelDelegate';
55
import { PrivateFDC3Security } from '../impl/PrivateFDC3Security';
66
import { PublicFDC3Security } from '../impl/PublicFDC3Security';
@@ -13,34 +13,29 @@ import { PublicFDC3Security } from '../impl/PublicFDC3Security';
1313
export class SigningChannelDelegate extends AbstractChannelDelegate {
1414
private readonly fdc3Security: PublicFDC3Security;
1515

16-
constructor(d: Channel | PrivateChannel, fdc3Security: PublicFDC3Security) {
17-
super(d);
16+
constructor(d: Channel | PrivateChannel, fdc3Security: PublicFDC3Security, metadataAvailable: boolean) {
17+
super(d, metadataAvailable);
1818
this.fdc3Security = fdc3Security;
1919
}
2020

2121
canSign(): boolean {
22-
return typeof (this.fdc3Security as any).sign === 'function';
22+
const hasSign = typeof (this.fdc3Security as any).sign === 'function';
23+
if (!hasSign) {
24+
console.log('SigningChannelDelegate: Security provider does not have sign method', this.fdc3Security);
25+
}
26+
return hasSign;
2327
}
2428

2529
async wrapContext(ctx: Context, meta?: ContextMetadata): Promise<{ ctx: Context; meta?: ContextMetadata }> {
2630
if (this.canSign()) {
27-
const { signature, antiReplay } = await (this.fdc3Security as any as PrivateFDC3Security).sign(ctx);
28-
const metaOut = {
29-
...meta,
30-
signature,
31-
antiReplay,
32-
} as ContextMetadata;
33-
return { ctx, meta: metaOut };
31+
return signContext(this.fdc3Security as PrivateFDC3Security, ctx, meta);
3432
}
3533
return { ctx, meta };
3634
}
3735

3836
addContextListener(context: any, handler?: any): Promise<Listener> {
3937
const theHandler: ContextHandler = handler ? handler : (context as ContextHandler);
4038
const theContextType: string | null = context && handler ? (context as string) : null;
41-
return super.addContextListener(
42-
theContextType,
43-
signingContextHandler(this.fdc3Security, theHandler, async () => this.delegate)
44-
);
39+
return super.addContextListener(theContextType, signatureCheckingContextHandler(this.fdc3Security, theHandler));
4540
}
4641
}

0 commit comments

Comments
 (0)