Skip to content

Commit f9a8f2b

Browse files
Remove secret length requirement (#869)
* update signingErrors state when encoding result is ok * remove buffer change * add tests
1 parent 39045ff commit f9a8f2b

File tree

3 files changed

+141
-50
lines changed

3 files changed

+141
-50
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface EncoderResult {
2+
jwt: string;
3+
signingErrors: string[] | null;
4+
}

src/features/encoder/services/token-encoder.service.ts

Lines changed: 78 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { AsymmetricKeyFormatValues } from "@/features/common/values/asymmetric-k
3636
import { useDebuggerStore } from "@/features/debugger/services/debugger.store";
3737
import { SigningAlgCategoryValues } from "@/features/common/values/signing-alg-category.values";
3838
import { EncoderInputsModel } from "@/features/debugger/models/encoder-inputs.model";
39+
import { EncoderResult } from "@/features/common/models/encoder-result.model";
3940

4041
type EncodingHeaderErrors = {
4142
headerErrors: string[] | null;
@@ -183,7 +184,8 @@ class _TokenEncoderService {
183184
}
184185

185186
if (encodeJWTResult.isOk()) {
186-
stateUpdate.jwt = encodeJWTResult.value.trim();
187+
stateUpdate.jwt = encodeJWTResult.value.jwt.trim();
188+
stateUpdate.signingErrors = encodeJWTResult.value.signingErrors;
187189
}
188190

189191
return {
@@ -214,7 +216,7 @@ class _TokenEncoderService {
214216
}
215217

216218
if (encodeJWTResult.isOk()) {
217-
stateUpdate.jwt = encodeJWTResult.value.trim();
219+
stateUpdate.jwt = encodeJWTResult.value.jwt.trim();
218220

219221
useDebuggerStore.getState().setStash$({
220222
asymmetricPublicKey: digitallySignedToken.publicKey,
@@ -379,7 +381,7 @@ class _TokenEncoderService {
379381
}
380382

381383
if (encodeJWTResult.isOk()) {
382-
stateUpdate.jwt = encodeJWTResult.value.trim();
384+
stateUpdate.jwt = encodeJWTResult.value.jwt.trim();
383385
}
384386

385387
return {
@@ -409,7 +411,7 @@ class _TokenEncoderService {
409411
}
410412

411413
if (encodeJWTResult.isOk()) {
412-
stateUpdate.jwt = encodeJWTResult.value.trim();
414+
stateUpdate.jwt = encodeJWTResult.value.jwt.trim();
413415
}
414416

415417
return {
@@ -484,48 +486,61 @@ class _TokenEncoderService {
484486
payload: DecodedJwtPayloadModel,
485487
key: string,
486488
encodingFormat: EncodingValues,
487-
): Promise<Result<string, DebuggerErrorModel>> {
488-
if (isHmacAlg(header.alg)) {
489-
if (!key) {
490-
return err({
491-
task: DebuggerTaskValues.ENCODE,
492-
input: DebuggerInputValues.KEY,
493-
message: "Secret must not be empty.",
494-
});
495-
}
489+
): Promise<Result<EncoderResult, DebuggerErrorModel>> {
490+
if (!isHmacAlg(header.alg)) {
491+
return err({
492+
task: DebuggerTaskValues.ENCODE,
493+
input: DebuggerInputValues.HEADER,
494+
message: `Invalid MAC algorithm. Only use MAC "alg" parameter values in the header as defined by [RFC 7518 (JSON Web Algorithms)](https://datatracker.ietf.org/doc/html/rfc7518#section-3.1).`,
495+
});
496+
}
496497

497-
const getAlgSizeResult = getAlgSize(header.alg);
498+
if (!key) {
499+
return err({
500+
task: DebuggerTaskValues.ENCODE,
501+
input: DebuggerInputValues.KEY,
502+
message: "Secret must not be empty.",
503+
});
504+
}
498505

499-
if (getAlgSizeResult.isErr()) {
500-
return err({
501-
task: DebuggerTaskValues.ENCODE,
502-
input: DebuggerInputValues.KEY,
503-
message: getAlgSizeResult.error,
504-
});
505-
}
506+
const getAlgSizeResult = getAlgSize(header.alg);
506507

507-
const checkHmacSecretLengthResult = checkHmacSecretLength(
508-
key,
509-
getAlgSizeResult.value.size,
510-
encodingFormat,
511-
);
508+
if (getAlgSizeResult.isErr()) {
509+
return err({
510+
task: DebuggerTaskValues.ENCODE,
511+
input: DebuggerInputValues.KEY,
512+
message: getAlgSizeResult.error,
513+
});
514+
}
512515

513-
if (checkHmacSecretLengthResult.isErr()) {
514-
return err(checkHmacSecretLengthResult.error);
515-
}
516+
const checkHmacSecretLengthResult = checkHmacSecretLength(
517+
key,
518+
getAlgSizeResult.value.size,
519+
encodingFormat,
520+
);
516521

517-
return await signWithSymmetricSecretKey(
518-
header as CompactJWSHeaderParameters,
519-
payload,
520-
key,
521-
encodingFormat,
522-
);
522+
const signingError = checkHmacSecretLengthResult.isErr()
523+
? [checkHmacSecretLengthResult.error.message]
524+
: null;
525+
526+
const signWithSymmetricSecretKeyResult = await signWithSymmetricSecretKey(
527+
header as CompactJWSHeaderParameters,
528+
payload,
529+
key,
530+
encodingFormat,
531+
);
532+
533+
if (signWithSymmetricSecretKeyResult.isErr()) {
534+
return err({
535+
task: DebuggerTaskValues.ENCODE,
536+
input: DebuggerInputValues.KEY,
537+
message: signWithSymmetricSecretKeyResult.error.message,
538+
});
523539
}
524540

525-
return err({
526-
task: DebuggerTaskValues.ENCODE,
527-
input: DebuggerInputValues.HEADER,
528-
message: `Invalid MAC algorithm. Only use MAC "alg" parameter values in the header as defined by [RFC 7518 (JSON Web Algorithms)](https://datatracker.ietf.org/doc/html/rfc7518#section-3.1).`,
541+
return ok<EncoderResult>({
542+
jwt: signWithSymmetricSecretKeyResult.value,
543+
signingErrors: signingError,
529544
});
530545
}
531546

@@ -534,7 +549,7 @@ class _TokenEncoderService {
534549
payload: DecodedJwtPayloadModel,
535550
key: string,
536551
keyFormat: AsymmetricKeyFormatValues,
537-
): Promise<Result<string, DebuggerErrorModel>> {
552+
): Promise<Result<EncoderResult, DebuggerErrorModel>> {
538553
if (isDigitalSignatureAlg(header.alg)) {
539554
if (!key) {
540555
return err({
@@ -544,12 +559,25 @@ class _TokenEncoderService {
544559
});
545560
}
546561

547-
return await signWithAsymmetricPrivateKey(
562+
const jwt = await signWithAsymmetricPrivateKey(
548563
header as CompactJWSHeaderParameters,
549564
payload,
550565
key,
551566
keyFormat,
552567
);
568+
569+
if (jwt.isErr()) {
570+
return err({
571+
task: DebuggerTaskValues.ENCODE,
572+
input: DebuggerInputValues.KEY,
573+
message: "Private key must not be empty.",
574+
})
575+
}
576+
577+
return ok({
578+
jwt: jwt.value,
579+
signingErrors: null,
580+
});
553581
}
554582

555583
return err({
@@ -684,9 +712,7 @@ class _TokenEncoderService {
684712
symmetricSecretKeyEncoding: EncodingValues;
685713
}): Promise<
686714
Result<
687-
{
688-
jwt: string;
689-
},
715+
EncoderResult,
690716
EncodingSymmetricSecretKeyErrors
691717
>
692718
> {
@@ -767,6 +793,7 @@ class _TokenEncoderService {
767793

768794
return ok({
769795
jwt: encodeJwtResult.value.jwt.trim(),
796+
signingErrors: encodeJwtResult.value.signingErrors,
770797
});
771798
}
772799

@@ -861,17 +888,15 @@ class _TokenEncoderService {
861888
},
862889
): Promise<
863890
Result<
864-
{
865-
jwt: string;
866-
},
891+
EncoderResult,
867892
EncodingJwtErrors
868893
>
869894
> {
870895
const algType = params.algType;
871896
const header = params.header;
872897
const payload = params.payload;
873898

874-
let encodeJWTResult: Result<string, DebuggerErrorModel> | null = null;
899+
let encodeJWTResult: Result<EncoderResult, DebuggerErrorModel> | null = null;
875900

876901
if (algType === SigningAlgCategoryValues.ANY) {
877902
const symmetricSecretKey = params.symmetricSecretKey;
@@ -998,8 +1023,9 @@ class _TokenEncoderService {
9981023
}
9991024
}
10001025

1001-
return ok({
1002-
jwt: encodeJWTResult.value,
1026+
return ok<EncoderResult>({
1027+
jwt: encodeJWTResult.value.jwt,
1028+
signingErrors: encodeJWTResult.value.signingErrors,
10031029
});
10041030
}
10051031

@@ -1235,6 +1261,7 @@ class _TokenEncoderService {
12351261
}
12361262

12371263
stateUpdate.jwt = processSymmetricSecretKeyResult.value.jwt.trim();
1264+
stateUpdate.signingErrors = processSymmetricSecretKeyResult.value.signingErrors;
12381265

12391266
return stateUpdate;
12401267
}
@@ -1269,6 +1296,7 @@ class _TokenEncoderService {
12691296
}
12701297

12711298
stateUpdate.jwt = processSymmetricSecretKeyResult.value.jwt.trim();
1299+
stateUpdate.signingErrors = processSymmetricSecretKeyResult.value.signingErrors;
12721300

12731301
return stateUpdate;
12741302
}

tests/token-encoder.service.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { EncodingValues } from "@/features/common/values/encoding.values";
2+
import { describe, expect, test } from "vitest";
3+
import { TokenEncoderService } from "@/features/encoder/services/token-encoder.service";
4+
import {
5+
DefaultTokensValues,
6+
DefaultTokenWithSecretModel,
7+
} from "@/features/common/values/default-tokens.values";
8+
import { EncoderResult } from "@/features/common/models/encoder-result.model";
9+
10+
describe("processSymmetricSecretKey", () => {
11+
describe("should encode a JWT for SYMMETRIC type with HMAC algorithm", () => {
12+
test("should return an object with a jwt and signingErrors should be null", async () => {
13+
const params = {
14+
header: JSON.stringify({ alg: "HS256", typ: "JWT" }),
15+
payload: JSON.stringify({
16+
sub: "1234567890",
17+
name: "John Doe",
18+
admin: true,
19+
iat: 1516239022,
20+
}),
21+
symmetricSecretKey: (
22+
DefaultTokensValues.HS256 as DefaultTokenWithSecretModel
23+
).secret,
24+
symmetricSecretKeyEncoding: EncodingValues.UTF8,
25+
};
26+
const result =
27+
await TokenEncoderService.processSymmetricSecretKey(params);
28+
29+
expect(result.isOk()).toBe(true);
30+
expect(result.unwrapOr({})).toEqual({
31+
jwt: DefaultTokensValues.HS256.token,
32+
signingErrors: null,
33+
});
34+
});
35+
36+
test("should return an object with a jwt and signingErrors should not be null", async () => {
37+
const params = {
38+
header: JSON.stringify({ alg: "HS256", typ: "JWT" }),
39+
payload: JSON.stringify({
40+
sub: "1234567890",
41+
name: "John Doe",
42+
admin: true,
43+
iat: 1516239022,
44+
}),
45+
symmetricSecretKey: "secret",
46+
symmetricSecretKeyEncoding: EncodingValues.UTF8,
47+
};
48+
const algSize = 256;
49+
const result =
50+
await TokenEncoderService.processSymmetricSecretKey(params);
51+
52+
expect(result.isOk()).toBe(true);
53+
expect((result.unwrapOr({}) as EncoderResult).jwt).not.toBeNull();
54+
expect((result.unwrapOr({}) as EncoderResult).signingErrors).toEqual(
55+
[`A key of ${algSize} bits or larger MUST be used with HS${algSize} as specified on [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-3.2).`]
56+
);
57+
});
58+
});
59+
});

0 commit comments

Comments
 (0)