From d6f502748033ef4b764dfbe5c2142fe47e3782c8 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 19 Aug 2019 05:04:55 +0000 Subject: [PATCH] add nonce endpoint --- mockid.go | 112 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 28 deletions(-) diff --git a/mockid.go b/mockid.go index 6e2fbba..c13125a 100644 --- a/mockid.go +++ b/mockid.go @@ -34,8 +34,13 @@ type PublicJWK struct { Y string `json:"y"` } +var nonces map[string]int64 var jwksPrefix string +func init() { + nonces = make(map[string]int64) +} + func main() { done := make(chan bool) var port int @@ -92,6 +97,34 @@ func main() { os.Exit(1) } + http.HandleFunc("/api/new-nonce", func(w http.ResponseWriter, r *http.Request) { + baseURL := getBaseURL(r) + /* + res.statusCode = 200; + res.setHeader("Cache-Control", "max-age=0, no-cache, no-store"); + // TODO + //res.setHeader("Date", "Sun, 10 Mar 2019 08:04:45 GMT"); + // is this the expiration of the nonce itself? methinks maybe so + //res.setHeader("Expires", "Sun, 10 Mar 2019 08:04:45 GMT"); + // TODO use one of the registered domains + //var indexUrl = "https://acme-staging-v02.api.letsencrypt.org/index" + */ + //var port = (state.config.ipc && state.config.ipc.port || state._ipc.port || undefined); + //var indexUrl = "http://localhost:" + port + "/index"; + indexUrl := baseURL + "/index"; + w.Header().Set("Link", "<" + indexUrl + ">;rel=\"index\""); + w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store"); + w.Header().Set("Pragma", "no-cache"); + //res.setHeader("Strict-Transport-Security", "max-age=604800"); + + w.Header().Set("X-Frame-Options", "DENY") + issueNonce(w, r) + }) + + http.HandleFunc("/api/new-account", requireNonce(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Not Implemented", http.StatusNotImplemented) + })) + http.HandleFunc("/api/jwks", func(w http.ResponseWriter, r *http.Request) { log.Printf("%s %s %s", r.Method, r.Host, r.URL.Path) if "POST" != r.Method { @@ -185,37 +218,20 @@ func main() { return } - var scheme string - if nil != r.TLS || "https" == r.Header.Get("X-Forwarded-Proto") { - scheme = "https://" - } else { - scheme = "http://" - } + baseURL := getBaseURL(r) w.Write([]byte(fmt.Sprintf( - `{ "iss":%q, "jwks_url":%q }`, scheme+r.Host+"/", scheme+r.Host+"/.well-known/jwks.json", + `{ "iss":%q, "jwks_url":%q }`, baseURL+"/", baseURL+"/.well-known/jwks.json", ))) }) http.HandleFunc("/access_token", func(w http.ResponseWriter, r *http.Request) { log.Printf("%s %s\n", r.Method, r.URL.Path) - var scheme string - if nil != r.TLS || "https" == r.Header.Get("X-Forwarded-Proto") { - scheme = "https://" - } else { - scheme = "http://" - } - _, _, token := genToken(scheme+r.Host, priv, r.URL.Query()) + _, _, token := genToken(getBaseURL(r), priv, r.URL.Query()) fmt.Fprintf(w, token) }) http.HandleFunc("/authorization_header", func(w http.ResponseWriter, r *http.Request) { log.Printf("%s %s\n", r.Method, r.URL.Path) - var scheme string - if nil != r.TLS || "https" == r.Header.Get("X-Forwarded-Proto") { - scheme = "https://" - } else { - scheme = "http://" - } var header string headers, _ := r.URL.Query()["header"] @@ -233,7 +249,7 @@ func main() { prefix = prefixes[0] } - _, _, token := genToken(scheme+r.Host, priv, r.URL.Query()) + _, _, token := genToken(getBaseURL(r), priv, r.URL.Query()) fmt.Fprintf(w, "%s: %s%s", header, prefix, token) }) @@ -243,14 +259,9 @@ func main() { }) http.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) { - var scheme string - if nil != r.TLS || "https" == r.Header.Get("X-Forwarded-Proto") { - scheme = "https://" - } else { - scheme = "http://" - } + baseURL := getBaseURL(r) log.Printf("%s %s\n", r.Method, r.URL.Path) - fmt.Fprintf(w, `{ "issuer": "%s", "jwks_uri": "%s/.well-known/jwks.json" }`, scheme+r.Host, scheme+r.Host) + fmt.Fprintf(w, `{ "issuer": "%s", "jwks_uri": "%s/.well-known/jwks.json" }`, baseURL, baseURL) }) http.HandleFunc("/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) { @@ -407,3 +418,48 @@ func thumbprintKey(pub *ecdsa.PublicKey) string { sha := sha256.Sum256(minpub) return base64.RawURLEncoding.EncodeToString(sha[:]) } + +func issueNonce(w http.ResponseWriter, r *http.Request) { + b := make([]byte, 16) + _, _ = rand.Read(b) + nonce := base64.RawURLEncoding.EncodeToString(b); + nonces[nonce] = time.Now().Unix() + + w.Header().Set("Replay-Nonce", nonce); +} + + +func requireNonce(next http.HandlerFunc) http.HandlerFunc { + return func (w http.ResponseWriter, r *http.Request) { + nonce := r.Header.Get("Replay-Nonce") + // TODO expire nonces every so often + t := nonces[nonce] + if 0 == t { + http.Error( + w, + `{ "error": "invalid or expired nonce", "error_code": "ENONCE" }`, + http.StatusBadRequest, + ) + return + } + + delete(nonces, nonce) + issueNonce(w, r) + + next(w, r); + } +} + +func getBaseURL(r *http.Request) string { + var scheme string + if nil != r.TLS || "https" == r.Header.Get("X-Forwarded-Proto") { + scheme = "https:" + } else { + scheme = "http:" + } + return fmt.Sprintf( + "%s//%s", + scheme, + r.Host, + ) +}