Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
9b4b2a5
Feature/Added Api DPoP Token generation (PIN-8909_pt2)
rGregnanin Jan 19, 2026
3676768
Fix/Fix Lint and check (PIN-8909_pt2)
rGregnanin Jan 19, 2026
d5af01b
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Jan 19, 2026
a6227f8
WIP WI 3 auth server - refactoring generateInteropApiToken
paolomanca-pagopa Jan 19, 2026
c3a0078
WIP WI 3 auth server - refactoring generateInteropApiToken - test
paolomanca-pagopa Jan 19, 2026
9e7acb1
WIP WI 3 auth server - tokenService - remove block DPoP for m2m
paolomanca-pagopa Jan 19, 2026
1b616b5
WIP - m2m-gw-v3 - authorization logic
paolomanca-pagopa Jan 19, 2026
a24bff4
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Jan 20, 2026
12af85c
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Jan 22, 2026
9ea131c
WIP - m2m-gw-v3 - authorization logic - 2
paolomanca-pagopa Jan 21, 2026
40cd8d5
Access Token Binding Verification (cnf)
paolomanca-pagopa Jan 22, 2026
932be3a
Added bruno collection (PIN-8909)
rGregnanin Jan 22, 2026
6e8363c
auth with DPOP - WIP
paolomanca-pagopa Jan 22, 2026
3b7da2a
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
paolomanca-pagopa Jan 23, 2026
5671c72
strict check in header Auth and DPoP
paolomanca-pagopa Jan 23, 2026
1ce81aa
refactoring to leave untouched common function
paolomanca-pagopa Jan 23, 2026
ebf794f
fix wrong deps in dpop validation
paolomanca-pagopa Jan 23, 2026
52bd204
small comments
paolomanca-pagopa Jan 23, 2026
159f454
fix config export declaration
paolomanca-pagopa Jan 23, 2026
b27e5a5
revert fix export config
paolomanca-pagopa Jan 23, 2026
830ca7b
fix config export/import
paolomanca-pagopa Jan 23, 2026
806b48e
fix import config
paolomanca-pagopa Jan 23, 2026
507d02c
reafactor first check on Access Token DPoP
paolomanca-pagopa Jan 25, 2026
bcf025e
Feature/Added integration/api test (PIN-8909)
rGregnanin Jan 26, 2026
a871afb
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Jan 26, 2026
2713ba3
refactoring DPoP validation in one file
paolomanca-pagopa Jan 26, 2026
0dab72b
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Jan 27, 2026
96db613
chore/updated as suggested (PIN-8909)
rGregnanin Jan 28, 2026
1d3008a
chore/Added dpopProofJtiAlreadyUsed test (PIN-8909)
rGregnanin Jan 28, 2026
a74979e
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Jan 28, 2026
063fc34
Fix/fix lint(PIN-8909)
rGregnanin Jan 28, 2026
9124118
Chore/Updated as suggested (PIN-8909)
rGregnanin Jan 28, 2026
fdb67f3
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Jan 28, 2026
93ba4ce
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
paolomanca-pagopa Jan 28, 2026
a7d9cda
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Jan 29, 2026
d6c7933
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
paolomanca-pagopa Jan 29, 2026
81ababa
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Jan 29, 2026
f7f532b
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Jan 30, 2026
014746e
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Jan 30, 2026
d9ae60e
refactor internal command and structure
paolomanca-pagopa Jan 31, 2026
2e0966e
enforce check on DPoP token with 401 err
paolomanca-pagopa Jan 31, 2026
0b390e6
jsdoc fix
paolomanca-pagopa Jan 31, 2026
4c39771
enforce, manage error dpop validation
paolomanca-pagopa Jan 31, 2026
d9fd0f3
adding check on MTU
paolomanca-pagopa Jan 31, 2026
f90b8d0
comments and setup a mockmiddleware to pass tests as usual behavior
paolomanca-pagopa Feb 2, 2026
f2ee49c
add skeleton implementation for authenticationDPoPMiddleware.test
paolomanca-pagopa Feb 2, 2026
66a1f7e
adding parseAuthHeader.test in commons-test
paolomanca-pagopa Feb 2, 2026
b847cd2
adding test for calculateThumbprint in commons-test
paolomanca-pagopa Feb 2, 2026
f46925a
Chore/updated file .bru
rGregnanin Feb 2, 2026
f1edf7c
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Feb 2, 2026
d70cd41
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Feb 3, 2026
44e61c4
refactoring function in dpop-validation package and m2m-gateway-v3, a…
paolomanca-pagopa Feb 3, 2026
6678a09
removing uthenticationDPoPMiddleware.test
paolomanca-pagopa Feb 3, 2026
a7d4316
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Feb 3, 2026
cd7940c
chore/updated file create token .bru (PIN-8909)
rGregnanin Feb 3, 2026
6506a59
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' of h…
rGregnanin Feb 3, 2026
78f3b45
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Feb 3, 2026
875b9ae
fix test thumbprint
paolomanca-pagopa Feb 3, 2026
d5edde7
chore/ resolved minor comment (PIN-8909)
rGregnanin Feb 4, 2026
977560b
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
rGregnanin Feb 4, 2026
1dd9fc3
fix typo in APIerror
paolomanca-pagopa Feb 4, 2026
97112aa
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Feb 4, 2026
ff6df45
resolving comments on PR
paolomanca-pagopa Feb 4, 2026
cd6850e
remove jkt validation from verifyDPoPThumbprintMatch()
paolomanca-pagopa Feb 5, 2026
bf3391f
resolve comments on mandatory htm, adding constant to manage htm from…
paolomanca-pagopa Feb 5, 2026
f2c1bce
Merge branch 'develop' into PIN-8909_WI3_AuthorizationServer_JWT_M2M_…
paolomanca-pagopa Feb 5, 2026
1be575f
adding support for EC keyType on calculate thumbprint
paolomanca-pagopa Feb 5, 2026
93e479d
check on http header case insensitive
paolomanca-pagopa Feb 5, 2026
b4f6d11
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Feb 5, 2026
b24c19d
refactor calculateThumbprint with parsing alg with type
paolomanca-pagopa Feb 6, 2026
cdc8d21
fix/fixed as suggested (PIN-8909)
rGregnanin Feb 6, 2026
3a81691
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' of h…
rGregnanin Feb 6, 2026
5017b68
refactof extraction of Htu and Htm, add test in dpop-validation to ch…
paolomanca-pagopa Feb 6, 2026
6c4479d
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Feb 6, 2026
ee7396b
rename config var DPOP_HTU_BASE, strictness to check jwk claim
paolomanca-pagopa Feb 6, 2026
09fe778
Feature/added test (PIN-8909)
rGregnanin Feb 6, 2026
10bf608
Merge branch 'PIN-8909_WI3_AuthorizationServer_JWT_M2M_DPOP_pt2' into…
paolomanca-pagopa Feb 6, 2026
f30cfe5
Merge branch 'develop' into PIN-8863_WI4_added_Dpop_to_m2mGatewayV3_pt2
rGregnanin Feb 6, 2026
af2e1ef
fix build error, rename calculateJWKThumbprint
paolomanca-pagopa Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/authorization-server/.env
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ FEATURE_FLAG_IMPROVED_PRODUCER_VERIFICATION_CLAIMS=false
FEATURE_FLAG_CLIENT_ASSERTION_STRICT_CLAIMS_VALIDATION=false

DPOP_CACHE_TABLE=dpop-cache
DPOP_HTU=test/authorization-server/token.oauth2
DPOP_HTU_BASE=test/authorization-server/token.oauth2
DPOP_IAT_TOLERANCE_SECONDS=10
DPOP_DURATION_SECONDS=60
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ import {
} from "../model/domain/errors.js";
import { HttpDPoPHeader } from "../model/domain/models.js";

const EXPECTED_HTM = "POST";

export type GeneratedTokenData =
| {
limitReached: true;
Expand Down Expand Up @@ -545,7 +547,8 @@ const validateDPoPProof = async (
const { data, errors: dpopProofErrors } = dpopProofHeader
? verifyDPoPProof({
dpopProofJWS: dpopProofHeader,
expectedDPoPProofHtu: config.dpopHtu,
expectedDPoPProofHtu: config.dpopHtuBase,
expectedDPoPProofHtm: EXPECTED_HTM,
dpopProofIatToleranceSeconds: config.dpopIatToleranceSeconds,
dpopProofDurationSeconds: config.dpopDurationSeconds,
})
Expand Down
103 changes: 102 additions & 1 deletion packages/commons-test/test/jwk.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { JsonWebKey } from "crypto";
import { calculateKid, createJWK, sortJWK } from "pagopa-interop-commons";
import {
calculateKid,
calculateJWKThumbprint,
createJWK,
sortJWK,
} from "pagopa-interop-commons";
import { keyTypeNotAllowed, invalidJWKClaim } from "pagopa-interop-models";
import { describe, expect, it } from "vitest";

describe("jwk test", () => {
Expand All @@ -22,4 +28,99 @@ describe("jwk test", () => {
);
expect(kid).toEqual(expetedKid);
});

describe("calculateJWKThumbprint", () => {
// https://datatracker.ietf.org/doc/html/rfc7638#section-3.1 (example from RFC 7638)
const validRsaKey: JsonWebKey = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor. In commons-test we have the function generateKeySet. We could use that and compare the result of calculateThumbprint with a crypto hash.

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'd prefer using this static declaration

kty: "RSA",
n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
e: "AQAB",
};
const validEcKey: JsonWebKey = {
kty: "EC",
crv: "P-256",
x: "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
y: "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
};

it("should return the correct SHA-256 thumbprint (RFC 7638 match)", () => {
expect(calculateJWKThumbprint(validRsaKey)).toEqual(
"NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
);
});

it("should return the correct SHA-256 thumbprint for EC", () => {
expect(calculateJWKThumbprint(validEcKey)).toEqual(
"cn-I_WNMClehiVp51i_0VpOENW1upEerA8sEam5hn-s"
);
});

it("should throw if there are extra properties (RSA)", () => {
const keyWithExtras: JsonWebKey = {
...validRsaKey,
alg: "RS256",
kid: "ignore-me",
use: "sig",
};
expect(() => calculateJWKThumbprint(keyWithExtras)).toThrow(
invalidJWKClaim()
);
});
it("should throw if there are extra properties (EC)", () => {
const keyWithExtras: JsonWebKey = {
...validEcKey,
alg: "ES256",
kid: "ignore-me-ec",
use: "sig",
};
expect(() => calculateJWKThumbprint(keyWithExtras)).toThrow(
invalidJWKClaim()
);
});

it.skip("should produce the same thumbprint ignoring extra properties (RSA)", () => {
const keyWithExtras: JsonWebKey = {
...validRsaKey,
alg: "RS256",
kid: "ignore-me",
use: "sig",
};

const hashClean = calculateJWKThumbprint(validRsaKey);
const hashExtras = calculateJWKThumbprint(keyWithExtras);

expect(hashExtras).toEqual(hashClean);
});

it.skip("should produce the same thumbprint ignoring extra properties (EC)", () => {
const keyWithExtras: JsonWebKey = {
...validEcKey,
alg: "ES256",
kid: "ignore-me-ec",
use: "sig",
};

const hashClean = calculateJWKThumbprint(validEcKey);
const hashExtras = calculateJWKThumbprint(keyWithExtras);

expect(hashExtras).toEqual(hashClean);
});

it("should throw if kty is unsupported (e.g. oct)", () => {
const octKey: JsonWebKey = { kty: "oct", k: "secret-key" };
expect(() => calculateJWKThumbprint(octKey)).toThrow(
keyTypeNotAllowed("oct")
);
});

it("should throw if required RSA properties (n, e) are missing", () => {
const missingE = { kty: "RSA", n: "foo" } as JsonWebKey;
expect(() => calculateJWKThumbprint(missingE)).toThrow(invalidJWKClaim());
});

it("should throw if required EC properties (crv, x, y) are missing", () => {
const missingX = { kty: "EC", crv: "P-256", y: "bar" } as JsonWebKey;
expect(() => calculateJWKThumbprint(missingX)).toThrow(invalidJWKClaim());
});
});
});
90 changes: 90 additions & 0 deletions packages/commons-test/test/parseAuthHeader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { missingHeader, badDPoPToken } from "pagopa-interop-models";
import {
genericLogger,
jwtsFromAuthAndDPoPHeaders,
} from "pagopa-interop-commons";

describe("headers", () => {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const mockRequest = (headers: Record<string, string | undefined>) =>
({
headers,
method: "GET",
url: "/test/example/endpoint",
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);

afterEach(() => {
vi.restoreAllMocks();
});

describe("jwtsFromAuthAndDPoPHeaders", () => {
it("Should correctly extract Access Token and DPoP Proof when headers are valid", () => {
const req = mockRequest({
authorization: "DPoP some-access-token",
dpop: "some-dpop-proof",
});

const result = jwtsFromAuthAndDPoPHeaders(req, genericLogger);

expect(result).toEqual({
accessToken: "some-access-token",
dpopProofJWS: "some-dpop-proof",
});
});

it("Should throw missingHeader('Authorization') if Authorization header is missing", () => {
const req = mockRequest({
dpop: "some-dpop-proof",
});

expect(() => jwtsFromAuthAndDPoPHeaders(req, genericLogger)).toThrowError(
missingHeader("Authorization")
);
});

it("Should throw badDPoPToken if Authorization token scheme is not 'DPoP'", () => {
const req = mockRequest({
authorization: "Bearer some-token",
dpop: "some-dpop-proof",
});

expect(() => jwtsFromAuthAndDPoPHeaders(req, genericLogger)).toThrowError(
badDPoPToken
);
});

it("Should throw badDPoPToken if Authorization token value is missing", () => {
const req = mockRequest({
authorization: "DPoP",
dpop: "some-dpop-proof",
});

expect(() => jwtsFromAuthAndDPoPHeaders(req, genericLogger)).toThrowError(
badDPoPToken
);
});

it("Should throw missingHeader('DPoP') if DPoP header is missing", () => {
const req = mockRequest({
authorization: "DPoP valid-token",
});

expect(() => jwtsFromAuthAndDPoPHeaders(req, genericLogger)).toThrowError(
missingHeader("DPoP")
);
});

it("Should throw missingHeader('DPoP') if DPoP header is empty string", () => {
const req = mockRequest({
authorization: "DPoP valid-token",
dpop: "",
});

expect(() => jwtsFromAuthAndDPoPHeaders(req, genericLogger)).toThrowError(
missingHeader("DPoP")
);
});
});
});
66 changes: 64 additions & 2 deletions packages/commons/src/auth/headers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Request, Response } from "express";
import { badBearerToken, missingHeader } from "pagopa-interop-models";
import {
badBearerToken,
badDPoPToken,
missingHeader,
} from "pagopa-interop-models";
import { z } from "zod";
import { Logger } from "../logging/index.js";

Expand All @@ -22,14 +26,30 @@ export function parseAuthHeader(req: Request): string | undefined {
return undefined;
}

export function parseDPoPHeader(req: Request, logger: Logger): string {
const parsed = z.object({ dpop: z.string().min(1) }).safeParse(req.headers);

if (!parsed.success) {
logger.warn(
`Invalid authentication provided for this call ${req.method} ${req.url} - missing or malformed DPoP header`
);
throw missingHeader("DPoP");
}

return parsed.data.dpop;
}

export function jwtFromAuthHeader(req: Request, logger: Logger): string {
const authHeader = parseAuthHeader(req);
if (!authHeader) {
throw missingHeader("Authorization");
}

const authHeaderParts = authHeader.split(" ");
if (authHeaderParts.length !== 2 || authHeaderParts[0] !== "Bearer") {
if (
authHeaderParts.length !== 2 ||
authHeaderParts[0].toLowerCase() !== "bearer"
) {
logger.warn(
`Invalid authentication provided for this call ${req.method} ${req.url}`
);
Expand All @@ -39,6 +59,48 @@ export function jwtFromAuthHeader(req: Request, logger: Logger): string {
return authHeaderParts[1];
}

/**
* Extracts and validates the presence of the:
* (1) Access Token DPoP and
* (2) the DPoP Proof
* from the HTTP request headers.
*
* This function performs a syntax and presence check on the `Authorization` and `DPoP` headers.
* It does not verify the cryptographic signatures or the claims of the tokens.
*
* @param req - The Express `Request` object containing the HTTP headers.
* @param logger - The `Logger` instance used to log warnings in case of missing or malformed headers.
*
* @returns An object containing the raw strings of the Access Token and the DPoP Proof JWS.
*
* @throws {missingHeader} If the `Authorization` header is missing entirely.
* @throws {badDPoPToken} If the `Authorization` scheme is not "DPoP" or the token value is missing.
* @throws {missingHeader} If the `DPoP` header is missing or malformed (e.g., empty or invalid format according to `parseDPoPHeader`)
*
*/
export function jwtsFromAuthAndDPoPHeaders(
req: Request,
logger: Logger
): { accessToken: string; dpopProofJWS: string } {
const authHeader = parseAuthHeader(req);
if (!authHeader) {
throw missingHeader("Authorization");
}

const [scheme, accessToken] = authHeader.split(" ");
if (scheme.toLowerCase() !== "dpop" || !accessToken) {
logger.warn(
`Invalid authentication provided for this call ${req.method} ${req.url}`
);
throw badDPoPToken;
}
const dpopProofJWS = parseDPoPHeader(req, logger);
return {
accessToken,
dpopProofJWS,
};
}

export const METADATA_VERSION_HEADER = "x-metadata-version";
export function setMetadataVersionHeader(
res: Response,
Expand Down
37 changes: 35 additions & 2 deletions packages/commons/src/auth/jwk.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import crypto, { JsonWebKey, KeyObject } from "crypto";
import crypto, { createHash, JsonWebKey, KeyObject } from "crypto";
import jwksClient, { JwksClient } from "jwks-rsa";
import {
notAnRSAKey,
invalidKeyLength,
invalidPublicKey,
jwkDecodingError,
invalidJWKClaim,
notAllowedCertificateException,
notAllowedMultipleKeysException,
notAllowedPrivateKeyException,
keyTypeNotAllowed,
JWKKeyRS256,
JWKKeyES256,
} from "pagopa-interop-models";
import { match } from "ts-pattern";
import { JWTConfig } from "../config/index.js";

export const decodeBase64ToPem = (base64String: string): string => {
Expand All @@ -35,12 +40,40 @@ export const calculateKid = (jwk: JsonWebKey): string => {
const jwkString = JSON.stringify(sortedJwk);
return crypto.createHash("sha256").update(jwkString).digest("base64url");
};

/* This is to avoid repeating the logic of the "calculateKid",
and to have a more meaningful name
for the generation of the CNF field inside the DPoP tokens */
export const calculateDPoPThumbprint = calculateKid;

export const calculateJWKThumbprint = (jwk: JsonWebKey): string => {
const parsedJwk = match(jwk.kty)
.with("RSA", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

We could just parse these with JWKKeyES256 and JWKKeyRS256

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

const result = JWKKeyRS256.safeParse(jwk);

if (!result.success) {
throw invalidJWKClaim();
}
return result.data;
})
.with("EC", () => {
const result = JWKKeyES256.safeParse(jwk);

if (!result.success) {
throw invalidJWKClaim();
}
return result.data;
})
.otherwise(() => {
throw keyTypeNotAllowed(jwk.kty);
});

const canonicalJwk = sortJWK(parsedJwk);

return createHash("sha256")
.update(JSON.stringify(canonicalJwk))
.digest("base64url");
};

function assertNotCertificate(key: string): void {
try {
new crypto.X509Certificate(key);
Expand Down
Loading