Skip to content
This repository has been archived by the owner on Jan 15, 2025. It is now read-only.

Commit

Permalink
Merge pull request #30 from pagopa/IOCIT-373--add-custom-verifiers
Browse files Browse the repository at this point in the history
[#IOCIT-373]  Add custom verifiers to signature middleware
  • Loading branch information
gquadrati authored Mar 15, 2023
2 parents e26b391 + 0ce0194 commit 8949a9d
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 19 deletions.
58 changes: 58 additions & 0 deletions utils/httpSignature.verifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as crypto from "crypto";
import { JsonWebKey } from "crypto";
import { algMap } from "@mattrglobal/http-signatures";

// ----------------------
// Custom Verifiers
// ----------------------

/**
* Builder for `rsa-pss-sha256` signature verifier.
* It's based on the`rsa-pss-sha512` one, defined in @mattrglobal/http-signatures library.
* See https://github.com/mattrglobal/http-signatures/blob/v4.0.1/src/common/cryptoPrimatives.ts
*
* @param key the public key
* @returns a function that takes the data and the signature
* and return the comparison between them, based on the algorithm and the public key
*/
export const getVerifyRsaPssSha256 = (key: JsonWebKey) => async (
data: Uint8Array,
signature: Uint8Array
): Promise<boolean> => {
const keyObject = crypto.createPublicKey({ format: "jwk", key });
return crypto
.createVerify("SHA256")
.update(data)
.verify(
{
dsaEncoding: "ieee-p1363",
key: keyObject,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING
},
signature
);
};

export type SupportedAlgTypes = keyof typeof extendedAlgMap;

export const extendedAlgMap = {
["rsa-pss-sha256"]: {
verify: getVerifyRsaPssSha256
},
...algMap
};

export const customVerify = (keyMap: {
readonly [keyid: string]: { readonly key: JsonWebKey };
}) => async (
signatureParams: { readonly keyid: string; readonly alg: SupportedAlgTypes },
data: Uint8Array,
signature: Uint8Array
): Promise<boolean> => {
if (keyMap[signatureParams.keyid] === undefined) {
return Promise.resolve(false);
}
return extendedAlgMap[signatureParams.alg].verify(
keyMap[signatureParams.keyid].key
)(data, signature);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
validLollipopHeaders,
validMultisignatureHeaders
} from "../../../__mocks__/lollipopSignature.mock";
import { aValidSha512AssertionRef } from "../../../__mocks__/lollipopPubKey.mock";

describe("HttpMessageSignatureMiddleware", () => {
describe("HttpMessageSignatureMiddleware - Success", () => {
test(`GIVEN a request with a signature
WHEN the signature is valid
WHEN the signature is valid and alg is 'ecdsa-p256-sha256'
THEN the middleware return true`, async () => {
const mockReq = ({
app: {
Expand All @@ -33,8 +34,41 @@ describe("HttpMessageSignatureMiddleware", () => {
expect(res).toMatchObject(E.right(true));
});

test(`GIVEN a request with a signature
WHEN the signature is valid and alg is 'rsa-pss-sha256'
THEN the middleware return true`, async () => {
const mockReq = ({
app: {
get: () => ({
bindings: {
req: { rawBody: JSON.stringify(aValidPayload) }
}
})
},
headers: {
...validLollipopHeaders,
["x-pagopa-lollipop-assertion-ref"]:
"sha256-A3OhKGLYwSvdJ2txHi_SGQ3G-sHLh2Ibu91ErqFx_58",
["x-pagopa-lollipop-public-key"]:
"eyJrZXlfb3BzIjpbInZlcmlmeSJdLCJleHQiOnRydWUsImt0eSI6IlJTQSIsIm4iOiJ2dHAwT3p5aGhsSUh2YjFmR0pKVERJSUtVVmtKcTJrOEJuekNJVThhbFdXWnd6bExVUERUUU55OW1CdUFVWndLZUdEam8xSlBuUjJXQzhWLTlOSVkxZzVUYlg5SGJnZk9rd3YzTVl1V0xhZUVBZmdfT0FFTEJjNjhxV0JZdTdxWXpjSlpPZ3dTdWZRNmxidUlFNXhrcnMtY3Fpc08zMGpFQmF0QjNReXJQZEdid0pXOEJ4bTBqQTZlN1NWcDd3NzUtYkdGeElhMkNDOTNuSWl4ZTNMRnNhRU5yX0lnYlNNM2NDcG00VEFYZHVGc1dHWjU2SkdHYkZBak5hV2s3VDJwMU9qd2owX0RVNzZIRUpEWGdNSkp4b3NtaE9GajdUZE95aE0zTVpWSUFUcEswYkZBdFZpcWk1WGxaTUpXY2FscmJpSkNHTjdBdzV2anBTdkpsOTVkVVRuR1ZTQlZXQ1gyVm9hb2FEVV9sTVNUYkw5Sm5HVlZzX3Ywb3dOR0lELVpHNXQ5Yk9ZYWt4MWJjWWN6ZzcyQVd1V3RHTXFtWDFSSDEwTGlEam10NDh0Yml3Q254ZGVRaGFYa19wNVk0bzFpcnpQWG5nUzVJWWlqZXVXdjlzM2hCY3hGQTRPYURGdjRURkoxVExiRGluUmpfa1hCdjEyWDNLRE9iYWNFdDVLcGVsNUluNlZxZHBNNWVFZnZ0WDU4U0Z2bDktTmphb29PZjZUeUJsOEtVdTZOTmc2OXNYQVFRVnBzdXhpaDl3NzA5RVRObWx1M2tZc21iTWt3MVRGM3hnRmxJSDh2SnI2WWJ6ajFxcDJ1djJaYkFtYXBNLUd1NVRTYUNOSzZKbVpvLV95UVA5dDFGOWZldmUxYzFHZWlvdEtXLUpDalRmYnNodkZ0M1V3eUdyOCIsImUiOiJBUUFCIiwiYWxnIjoiUFM1MTIifQ",
Signature:
"sig1=:LaN4n+miIZDzPHSV1CwFM/M+qzp0InLYlfxKfOC8b+VMv4q9leZSQcGwLC7uLpvdn4o8q9+LKc6zPbsD6NWtJclbwVLBXJKmpI7OzPkHJhho809JPHiFeQ49o/YfLXGKSlGp0kYmpP6KwsFivkuH54+/I5s5PDJacpQ5J2/j6MeFZH2arft9Kg5ZSOBZdG4EpvAxOrmje8bzIbaptb2DSxdX9YfNiYf6fuMjDnGOVAa3nz8z135tyvPj5tP8P2MKRDKVc2L1sUfpTIJ9HSjcEx6cxhBXdPwozdtVy/we7QXBl+c9fHzCytk3aNlaPh1B10MCwuKQiky8M1GCR1BMC5pIkhXenOcvj7hNkTe1FEwkvLWHCYdJzNLD8ERbTfsKEM1CZt7I5fMBr0/mitOJZ2GBIOnhZZya0oFnR5br22zvXoJL8lS1ajp3Wt3EXSdliDloy5LDOPqxAEH5nXInfxDaYQgZuLWa0I2oLmSr/DlW/KqX0KVCtoJXdwxJjhBS/ZRTdC0u2WmcE0QA0YXeJfUWaU8KS1Nx+Lbz22xEu7kNGTh7r8cmtVzl5bybtpvMUZ7dqNgoW5mcaCUDjeQOinRsugVWVTowTPjJWXW+8cV9LeBJRq4oc9HzoLQDfn/HGoMFpi79pZQIzNFBE3Jpdoouu392AqmEebzEgm2838A=:",
["Signature-Input"]:
'sig1=("content-digest" "x-pagopa-lollipop-original-method" "x-pagopa-lollipop-original-url");created=1678814391;nonce="aNonce";alg="rsa-pss-sha256";keyid="sha256-A3OhKGLYwSvdJ2txHi_SGQ3G-sHLh2Ibu91ErqFx_58"'
},
url:
"https://io-p-weu-lollipop-fn.azurewebsites.net/api/v1/first-lollipop-consumer",
method: "POST",
body: aValidPayload
} as unknown) as express.Request;

const res = await HttpMessageSignatureMiddleware()(mockReq);

expect(res).toMatchObject(E.right(true));
});

test(`GIVEN a request with a multi-signature
WHEN all the signatures are valid
WHEN all the signatures are valid and alg is 'ecdsa-p256-sha256'
THEN the middleware return true`, async () => {
const mockReq = ({
app: {
Expand All @@ -56,3 +90,39 @@ describe("HttpMessageSignatureMiddleware", () => {
expect(res).toMatchObject(E.right(true));
});
});

describe("HttpMessageSignatureMiddleware - Failures", () => {
test(`GIVEN a request with a signature
WHEN the keyid is different from the assertion ref
THEN the middleware return false`, async () => {
const mockReq = ({
app: {
get: () => ({
bindings: {
req: { rawBody: JSON.stringify(aValidPayload) }
}
})
},
headers: {
...validLollipopHeaders,
["x-pagopa-lollipop-assertion-ref"]: aValidSha512AssertionRef
},
url:
"https://io-p-weu-lollipop-fn.azurewebsites.net/api/v1/first-lollipop-consumer",
method: "POST",
body: aValidPayload
} as unknown) as express.Request;

const res = await HttpMessageSignatureMiddleware()(mockReq);

expect(res).toMatchObject(
E.left(
expect.objectContaining({
detail:
'Internal server error: Http Message Signature Validation failed: HTTP Request Signature failed {"type":"FailedToVerify","message":"Signatures are well formed but one or more failed to verify."}',
kind: "IResponseErrorInternal"
})
)
);
});
});
36 changes: 20 additions & 16 deletions utils/middleware/http_message_signature_middleware.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
/* eslint-disable sort-keys */
// TODO: Move this file into io-functions-commons

import { IRequestMiddleware } from "@pagopa/io-functions-commons/dist/src/utils/request_middleware";
import {
ResponseErrorFromValidationErrors,
ResponseErrorInternal
} from "@pagopa/ts-commons/lib/responses";
import * as express from "express";
import { verifySignatureHeader } from "@mattrglobal/http-signatures";
import * as jwkToPem from "jwk-to-pem";

import { constFalse, constTrue, pipe } from "fp-ts/lib/function";
import * as t from "io-ts";
import * as E from "fp-ts/Either";
import { NonEmptyString } from "@pagopa/ts-commons/lib/strings";
import { getAppContext } from "@pagopa/io-functions-commons/dist/src/utils/middlewares/context_middleware";
import * as TE from "fp-ts/TaskEither";
import * as express from "express";
import * as E from "fp-ts/Either";
import * as t from "io-ts";

import {
JwkPublicKey,
JwkPublicKeyFromToken
} from "@pagopa/ts-commons/lib/jwk";
import * as jwkToPem from "jwk-to-pem";
import {
ResponseErrorFromValidationErrors,
ResponseErrorInternal
} from "@pagopa/ts-commons/lib/responses";
import { NonEmptyString } from "@pagopa/ts-commons/lib/strings";
import { readableReportSimplified } from "@pagopa/ts-commons/lib/reporters";
import { verifySignatureHeader } from "@mattrglobal/http-signatures";
import * as crypto from "../crypto";
import { JwkPubKeyToken } from "../../generated/definitions/internal/JwkPubKeyToken";
import { IRequestMiddleware } from "@pagopa/io-functions-commons/dist/src/utils/request_middleware";
import { getAppContext } from "@pagopa/io-functions-commons/dist/src/utils/middlewares/context_middleware";

import { JwkPubKeyToken } from "../../generated/definitions/internal/JwkPubKeyToken";
import { AssertionRef } from "../../generated/definitions/internal/AssertionRef";

import * as crypto from "../crypto";
import { customVerify } from "../httpSignature.verifiers";

export const LollipopHeadersForSignature = t.intersection([
t.type({
["x-pagopa-lollipop-public-key"]: JwkPubKeyToken,
Expand Down Expand Up @@ -64,11 +68,11 @@ export const validateHttpSignature = (
method: request.method,
body,
verifier: {
keyMap: {
verify: customVerify({
[assertionRef]: {
key: publicKey
}
}
})
}
},
TE.of,
Expand Down

0 comments on commit 8949a9d

Please sign in to comment.