|
|
@ -12,6 +12,7 @@ import ( |
|
|
|
"log" |
|
|
|
"math/big" |
|
|
|
"net/http" |
|
|
|
"net/url" |
|
|
|
"os" |
|
|
|
"strconv" |
|
|
|
"time" |
|
|
@ -30,7 +31,7 @@ type PublicJWK struct { |
|
|
|
func main() { |
|
|
|
done := make(chan bool) |
|
|
|
var port int |
|
|
|
var host string |
|
|
|
var host string |
|
|
|
|
|
|
|
jwkm := map[string]string{ |
|
|
|
"crv": "P-256", |
|
|
@ -51,7 +52,7 @@ func main() { |
|
|
|
thumbprint := thumbprintKey(pub) |
|
|
|
|
|
|
|
portFlag := flag.Int("port", 0, "Port on which the HTTP server should run") |
|
|
|
urlFlag := flag.String("url", "", "Outward-facing address, such as https://example.com") |
|
|
|
urlFlag := flag.String("url", "", "Outward-facing address, such as https://example.com") |
|
|
|
flag.Parse() |
|
|
|
|
|
|
|
if nil != portFlag && *portFlag > 0 { |
|
|
@ -65,34 +66,62 @@ func main() { |
|
|
|
os.Exit(1) |
|
|
|
} |
|
|
|
|
|
|
|
if nil != urlFlag && "" != *urlFlag { |
|
|
|
host = *urlFlag |
|
|
|
} else { |
|
|
|
host = "http://localhost:" + strconv.Itoa(port) |
|
|
|
} |
|
|
|
if nil != urlFlag && "" != *urlFlag { |
|
|
|
host = *urlFlag |
|
|
|
} else { |
|
|
|
host = "http://localhost:" + strconv.Itoa(port) |
|
|
|
} |
|
|
|
|
|
|
|
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) |
|
|
|
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()) |
|
|
|
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"] |
|
|
|
if 0 == len(headers) { |
|
|
|
header = "Authorization" |
|
|
|
} else { |
|
|
|
header = headers[0] |
|
|
|
} |
|
|
|
|
|
|
|
var prefix string |
|
|
|
prefixes, _ := r.URL.Query()["prefix"] |
|
|
|
if 0 == len(prefixes) { |
|
|
|
prefix = "Bearer " |
|
|
|
} else { |
|
|
|
prefix = prefixes[0] |
|
|
|
} |
|
|
|
|
|
|
|
_, _, token := genToken(scheme+r.Host, priv, r.URL.Query()) |
|
|
|
fmt.Fprintf(w, "%s: %s%s", header, prefix, token) |
|
|
|
}) |
|
|
|
http.HandleFunc("/key.jwk.json", func(w http.ResponseWriter, r *http.Request) { |
|
|
|
log.Printf("%s %s", r.Method, r.URL.Path) |
|
|
|
fmt.Fprintf(w, `{ "kty": "EC" , "crv": %q , "d": %q , "x": %q , "y": %q , "ext": true , "key_ops": ["sign"] }`, jwk.Crv, jwk.D, jwk.X, jwk.Y) |
|
|
|
}) |
|
|
|
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://" |
|
|
|
} |
|
|
|
var scheme string |
|
|
|
if nil != r.TLS || "https" == r.Header.Get("X-Forwarded-Proto") { |
|
|
|
scheme = "https://" |
|
|
|
} else { |
|
|
|
scheme = "http://" |
|
|
|
} |
|
|
|
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) |
|
|
|
}) |
|
|
@ -106,14 +135,14 @@ func main() { |
|
|
|
fmt.Println(jwkstr) |
|
|
|
fmt.Fprintf(w, jwkstr) |
|
|
|
}) |
|
|
|
fs := http.FileServer(http.Dir("public")) |
|
|
|
http.Handle("/", fs) |
|
|
|
/* |
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|
|
|
log.Printf(r.Method, r.URL.Path) |
|
|
|
http.Error(w, "Not Found", http.StatusNotFound) |
|
|
|
}) |
|
|
|
*/ |
|
|
|
fs := http.FileServer(http.Dir("public")) |
|
|
|
http.Handle("/", fs) |
|
|
|
/* |
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|
|
|
log.Printf(r.Method, r.URL.Path) |
|
|
|
http.Error(w, "Not Found", http.StatusNotFound) |
|
|
|
}) |
|
|
|
*/ |
|
|
|
|
|
|
|
fmt.Printf("Serving on port %d\n", port) |
|
|
|
go func() { |
|
|
@ -125,7 +154,7 @@ func main() { |
|
|
|
fmt.Printf("Private Key:\n\t%s\n", string(b)) |
|
|
|
b, _ = json.Marshal(jwk.PublicJWK) |
|
|
|
fmt.Printf("Public Key:\n\t%s\n", string(b)) |
|
|
|
protected, payload, token := genToken(host, priv) |
|
|
|
protected, payload, token := genToken(host, priv, url.Values{}) |
|
|
|
fmt.Printf("Protected (Header):\n\t%s\n", protected) |
|
|
|
fmt.Printf("Payload (Claims):\n\t%s\n", payload) |
|
|
|
fmt.Printf("Access Token:\n\t%s\n", token) |
|
|
@ -133,15 +162,56 @@ func main() { |
|
|
|
<-done |
|
|
|
} |
|
|
|
|
|
|
|
func genToken(host string, priv *ecdsa.PrivateKey) (string, string, string) { |
|
|
|
func parseExp(exp string) (int, error) { |
|
|
|
if "" == exp { |
|
|
|
exp = "15m" |
|
|
|
} |
|
|
|
mult := 1 |
|
|
|
switch exp[len(exp)-1] { |
|
|
|
case 'w': |
|
|
|
mult *= 7 |
|
|
|
fallthrough |
|
|
|
case 'd': |
|
|
|
mult *= 24 |
|
|
|
fallthrough |
|
|
|
case 'h': |
|
|
|
mult *= 60 |
|
|
|
fallthrough |
|
|
|
case 'm': |
|
|
|
mult *= 60 |
|
|
|
fallthrough |
|
|
|
case 's': |
|
|
|
// no fallthrough
|
|
|
|
default: |
|
|
|
// could be 'k' or 'z', but we assume its empty
|
|
|
|
exp += "s" |
|
|
|
} |
|
|
|
|
|
|
|
num, err := strconv.Atoi(exp[:len(exp)-1]) |
|
|
|
if nil != err { |
|
|
|
return 0, err |
|
|
|
} |
|
|
|
return num * mult, nil |
|
|
|
} |
|
|
|
|
|
|
|
func genToken(host string, priv *ecdsa.PrivateKey, query url.Values) (string, string, string) { |
|
|
|
thumbprint := thumbprintKey(&priv.PublicKey) |
|
|
|
protected := fmt.Sprintf(`{"typ":"JWT","alg":"ES256","kid":"%s"}`, thumbprint) |
|
|
|
protected64 := base64.RawURLEncoding.EncodeToString([]byte(protected)) |
|
|
|
|
|
|
|
exp, err := parseExp(query.Get("exp")) |
|
|
|
if nil != err { |
|
|
|
// cryptic error code
|
|
|
|
// TODO propagate error
|
|
|
|
exp = 422 |
|
|
|
} |
|
|
|
|
|
|
|
payload := fmt.Sprintf( |
|
|
|
`{"iss":"%s/","sub":"dummy","exp":%s}`, |
|
|
|
host, strconv.FormatInt(time.Now().Add(15*time.Minute).Unix(), 10), |
|
|
|
host, strconv.FormatInt(time.Now().Add(time.Duration(exp)*time.Second).Unix(), 10), |
|
|
|
) |
|
|
|
payload64 := base64.RawURLEncoding.EncodeToString([]byte(payload)) |
|
|
|
|
|
|
|
hash := sha256.Sum256([]byte(fmt.Sprintf(`%s.%s`, protected64, payload64))) |
|
|
|
r, s, _ := ecdsa.Sign(rand.Reader, priv, hash[:]) |
|
|
|
rb := r.Bytes() |
|
|
|