Skip to content

Commit

Permalink
add entity statement to the trust chain
Browse files Browse the repository at this point in the history
  • Loading branch information
balanza committed Aug 8, 2023
1 parent 492e11e commit c44b2fd
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { it, expect, describe } from "vitest";
import { it, expect, describe, vi, beforeAll, afterAll } from "vitest";
import * as http from "http";

import * as H from "@pagopa/handler-kit";
import * as L from "@pagopa/logger";
import { pipe, flow } from "fp-ts/function";
import * as E from "fp-ts/Either";
import * as jose from "jose";
import { CreateWalletInstanceAttestationHandler } from "../create-wallet-instance-attestation";
import { ECKey, ECPrivateKey } from "../../../../jwk";
Expand Down Expand Up @@ -33,26 +35,54 @@ const signer = new CryptoSigner({
jwtDefaultDuration: "1h",
});

const url = flow(
UrlFromString.decode,
E.getOrElseW((_) => {
throw new Error(`Failed to parse url ${_[0].value}`);
})
);

const getEntityStatement = vi
.fn()
.mockImplementation(
() =>
`eyJ0eXAiOiJlbnRpdHktc3RhdGVtZW50K2p3dCIsImFsZyI6IlJTMjU2Iiwia2lkIjoiNGNPVDU3eGNmdmIzejlqYWtVcmpYRzM5TDNjbzB6OUJLXzVsS0t4VHl0USJ9.eyJleHAiOjE2OTE2ODE5NDcsImlhdCI6MTY5MTUwOTE0NywiaXNzIjoiaHR0cHM6Ly9kZW1vLmZlZGVyYXRpb24uZXVkaS53YWxsZXQuZGV2ZWxvcGVycy5pdGFsaWEuaXQiLCJzdWIiOiJodHRwczovL2lvLWQtd2FsbGV0LWl0LmF6dXJld2Vic2l0ZXMubmV0LyIsImp3a3MiOnsia2V5cyI6W3siY3J2IjoiUC0yNTYiLCJrdHkiOiJFQyIsIngiOiJxckpyajNBZl9CNTdzYk9JUnJjQk03YnI3d09jOHluajdsSEZQVGVmZlVrIiwieSI6IjFIMGNXRHlHZ3ZVOHcta1BLVV94eWNPQ1VOVDJvMGJ3c2xJUXRuUFU2aU0iLCJraWQiOiJFQyMxIn1dfSwibWV0YWRhdGFfcG9saWN5Ijp7IndhbGxldF9wcm92aWRlciI6eyJjb250YWN0cyI6eyJhZGQiOlsiaW8td2FsbGV0QHBhZ29wYS5pdCJdfX19LCJzb3VyY2VfZW5kcG9pbnQiOiJodHRwczovL2RlbW8uZmVkZXJhdGlvbi5ldWRpLndhbGxldC5kZXZlbG9wZXJzLml0YWxpYS5pdC9mZXRjaCIsInRydXN0X21hcmtzIjpbeyJpZCI6Imh0dHBzOi8vZGVtby5mZWRlcmF0aW9uLmV1ZGkud2FsbGV0LmRldmVsb3BlcnMuaXRhbGlhLml0L2VudGl0eS93YWxsZXRfcHJvdmlkZXIiLCJ0cnVzdF9tYXJrIjoiZXlKMGVYQWlPaUowY25WemRDMXRZWEpySzJwM2RDSXNJbUZzWnlJNklsSlRNalUySWl3aWEybGtJam9pTkdOUFZEVTNlR05tZG1JemVqbHFZV3RWY21wWVJ6TTVURE5qYnpCNk9VSkxYelZzUzB0NFZIbDBVU0o5LmV5SnBjM01pT2lKb2RIUndjem92TDJSbGJXOHVabVZrWlhKaGRHbHZiaTVsZFdScExuZGhiR3hsZEM1a1pYWmxiRzl3WlhKekxtbDBZV3hwWVM1cGRDSXNJbk4xWWlJNkltaDBkSEJ6T2k4dmFXOHRaQzEzWVd4c1pYUXRhWFF1WVhwMWNtVjNaV0p6YVhSbGN5NXVaWFF2SWl3aWFXRjBJam94TmpreE5UQTVNVFEzTENKcFpDSTZJbWgwZEhCek9pOHZaR1Z0Ynk1bVpXUmxjbUYwYVc5dUxtVjFaR2t1ZDJGc2JHVjBMbVJsZG1Wc2IzQmxjbk11YVhSaGJHbGhMbWwwTDJWdWRHbDBlUzkzWVd4c1pYUmZjSEp2ZG1sa1pYSWlmUS5WMElLaVZWcHlNQUNJZEFLdVdGbGc0YUVFM0RHVU9XaDI2MENIUHZmOTEwQnV3NjltMlhGY1NsNlFNTkk4Wl9iVGo1ODZEYzNvOWZJM1NKRFE0SU4xSUIwR1NVXzZzTUtOMGFvWDVqa2VLaUlndnQ1YmF0X0lGVk1FVUtObmZVT2ZPVEpCLXJaNmdYTEd2d1Q3OWN6dXdsU0p2MkNiM1hYdUU3ZlkwLWRFSWdKejB2cEZtNk5RZFpzNUVRbVh5OC1IZS1hbkFTbTBsNTJKTTZkMzctamdnRTUxZl9GM0lEQWYyTTdNbF9wQVJaX3hrTkhBOGpLekFEU3ktLXE0X3Z2dTVNYWdJX2kwbHhkR1AyVEY1dUxxVVZackJGYlRqeC1BRDJUaUZYaGhtZko4d094Mm4zOWxvVWg4UlRIekpIR0JYNG96bVliQXVSMHJWOHVjeFk5MUEifV19.HSDrSLaMClE1cFsrW1TUv30sk-YJnwRi3aRkC-piiDiznTsf2G-07q285tQRygpwp-vtIIfvhFWPsl2OQErzqRbwzPgQ5ck-4bUo5kVJm_a8g6Oz-55cyK1g_7Rqxk_cw7nm0mFGKkaCP6QiI0kB7Whhapp9CnHPOKiKMUQNseXhcSTfswxBR3oqoE5HPOvEnf3-TmhoVXFf1BDaxp-canY8SHPtZVZKaKTssEkhcLW8Syrk8-HsMGu_dyAaHWi6bmraqFDrUGrrM1EubF2Hb0xs1153_UIasFCtl16WWDVo9OhHeeRcG_MA78TXt66knyXp0YD92g64b3o7fFgejA`
);

const getTrustAnchotEntityConfiguration = vi
.fn()
.mockImplementation(
() =>
`eyJ0eXAiOiJlbnRpdHktc3RhdGVtZW50K2p3dCIsImFsZyI6IlJTMjU2Iiwia2lkIjoiNGNPVDU3eGNmdmIzejlqYWtVcmpYRzM5TDNjbzB6OUJLXzVsS0t4VHl0USJ9.eyJleHAiOjE2OTE2ODQ2NjYsImlhdCI6MTY5MTUxMTg2NiwiaXNzIjoiaHR0cHM6Ly9kZW1vLmZlZGVyYXRpb24uZXVkaS53YWxsZXQuZGV2ZWxvcGVycy5pdGFsaWEuaXQiLCJzdWIiOiJodHRwczovL2RlbW8uZmVkZXJhdGlvbi5ldWRpLndhbGxldC5kZXZlbG9wZXJzLml0YWxpYS5pdCIsImp3a3MiOnsia2V5cyI6W3sia3R5IjoiUlNBIiwibiI6InVJQnZRR3kyVGVqd1FHVXYyUjFhUWd3dlQxek0xdk0xVzNVc2xFaTduT2k3Zmk3blNleGdPZmFlcmdxb1Q1M2hQaENicV8tYk14Tl9JelA0end5c01lamFCREdNMk9hZHJ0QjNUb0JhOEVoWmZoVmgwTi1tNG9LYk5WWC1ETWtObnpNeDVkdlpTWVdlRm1PUUU4Vm9PODB3ZmpSZjB4ZWVfdGRtWDBvcEE1TTU2azFRY3JrR29ZOHFWWnJJQkZGSDd4R05wRUtZZEt0OGx4TDE5Qmx2aUJkMk5YSXg0WnRDV1phR2RoU184LVg3ZHhRV2tkdkhOZmxBdDhoNXRkUl93Y0xPaldtY3hsMG05eTgzTWhrNzlwNzAyUUdVb09QTUlETnZCNlpjV08tTlRHY2dtYXlKVEFTVUpHVzhWRl9wbHhQTnN6MldzSGZVQl9VY3Qta0tRUSIsImUiOiJBUUFCIiwia2lkIjoiNGNPVDU3eGNmdmIzejlqYWtVcmpYRzM5TDNjbzB6OUJLXzVsS0t4VHl0USJ9XX0sIm1ldGFkYXRhIjp7ImZlZGVyYXRpb25fZW50aXR5Ijp7ImNvbnRhY3RzIjpbImRlbWFyY29nODNAZ21haWwuY29tIl0sImZlZGVyYXRpb25fZmV0Y2hfZW5kcG9pbnQiOiJodHRwczovL2RlbW8uZmVkZXJhdGlvbi5ldWRpLndhbGxldC5kZXZlbG9wZXJzLml0YWxpYS5pdC9mZXRjaCIsImZlZGVyYXRpb25fcmVzb2x2ZV9lbmRwb2ludCI6Imh0dHBzOi8vZGVtby5mZWRlcmF0aW9uLmV1ZGkud2FsbGV0LmRldmVsb3BlcnMuaXRhbGlhLml0L3Jlc29sdmUiLCJmZWRlcmF0aW9uX3RydXN0X21hcmtfc3RhdHVzX2VuZHBvaW50IjoiaHR0cHM6Ly9kZW1vLmZlZGVyYXRpb24uZXVkaS53YWxsZXQuZGV2ZWxvcGVycy5pdGFsaWEuaXQvdHJ1c3RfbWFya19zdGF0dXMiLCJob21lcGFnZV91cmkiOiJodHRwczovL2RlbW8uZmVkZXJhdGlvbi5ldWRpLndhbGxldC5kZXZlbG9wZXJzLml0YWxpYS5pdCIsIm5hbWUiOiJUcnVzdCBBbmNob3IgLSBXYWxsZXQgaW50ZXJvcCBsYWIiLCJmZWRlcmF0aW9uX2xpc3RfZW5kcG9pbnQiOiJodHRwczovL2RlbW8uZmVkZXJhdGlvbi5ldWRpLndhbGxldC5kZXZlbG9wZXJzLml0YWxpYS5pdC9saXN0In19LCJjb25zdHJhaW50cyI6eyJtYXhfcGF0aF9sZW5ndGgiOjF9fQ.Yy15BIS8lyKA3ND2yhsK1kZiKPhiXXMbcWMT03ZsqcplaVhMY1Z40QnuOSXmPcAdBeCYZrYkbWX8-bUVCpYY21s5anP3Wum8hWfaPevp6nnaYZwN-2zPueQcoQO0_1AnCeOPpG5LkKD-8nuj-sn3IlOCtMT5sq2UWiFOJgrDDQC247qNJuZN1OhugLAbUqrpnQY2011s_RJiVADMN2JshkajmiruipJ8VVkI9kF_81UOOQJd4D3yUWUdthF8S6aeA5L6ZzWpQZNNkiB3EnvKR3B7_ksIuHG0yg1gyfQ6h9IWW292QjGAkRedr_F7BtkjT1wkWsSHHH0GhC-FdJTHWA`
);
const trustAnchorServerMock = http.createServer(function (req, res) {
const { pathname } = new URL(req.url || "", `https://${req.headers.host}`);
res.setHeader("Content-Type", "applicationz/entity-statement+jwt");
if (pathname.endsWith("/.well-known/openid-federation")) {
res.write(getTrustAnchotEntityConfiguration());
} else if (pathname.endsWith("/fetch")) {
res.write(getEntityStatement());
}
res.end();
});
const trustAnchorPort = 8123;

beforeAll(() => {
trustAnchorServerMock.listen(trustAnchorPort);
});

afterAll(() => {
trustAnchorServerMock.close();
});

const federationEntityMetadata: FederationEntityMetadata = {
basePath: new URL(
"https://wallet-provider.example.org"
) as unknown as ValidUrl,
basePath: url("https://wallet-provider.example.org"),
organizationName: "wallet provider" as NonEmptyString,
homePageUri: new URL(
"https://wallet-provider.example.org/privacy_policy"
) as unknown as ValidUrl,
policyUri: new URL(
"https://wallet-provider.example.org/info_policy"
) as unknown as ValidUrl,
tosUri: new URL(
"https://wallet-provider.example.org/logo.svg"
) as unknown as ValidUrl,
logoUri: new URL(
"https://wallet-provider.example.org/logo.svg"
) as unknown as ValidUrl,
trustAnchorUri: new URL(
"https://trust-anchor.example.org/logo.svg"
) as unknown as ValidUrl,
homePageUri: url("https://wallet-provider.example.org/privacy_policy"),
policyUri: url("https://wallet-provider.example.org/info_policy"),
tosUri: url("https://wallet-provider.example.org/logo.svg"),
logoUri: url("https://wallet-provider.example.org/logo.svg"),
trustAnchorUri: url(`http://localhost:${trustAnchorPort}`),
};

describe("CreateWalletInstanceAttestationHandler", async () => {
Expand Down Expand Up @@ -102,4 +132,62 @@ describe("CreateWalletInstanceAttestationHandler", async () => {
})
);
});

it("should return a 500 HTTP response on invalid entity statement", () => {
getEntityStatement.mockImplementationOnce(() => "invalid");

const run = CreateWalletInstanceAttestationHandler({
input: pipe(H.request("https://wallet-provider.example.org"), (req) => ({
...req,
method: "POST",
body: {
grant_type: GRANT_TYPE_KEY_ATTESTATION,
assertion: walletInstanceAttestationRequest,
},
})),
inputDecoder: H.HttpRequest,
logger: {
log: () => () => {},
format: L.format.simple,
},
federationEntityMetadata,
signer,
});
expect(run()).resolves.toEqual(
expect.objectContaining({
right: expect.objectContaining({
statusCode: 500,
}),
})
);
});

it("should return a 500 HTTP response on invalid trust anchor entity configuration", () => {
getTrustAnchotEntityConfiguration.mockImplementationOnce(() => "invalid");

const run = CreateWalletInstanceAttestationHandler({
input: pipe(H.request("https://wallet-provider.example.org"), (req) => ({
...req,
method: "POST",
body: {
grant_type: GRANT_TYPE_KEY_ATTESTATION,
assertion: walletInstanceAttestationRequest,
},
})),
inputDecoder: H.HttpRequest,
logger: {
log: () => () => {},
format: L.format.simple,
},
federationEntityMetadata,
signer,
});
expect(run()).resolves.toEqual(
expect.objectContaining({
right: expect.objectContaining({
statusCode: 500,
}),
})
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "@pagopa/ts-commons/lib/fetch";
import { Millisecond } from "@pagopa/ts-commons/lib/units";

import { sequenceS } from "fp-ts/lib/Apply";
import {
EntityStatementHeader,
EntityStatementPayload,
Expand Down Expand Up @@ -42,7 +43,9 @@ export class EidasTrustAnchor implements TrustAnchor {
new URL(oidFederation, this.#configuration.trustAnchorUri.href),
(metadataUrl) => metadataUrl.href,
getRequest(this.fetchWithTimeout),
TE.map(jose.decodeJwt),
TE.chain((value) =>
TE.tryCatch(async () => jose.decodeJwt(value), E.toError)
),
TE.chainEitherKW(
validate(
TrustAnchorEntityConfigurationPayload,
Expand All @@ -65,16 +68,21 @@ export class EidasTrustAnchor implements TrustAnchor {
return fetchUrl.href;
},
getRequest(this.fetchWithTimeout),
TE.chain(this.validateEntityStatementJwt)
TE.map((jwt) => ({
encoded: TE.right(jwt),
decoded: this.validateEntityStatementJwt(jwt),
})),
TE.chain(sequenceS(TE.ApplicativePar))
);

validateEntityStatementJwt = (jwt: string) =>
pipe(
jwt,
jose.decodeProtectedHeader,
validate(
EntityStatementHeader,
"Invalid trust anchor entity statement header"
E.tryCatch(() => jose.decodeProtectedHeader(jwt), E.toError),
E.chainW(
validate(
EntityStatementHeader,
"Invalid trust anchor entity statement header"
)
),
TE.fromEither,
TE.chain((es) =>
Expand Down
5 changes: 4 additions & 1 deletion apps/io-func-wallet-solution-backend/src/trust-anchor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@ export type EntityStatementPayload = t.TypeOf<typeof EntityStatementPayload>;

export type TrustAnchor = {
getPublicKeys: () => TE.TaskEither<Error, JwkPublicKey[]>;
getEntityStatement: () => TE.TaskEither<Error, EntityStatementPayload>;
getEntityStatement: () => TE.TaskEither<
Error,
{ encoded: string; decoded: EntityStatementPayload }
>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { verifyWalletInstanceAttestationRequest } from "./wallet-instance-attest
import { LoA, getLoAUri } from "./wallet-provider";
import { JwkPublicKey } from "./jwk";
import { WalletInstanceAttestationToJwtModel } from "./encoders/wallet-instance-attestation";
import { EidasTrustAnchor } from "./infra/trust-anchor";

export const WalletInstanceAttestationPayload = t.type({
iss: t.string,
Expand Down Expand Up @@ -45,8 +46,13 @@ export const createWalletInstanceAttestation =
signer.getSupportedSignAlgorithms(),
TE.fromEither
),
trustChain: pipe(
new EidasTrustAnchor(federationEntityMetadata),
(ta) => ta.getEntityStatement(),
TE.map(({ encoded }) => [encoded])
),
}),
TE.chain(({ request, publicJwk, supportedSignAlgorithms }) =>
TE.chain(({ request, publicJwk, supportedSignAlgorithms, trustChain }) =>
pipe(
{
iss: federationEntityMetadata.basePath.href,
Expand All @@ -66,7 +72,7 @@ export const createWalletInstanceAttestation =
},
WalletInstanceAttestationToJwtModel.encode,
signer.createJwtAndsign(
{ typ: "va+jwt", x5c: [], trust_chain: [] },
{ typ: "va+jwt", x5c: [], trust_chain: trustChain },
publicJwk.kid
)
)
Expand Down

0 comments on commit c44b2fd

Please sign in to comment.