diff options
| author | mo khan <mo@mokhan.ca> | 2022-04-21 10:21:35 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2022-04-21 10:21:35 -0600 |
| commit | c84a523b1e29fa70c18a6a8e889214ef854c6951 (patch) | |
| tree | e29d324160fcc38f73b30c909c5146b11dd12767 | |
| parent | ed7a6333ed475b84f64dcf22e7318297867348d9 (diff) | |
create a minimal oidc idp
| -rw-r--r-- | cmd/server/main.go | 20 | ||||
| -rw-r--r-- | insecure.pem | 51 | ||||
| -rw-r--r-- | main.go | 181 | ||||
| -rw-r--r-- | pkg/web/authorize.go | 53 | ||||
| -rw-r--r-- | pkg/web/default.go | 7 | ||||
| -rw-r--r-- | pkg/web/http_mux.go | 66 | ||||
| -rw-r--r-- | pkg/web/token.go | 48 | ||||
| -rw-r--r-- | pkg/web/well_known.go | 40 | ||||
| -rw-r--r-- | public/openid-configuration.json | 37 |
9 files changed, 322 insertions, 181 deletions
diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..7113f41 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "log" + "net/http" + + "mokhan.ca/xlgmokha/oauth/pkg/web" +) + +func main() { + log.Println("Starting server, listening on port 8282.") + server := &http.Server{ + Addr: ":8282", + Handler: web.NewHandler(), + ReadTimeout: 0, + WriteTimeout: 0, + IdleTimeout: 0, + } + log.Fatal(server.ListenAndServe()) +} diff --git a/insecure.pem b/insecure.pem new file mode 100644 index 0000000..2c2d50c --- /dev/null +++ b/insecure.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/main.go b/main.go deleted file mode 100644 index 3646d74..0000000 --- a/main.go +++ /dev/null @@ -1,181 +0,0 @@ -package main - -import ( - "crypto/x509" - "encoding/json" - "encoding/pem" - "fmt" - "io/ioutil" - "log" - "net/http" - "os" - "text/template" - "time" - - "github.com/golang-jwt/jwt" - "github.com/hashicorp/uuid" - "github.com/lestrrat-go/jwx/v2/jwk" -) - -type AuthorizationRequest struct { - ResponseType string - Scope string - ClientId string - State string - RedirectUri string - Nonce string -} - -type TokenRequest struct { - GrantType string - Code string - RedirectUri string -} - -type TokenResponse struct { - AccessToken string - TokenType string - RefreshToken string - ExpiresIn int - IdToken string -} - -var ( - tokens = map[string]string{} -) - -func createIdToken(clientId string) string { - now := time.Now() - if clientId == "" { - clientId = "clientId" - } - expiresAt := now.Add(time.Hour * time.Duration(1)) - - host, ok := os.LookupEnv("HOST") - if !ok { - host = "http://localhost:8282" - } - idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, &jwt.StandardClaims{ - Issuer: host, - Subject: "1", - Audience: clientId, - ExpiresAt: expiresAt.Unix(), - NotBefore: now.Unix(), - IssuedAt: now.Unix(), - Id: uuid.GenerateUUID(), - }) - - keyData, _ := ioutil.ReadFile("insecure.pem") - key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData) - signedIdToken, _ := idToken.SignedString(key) - return signedIdToken -} - -func handler(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" && r.Method == "GET" { - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "Hello, world!\n") - } else if r.URL.Path == "/authorize" && r.Method == "GET" { - responseType := r.FormValue("response_type") - if responseType == "code" { - // Authorization Code Flow https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth - ar := &AuthorizationRequest{ - ResponseType: r.FormValue("response_type"), - Scope: r.FormValue("scope"), - ClientId: r.FormValue("client_id"), - State: r.FormValue("state"), - RedirectUri: r.FormValue("redirect_uri"), - } - code := uuid.GenerateUUID() - tokens[code] = uuid.GenerateUUID() - url := fmt.Sprintf("%s?code=%s&state=%s", ar.RedirectUri, code, ar.State) - http.Redirect(w, r, url, 302) - } else if responseType == "id_token token" || responseType == "id_token" { - // Implicit Flow https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth - ar := &AuthorizationRequest{ - ResponseType: r.FormValue("response_type"), - RedirectUri: r.FormValue("redirect_uri"), - Nonce: r.FormValue("nonce"), - } - idToken := createIdToken(r.FormValue("client_id")) - url := fmt.Sprintf("%s?access_token=example&token_type=bearer&id_token=%s&expires_in=3600&state=%s", ar.RedirectUri, idToken, ar.State) - http.Redirect(w, r, url, 302) - } else if responseType == "code id_token" || responseType == "code token" || responseType == "code id_token token" { - // Hybrid Flow https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth - w.WriteHeader(http.StatusNotImplemented) - } else { - w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, "Not Found\n") - } - } else if r.URL.Path == "/token" && r.Method == "POST" { - tr := &TokenRequest{ - GrantType: r.FormValue("grant_type"), - Code: r.FormValue("code"), - RedirectUri: r.FormValue("redirect_uri"), - } - if tr.GrantType == "authorization_code" { - // Authorization Code Flow https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth - r := &TokenResponse{ - AccessToken: tokens[tr.Code], - TokenType: "Bearer", - RefreshToken: "TODO::", - ExpiresIn: 3600, - IdToken: createIdToken(r.FormValue("client_id")), - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Cache-Control", "no-store") - w.Header().Set("Pragma", "no-cache") - fmt.Fprintf(w, `{"access_token": "%s","token_type": "%s","refresh_token": "%s","expires_in": %d,"id_token": "%s"}`, r.AccessToken, r.TokenType, r.RefreshToken, r.ExpiresIn, r.IdToken) - } else { - w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, "Not Found\n") - } - } else if r.URL.Path == "/.well-known/openid-configuration" { - w.Header().Set("Content-Type", "application/json") - data, _ := ioutil.ReadFile("openid-configuration.json") - tmpl, _ := template.New("test").Parse(string(data)) - host, ok := os.LookupEnv("HOST") - if !ok { - host = "http://localhost:8282" - } - tmpl.Execute(w, struct{ Host string }{Host: host}) - } else if r.URL.Path == "/userinfo" { - w.WriteHeader(http.StatusNotImplemented) - } else if r.URL.Path == "/.well-known/jwks.json" { - w.Header().Set("Content-Type", "application/json") - keyData, _ := ioutil.ReadFile("insecure.pem") - privatePem, _ := pem.Decode(keyData) - parsedKey, _ := x509.ParsePKCS1PrivateKey(privatePem.Bytes) - key, _ := jwk.FromRaw(parsedKey) - pubKey, _ := jwk.PublicKeyOf(key) - pubKey.Set(jwk.KeyIDKey, "X") - pubKey.Set(jwk.KeyUsageKey, "sig") - - set := jwk.NewSet() - set.Add(pubKey) - json.NewEncoder(w).Encode(set) - } else if r.URL.Path == "/revoke" { - w.WriteHeader(http.StatusNotImplemented) - } else { - w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, "Not Found\n") - } -} - -func main() { - log.Println("Starting server, listening on port 8282.") - - server := &http.Server{ - Addr: ":8282", - Handler: http.HandlerFunc(handler), - ReadTimeout: 0, - WriteTimeout: 0, - IdleTimeout: 0, - } - // config, _ := server.LoadConfigFile(os.Args[1]) - // srv, _ := server.New(config) - // srv.Start() - - log.Fatal(server.ListenAndServe()) -} diff --git a/pkg/web/authorize.go b/pkg/web/authorize.go new file mode 100644 index 0000000..b223699 --- /dev/null +++ b/pkg/web/authorize.go @@ -0,0 +1,53 @@ +package web + +import ( + "fmt" + "net/http" + + "github.com/hashicorp/uuid" +) + +type AuthorizationRequest struct { + ResponseType string + Scope string + ClientId string + State string + RedirectUri string + Nonce string +} + +func (h *HttpContext) Authorize(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + responseType := r.FormValue("response_type") + if responseType == "code" { + // Authorization Code Flow https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth + ar := &AuthorizationRequest{ + ResponseType: r.FormValue("response_type"), + Scope: r.FormValue("scope"), + ClientId: r.FormValue("client_id"), + State: r.FormValue("state"), + RedirectUri: r.FormValue("redirect_uri"), + } + code := uuid.GenerateUUID() + tokens[code] = uuid.GenerateUUID() + url := fmt.Sprintf("%s?code=%s&state=%s", ar.RedirectUri, code, ar.State) + http.Redirect(w, r, url, 302) + } else if responseType == "id_token token" || responseType == "id_token" { + // Implicit Flow https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth + ar := &AuthorizationRequest{ + ResponseType: r.FormValue("response_type"), + RedirectUri: r.FormValue("redirect_uri"), + Nonce: r.FormValue("nonce"), + } + idToken := h.createIdToken(r.FormValue("client_id")) + url := fmt.Sprintf("%s?access_token=example&token_type=bearer&id_token=%s&expires_in=3600&state=%s", ar.RedirectUri, idToken, ar.State) + http.Redirect(w, r, url, 302) + } else if responseType == "code id_token" || responseType == "code token" || responseType == "code id_token token" { + // Hybrid Flow https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth + w.WriteHeader(http.StatusNotImplemented) + } else { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, "Not Found\n") + } + } +} diff --git a/pkg/web/default.go b/pkg/web/default.go new file mode 100644 index 0000000..c9d54f2 --- /dev/null +++ b/pkg/web/default.go @@ -0,0 +1,7 @@ +package web + +import "net/http" + +func (h *HttpContext) Default(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} diff --git a/pkg/web/http_mux.go b/pkg/web/http_mux.go new file mode 100644 index 0000000..fbb6b67 --- /dev/null +++ b/pkg/web/http_mux.go @@ -0,0 +1,66 @@ +package web + +import ( + "io/ioutil" + "log" + "net/http" + "os" + "time" + + "github.com/golang-jwt/jwt" + "github.com/hashicorp/uuid" +) + +var ( + tokens = map[string]string{} +) + +type IdTokenFactory func(clientId string) string + +func (h *HttpContext) createIdToken(clientId string) string { + now := time.Now() + if clientId == "" { + clientId = "clientId" + } + expiresAt := now.Add(time.Hour * time.Duration(1)) + + host, ok := os.LookupEnv("HOST") + if !ok { + host = "http://localhost:8282" + } + idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, &jwt.StandardClaims{ + Issuer: host, + Subject: "1", + Audience: clientId, + ExpiresAt: expiresAt.Unix(), + NotBefore: now.Unix(), + IssuedAt: now.Unix(), + Id: uuid.GenerateUUID(), + }) + + key, _ := jwt.ParseRSAPrivateKeyFromPEM(h.keyData) + signedIdToken, _ := idToken.SignedString(key) + return signedIdToken +} + +type HttpContext struct { + log *log.Logger + keyData []byte +} + +func NewHandler() http.Handler { + keyData, _ := ioutil.ReadFile("insecure.pem") + h := &HttpContext{ + log: log.Default(), + keyData: keyData, + } + mux := http.NewServeMux() + mux.Handle("/", http.HandlerFunc(h.Default)) + mux.Handle("/.well-known/jwks.json", http.HandlerFunc(h.WellKnown)) + mux.Handle("/.well-known/openid-configuration", http.HandlerFunc(h.WellKnown)) + mux.Handle("/authorize", http.HandlerFunc(h.Authorize)) + mux.Handle("/revoke", http.HandlerFunc(http.NotFound)) + mux.Handle("/token", http.HandlerFunc(h.Token)) + mux.Handle("/userinfo", http.HandlerFunc(http.NotFound)) + return mux +} diff --git a/pkg/web/token.go b/pkg/web/token.go new file mode 100644 index 0000000..d6fbdfb --- /dev/null +++ b/pkg/web/token.go @@ -0,0 +1,48 @@ +package web + +import ( + "fmt" + "net/http" +) + +type TokenRequest struct { + GrantType string + Code string + RedirectUri string +} + +type TokenResponse struct { + AccessToken string + TokenType string + RefreshToken string + ExpiresIn int + IdToken string +} + +func (h *HttpContext) Token(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + tr := &TokenRequest{ + GrantType: r.FormValue("grant_type"), + Code: r.FormValue("code"), + RedirectUri: r.FormValue("redirect_uri"), + } + if tr.GrantType == "authorization_code" { + // Authorization Code Flow https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth + r := &TokenResponse{ + AccessToken: tokens[tr.Code], + TokenType: "Bearer", + RefreshToken: "TODO::", + ExpiresIn: 3600, + IdToken: h.createIdToken(r.FormValue("client_id")), + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "no-store") + w.Header().Set("Pragma", "no-cache") + fmt.Fprintf(w, `{"access_token": "%s","token_type": "%s","refresh_token": "%s","expires_in": %d,"id_token": "%s"}`, r.AccessToken, r.TokenType, r.RefreshToken, r.ExpiresIn, r.IdToken) + } else { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, "Not Found\n") + } + } +} diff --git a/pkg/web/well_known.go b/pkg/web/well_known.go new file mode 100644 index 0000000..4a87a79 --- /dev/null +++ b/pkg/web/well_known.go @@ -0,0 +1,40 @@ +package web + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io/ioutil" + "net/http" + "os" + "text/template" + + "github.com/lestrrat-go/jwx/v2/jwk" +) + +func (h *HttpContext) WellKnown(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.URL.Path) + if r.URL.Path == "/.well-known/openid-configuration" { + w.Header().Set("Content-Type", "application/json") + data, _ := ioutil.ReadFile("./public/openid-configuration.json") + tmpl, _ := template.New("test").Parse(string(data)) + host, ok := os.LookupEnv("HOST") + if !ok { + host = "http://localhost:8282" + } + tmpl.Execute(w, struct{ Host string }{Host: host}) + } else if r.URL.Path == "/.well-known/jwks.json" { + w.Header().Set("Content-Type", "application/json") + privatePem, _ := pem.Decode(h.keyData) + parsedKey, _ := x509.ParsePKCS1PrivateKey(privatePem.Bytes) + key, _ := jwk.FromRaw(parsedKey) + pubKey, _ := jwk.PublicKeyOf(key) + pubKey.Set(jwk.KeyIDKey, "X") + pubKey.Set(jwk.KeyUsageKey, "sig") + + set := jwk.NewSet() + set.Add(pubKey) + json.NewEncoder(w).Encode(set) + } +} diff --git a/public/openid-configuration.json b/public/openid-configuration.json new file mode 100644 index 0000000..f30cf66 --- /dev/null +++ b/public/openid-configuration.json @@ -0,0 +1,37 @@ +{ + "issuer": "{{.Host}}", + "authorization_endpoint": "{{.Host}}/authorize", + "token_endpoint": "{{.Host}}/token", + "userinfo_endpoint": "{{.Host}}/userinfo", + "jwks_uri": "{{.Host}}/.well-known/jwks.json", + "revocation_endpoint": "{{.Host}}/revoke", + "scopes_supported": [ + "openid" + ], + "response_types_supported": [ + "code id_token token", + "code id_token", + "code token", + "code", + "id_token token", + "id_token" + ], + "response_modes_supported": [ + "query", + "fragment", + "form_post" + ], + "subject_types_supported": [ + "public" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ], + "claims_supported": [ + "aud", + "exp", + "iat", + "iss", + "sub" + ] +} |
