From a0effb313fa3d71a197cd52266ce9d1df713a92f Mon Sep 17 00:00:00 2001 From: Max Wagner Date: Thu, 7 Sep 2023 16:15:26 +0000 Subject: [PATCH 1/5] Custom debug the token --- actionsoidc/actions-oidc.go | 14 +++++++++++++- cmd/oidc-debug.go | 15 +++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/actionsoidc/actions-oidc.go b/actionsoidc/actions-oidc.go index e37aeee..b108e2d 100644 --- a/actionsoidc/actions-oidc.go +++ b/actionsoidc/actions-oidc.go @@ -111,8 +111,14 @@ func (c *ActionsOIDCClient) GetJWT() (*ActionsJWT, error) { return &jwt, err } +func (c *ActionsOIDCClient) ParseJWTFromJSON(rawBody []byte) (*ActionsJWT, error) { + var jwt ActionsJWT + err := json.Unmarshal(rawBody, &jwt) + return &jwt, err +} + func (j *ActionsJWT) Parse() { - j.ParsedToken, _ = jwt.Parse(j.Value, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.Parse(j.Value, func(token *jwt.Token) (interface{}, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) @@ -121,6 +127,12 @@ func (j *ActionsJWT) Parse() { // we don't need a real check here return []byte{}, nil }) + + if err != nil { + log.Fatal(err) + } + + j.ParsedToken = token } func (j *ActionsJWT) PrettyPrintClaims() string { diff --git a/cmd/oidc-debug.go b/cmd/oidc-debug.go index ecfb776..acfa2c8 100644 --- a/cmd/oidc-debug.go +++ b/cmd/oidc-debug.go @@ -3,21 +3,24 @@ package main import ( "flag" "fmt" + "os" "github.com/github/actions-oidc-debugger/actionsoidc" ) func main() { - - audience := flag.String("audience", "https://github.com/", "the audience for the requested jwt") + tokenPath := flag.String("token-path", "oidc-token", "the path to the token to debug") flag.Parse() - if *audience == "https://github.com/" { - actionsoidc.QuitOnErr(fmt.Errorf("-audience cli argument must be specified")) + if *tokenPath == "oidc-token" { + actionsoidc.QuitOnErr(fmt.Errorf("token-path must be specified")) } - c := actionsoidc.DefaultOIDCClient(*audience) - jwt, err := c.GetJWT() + c := actionsoidc.DefaultOIDCClient("blah-dont-care-right-now") + + contents, err := os.ReadFile(*tokenPath) + actionsoidc.QuitOnErr(err) + jwt, err := c.ParseJWTFromJSON(contents) actionsoidc.QuitOnErr(err) jwt.Parse() From 2b3119d366d7cc9a91fc114285ab3d53e640f83e Mon Sep 17 00:00:00 2001 From: Max Wagner Date: Thu, 7 Sep 2023 16:52:08 +0000 Subject: [PATCH 2/5] We don't need to parse json --- actionsoidc/actions-oidc.go | 6 +++--- cmd/oidc-debug.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actionsoidc/actions-oidc.go b/actionsoidc/actions-oidc.go index b108e2d..c0ababe 100644 --- a/actionsoidc/actions-oidc.go +++ b/actionsoidc/actions-oidc.go @@ -111,10 +111,10 @@ func (c *ActionsOIDCClient) GetJWT() (*ActionsJWT, error) { return &jwt, err } -func (c *ActionsOIDCClient) ParseJWTFromJSON(rawBody []byte) (*ActionsJWT, error) { +func (c *ActionsOIDCClient) CreateOIDCClientFromValue(rawValue []byte) (*ActionsJWT, error) { var jwt ActionsJWT - err := json.Unmarshal(rawBody, &jwt) - return &jwt, err + jwt.Value = string(rawValue) + return &jwt, nil } func (j *ActionsJWT) Parse() { diff --git a/cmd/oidc-debug.go b/cmd/oidc-debug.go index acfa2c8..4384b3f 100644 --- a/cmd/oidc-debug.go +++ b/cmd/oidc-debug.go @@ -20,7 +20,7 @@ func main() { contents, err := os.ReadFile(*tokenPath) actionsoidc.QuitOnErr(err) - jwt, err := c.ParseJWTFromJSON(contents) + jwt, err := c.CreateOIDCClientFromValue(contents) actionsoidc.QuitOnErr(err) jwt.Parse() From edd0bf0adcd64e2c0496057927ef27d5090e84b3 Mon Sep 17 00:00:00 2001 From: Max Wagner Date: Thu, 7 Sep 2023 16:57:28 +0000 Subject: [PATCH 3/5] Try rsa signing method --- actionsoidc/actions-oidc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionsoidc/actions-oidc.go b/actionsoidc/actions-oidc.go index c0ababe..cab41af 100644 --- a/actionsoidc/actions-oidc.go +++ b/actionsoidc/actions-oidc.go @@ -120,7 +120,7 @@ func (c *ActionsOIDCClient) CreateOIDCClientFromValue(rawValue []byte) (*Actions func (j *ActionsJWT) Parse() { token, err := jwt.Parse(j.Value, func(token *jwt.Token) (interface{}, error) { // Don't forget to validate the alg is what you expect: - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } From c8f567086985fc3dd7582d57326007afb04f6838 Mon Sep 17 00:00:00 2001 From: Max Wagner Date: Thu, 7 Sep 2023 17:11:55 +0000 Subject: [PATCH 4/5] use jwks --- actionsoidc/actions-oidc.go | 83 +++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/actionsoidc/actions-oidc.go b/actionsoidc/actions-oidc.go index cab41af..e70c5fb 100644 --- a/actionsoidc/actions-oidc.go +++ b/actionsoidc/actions-oidc.go @@ -1,10 +1,13 @@ package actionsoidc import ( + "crypto/rsa" + "encoding/base64" "encoding/json" "fmt" "io/ioutil" "log" + "math/big" "net/http" "net/url" "os" @@ -27,6 +30,21 @@ type ActionsJWT struct { ParsedToken *jwt.Token } +type JWK struct { + N string + Kty string + Kid string + Alg string + E string + Use string + X5c []string + X5t string +} + +type JWKS struct { + Keys []JWK +} + func GetEnvironmentVariable(e string) (string, error) { value := os.Getenv(e) if value == "" { @@ -117,21 +135,72 @@ func (c *ActionsOIDCClient) CreateOIDCClientFromValue(rawValue []byte) (*Actions return &jwt, nil } -func (j *ActionsJWT) Parse() { - token, err := jwt.Parse(j.Value, func(token *jwt.Token) (interface{}, error) { - // Don't forget to validate the alg is what you expect: +func getKeyFromJwks(jwksBytes []byte) func(*jwt.Token) (interface{}, error) { + return func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } - // we don't need a real check here - return []byte{}, nil - }) + var jwks JWKS + if err := json.Unmarshal(jwksBytes, &jwks); err != nil { + return nil, fmt.Errorf("Unable to parse JWKS") + } + + for _, jwk := range jwks.Keys { + if jwk.Kid == token.Header["kid"] { + nBytes, err := base64.RawURLEncoding.DecodeString(jwk.N) + if err != nil { + return nil, fmt.Errorf("Unable to parse key") + } + var n big.Int + + eBytes, err := base64.RawURLEncoding.DecodeString(jwk.E) + if err != nil { + return nil, fmt.Errorf("Unable to parse key") + } + var e big.Int + + key := rsa.PublicKey{ + N: n.SetBytes(nBytes), + E: int(e.SetBytes(eBytes).Uint64()), + } + + return &key, nil + } + } + return nil, fmt.Errorf("Unknown kid: %v", token.Header["kid"]) + } +} + +func (j *ActionsJWT) Parse() { + // get jwks + resp, err := http.Get("https://token.actions.githubusercontent.com/.well-known/jwks") + if err != nil { + fmt.Println(err) + } + + jwksBytes, err := ioutil.ReadAll(resp.Body) if err != nil { + fmt.Println(err) + } + + token, err := jwt.Parse(string(j.Value), getKeyFromJwks(jwksBytes)) + if err != nil || !token.Valid { + fmt.Println("unable to validate jwt") log.Fatal(err) } + // token, err := jwt.Parse(j.Value, func(token *jwt.Token) (interface{}, error) { + // // Don't forget to validate the alg is what you expect: + // if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + // return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + // } + + // we don't need a real check here + // return []byte{}, nil + // }) + j.ParsedToken = token } From 821018ff4d3a02683fca32bdcc3209dd008da6ba Mon Sep 17 00:00:00 2001 From: Max Wagner Date: Fri, 8 Sep 2023 17:50:32 +0000 Subject: [PATCH 5/5] Debug header --- actionsoidc/actions-oidc.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actionsoidc/actions-oidc.go b/actionsoidc/actions-oidc.go index e70c5fb..2e24f2c 100644 --- a/actionsoidc/actions-oidc.go +++ b/actionsoidc/actions-oidc.go @@ -205,6 +205,8 @@ func (j *ActionsJWT) Parse() { } func (j *ActionsJWT) PrettyPrintClaims() string { + fmt.Println(j.ParsedToken.Header) + if claims, ok := j.ParsedToken.Claims.(jwt.MapClaims); ok { jsonClaims, err := json.MarshalIndent(claims, "", " ") if err != nil {