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 #38 from pagopa/IOPID-148
Browse files Browse the repository at this point in the history
[#IOPID-148] update LC middleware with multiple encoding verification
  • Loading branch information
gquadrati authored Apr 19, 2023
2 parents 4344da3 + f818ec3 commit 345354f
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 22 deletions.
57 changes: 48 additions & 9 deletions utils/httpSignature.verifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,45 @@ import { algMap } from "@mattrglobal/http-signatures";
// ----------------------

/**
* Builder for `rsa-pss-sha256` signature verifier.
* Builder for `ecdsa-p256-sha256` signature verifier with dsaEncoding defined by the caller.
* It's based on the`ecdsa-p256-sha256` 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 getVerifyEcdsaSha256WithEncoding = (
dsaEncoding: crypto.DSAEncoding
) => (key: JsonWebKey) => async (
data: Uint8Array,
signature: Uint8Array
): Promise<boolean> => {
const keyObject = crypto.createPublicKey({ format: "jwk", key });
return crypto
.createVerify("SHA256")
.update(data)
.verify(
{
dsaEncoding,
key: keyObject
},
signature
);
};

/**
* Builder for `rsa-pss-sha256` signature verifier with dsaEncoding defined by the caller
* 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 (
export const getVerifyRsaPssSha256WithEncoding = (
dsaEncoding: crypto.DSAEncoding
) => (key: JsonWebKey) => async (
data: Uint8Array,
signature: Uint8Array
): Promise<boolean> => {
Expand All @@ -25,7 +55,7 @@ export const getVerifyRsaPssSha256 = (key: JsonWebKey) => async (
.update(data)
.verify(
{
dsaEncoding: "ieee-p1363",
dsaEncoding,
key: keyObject,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING
},
Expand All @@ -36,23 +66,32 @@ export const getVerifyRsaPssSha256 = (key: JsonWebKey) => async (
export type SupportedAlgTypes = keyof typeof extendedAlgMap;

export const extendedAlgMap = {
["rsa-pss-sha256"]: {
verify: getVerifyRsaPssSha256
...algMap,
// Override standard algos
["ecdsa-p256-sha256"]: {
verify: getVerifyEcdsaSha256WithEncoding
},
...algMap
["rsa-pss-sha256"]: {
verify: getVerifyRsaPssSha256WithEncoding
}
};

export const customVerify = (keyMap: {
export const getCustomVerifyWithEncoding = (
dsaEncoding: crypto.DSAEncoding
) => (keyMap: {
readonly [keyid: string]: { readonly key: JsonWebKey };
}) => async (
signatureParams: { readonly keyid: string; readonly alg: SupportedAlgTypes },
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(
return extendedAlgMap[signatureParams.alg].verify(dsaEncoding)(
keyMap[signatureParams.keyid].key
)(data, signature);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import {
validLollipopHeaders,
validMultisignatureHeaders
} from "../../../__mocks__/lollipopSignature.mock";
import { aValidSha512AssertionRef } from "../../../__mocks__/lollipopPubKey.mock";
import {
aFiscalCode,
aValidSha512AssertionRef
} from "../../../__mocks__/lollipopPubKey.mock";
import { NonEmptyString } from "@pagopa/ts-commons/lib/strings";
import { AssertionTypeEnum } from "@pagopa/io-functions-commons/dist/generated/definitions/lollipop/AssertionType";

describe("HttpMessageSignatureMiddleware - Success", () => {
test(`GIVEN a request with a signature
WHEN the signature is valid and alg is 'ecdsa-p256-sha256'
WHEN the signature is valid, encoding is 'ieee-p1363' and alg is 'ecdsa-p256-sha256'
THEN the middleware return true`, async () => {
const mockReq = ({
app: {
Expand All @@ -35,7 +40,7 @@ describe("HttpMessageSignatureMiddleware - Success", () => {
});

test(`GIVEN a request with a signature
WHEN the signature is valid and alg is 'rsa-pss-sha256'
WHEN the signature is valid, encoding is 'ieee-p1363' and alg is 'rsa-pss-sha256'
THEN the middleware return true`, async () => {
const mockReq = ({
app: {
Expand Down Expand Up @@ -68,7 +73,7 @@ describe("HttpMessageSignatureMiddleware - Success", () => {
});

test(`GIVEN a request with a multi-signature
WHEN all the signatures are valid and alg is 'ecdsa-p256-sha256'
WHEN all the signatures are valid, encoding is 'ieee-p1363' and alg is 'ecdsa-p256-sha256'
THEN the middleware return true`, async () => {
const mockReq = ({
app: {
Expand All @@ -89,6 +94,42 @@ describe("HttpMessageSignatureMiddleware - Success", () => {

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

test(`GIVEN a request with a signature
WHEN the signature is valid, encoding is 'der' and alg is 'ecdsa-p256-sha256'
THEN the middleware return true`, async () => {
const mockReq = ({
app: {
get: () => ({
bindings: {
req: { rawBody: JSON.stringify(aValidPayload) }
}
})
},
headers: {
["x-pagopa-lollipop-assertion-ref"]:
"sha256-HiNolL87UYKQfaKISwIzyWY4swKPUzpaOWJCxaHy89M",
["x-pagopa-lollipop-assertion-type"]: AssertionTypeEnum.SAML,
["x-pagopa-lollipop-user-id"]: aFiscalCode,
["x-pagopa-lollipop-public-key"]:
"eyJrdHkiOiJFQyIsInkiOiJNdkVCMENsUHFnTlhrNVhIYm9xN1hZUnE2TnJTQkFTVmZhT2wzWnAxQmJzPSIsImNydiI6IlAtMjU2IiwieCI6InF6YTQzdGtLTnIrYWlTZFdNL0Q1cTdxMElmV3lZVUFIVEhSNng3dFByZEU9In0",
["x-pagopa-lollipop-auth-jwt"]: "aValidJWT" as NonEmptyString,
"x-pagopa-lollipop-original-method": "POST",
"x-pagopa-lollipop-original-url":
"https://api-app.io.pagopa.it/first-lollipop/sign",
"content-digest":
"sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:",
"signature-input": `sig1=("x-pagopa-lollipop-original-method" "x-pagopa-lollipop-original-url");created=1681473980;nonce="aNonce";alg="ecdsa-p256-sha256";keyid="sha256-HiNolL87UYKQfaKISwIzyWY4swKPUzpaOWJCxaHy89M"`,
signature: `sig1=:MEUCIFiZHxuLhk2Jlt46E5kbB8hCx7fN7QeeAj2gaSK3Y+WzAiEAtggj3Jwu8RbTGdNmsDix2zymh0gKwKxoPlolL7j6VTg=:`
},
url: "https://api-app.io.pagopa.it/first-lollipop/sign",
method: "POST",
body: aValidPayload
} as unknown) as express.Request;

const res = await HttpMessageSignatureMiddleware()(mockReq);
expect(res).toMatchObject(E.right(true));
});
});

describe("HttpMessageSignatureMiddleware - Failures", () => {
Expand Down
32 changes: 23 additions & 9 deletions utils/middleware/http_message_signature_middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable sort-keys */
// TODO: Move this file into io-functions-commons

import * as crypto_lib from "crypto";
import * as express from "express";
import { verifySignatureHeader } from "@mattrglobal/http-signatures";
import * as jwkToPem from "jwk-to-pem";
Expand All @@ -27,7 +28,7 @@ import * as crypto from "@pagopa/io-functions-commons/dist/src/utils/crypto";
import { JwkPubKeyToken } from "../../generated/definitions/internal/JwkPubKeyToken";
import { AssertionRef } from "../../generated/definitions/internal/AssertionRef";

import { customVerify } from "../httpSignature.verifiers";
import { getCustomVerifyWithEncoding } from "../httpSignature.verifiers";

export const LollipopHeadersForSignature = t.intersection([
t.type({
Expand Down Expand Up @@ -55,7 +56,9 @@ export const isValidDigestHeader = (
E.fold(constFalse, constTrue)
);

export const validateHttpSignature = (
export const validateHttpSignatureWithEconding = (
dsaEncoding: crypto_lib.DSAEncoding
) => (
request: express.Request,
assertionRef: AssertionRef,
publicKey: JwkPublicKey,
Expand All @@ -68,7 +71,7 @@ export const validateHttpSignature = (
method: request.method,
body,
verifier: {
verify: customVerify({
verify: getCustomVerifyWithEncoding(dsaEncoding)({
[assertionRef]: {
key: publicKey
}
Expand Down Expand Up @@ -148,12 +151,23 @@ export const HttpMessageSignatureMiddleware = (): IRequestMiddleware<
JwkPublicKeyFromToken.decode,
E.mapLeft(errors => new Error(readableReportSimplified(errors))),
TE.fromEither,
TE.chain(key =>
validateHttpSignature(
request,
lollipopHeaders["x-pagopa-lollipop-assertion-ref"],
key,
rawBody
TE.map(
key =>
[
request,
lollipopHeaders["x-pagopa-lollipop-assertion-ref"],
key,
rawBody
] as const
),
TE.chain(params =>
// IO app is currently signing using 'der' algorithm only.
// Anyway, a LC should be ready to verify 'ieee-p1363' algorithm too.
pipe(
validateHttpSignatureWithEconding("der")(...params),
TE.orElse(() =>
validateHttpSignatureWithEconding("ieee-p1363")(...params)
)
)
),
TE.mapLeft(error =>
Expand Down

0 comments on commit 345354f

Please sign in to comment.