Skip to content
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

feat(auth): httpsig request validation #387

Merged
merged 33 commits into from Aug 25, 2022
Merged

feat(auth): httpsig request validation #387

merged 33 commits into from Aug 25, 2022

Conversation

njlie
Copy link
Contributor

@njlie njlie commented Jun 29, 2022

Changes proposed in this pull request

  • Adds httpsig validation to requests where the client must present a public key.

Context

Closes #274.

Bumped jest to 28 to fix: jestjs/jest#10221 (comment), which was affecting one of the tests using fake-timers.
https://github.com/facebook/jest/releases/tag/v28.0.0

Checklist

  • Related issues linked using fixes #number
  • Tests added/updated
  • Documentation added
  • Make sure that all checks pass

@github-actions github-actions bot added pkg: auth Changes in the GNAP auth package. type: source Changes business logic type: tests Testing related labels Jun 29, 2022
@github-actions github-actions bot added pkg: backend Changes in the backend package. pkg: openapi pkg: rates Changes in the rates package. labels Jul 1, 2022
@njlie njlie force-pushed the nl-httpsig branch 2 times, most recently from 807b8dd to 739fa38 Compare July 5, 2022 18:01
@njlie njlie marked this pull request as ready for review July 13, 2022 22:40
Comment on lines 47 to 78
try {
const sig = ctx.headers['signature']
const sigInput = ctx.headers['signature-input']

if (
!sig ||
!sigInput ||
typeof sig !== 'string' ||
typeof sigInput !== 'string'
) {
ctx.status = 400
ctx.body = {
error: 'invalid_request'
}
return
}

const verified = await deps.clientService.verifySigFromBoundKey(
sig,
sigInput,
body['access_token'],
ctx
)
if (introspectionResult) {
ctx.body = introspectionToBody(introspectionResult)
if (!verified) {
ctx.status = 401
ctx.body = {
error: 'invalid_client'
}
}
} catch (err) {
if ((err as Error).name === 'InvalidSigInputError') {
ctx.status = 400
ctx.body = {
error: 'invalid_request'
}
return
} else {
return ctx.throw(404, 'token not found')
ctx.status = 401
ctx.body = {
error: 'invalid_client'
}
return
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider extracting this logic into a helper function so we don't have to duplicate this everywhere?

Copy link
Contributor Author

@njlie njlie Jul 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this should be better as a middleware we could place in front of the relevant routes. Will look into changing that.

@njlie njlie force-pushed the nl-httpsig branch 2 times, most recently from c5147fd to 30c65a8 Compare August 1, 2022 20:56
packages/auth/src/grant/service.ts Outdated Show resolved Hide resolved
packages/auth/src/grant/routes.ts Outdated Show resolved Hide resolved
package.json Outdated
"lint-staged": ">=10",
"prettier": "2.2.1",
"ts-jest": "^26.5.5",
"ts-jest": "^28.0.5",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing this message when running tests:

ts-jest[versions] (WARN) Version 4.2.4 of typescript installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version (>=4.3.0 <5.0.0-0). Please do not report issues in ts-jest if you are using unsupported versions.

packages/auth/src/client/service.ts Outdated Show resolved Hide resolved
packages/auth/src/grant/routes.ts Outdated Show resolved Hide resolved
@@ -168,12 +169,15 @@ export class App {
for (const path in openApi.paths) {
for (const method in openApi.paths[path]) {
if (isHttpMethod(method)) {
let useHttpSigMiddleware = false
let route: (ctx: AppContext) => Promise<void>
if (path.includes('continue')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no useHttpSigMiddleware for continuation requests?

   When used for delegation in GNAP, these key binding mechanisms allow
   the AS to ensure that the keys presented by the client instance in
   the initial request are in control of the party calling any follow-up
   or continuation requests.  To facilitate this requirement, the
   continuation response ([Section 3.1](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-3.1)) includes an access token bound to
   the client instance's key ([Section 2.3](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-2.3)), and that key (or its most
   recent rotation) MUST be proved in all continuation requests
   [Section 5](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-5).  Token management requests [Section 6](https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-6) are similarly bound
   to either the access token's own key or, in the case of bearer
   tokens, the client instance's key.

https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-7.3

if (!body['access_token']) {
ctx.status = 400
ctx.body = {
error: 'invalid_request',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these error responses only necessary for the client (not the RO)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only for the client. Though looking at the spec, it does seem like these are only necessary during the grant authorization flow and not necessarily for token management endpoints.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was thinking that since the RO would be sending token introspection requests that these error responses don't have to be used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that covered by checking against the spec?

packages/auth/src/client/service.ts Outdated Show resolved Hide resolved
@njlie njlie requested a review from wilsonianb August 8, 2022 19:10
} else if (path.includes('/token') && method === 'DELETE') {
keyName = 'managementId'
value = ctx.params['managementId']
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should continuation requests be included here?
#387 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, they should. Thanks for pointing that out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -44,6 +44,6 @@
"jest-openapi": "^0.14.2",
"nock": "^13.2.4",
"openapi-types": "^12.0.0",
"typescript": "^4.2.4"
"typescript": "^4.3.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still getting the warning and so is GitHub Actions
#387 (comment)
https://github.com/interledger/rafiki/runs/7682195245?check_suite_focus=true

I wonder if typescript should be updated in the root package.json

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try it and see what happens

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumping typescript breaks a lot of stuff

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't make any sense because ^4.2.4 actually installs 4.7.2 currently as the caret means any non breaking changes.

ctx.status = 200
})

this.publicRouter.del('/auth/token/:id', accessTokenRoutes.revoke)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess because we have this section further up that also includes checking against the openapi spec (L168 - L1206). In the end, all of those individual routes should be moved into the block that also checks the openapi spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is redundant to this block of code now. I originally stubbed it out when scaffolding the auth server but it didn't get removed once OpenApi was added.

Comment on lines 299 to 301
// afterAll(async (): Promise<void> => {
// nock.restore()
// })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?


const { body } = ctx.request
const { path, method } = ctx
// TODO: replace with HttpMethod types instead of string literals
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump

Comment on lines 114 to 105
const requestBody = {
access_token: v4(),
proof: 'httpsig',
resource_server: 'test'
}
const { signature, sigInput, contentDigest } = await generateSigHeaders(
url,
method,
requestBody
)
const ctx = createContext(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could add a wrapper for createContext that takes url, method, requestBody, etc as parameters and does generateSigHeaders + createContext instead of having to do this full setup in all these tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can actually just remove it since the test doesn't run the middleware.

@njlie njlie force-pushed the nl-httpsig branch 3 times, most recently from 0f8a049 to 16deb5b Compare August 16, 2022 08:20
packages/auth/jest.config.js Outdated Show resolved Hide resolved
if (!body['access_token']) {
ctx.status = 400
ctx.body = {
error: 'invalid_request',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that covered by checking against the spec?

@@ -2,7 +2,7 @@ exports.up = function (knex) {
return knex.schema.createTable('accessTokens', function (table) {
table.uuid('id').notNullable().primary()
table.string('value').notNullable().unique()
table.string('managementId').notNullable()
table.string('managementId').notNullable().unique()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we introduce that? The id is already unique, so why adding another unique id?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured it would be the right practice to expose an external id instead of an internal database id. I should update it to a .uuid() instead of a .string() though since in practice it's always a uuid/v4.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should update it to a .uuid() instead of a .string() though since in practice it's always a uuid/v4.

bump

ctx.status = 200
})

this.publicRouter.del('/auth/token/:id', accessTokenRoutes.revoke)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess because we have this section further up that also includes checking against the openapi spec (L168 - L1206). In the end, all of those individual routes should be moved into the block that also checks the openapi spec.

packages/auth/src/client/service.ts Outdated Show resolved Hide resolved
@njlie njlie requested a review from wilsonianb August 16, 2022 11:54
@njlie njlie merged commit 872c877 into main Aug 25, 2022
@njlie njlie deleted the nl-httpsig branch August 25, 2022 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg: auth Changes in the GNAP auth package. type: source Changes business logic type: tests Testing related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Signing/Request Library "Modern" fake-timer implementation doesn't work with PromiseJS.
4 participants