-
Notifications
You must be signed in to change notification settings - Fork 42
feat: ESSR authentication mode for KERIA API #335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 16 commits
765d5f7
db78543
1fabf58
41a9c26
3131cf4
b49443e
73406b1
510f735
6d235e6
fe377a9
2c064c7
d211027
04505a7
31ca9fe
17514db
3546644
28fb58e
42c00f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,13 +19,13 @@ | |
| proxy?: string; | ||
| delpre?: string; | ||
| dcode?: string; | ||
| data?: any; | ||
|
Check warning on line 22 in src/keri/app/aiding.ts
|
||
| algo?: Algos; | ||
| pre?: string; | ||
| states?: any[]; | ||
|
Check warning on line 25 in src/keri/app/aiding.ts
|
||
| rstates?: any[]; | ||
|
Check warning on line 26 in src/keri/app/aiding.ts
|
||
| prxs?: any[]; | ||
|
Check warning on line 27 in src/keri/app/aiding.ts
|
||
| nxts?: any[]; | ||
|
Check warning on line 28 in src/keri/app/aiding.ts
|
||
| mhab?: HabState; | ||
| keys?: string[]; | ||
| ndigs?: string[]; | ||
|
|
@@ -34,7 +34,7 @@ | |
| ncount?: number; | ||
| tier?: Tier; | ||
| extern_type?: string; | ||
| extern?: any; | ||
|
Check warning on line 37 in src/keri/app/aiding.ts
|
||
| } | ||
|
|
||
| /** Arguments required to rotate an identfier */ | ||
|
|
@@ -48,8 +48,8 @@ | |
| ncode?: string; | ||
| ncount?: number; | ||
| ncodes?: string[]; | ||
| states?: any[]; | ||
|
Check warning on line 51 in src/keri/app/aiding.ts
|
||
| rstates?: any[]; | ||
|
Check warning on line 52 in src/keri/app/aiding.ts
|
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -92,7 +92,7 @@ | |
| * @param {number} [end=24] End index of list of notifications, defaults to 24 | ||
| * @returns {Promise<any>} A promise to the list of managed identifiers | ||
| */ | ||
| async list(start: number = 0, end: number = 24): Promise<any> { | ||
|
Check warning on line 95 in src/keri/app/aiding.ts
|
||
| const extraHeaders = new Headers(); | ||
| extraHeaders.append('Range', `aids=${start}-${end}`); | ||
|
|
||
|
|
@@ -120,7 +120,7 @@ | |
| * @returns {Promise<HabState>} A promise to the identifier information | ||
| */ | ||
| async get(name: string): Promise<HabState> { | ||
| const path = `/identifiers/${encodeURIComponent(name)}`; | ||
| const path = `/identifiers/${name}`; | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| const data = null; | ||
| const method = 'GET'; | ||
| const res = await this.client.fetch(path, method, data); | ||
|
|
@@ -249,7 +249,7 @@ | |
| } | ||
|
|
||
| const sigs = await keeper!.sign(b(serder.raw)); | ||
| const jsondata: any = { | ||
|
Check warning on line 252 in src/keri/app/aiding.ts
|
||
| name: name, | ||
| icp: serder.sad, | ||
| sigs: sigs, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,9 @@ | ||
| import { Authenticater } from '../core/authing.ts'; | ||
| import { HEADER_SIG_TIME } from '../core/httping.ts'; | ||
| import { | ||
| EssrAuthenticator, | ||
| SignedHeaderAuthenticator, | ||
| Authenticator, | ||
| } from '../core/authing.ts'; | ||
| import { HEADER_SIG_SENDER, HEADER_SIG_TIME } from '../core/httping.ts'; | ||
| import { ExternalModule, IdentifierManagerFactory } from '../core/keeping.ts'; | ||
| import { Tier } from '../core/salter.ts'; | ||
|
|
||
|
|
@@ -30,21 +34,27 @@ class State { | |
| } | ||
| } | ||
|
|
||
| export enum AuthMode { | ||
| SignedHeaders = 'SIGNED_HEADERS', | ||
| ESSR = 'ESSR', | ||
| } | ||
|
|
||
| /** | ||
| * An in-memory key manager that can connect to a KERIA Agent and use it to | ||
| * receive messages and act as a proxy for multi-signature operations and delegation operations. | ||
| */ | ||
| export class SignifyClient { | ||
| public controller: Controller; | ||
| public url: string; | ||
| public bran: string; | ||
| public pidx: number; | ||
| public agent: Agent | null; | ||
| public authn: Authenticater | null; | ||
| public manager: IdentifierManagerFactory | null; | ||
| public tier: Tier; | ||
| public bootUrl: string; | ||
| public exteralModules: ExternalModule[]; | ||
| controller: Controller; | ||
| url: string; | ||
| bran: string; | ||
| pidx: number; | ||
| agent: Agent | null; | ||
| authn: Authenticator | null; | ||
| manager: IdentifierManagerFactory | null; | ||
| tier: Tier; | ||
| bootUrl: string; | ||
| exteralModules: ExternalModule[]; | ||
| authMode: AuthMode; | ||
|
|
||
| /** | ||
| * SignifyClient constructor | ||
|
|
@@ -59,7 +69,8 @@ export class SignifyClient { | |
| bran: string, | ||
| tier: Tier = Tier.low, | ||
| bootUrl: string = DEFAULT_BOOT_URL, | ||
| externalModules: ExternalModule[] = [] | ||
| externalModules: ExternalModule[] = [], | ||
| authMode: AuthMode = AuthMode.SignedHeaders | ||
| ) { | ||
| this.url = url; | ||
| if (bran.length < 21) { | ||
|
|
@@ -74,6 +85,7 @@ export class SignifyClient { | |
| this.tier = tier; | ||
| this.bootUrl = bootUrl; | ||
| this.exteralModules = externalModules; | ||
| this.authMode = authMode; | ||
| } | ||
|
|
||
| get data() { | ||
|
|
@@ -154,10 +166,18 @@ export class SignifyClient { | |
| this.controller.salter, | ||
| this.exteralModules | ||
| ); | ||
| this.authn = new Authenticater( | ||
| this.controller.signer, | ||
| this.agent.verfer! | ||
| ); | ||
|
|
||
| if (this.authMode === AuthMode.SignedHeaders) { | ||
| this.authn = new SignedHeaderAuthenticator( | ||
| this.controller.signer, | ||
| this.agent.verfer! | ||
| ); | ||
| } else { | ||
| this.authn = new EssrAuthenticator( | ||
| this.controller.signer, | ||
| this.agent.verfer! | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -175,63 +195,55 @@ export class SignifyClient { | |
| data: any, | ||
| extraHeaders?: Headers | ||
| ): Promise<Response> { | ||
| const headers = new Headers(); | ||
| let signed_headers = new Headers(); | ||
| const final_headers = new Headers(); | ||
| if (!this.authn) { | ||
| throw new Error('Client needs to call connect first'); | ||
| } | ||
|
|
||
| headers.set('Signify-Resource', this.controller.pre); | ||
| const headers = new Headers(); | ||
| headers.set(HEADER_SIG_SENDER, this.controller.pre); | ||
| headers.set( | ||
| HEADER_SIG_TIME, | ||
| new Date().toISOString().replace('Z', '000+00:00') | ||
| ); | ||
| headers.set('Content-Type', 'application/json'); | ||
|
|
||
| const _body = method == 'GET' ? null : JSON.stringify(data); | ||
|
|
||
| if (this.authn) { | ||
| signed_headers = this.authn.sign( | ||
| headers, | ||
| method, | ||
| path.split('?')[0] | ||
| ); | ||
| } else { | ||
| throw new Error('client need to call connect first'); | ||
| } | ||
|
|
||
| signed_headers.forEach((value, key) => { | ||
| final_headers.set(key, value); | ||
| }); | ||
| if (extraHeaders !== undefined) { | ||
| if (extraHeaders) { | ||
| extraHeaders.forEach((value, key) => { | ||
| final_headers.append(key, value); | ||
| headers.append(key, value); | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see any reason to not sign |
||
| }); | ||
| } | ||
| const res = await fetch(this.url + path, { | ||
| method: method, | ||
| body: _body, | ||
| headers: final_headers, | ||
| }); | ||
| if (!res.ok) { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. imo signature should be verified for non 2xx responses too |
||
| const error = await res.text(); | ||
| const message = `HTTP ${method} ${path} - ${res.status} ${res.statusText} - ${error}`; | ||
| throw new Error(message); | ||
| } | ||
| const isSameAgent = | ||
| this.agent?.pre === res.headers.get('signify-resource'); | ||
| if (!isSameAgent) { | ||
| throw new Error('message from a different remote agent'); | ||
|
|
||
| const body = method == 'GET' ? null : JSON.stringify(data); | ||
| if (body) { | ||
| headers.set('Content-Type', 'application/json'); | ||
| headers.set('Content-Length', body.length.toString()); | ||
| } | ||
|
|
||
| const verification = this.authn.verify( | ||
| res.headers, | ||
| const baseRequest = new Request(this.url + path, { | ||
| method, | ||
| path.split('?')[0] | ||
| body, | ||
| headers, | ||
| }); | ||
| const request = await this.authn.prepare( | ||
| baseRequest, | ||
| this.controller.pre, | ||
| this.agent!.pre | ||
| ); | ||
| if (verification) { | ||
| return res; | ||
| } else { | ||
| throw new Error('response verification failed'); | ||
|
|
||
| const res = await this.authn.verify( | ||
| baseRequest, | ||
| await fetch(request), | ||
| this.controller.pre, | ||
| this.agent!.pre | ||
| ); | ||
|
|
||
| if (!res.ok) { | ||
| const error = await res.text(); | ||
| throw new Error( | ||
| `HTTP ${method} ${path} - ${res.status} ${res.statusText} - ${error}` | ||
| ); | ||
| } | ||
|
|
||
| return res; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -256,34 +268,35 @@ export class SignifyClient { | |
| const hab = await this.identifiers().get(aidName); | ||
| const keeper = this.manager!.get(hab); | ||
|
|
||
| const authenticator = new Authenticater( | ||
| const authenticator = new SignedHeaderAuthenticator( | ||
| keeper.signers[0], | ||
| keeper.signers[0].verfer | ||
| ); | ||
|
|
||
| const headers = new Headers(req.headers); | ||
| headers.set('Signify-Resource', hab['prefix']); | ||
| headers.set(HEADER_SIG_SENDER, hab.prefix); | ||
| headers.set( | ||
| HEADER_SIG_TIME, | ||
| new Date().toISOString().replace('Z', '000+00:00') | ||
| ); | ||
|
|
||
| const signed_headers = authenticator.sign( | ||
| new Headers(headers), | ||
| req.method ?? 'GET', | ||
| new URL(url).pathname | ||
| return await authenticator.prepare( | ||
| new Request(url, { | ||
| headers, | ||
| method: req.method ?? 'GET', | ||
| body: req.body, | ||
| }), | ||
| hab.prefix, | ||
| hab.prefix | ||
| ); | ||
| req.headers = signed_headers; | ||
|
|
||
| return new Request(url, req); | ||
| } | ||
|
|
||
| /** | ||
| * Approve the delegation of the client AID to the KERIA agent | ||
| * @async | ||
| * @returns {Promise<Response>} A promise to the result of the approval | ||
| */ | ||
| async approveDelegation(): Promise<Response> { | ||
| private async approveDelegation(): Promise<Response> { | ||
| const sigs = this.controller.approveDelegation(this.agent!); | ||
|
|
||
| const data = { | ||
|
|
@@ -303,39 +316,6 @@ export class SignifyClient { | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Save old client passcode in KERIA agent | ||
| * @async | ||
| * @param {string} passcode Passcode to be saved | ||
| * @returns {Promise<Response>} A promise to the result of the save | ||
| */ | ||
| async saveOldPasscode(passcode: string): Promise<Response> { | ||
| const caid = this.controller?.pre; | ||
| const body = { salt: passcode }; | ||
| return await fetch(this.url + '/salt/' + caid, { | ||
| method: 'PUT', | ||
| body: JSON.stringify(body), | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Delete a saved passcode from KERIA agent | ||
| * @async | ||
| * @returns {Promise<Response>} A promise to the result of the deletion | ||
| */ | ||
| async deletePasscode(): Promise<Response> { | ||
| const caid = this.controller?.pre; | ||
| return await fetch(this.url + '/salt/' + caid, { | ||
| method: 'DELETE', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Rotate the client AID | ||
| * @async | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kentbull There seems to be a problem with the latest vLEI docker image - see here: https://github.com/WebOfTrust/signify-ts/actions/runs/15855574402/job/44699896180 in
Print logssection.Server seems to be returning
application/jsoninstead ofapplication/schema+jsonand it's causing KERIA to crash. (which sounds like another unhandled issue)For now, locking it to the old version here works to get the integration tests to pass.