Skip to content

Commit

Permalink
chore: check for invalid 'typ' headers
Browse files Browse the repository at this point in the history
  • Loading branch information
nstott committed Oct 26, 2024
1 parent 87cfecf commit 98a2f2d
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 19 deletions.
10 changes: 10 additions & 0 deletions v2/svid/jwtsvid/svid.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ func ParseAndValidate(token string, bundles jwtbundle.Source, audience []string)
return nil, jwtsvidErr.New("token header missing key id")
}

// forbid tokens which have the `typ` header, which is not either "JOSE" or "JWT"
if typ := tok.Headers[0].ExtraHeaders[jose.HeaderType]; typ != "JOSE" && typ != "JWT" {
return nil, jwtsvidErr.New("token header type not equal to either JWT or JOSE")
}

// Get JWT Bundle
bundle, err := bundles.GetJWTBundleForTrustDomain(trustDomain)
if err != nil {
Expand All @@ -83,6 +88,11 @@ func ParseAndValidate(token string, bundles jwtbundle.Source, audience []string)
// JWT-SVID. The JWT-SVID signature is not verified.
func ParseInsecure(token string, audience []string) (*SVID, error) {
return parse(token, audience, func(tok *jwt.JSONWebToken, td spiffeid.TrustDomain) (map[string]interface{}, error) {
// forbid tokens which have the `typ` header, which is not either "JOSE" or "JWT"
if typ := tok.Headers[0].ExtraHeaders[jose.HeaderType]; typ != "JOSE" && typ != "JWT" {
return nil, jwtsvidErr.New("token header type not equal to either JWT or JOSE")
}

// Obtain the token claims insecurely, i.e. without signature verification
claimsMap := make(map[string]interface{})
if err := tok.UnsafeClaimsWithoutVerification(&claimsMap); err != nil {
Expand Down
74 changes: 55 additions & 19 deletions v2/svid/jwtsvid/svid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "authority1")
return generateToken(tb, claims, key1, "authority1", "")
},
svid: &jwtsvid.SVID{
ID: spiffeid.RequireFromPath(trustDomain1, "/host"),
Expand Down Expand Up @@ -110,7 +110,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "authority1")
return generateToken(tb, claims, key1, "authority1", "")
},
err: "jwtsvid: token missing subject claim",
},
Expand All @@ -125,7 +125,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "authority1")
return generateToken(tb, claims, key1, "authority1", "")
},
err: "jwtsvid: token missing exp claim",
},
Expand All @@ -142,7 +142,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "authority1")
return generateToken(tb, claims, key1, "authority1", "")
},
err: "jwtsvid: token has expired",
},
Expand All @@ -159,7 +159,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "authority1")
return generateToken(tb, claims, key1, "authority1", "")
},
err: `jwtsvid: expected audience in ["another"] (audience=["audience"])`,
},
Expand All @@ -176,7 +176,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "authority1")
return generateToken(tb, claims, key1, "authority1", "")
},
err: `jwtsvid: token has an invalid subject claim: scheme is missing or invalid`,
},
Expand All @@ -193,7 +193,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "")
return generateToken(tb, claims, key1, "", "")
},
err: "jwtsvid: token header missing key id",
},
Expand All @@ -210,7 +210,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "noAuthority")
return generateToken(tb, claims, key1, "noAuthority", "")
},
err: `jwtsvid: no bundle found for trust domain "another.domain"`,
},
Expand All @@ -227,7 +227,7 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "noKey")
return generateToken(tb, claims, key1, "noKey", "")
},
err: `jwtsvid: no JWT authority "noKey" found for trust domain "trustdomain"`,
},
Expand All @@ -244,10 +244,26 @@ func TestParseAndValidate(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key2, "authority1")
return generateToken(tb, claims, key2, "authority1", "")
},
err: "jwtsvid: unable to get claims from token: go-jose/go-jose: error in cryptographic primitive",
},
{
name: "invalid typ",
bundle: bundle1,
generateToken: func(tb testing.TB) string {
claims := jwt.Claims{
Subject: spiffeid.RequireFromPath(trustDomain1, "/host").String(),
Issuer: "issuer",
Expiry: expires,
Audience: []string{"audience"},
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "authority1", "invalid")
},
err: "jwtsvid: token header type not equal to either JWT or JOSE",
},
}

for _, testCase := range testCases {
Expand Down Expand Up @@ -304,7 +320,7 @@ func TestParseInsecure(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "key1")
return generateToken(tb, claims, key1, "key1", "")
},
svid: &jwtsvid.SVID{
ID: spiffeid.RequireFromPath(trustDomain1, "/host"),
Expand Down Expand Up @@ -336,7 +352,7 @@ func TestParseInsecure(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "key1")
return generateToken(tb, claims, key1, "key1", "")
},
err: "jwtsvid: token missing subject claim",
},
Expand All @@ -350,7 +366,7 @@ func TestParseInsecure(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "key1")
return generateToken(tb, claims, key1, "key1", "")
},
err: "jwtsvid: token missing exp claim",
},
Expand All @@ -366,7 +382,7 @@ func TestParseInsecure(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "key1")
return generateToken(tb, claims, key1, "key1", "")
},
err: "jwtsvid: token has expired",
},
Expand All @@ -382,7 +398,7 @@ func TestParseInsecure(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "key1")
return generateToken(tb, claims, key1, "key1", "")
},
err: `jwtsvid: expected audience in ["another"] (audience=["audience"])`,
},
Expand All @@ -398,10 +414,25 @@ func TestParseInsecure(t *testing.T) {
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "key1")
return generateToken(tb, claims, key1, "key1", "")
},
err: `jwtsvid: token has an invalid subject claim: scheme is missing or invalid`,
},
{
name: "success",
generateToken: func(tb testing.TB) string {
claims := jwt.Claims{
Subject: spiffeid.RequireFromPath(trustDomain1, "/host").String(),
Issuer: "issuer",
Expiry: expires,
Audience: []string{"audience"},
IssuedAt: issuedAt,
}

return generateToken(tb, claims, key1, "key1", "invalid")
},
err: `jwtsvid: token header type not equal to either JWT or JOSE`,
},
}

for _, testCase := range testCases {
Expand Down Expand Up @@ -443,7 +474,7 @@ func TestMarshal(t *testing.T) {
Audience: []string{"audience"},
IssuedAt: jwt.NewNumericDate(time.Now().Add(time.Minute)),
}
token := generateToken(t, claims, key1, "key1")
token := generateToken(t, claims, key1, "key1", "")

// Create SVID
svid, err := jwtsvid.ParseInsecure(token, []string{"audience"})
Expand All @@ -470,11 +501,16 @@ func parseToken(t testing.TB, token string) map[string]interface{} {
}

// Generate generates a signed string token
func generateToken(tb testing.TB, claims jwt.Claims, signer crypto.Signer, keyID string) string {
func generateToken(tb testing.TB, claims jwt.Claims, signer crypto.Signer, keyID string, typ string) string {
// Get signer algorithm
alg, err := getSignerAlgorithm(signer)
require.NoError(tb, err)

options := new(jose.SignerOptions).WithType("JWT")
if typ != "" {
options = options.WithHeader(jose.HeaderType, typ)
}

// Create signer using crypto.Signer and its algorithm along with provided key ID
jwtSigner, err := jose.NewSigner(
jose.SigningKey{
Expand All @@ -484,7 +520,7 @@ func generateToken(tb testing.TB, claims jwt.Claims, signer crypto.Signer, keyID
KeyID: keyID,
},
},
new(jose.SignerOptions).WithType("JWT"),
options,
)
require.NoError(tb, err)

Expand Down

0 comments on commit 98a2f2d

Please sign in to comment.