Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: check for invalid 'typ' headers #307

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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