-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: bug in DPoP confirmation claim verification
- Loading branch information
1 parent
0cbb504
commit fbdeb4a
Showing
8 changed files
with
177 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,143 +1,214 @@ | ||
import jwtVerify from "jose/jwt/verify"; | ||
import { verify } from "../src/lib/DPoP"; | ||
import { | ||
dpopTokenHeaderUnsupported, | ||
dpopTokenEC, | ||
dpopTokenRSA, | ||
} from "./fixture/DPoPToken"; | ||
import type { DPoPToken } from "../src/types"; | ||
/* | ||
* import { | ||
* dpopTokenHeaderUnsupported, | ||
* dpopTokenEC, | ||
* } from "./fixture/DPoPToken"; | ||
*/ | ||
import { encodeToken } from "./fixture/EncodeToken"; | ||
|
||
jest.mock("jose/jwt/verify"); | ||
|
||
describe("DPoP proof", () => { | ||
const accessToken: any = { payload: { cnf: { jkt: "confirmed_ID" } } }; | ||
const wrongAccessToken: any = { payload: { cnf: { jkt: "unconfirmed_ID" } } }; | ||
const dpop: DPoPToken = { | ||
header: { | ||
typ: "dpop+jwt", | ||
alg: "ES256", | ||
jwk: { | ||
kty: "EC", | ||
x: "l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs", | ||
y: "9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA", | ||
crv: "P-256", | ||
}, | ||
}, | ||
payload: { | ||
jti: "e1j3V_bKic8-LAEB", | ||
htm: "GET", | ||
htu: "https://resource.example.org/protectedresource", | ||
iat: 1562262618, | ||
}, | ||
signature: | ||
"lNhmpAX1WwmpBvwhok4E74kWCiGBNdavjLAeevGy32H3dbF0Jbri69Nm2ukkwb-uyUI4AUg1JSskfWIyo4UCbQ", | ||
}; | ||
|
||
const dpopRSA: DPoPToken = { | ||
header: { | ||
typ: "dpop+jwt", | ||
alg: "RS256", | ||
jwk: { | ||
alg: "RS256", | ||
kty: "RSA", | ||
e: "AQAB", | ||
n: | ||
"12oBZRhCiZFJLcPg59LkZZ9mdhSMTKAQZYq32k_ti5SBB6jerkh-WzOMAO664r_qyLkqHUSp3u5SbXtseZEpN3XPWGKSxjsy-1JyEFTdLSYe6f9gfrmxkUF_7DTpq0gn6rntP05g2-wFW50YO7mosfdslfrTJYWHFhJALabAeYirYD7-9kqq9ebfFMF4sRRELbv9oi36As6Q9B3Qb5_C1rAzqfao_PCsf9EPsTZsVVVkA5qoIAr47lo1ipfiBPxUCCNSdvkmDTYgvvRm6ZoMjFbvOtgyts55fXKdMWv7I9HMD5HwE9uW839PWA514qhbcIsXEYSFMPMV6fnlsiZvQQ", | ||
}, | ||
}, | ||
payload: dpop.payload, | ||
signature: "", | ||
}; | ||
|
||
const dpopWrongKeyType: DPoPToken = { | ||
header: { | ||
typ: "dpop+jwt", | ||
alg: "RS256", | ||
jwk: { | ||
alg: "RS256", | ||
kty: "UNSUPPORTED_KEY_TYPE", | ||
e: "AQAB", | ||
n: | ||
"12oBZRhCiZFJLcPg59LkZZ9mdhSMTKAQZYq32k_ti5SBB6jerkh-WzOMAO664r_qyLkqHUSp3u5SbXtseZEpN3XPWGKSxjsy-1JyEFTdLSYe6f9gfrmxkUF_7DTpq0gn6rntP05g2-wFW50YO7mosfdslfrTJYWHFhJALabAeYirYD7-9kqq9ebfFMF4sRRELbv9oi36As6Q9B3Qb5_C1rAzqfao_PCsf9EPsTZsVVVkA5qoIAr47lo1ipfiBPxUCCNSdvkmDTYgvvRm6ZoMjFbvOtgyts55fXKdMWv7I9HMD5HwE9uW839PWA514qhbcIsXEYSFMPMV6fnlsiZvQQ", | ||
}, | ||
}, | ||
payload: dpop.payload, | ||
signature: "", | ||
}; | ||
|
||
it("Checks conforming proof", async () => { | ||
describe("DPoP proof", () => { | ||
it("Checks conforming proof with EC Key", async () => { | ||
(jwtVerify as jest.Mock).mockResolvedValueOnce({ | ||
payload: dpopTokenEC.payload, | ||
protectedHeader: dpopTokenEC.header, | ||
payload: dpop.payload, | ||
protectedHeader: dpop.header, | ||
}); | ||
|
||
expect( | ||
await verify( | ||
encodeToken(dpopTokenEC), | ||
accessToken, | ||
encodeToken(dpop), | ||
{ | ||
payload: { | ||
cnf: { jkt: "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" }, | ||
}, | ||
} as any, | ||
"GET", | ||
"https://example.com", | ||
"https://resource.example.org/protectedresource", | ||
() => false | ||
) | ||
).toStrictEqual(dpopTokenEC); | ||
).toStrictEqual(dpop); | ||
}); | ||
|
||
it("Checks conforming proof with RSA JWK", async () => { | ||
it("Checks conforming proof with RSA Key", async () => { | ||
(jwtVerify as jest.Mock).mockResolvedValueOnce({ | ||
payload: dpopTokenRSA.payload, | ||
protectedHeader: dpopTokenRSA.header, | ||
payload: dpopRSA.payload, | ||
protectedHeader: dpopRSA.header, | ||
}); | ||
|
||
expect( | ||
await verify( | ||
encodeToken(dpopTokenRSA), | ||
accessToken, | ||
encodeToken(dpopRSA), | ||
{ | ||
payload: { | ||
cnf: { jkt: "cbaZgHZazjgQq0Q2-Hy_o2-OCDpPu02S30lNhTsNU1Q" }, | ||
}, | ||
} as any, | ||
"GET", | ||
"https://example.com", | ||
"https://resource.example.org/protectedresource", | ||
() => false | ||
) | ||
).toStrictEqual(dpopTokenRSA); | ||
).toStrictEqual(dpopRSA); | ||
}); | ||
|
||
it("Throws on unsupported JWK", async () => { | ||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ | ||
it("Fails unsupported Key type", async () => { | ||
(jwtVerify as jest.Mock).mockResolvedValueOnce({ | ||
payload: dpopTokenRSA.payload, | ||
protectedHeader: dpopTokenHeaderUnsupported, | ||
payload: dpopWrongKeyType.payload, | ||
protectedHeader: dpopWrongKeyType.header, | ||
}); | ||
const unsupportedKeyType = { | ||
header: dpopTokenHeaderUnsupported, | ||
payload: dpopTokenRSA.payload, | ||
signature: "", | ||
}; | ||
/* eslint-enable @typescript-eslint/no-unsafe-assignment */ | ||
|
||
await expect( | ||
verify( | ||
encodeToken(unsupportedKeyType), | ||
accessToken, | ||
encodeToken(dpopRSA), | ||
{ | ||
payload: { | ||
cnf: { jkt: "cbaZgHZazjgQq0Q2-Hy_o2-OCDpPu02S30lNhTsNU1Q" }, | ||
}, | ||
} as any, | ||
"GET", | ||
"https://example.com", | ||
"https://resource.example.org/protectedresource", | ||
() => false | ||
) | ||
).rejects.toThrow("Expected EC or RSA, got:\nUNSUPPORTED_KEY_TYPE"); | ||
}); | ||
|
||
it("Throws on wrong method", async () => { | ||
(jwtVerify as jest.Mock).mockResolvedValueOnce({ | ||
payload: dpopTokenEC.payload, | ||
protectedHeader: dpopTokenEC.header, | ||
payload: dpop.payload, | ||
protectedHeader: dpop.header, | ||
}); | ||
|
||
await expect( | ||
verify( | ||
encodeToken(dpopTokenEC), | ||
accessToken, | ||
encodeToken(dpop), | ||
{ | ||
payload: { | ||
cnf: { jkt: "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" }, | ||
}, | ||
} as any, | ||
"POST", | ||
"https://example.com", | ||
"https://resource.example.org/protectedresource", | ||
() => false | ||
) | ||
).rejects.toThrow("Expected POST, got:\nGET"); | ||
}); | ||
|
||
it("Throws on wrong URL", async () => { | ||
it("Throws on wrong url", async () => { | ||
(jwtVerify as jest.Mock).mockResolvedValueOnce({ | ||
payload: dpopTokenEC.payload, | ||
protectedHeader: dpopTokenEC.header, | ||
payload: dpop.payload, | ||
protectedHeader: dpop.header, | ||
}); | ||
|
||
await expect( | ||
verify( | ||
encodeToken(dpopTokenEC), | ||
accessToken, | ||
encodeToken(dpop), | ||
{ | ||
payload: { | ||
cnf: { jkt: "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" }, | ||
}, | ||
} as any, | ||
"GET", | ||
"https://example.coms", | ||
"https://resource.example.org/otherresource", | ||
() => false | ||
) | ||
).rejects.toThrow( | ||
"Expected https://example.coms, got:\nhttps://example.com" | ||
"Expected https://resource.example.org/otherresource, got:\nhttps://resource.example.org/protectedresource" | ||
); | ||
}); | ||
|
||
it("Throws on duplicate JTI", async () => { | ||
(jwtVerify as jest.Mock).mockResolvedValueOnce({ | ||
payload: dpopTokenEC.payload, | ||
protectedHeader: dpopTokenEC.header, | ||
payload: dpop.payload, | ||
protectedHeader: dpop.header, | ||
}); | ||
|
||
await expect( | ||
verify( | ||
encodeToken(dpopTokenEC), | ||
accessToken, | ||
encodeToken(dpop), | ||
{ | ||
payload: { | ||
cnf: { jkt: "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" }, | ||
}, | ||
} as any, | ||
"GET", | ||
"https://example.com", | ||
"https://resource.example.org/protectedresource", | ||
() => true | ||
) | ||
).rejects.toThrow("Expected false, got:\ntrue"); | ||
}); | ||
|
||
it("Throws on wrong confirmation claim", async () => { | ||
(jwtVerify as jest.Mock).mockResolvedValueOnce({ | ||
payload: dpopTokenEC.payload, | ||
protectedHeader: dpopTokenEC.header, | ||
payload: dpop.payload, | ||
protectedHeader: dpop.header, | ||
}); | ||
|
||
await expect( | ||
verify( | ||
encodeToken(dpopTokenEC), | ||
wrongAccessToken, | ||
encodeToken(dpop), | ||
{ payload: { cnf: { jkt: "UNCONFIRMED_KEY_THUMBPRINT" } } } as any, | ||
"GET", | ||
"https://example.com", | ||
() => true | ||
"https://resource.example.org/protectedresource", | ||
() => false | ||
) | ||
).rejects.toThrow("Expected unconfirmed_ID, got:\nconfirmed_ID"); | ||
).rejects.toThrow( | ||
"Expected UNCONFIRMED_KEY_THUMBPRINT, got:\n0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" | ||
); | ||
}); | ||
}); |
Oops, something went wrong.