Skip to content

Commit

Permalink
feat: move http signature related code to http-signature-utils package (
Browse files Browse the repository at this point in the history
#797)

* feat(http-signature-utils): initial commit

* chore(http-signature-utils): update lockfile

* fix(http-signature-utils): fix package.json

* fix: add http-signature-utils to tsconfig

* fix: include http-signature-utils in gh workflow

* fix: gh workflow build deps

* fix: builds

* feat(HSU): pass keyId

* feat(HSU): add Dockerfile and add app to local infrastructure

* feat(HSU): move content digest creation into HSU

* Revert "feat(HSU): move content digest creation into HSU"

This reverts commit f6eae9c.

* feat(MAP): load private key

* chore: fix build:deps

* fix(MAP): graphql url

* feat(HSU): add content headers to app

* fix(HSU): remove `conent-type` and `content-length` headers from app

* feat(HSU): add content digest header to app

* fix(HSU): allow for lower case headers

* feat(HSU): move sig verification from auth to HSU

* fix: export and imports

* feat(HSU): move sig verification to HSU

* refactor(HSU): remove koa context from HSU

* fix(backend): middleware context

* fix(backend): imports

* fix(HSU): remove conent type and length headers from app response

* fix(HSU): add missing dependency

* fix: request URL

* fix(local): readd redis network

* fix(HSU): removing whitespace

* feat(local): enable sig validation

* test(HSU): add tests

* fix: types

* refactor(auth): remove console.log

* fix(MAP): quote fees

* docs(HSU): add Readme

* feat(HSU): add postman scripts

* fix(HSU): formatting

* fix(HSU): add postman scripts to lintignore file

* refactor(HSU): verification

* fix(HSU): key tmp dir

* refactor(infrastructure): rename key files

* chore(auth): remove jose

* refactor(open-payments): use createHeaders from utils package

* style(HSU): rename validateHttpSigHeaders and verifySigAndChallenge

* refactor(auth): only store keyId, not entire key

* refactor: remove JWKWithRequired

* refactor(HSU): remove unnecessary type cast

* chore: update gh workflow actions version

* chore(HSU): move postman scripts out of src

* test(HSU): add positive header validation tests

* style(HSU): rename verification -> validation
  • Loading branch information
sabineschaller authored Dec 15, 2022
1 parent ac84eb1 commit 73e250d
Show file tree
Hide file tree
Showing 57 changed files with 1,164 additions and 649 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
public
generated
dist
build
build
postman-scripts
12 changes: 11 additions & 1 deletion .github/workflows/lint_test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,18 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/workflows/rafiki/env-setup
- run: pnpm --filter openapi build
- run: pnpm --filter open-payments build:deps
- run: pnpm --filter open-payments test

http-signature-utils:
runs-on: ubuntu-latest
needs: checkout
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: ./.github/workflows/rafiki/env-setup
- run: pnpm --filter http-signature-utils test

build:
runs-on: ubuntu-latest
timeout-minutes: 5
Expand All @@ -93,6 +102,7 @@ jobs:
- openapi
- mock-account-provider
- open-payments
- http-signature-utils
steps:
- uses: actions/checkout@v3
- uses: ./.github/workflows/rafiki/env-setup
Expand Down
19 changes: 17 additions & 2 deletions infrastructure/local/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
NODE_ENV: development
AUTH_DATABASE_URL: postgresql://auth:auth@database/auth
INTROSPECTION_HTTPSIG: "false"
BYPASS_SIGNATURE_VALIDATION: "true"
BYPASS_SIGNATURE_VALIDATION: "false"
depends_on:
- tigerbeetle
- database
Expand All @@ -32,8 +32,10 @@ services:
LOG_LEVEL: debug
PORT: 80
SEED_FILE_LOCATION: /workspace/seed.primary.yml
KEY_FILE: /workspace/private-key.pem
volumes:
- ./seed.primary.yml:/workspace/seed.primary.yml
- ./private-key.pem:/workspace/private-key.pem
depends_on:
- backend
backend:
Expand Down Expand Up @@ -70,7 +72,7 @@ services:
PRICES_URL: http://fynbos/prices
REDIS_URL: redis://redis:6379/0
QUOTE_URL: http://fynbos/quotes
BYPASS_SIGNATURE_VALIDATION: "true"
BYPASS_SIGNATURE_VALIDATION: "false"
PAYMENT_POINTER_URL: https://backend/.well-known/pay
depends_on:
- tigerbeetle
Expand Down Expand Up @@ -121,6 +123,19 @@ services:
restart: unless-stopped
networks:
- rafiki
signatures:
build:
context: ../..
dockerfile: ./packages/http-signature-utils/Dockerfile
restart: always
ports:
- '3040:3000'
environment:
KEY_FILE: /workspace/private-key.pem
volumes:
- ./private-key.pem:/workspace/private-key.pem
networks:
- rafiki
volumes:
database-data: # named volumes can be managed easier using docker-compose
tigerbeetle-data: # named volumes can be managed easier using docker-compose
Expand Down
17 changes: 15 additions & 2 deletions infrastructure/local/peer-docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
NODE_ENV: development
AUTH_DATABASE_URL: postgresql://peerauth:peerauth@database/peerauth
INTROSPECTION_HTTPSIG: "false"
BYPASS_SIGNATURE_VALIDATION: "true"
BYPASS_SIGNATURE_VALIDATION: "false"
AUTH_SERVER_DOMAIN: "http://localhost:4006"
peer-backend:
image: ghcr.io/interledger/rafiki-backend:latest
Expand Down Expand Up @@ -50,7 +50,7 @@ services:
PRICES_URL: http://local-bank/prices
REDIS_URL: redis://redis:6379/1
QUOTE_URL: http://local-bank/quote
BYPASS_SIGNATURE_VALIDATION: "true"
BYPASS_SIGNATURE_VALIDATION: "false"
PAYMENT_POINTER_URL: https://peer-backend/.well-known/pay
local-bank:
build:
Expand All @@ -66,10 +66,23 @@ services:
LOG_LEVEL: debug
PORT: 80
SEED_FILE_LOCATION: /workspace/seed.peer.yml
KEY_FILE: /workspace/private-key.pem
volumes:
- ./seed.peer.yml:/workspace/seed.peer.yml
- ./peer-private-key.pem:/workspace/private-key.pem
depends_on:
- peer-backend
peer-signatures:
build:
context: ../..
dockerfile: ./packages/http-signature-utils/Dockerfile
restart: always
ports:
- '3041:3000'
environment:
KEY_FILE: /workspace/private-key.pem
volumes:
- ./peer-private-key.pem:/workspace/private-key.pem
networks:
local_rafiki:
external: true
3 changes: 3 additions & 0 deletions infrastructure/local/peer-private-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIEqezmcPhOE8bkwN+jQrppfRYzGIdFTVWQGTHJIKpz88
-----END PRIVATE KEY-----
3 changes: 3 additions & 0 deletions infrastructure/local/private-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICxfM9mUurUGnwlMMQEDclDEQnX7c49BoGKOB48URBxO
-----END PRIVATE KEY-----
3 changes: 2 additions & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"@koa/router": "^12.0.0",
"ajv": "^8.11.2",
"axios": "^0.27.2",
"http-signature-utils": "workspace:../http-signature-utils",
"httpbis-digest-headers": "github:interledger/httpbis-digest-headers",
"jose": "^4.9.0",
"knex": "^0.95",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
Expand All @@ -39,6 +39,7 @@
"uuid": "^8.3.2"
},
"devDependencies": {
"@faker-js/faker": "^7.4.0",
"@types/jest": "^28.1.8",
"@types/koa": "2.13.5",
"@types/koa-bodyparser": "^4.3.7",
Expand Down
34 changes: 21 additions & 13 deletions packages/auth/src/accessToken/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@ import { AccessToken } from './model'
import { Access } from '../access/model'
import { AccessTokenRoutes } from './routes'
import { createContext } from '../tests/context'
import { generateTestKeys } from '../tests/signature'
import { JWKWithRequired } from '../client/service'
import {
generateJwk,
generateTestKeys,
JWK,
TestKeys
} from 'http-signature-utils'

describe('Access Token Routes', (): void => {
let deps: IocContract<AppServices>
let appContainer: TestContainer
let knex: Knex
let trx: Knex.Transaction
let accessTokenRoutes: AccessTokenRoutes
let testJwk: JWKWithRequired
let testKeys: TestKeys
let testClientKey: JWK

beforeAll(async (): Promise<void> => {
deps = await initIocContainer(Config)
Expand All @@ -36,8 +41,11 @@ describe('Access Token Routes', (): void => {
const openApi = await deps.use('openApi')
jestOpenAPI(openApi.authServerSpec)

const keys = await generateTestKeys()
testJwk = keys.publicKey
testKeys = await generateTestKeys()
testClientKey = generateJwk({
privateKey: testKeys.privateKey,
keyId: testKeys.keyId
})
})

afterEach(async (): Promise<void> => {
Expand Down Expand Up @@ -98,7 +106,7 @@ describe('Access Token Routes', (): void => {
beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
clientKeyId: testJwk.kid
clientKeyId: testKeys.keyId
})
access = await Access.query(trx).insertAndFetch({
grantId: grant.id,
Expand Down Expand Up @@ -141,7 +149,7 @@ describe('Access Token Routes', (): void => {
const scope = nock(CLIENT)
.get('/jwks.json')
.reply(200, {
keys: [testJwk]
keys: [testClientKey]
})

const ctx = createContext(
Expand Down Expand Up @@ -179,7 +187,7 @@ describe('Access Token Routes', (): void => {
],
key: {
proof: 'httpsig',
jwk: testJwk
jwk: testClientKey
},
client_id: clientId
})
Expand All @@ -190,7 +198,7 @@ describe('Access Token Routes', (): void => {
const scope = nock(CLIENT)
.get('/jwks.json')
.reply(200, {
keys: [testJwk]
keys: [testClientKey]
})
const tokenCreatedDate = new Date(token.createdAt)
const now = new Date(
Expand Down Expand Up @@ -238,7 +246,7 @@ describe('Access Token Routes', (): void => {
beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
clientKeyId: testJwk.kid
clientKeyId: testKeys.keyId
})
token = await AccessToken.query(trx).insertAndFetch({
grantId: grant.id,
Expand Down Expand Up @@ -269,7 +277,7 @@ describe('Access Token Routes', (): void => {
const scope = nock(CLIENT)
.get('/jwks.json')
.reply(200, {
keys: [testJwk]
keys: [testClientKey]
})

const ctx = createContext(
Expand Down Expand Up @@ -298,7 +306,7 @@ describe('Access Token Routes', (): void => {
const scope = nock(CLIENT)
.get('/jwks.json')
.reply(200, {
keys: [testJwk]
keys: [testClientKey]
})

const ctx = createContext(
Expand Down Expand Up @@ -333,7 +341,7 @@ describe('Access Token Routes', (): void => {
beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
clientKeyId: testJwk.kid
clientKeyId: testKeys.keyId
})
access = await Access.query(trx).insertAndFetch({
grantId: grant.id,
Expand Down
29 changes: 19 additions & 10 deletions packages/auth/src/accessToken/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,41 @@ import { v4 } from 'uuid'
import { createTestApp, TestContainer } from '../tests/app'
import { Config } from '../config/app'
import { IocContract } from '@adonisjs/fold'
import { initIocContainer, JWKWithRequired } from '..'
import { initIocContainer } from '..'
import { AppServices } from '../app'
import { truncateTables } from '../tests/tableManager'
import { FinishMethod, Grant, GrantState, StartMethod } from '../grant/model'
import { AccessType, Action } from '../access/types'
import { AccessToken } from './model'
import { AccessTokenService } from './service'
import { Access } from '../access/model'
import { generateTestKeys } from '../tests/signature'
import {
generateJwk,
generateTestKeys,
JWK,
TestKeys
} from 'http-signature-utils'

describe('Access Token Service', (): void => {
let deps: IocContract<AppServices>
let appContainer: TestContainer
let knex: Knex
let trx: Knex.Transaction
let accessTokenService: AccessTokenService
let testJwk: JWKWithRequired
let testKeys: TestKeys
let testClientKey: JWK

beforeAll(async (): Promise<void> => {
deps = await initIocContainer(Config)
appContainer = await createTestApp(deps)
knex = await deps.use('knex')
accessTokenService = await deps.use('accessTokenService')

const keys = await generateTestKeys()
testJwk = keys.publicKey
testKeys = await generateTestKeys()
testClientKey = generateJwk({
privateKey: testKeys.privateKey,
keyId: testKeys.keyId
})
})

afterEach(async (): Promise<void> => {
Expand Down Expand Up @@ -81,7 +90,7 @@ describe('Access Token Service', (): void => {
beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
clientKeyId: testJwk.kid,
clientKeyId: testKeys.keyId,
continueToken: crypto.randomBytes(8).toString('hex').toUpperCase(),
continueId: v4(),
interactId: v4(),
Expand Down Expand Up @@ -146,7 +155,7 @@ describe('Access Token Service', (): void => {
const scope = nock(CLIENT)
.get('/jwks.json')
.reply(200, {
keys: [testJwk]
keys: [testClientKey]
})

const introspection = await accessTokenService.introspect(token.value)
Expand All @@ -155,7 +164,7 @@ describe('Access Token Service', (): void => {
expect(introspection).toMatchObject({
...grant,
access: [access],
key: { proof: 'httpsig', jwk: testJwk },
key: { proof: 'httpsig', jwk: testClientKey },
clientId
})
scope.isDone()
Expand Down Expand Up @@ -194,7 +203,7 @@ describe('Access Token Service', (): void => {
beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
clientKeyId: testJwk.kid,
clientKeyId: testKeys.keyId,
continueToken: crypto.randomBytes(8).toString('hex').toUpperCase(),
continueId: v4(),
interactId: v4(),
Expand Down Expand Up @@ -241,7 +250,7 @@ describe('Access Token Service', (): void => {
beforeEach(async (): Promise<void> => {
grant = await Grant.query(trx).insertAndFetch({
...BASE_GRANT,
clientKeyId: testJwk.kid,
clientKeyId: testKeys.keyId,
continueToken: crypto.randomBytes(8).toString('hex').toUpperCase(),
continueId: v4(),
interactId: v4(),
Expand Down
5 changes: 3 additions & 2 deletions packages/auth/src/accessToken/service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as crypto from 'crypto'
import { v4 } from 'uuid'
import { Transaction, TransactionOrKnex } from 'objection'
import { JWK } from 'http-signature-utils'

import { BaseService } from '../shared/baseService'
import { Grant, GrantState } from '../grant/model'
import { ClientService, JWKWithRequired } from '../client/service'
import { ClientService } from '../client/service'
import { AccessToken } from './model'
import { IAppConfig } from '../config/app'
import { Access } from '../access/model'
Expand All @@ -26,7 +27,7 @@ interface ServiceDependencies extends BaseService {

export interface KeyInfo {
proof: string
jwk: JWKWithRequired
jwk: JWK
}

export interface Introspection extends Partial<Grant> {
Expand Down
Loading

0 comments on commit 73e250d

Please sign in to comment.