Skip to content
Snippets Groups Projects
Commit 2b4ff57b authored by Cédric Moreau's avatar Cédric Moreau
Browse files

[enh] #1084 WS2P: allow requests only when strict bidirectionnal connection is established

parent b78e3815
No related branches found
No related tags found
No related merge requests found
......@@ -24,19 +24,20 @@ enum WS2P_ERR {
export interface WS2PAuth {
isAuthorizedPubkey(pub:string): Promise<boolean>
authenticationIsDone(): Promise<void>
}
export interface WS2PRemoteAuth extends WS2PAuth {
sendACK(ws:any): Promise<void>
registerCONNECT(challenge:string, sig: string, pub: string): Promise<boolean>
sendACK(ws:any): Promise<void>
registerOK(sig: string): Promise<boolean>
isAuthenticated(): boolean
isAuthenticatedByRemote(): boolean
}
export interface WS2PLocalAuth extends WS2PAuth {
sendCONNECT(ws:any): Promise<void>
registerACK(sig: string, pub: string): Promise<boolean>
sendOK(ws:any): Promise<void>
isRemoteAuthenticated(): boolean
}
......@@ -46,7 +47,6 @@ export interface WS2PLocalAuth extends WS2PAuth {
export class WS2PPubkeyRemoteAuth implements WS2PRemoteAuth {
protected challenge:string
protected authenticated = false
protected authenticatedByRemote = false
protected remotePub = ""
protected serverAuth:Promise<void>
......@@ -88,11 +88,12 @@ export class WS2PPubkeyRemoteAuth implements WS2PRemoteAuth {
async registerOK(sig: string): Promise<boolean> {
const challengeMessage = `WS2P:OK:${this.remotePub}:${this.challenge}`
this.authenticatedByRemote = verify(challengeMessage, sig, this.remotePub)
return this.authenticatedByRemote
if (!this.authenticatedByRemote) {
this.serverAuthReject("Wrong signature from remote OK")
} else {
this.serverAuthResolve()
}
isAuthenticated(): boolean {
return this.authenticated
return this.authenticatedByRemote
}
isAuthenticatedByRemote(): boolean {
......@@ -102,6 +103,10 @@ export class WS2PPubkeyRemoteAuth implements WS2PRemoteAuth {
async isAuthorizedPubkey(pub: string): Promise<boolean> {
return true
}
authenticationIsDone(): Promise<void> {
return this.serverAuth
}
}
/**
......@@ -150,10 +155,24 @@ export class WS2PPubkeyLocalAuth implements WS2PLocalAuth {
return this.authenticated
}
async sendOK(ws:any): Promise<void> {
const challengeMessage = `WS2P:OK:${this.pair.pub}:${this.challenge}`
const sig = this.pair.signSync(challengeMessage)
await ws.send(JSON.stringify({
auth: 'OK',
sig
}))
return this.serverAuth
}
isRemoteAuthenticated(): boolean {
return this.authenticated
}
authenticationIsDone(): Promise<void> {
return this.serverAuth
}
async isAuthorizedPubkey(pub: string): Promise<boolean> {
return true
}
......@@ -271,6 +290,10 @@ export class WS2PConnection {
await this.onWsOpened
try {
await this.localAuth.sendCONNECT(this.ws)
await Promise.all([
this.localAuth.authenticationIsDone(),
this.remoteAuth.authenticationIsDone()
])
resolve()
} catch (e) {
reject(e)
......@@ -296,7 +319,7 @@ export class WS2PConnection {
if (data.auth && typeof data.auth === "string") {
if (data.auth === "CONNECT") {
if (this.remoteAuth.isAuthenticated()) {
if (this.remoteAuth.isAuthenticatedByRemote()) {
return this.errorDetected(WS2P_ERR.ALREADY_AUTHENTICATED_BY_REMOTE)
}
else if (
......@@ -327,7 +350,10 @@ export class WS2PConnection {
await this.errorDetected(WS2P_ERR.INCORRECT_PUBKEY_FOR_REMOTE)
} else {
try {
await this.localAuth.registerACK(data.sig, data.pub)
const valid = await this.localAuth.registerACK(data.sig, data.pub)
if (valid) {
await this.localAuth.sendOK(this.ws)
}
} catch (e) {
await this.errorDetected(WS2P_ERR.INCORRECT_ACK_SIGNATURE_FROM_REMOTE)
}
......
......@@ -114,7 +114,7 @@ describe('WS2P', () => {
it('should accept the connection if the server answers with a good signature', async () => {
const keypair = new Key('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP')
const ws2p = WS2PConnection.newConnectionToAddress('localhost:20903', () => {}, new WS2PPubkeyLocalAuth(keypair), new WS2PPubkeyRemoteAuth(keypair), {
const ws2p = WS2PConnection.newConnectionToAddress('localhost:20903', () => {}, new WS2PPubkeyLocalAuth(keypair), new WS2PNoRemoteAuth(), {
connectionTimeout: 1000,
requestTimeout: 1000
})
......@@ -201,11 +201,13 @@ describe('WS2P', () => {
let resolveS3:any
let resolveS4:any
let resolveS5:any
let resolveS6:any
let s1p:Promise<WS2PConnection> = new Promise(res => resolveS1 = res)
let s2p:Promise<WS2PConnection> = new Promise(res => resolveS2 = res)
let s3p:Promise<WS2PConnection> = new Promise(res => resolveS3 = res)
let s4p:Promise<WS2PConnection> = new Promise(res => resolveS4 = res)
let s5p:Promise<WS2PConnection> = new Promise(res => resolveS5 = res)
let s6p:Promise<WS2PConnection> = new Promise(res => resolveS6 = res)
before(async () => {
let i = 1
......@@ -215,8 +217,8 @@ describe('WS2P', () => {
switch (i) {
case 1:
resolveS1(WS2PConnection.newConnectionFromWebSocketServer(ws, () => {}, new WS2PPubkeyLocalAuth(serverKeypair), new WS2PPubkeyRemoteAuth(serverKeypair), {
connectionTimeout: 1000,
requestTimeout: 1000
connectionTimeout: 100,
requestTimeout: 100
}));
(await s1p).connect().catch((e:any) => console.error('WS2P: newConnectionFromWebSocketServer connection error'))
break
......@@ -229,24 +231,24 @@ describe('WS2P', () => {
}
resolveS2(WS2PConnection.newConnectionFromWebSocketServer(ws, () => {}, new WS2PPubkeyLocalAuth(serverKeypair), new WS2PPubkeyNotAnsweringWithOKAuth(serverKeypair), {
connectionTimeout: 1000,
requestTimeout: 1000
connectionTimeout: 100,
requestTimeout: 100
}));
(await s2p).connect().catch((e:any) => console.error('WS2P: newConnectionFromWebSocketServer connection error'))
break
case 3:
resolveS3(WS2PConnection.newConnectionFromWebSocketServer(ws, () => {}, new WS2PPubkeyLocalAuth(serverKeypair), new WS2PPubkeyRemoteAuth(serverKeypair), {
connectionTimeout: 1000,
requestTimeout: 1000
connectionTimeout: 100,
requestTimeout: 100
}));
(await s3p).connect().catch((e:any) => console.error('WS2P: newConnectionFromWebSocketServer connection error'))
break
case 4:
resolveS4(WS2PConnection.newConnectionFromWebSocketServer(ws, () => {}, new WS2PPubkeyLocalAuth(serverKeypair), new WS2PPubkeyRemoteAuth(serverKeypair), {
connectionTimeout: 1000,
requestTimeout: 1000
connectionTimeout: 100,
requestTimeout: 100
}));
(await s4p).connect().catch((e:any) => console.error('WS2P: newConnectionFromWebSocketServer connection error'))
break
......@@ -255,6 +257,15 @@ describe('WS2P', () => {
resolveS5(WS2PConnection.newConnectionFromWebSocketServer(ws, () => {}, new WS2PPubkeyLocalAuth(serverKeypair), new WS2PPubkeyRemoteAuth(serverKeypair)));
(await s5p).connect().catch((e:any) => console.error('WS2P: newConnectionFromWebSocketServer connection error'))
break
case 6:
resolveS6(WS2PConnection.newConnectionFromWebSocketServer(ws, () => {}, new WS2PPubkeyLocalAuth(serverKeypair), new WS2PPubkeyRemoteAuth(serverKeypair), {
connectionTimeout: 100,
requestTimeout: 100
}));
(await s6p).connect().catch((e:any) => console.error('WS2P: newConnectionFromWebSocketServer connection error'))
break
}
i++
})
......@@ -264,7 +275,7 @@ describe('WS2P', () => {
wss.close(done)
})
it('should refuse the connection if the client does not sendCONNECT', async () => {
it('should refuse the connection if the client does not send ACK', async () => {
class WS2PPubkeyNotAnsweringWithACKAuth extends WS2PPubkeyRemoteAuth {
async sendACK(ws: any): Promise<void> {
......@@ -274,7 +285,7 @@ describe('WS2P', () => {
const keypair = new Key('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP')
const c1 = WS2PConnection.newConnectionToAddress('localhost:20903', () => {}, new WS2PPubkeyLocalAuth(keypair), new WS2PPubkeyNotAnsweringWithACKAuth(keypair))
await c1.connect()
c1.connect()
const s1 = await s1p
await assertThrows(s1.request({ message: 'something' }), "WS2P connection timeout")
})
......@@ -302,7 +313,7 @@ describe('WS2P', () => {
const keypair = new Key('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP')
const c3 = WS2PConnection.newConnectionToAddress('localhost:20903', () => {}, new WS2PPubkeyLocalAuth(keypair), new WS2PPubkeyAnsweringWithWrongSigForACK(keypair))
await c3.connect()
c3.connect()
const s3 = await s3p
await assertThrows(s3.request({ message: 'something' }), "Wrong signature from server ACK")
})
......@@ -340,6 +351,20 @@ describe('WS2P', () => {
const s5 = await s5p
assert.deepEqual({ answer: 'success!' }, await s5.request({ message: 'connection?'} ))
})
it('should refuse the connection if the client does not send OK', async () => {
class WS2PPubkeyNotAnsweringWithOKAuth extends WS2PPubkeyLocalAuth {
async sendOK(ws: any): Promise<void> {
}
}
const keypair = new Key('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP')
const c6 = WS2PConnection.newConnectionToAddress('localhost:20903', () => {}, new WS2PPubkeyNotAnsweringWithOKAuth(keypair), new WS2PPubkeyRemoteAuth(keypair))
c6.connect()
const s6 = await s6p
await assertThrows(s6.request({ message: 'something' }), "WS2P connection timeout")
})
})
})
})
......@@ -364,6 +389,12 @@ class WS2PNoLocalAuth implements WS2PLocalAuth {
async isAuthorizedPubkey(pub: string): Promise<boolean> {
return true
}
async sendOK(ws: any): Promise<void> {
}
async authenticationIsDone(): Promise<void> {
}
}
class WS2PNoRemoteAuth implements WS2PRemoteAuth {
......@@ -379,10 +410,6 @@ class WS2PNoRemoteAuth implements WS2PRemoteAuth {
return true
}
isAuthenticated(): boolean {
return true
}
isAuthenticatedByRemote(): boolean {
return true
}
......@@ -390,4 +417,7 @@ class WS2PNoRemoteAuth implements WS2PRemoteAuth {
async isAuthorizedPubkey(pub: string): Promise<boolean> {
return true
}
async authenticationIsDone(): Promise<void> {
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment