Skip to content

Commit 0da7ad3

Browse files
committed
Encrypting example now working
1 parent 520ccb2 commit 0da7ad3

File tree

5 files changed

+146
-113
lines changed

5 files changed

+146
-113
lines changed
Lines changed: 62 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,75 @@
1-
import { Listener, PrivateChannel } from '@finos/fdc3-standard';
21
import { Context } from '@finos/fdc3-context';
3-
4-
import { createJosePrivateFDC3Security } from '../src/impl/JosePrivateFDC3Security';
5-
import { MockChannel } from '../test/mocks/MockChannel';
2+
import { WebSocket } from 'ws';
3+
import { JosePrivateFDC3Security } from '../src/impl/JosePrivateFDC3Security';
64
import { EncryptingChannelDelegate } from '../src/encryption/EncryptingChannelDelegate';
75
import {
86
createSymmetricKeyRequestContextListener,
97
createSymmetricKeyResponseContextListener,
108
} from '../src/encryption/SymmetricKeyContextListener';
11-
import * as jose from 'jose';
12-
import { JWKSResolver } from '../src/impl/JosePublicFDC3Security';
13-
14-
class MockPrivateChannel extends MockChannel implements PrivateChannel {
15-
constructor(id: string) {
16-
super(id, 'private');
17-
}
18-
onAddContextListener(handler: (contextType?: string) => void): Listener {
19-
return { unsubscribe: async () => {} };
20-
}
21-
onUnsubscribe(handler: (contextType?: string) => void): Listener {
22-
return { unsubscribe: async () => {} };
23-
}
24-
onDisconnect(handler: () => void): Listener {
25-
return { unsubscribe: async () => {} };
26-
}
27-
async disconnect(): Promise<void> {}
28-
async addEventListener(type: string | null, handler: any): Promise<Listener> {
29-
return { unsubscribe: async () => {} };
30-
}
9+
import { DefaultFDC3Handlers } from '../src/secure-boundary/FDC3Handlers';
10+
import { AppBackEnd } from '../test/mocks/AppBackEnd';
11+
import { MockPrivateChannel } from '../test/mocks/MockPrivateChannel';
12+
13+
/**
14+
* STEP 1: Setup App A (listener)
15+
*/
16+
async function step1SetupAppA() {
17+
console.log('1. Starting App A backend...');
18+
return AppBackEnd.start(0, (_ws: WebSocket, _security: JosePrivateFDC3Security) => new DefaultFDC3Handlers());
3119
}
3220

33-
async function runExample() {
34-
console.log('--- FDC3 Encrypted Private Channel Example Start ---');
35-
36-
// Setup local registry for JWKS resolving without a real HTTP server
37-
const registry: Record<string, JsonWebKey[]> = {};
38-
const resolver = (url: string) => {
39-
const keys = registry[url] || [];
40-
const localSet = jose.createLocalJWKSet({ keys: keys as any });
41-
const r: any = async (ph: any, tok: any) => localSet(ph, tok);
42-
r.reload = async () => {};
43-
r.jwks = () => ({ keys });
44-
return r as JWKSResolver;
45-
};
46-
47-
// 1. Create App A and App B Security Objects
48-
const appASecurity = await createJosePrivateFDC3Security('http://localhost:1111', resolver, () => true);
49-
const appBSecurity = await createJosePrivateFDC3Security('http://localhost:2222', resolver, () => true);
50-
51-
registry['http://localhost:1111/.well-known/jwks.json'] = [
52-
appASecurity.signingPublicKey,
53-
appASecurity.wrappingPublicKey,
54-
];
55-
registry['http://localhost:2222/.well-known/jwks.json'] = [
56-
appBSecurity.signingPublicKey,
57-
appBSecurity.wrappingPublicKey,
58-
];
59-
60-
// 2. Setup the underlying "wire" mock channel
61-
const privateChannel = new MockPrivateChannel('private-channel-1');
21+
/**
22+
* STEP 2: Setup App B (broadcaster)
23+
*/
24+
async function step2SetupAppB() {
25+
console.log('2. Starting App B backend...');
26+
return AppBackEnd.start(0, (_ws: WebSocket, _security: JosePrivateFDC3Security) => new DefaultFDC3Handlers());
27+
}
6228

63-
// 3. App B Setup
64-
console.log('[App B] Initializing private channel delegate...');
29+
/**
30+
* STEP 3: Setup App B (The Broadcaster & Key Creator)
31+
*/
32+
async function step3SetupAppBDelegate(appBSecurity: JosePrivateFDC3Security, privateChannel: MockPrivateChannel) {
33+
console.log('3. App B Setup: Initializing private channel delegate for broadcasting...');
6534
// Note: We use metadataAvailable: true here so signatures are propagated in standard metadata argument
6635
const appBDelegate = new EncryptingChannelDelegate(privateChannel, true, appBSecurity);
6736
await appBDelegate.setChannelEncryption(type => {
68-
return type !== 'fdc3.security.symmetricKeyRequest' && type !== 'fdc3.security.symmetricKeyResponse';
69-
}); // Encrypt general contexts, but keep protocol negotiation in clear text
37+
return type == 'test.encrypted';
38+
});
7039

7140
// Start the symmetric key request listener.
7241
// It will create a symmetric key automatically and provide it upon valid request.
7342
await createSymmetricKeyRequestContextListener(appBSecurity, appBDelegate);
7443

75-
console.log('[App A] Resolving intent and connecting to Private Channel...');
44+
return appBDelegate;
45+
}
46+
47+
/**
48+
* STEP 4: Setup App A (The Listener / Receiver)
49+
*/
50+
async function step4SetupAppADelegate(appASecurity: JosePrivateFDC3Security, privateChannel: MockPrivateChannel) {
51+
console.log('4. App A Setup: Resolving intent and connecting to Private Channel as listener...');
7652

77-
// 4. App A Setup
7853
const appADelegate = new EncryptingChannelDelegate(privateChannel, true, appASecurity);
7954

8055
// App A needs a listener to receive the wrapped symmetric key when requested
8156
await createSymmetricKeyResponseContextListener(appASecurity, appADelegate);
8257

83-
// App A adds a normal context listener
58+
// App A adds a normal context listener for the encrypted content
8459
appADelegate.addContextListener('test.encrypted', (ctx: Context) => {
8560
console.log(`\n[App A] \u2705 Successfully Received and Decrypted Context:`);
8661
console.log(JSON.stringify(ctx, null, 2));
8762
});
8863

64+
return appADelegate;
65+
}
66+
67+
/**
68+
* STEP 5: App B Broadcasts Encrypted Messages
69+
*/
70+
function step5AppBBroadcasts(appBDelegate: EncryptingChannelDelegate, apps: AppBackEnd[]): void {
8971
console.log('[App B] Starting to broadcast encrypted messages...');
9072

91-
// 5. App B begins broadcasting
9273
let count = 0;
9374
const interval = setInterval(async () => {
9475
count++;
@@ -98,10 +79,30 @@ async function runExample() {
9879
if (count >= 10) {
9980
clearInterval(interval);
10081
console.log('\n--- FDC3 Encrypted Private Channel Example Complete ---');
82+
apps.forEach(app => app.shutdown());
10183
}
10284
}, 1000);
10385
}
10486

87+
/**
88+
* MAIN EXECUTION
89+
*
90+
* This example simulates a private channel over which two applications stream encrypted
91+
* data securely using symmetric encryption, securely distributing the symmetric key
92+
* upon request.
93+
*/
94+
async function runExample() {
95+
console.log('--- FDC3 Encrypted Private Channel Example Start ---');
96+
97+
const appA = await step1SetupAppA();
98+
const appB = await step2SetupAppB();
99+
const privateChannel = new MockPrivateChannel('private-channel-1');
100+
101+
const appBDelegate = await step3SetupAppBDelegate(appB.security, privateChannel);
102+
await step4SetupAppADelegate(appA.security, privateChannel);
103+
step5AppBBroadcasts(appBDelegate, [appA, appB]);
104+
}
105+
105106
runExample().catch(err => {
106107
console.error('Error running example:', err);
107108
});

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ 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';
7-
import { startAppBackEnd } from '../test/mocks/AppBackEnd';
7+
import { AppBackEnd } from '../test/mocks/AppBackEnd';
88
import { JosePrivateFDC3Security } from '../src/impl/JosePrivateFDC3Security';
99
import { SigningChannelDelegate } from '../src/signing/SigningChannelDelegate';
1010

@@ -49,7 +49,7 @@ class AppABackendHandlers extends DefaultFDC3Handlers {
4949
*/
5050
async function step1SetupAppABackend() {
5151
console.log('1. Start App A Backend (Hosts JWKS and FDC3 Handlers)');
52-
const result = await startAppBackEnd(0, (_, security) => new AppABackendHandlers(security));
52+
const result = await AppBackEnd.start(0, (_, security) => new AppABackendHandlers(security));
5353
const wsUrl = result.baseUrl.replace('http', 'ws');
5454
console.log(`[Server] Listening at ${result.baseUrl}`);
5555
return { ...result, wsUrl };

packages/fdc3-security/src/signing/SigningSupport.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import {
2-
ContextHandler,
3-
IntentHandler,
4-
IntentResult,
5-
DesktopAgentProvidableContextMetadata,
6-
ContextMetadata,
7-
} from '@finos/fdc3-standard';
1+
import { ContextHandler, DesktopAgentProvidableContextMetadata, ContextMetadata } from '@finos/fdc3-standard';
82
import { PrivateFDC3Security } from '../impl/PrivateFDC3Security';
93
import { Context } from '@finos/fdc3-context';
104
import { BrowserTypes } from '@finos/fdc3-schema';

packages/fdc3-security/test/mocks/AppBackEnd.ts

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,65 @@ import { setupWebsocketServer } from '../../src/secure-boundary/ServerSideHandle
77
import { FDC3Handlers } from '../../src/secure-boundary/FDC3Handlers';
88

99
/**
10-
* Starts a mock backend server (on localhost) that hosts JWKS and handles FDC3 WebSocket connections.
11-
*
12-
* @param port - The port to listen on (0 for dynamic).
13-
* @param createHandlers - A factory to create the FDC3 handlers for each connection,
14-
* receiving the initialized security instance.
15-
* @returns An object containing the base URL, initialized security, and the raw HTTP server.
10+
* A mock backend server (on localhost) that hosts JWKS and handles FDC3 WebSocket connections.
1611
*/
17-
export async function startAppBackEnd(
18-
port: number,
19-
createHandlers: (ws: WebSocket, security: JosePrivateFDC3Security) => FDC3Handlers
20-
): Promise<{ baseUrl: string; security: JosePrivateFDC3Security; httpServer: Server }> {
21-
let security: JosePrivateFDC3Security;
22-
23-
const httpServer = createServer((req, res) => {
24-
if (req.url === '/.well-known/jwks.json') {
25-
res.writeHead(200, {
26-
'Content-Type': 'application/json',
27-
'Access-Control-Allow-Origin': '*',
28-
});
29-
// We'll populate this once security is initialized
30-
res.end(JSON.stringify({ keys: security.getPublicKeys() }));
31-
} else {
32-
res.writeHead(404);
33-
res.end();
34-
}
35-
});
36-
37-
await new Promise<void>(resolve => httpServer.listen(port, resolve));
38-
const actualPort = (httpServer.address() as AddressInfo).port;
39-
const baseUrl = `http://localhost:${actualPort}`;
40-
41-
security = await createJosePrivateFDC3Security(
42-
baseUrl,
43-
url => provisionJWKS(url),
44-
() => true
45-
);
46-
47-
setupWebsocketServer(
48-
httpServer,
49-
() => console.log('Client disconnected'),
50-
ws => createHandlers(ws, security)
51-
);
52-
53-
return { baseUrl, security, httpServer };
12+
export class AppBackEnd {
13+
readonly baseUrl: string;
14+
readonly security: JosePrivateFDC3Security;
15+
readonly httpServer: Server;
16+
17+
private constructor(baseUrl: string, security: JosePrivateFDC3Security, httpServer: Server) {
18+
this.baseUrl = baseUrl;
19+
this.security = security;
20+
this.httpServer = httpServer;
21+
}
22+
23+
shutdown(): void {
24+
this.httpServer.close();
25+
}
26+
27+
/**
28+
* Creates and starts a mock backend server.
29+
*
30+
* @param port - The port to listen on (0 for dynamic).
31+
* @param createHandlers - A factory to create the FDC3 handlers for each connection,
32+
* receiving the initialized security instance.
33+
*/
34+
static async start(
35+
port: number,
36+
createHandlers: (ws: WebSocket, security: JosePrivateFDC3Security) => FDC3Handlers
37+
): Promise<AppBackEnd> {
38+
let security: JosePrivateFDC3Security;
39+
40+
const httpServer = createServer((req, res) => {
41+
if (req.url === '/.well-known/jwks.json') {
42+
res.writeHead(200, {
43+
'Content-Type': 'application/json',
44+
'Access-Control-Allow-Origin': '*',
45+
});
46+
res.end(JSON.stringify({ keys: security.getPublicKeys() }));
47+
} else {
48+
res.writeHead(404);
49+
res.end();
50+
}
51+
});
52+
53+
await new Promise<void>(resolve => httpServer.listen(port, resolve));
54+
const actualPort = (httpServer.address() as AddressInfo).port;
55+
const baseUrl = `http://localhost:${actualPort}`;
56+
57+
security = await createJosePrivateFDC3Security(
58+
baseUrl,
59+
url => provisionJWKS(url),
60+
() => true
61+
);
62+
63+
setupWebsocketServer(
64+
httpServer,
65+
() => console.log('Client disconnected'),
66+
ws => createHandlers(ws, security)
67+
);
68+
69+
return new AppBackEnd(baseUrl, security, httpServer);
70+
}
5471
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Listener, PrivateChannel } from '@finos/fdc3-standard';
2+
import { MockChannel } from './MockChannel';
3+
4+
export class MockPrivateChannel extends MockChannel implements PrivateChannel {
5+
constructor(id: string) {
6+
super(id, 'private');
7+
}
8+
onAddContextListener(handler: (contextType?: string) => void): Listener {
9+
return { unsubscribe: async () => {} };
10+
}
11+
onUnsubscribe(handler: (contextType?: string) => void): Listener {
12+
return { unsubscribe: async () => {} };
13+
}
14+
onDisconnect(handler: () => void): Listener {
15+
return { unsubscribe: async () => {} };
16+
}
17+
async disconnect(): Promise<void> {}
18+
async addEventListener(type: string | null, handler: any): Promise<Listener> {
19+
return { unsubscribe: async () => {} };
20+
}
21+
}

0 commit comments

Comments
 (0)