diff --git a/mockid/route.go b/mockid/route.go index 3b90fdf..09b5cb1 100644 --- a/mockid/route.go +++ b/mockid/route.go @@ -283,7 +283,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler { xkeypairs.Object{}, xkeypairs.Object{ "sub": sub, - "iss": baseURL, + "iss": baseURL + "/", "jti": base64.RawURLEncoding.EncodeToString(nonce), "iat": time.Now().Unix(), "exp": "1h", @@ -302,6 +302,84 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler { w.Write(b) }) + http.HandleFunc("/api/authz/email", func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + log.Printf("%s %s %s\n", r.Method, r.URL.Path, token) + + if "" == token { + http.Error( + w, + "Bad Format: missing Authorization header and 'access_token' query", + http.StatusBadRequest, + ) + return + } + parts := strings.Split(token, " ") + if 2 != len(parts) { + http.Error( + w, + "Bad Format: expected Authorization header to be in the format of 'Bearer '", + http.StatusBadRequest, + ) + return + } + token = parts[1] + + inspected, err := verifyToken(token) + if nil != err { + http.Error(w, "Invalid Token: "+err.Error(), http.StatusBadRequest) + return + } + if !inspected.Verified { + http.Error(w, "bad token signature", http.StatusBadRequest) + return + } + + iss, _ := inspected.Payload["iss"].(string) + baseURL := getBaseURL(r) + if baseURL != strings.TrimSuffix(iss, "/") { + http.Error(w, "unacceptable token issuer", http.StatusBadRequest) + return + } + + subject, _ := inspected.Payload["sub"].(string) + value, ok, err := subjectKV.Load(subject, &Subject{}) + if nil != err { + http.Error(w, "Server Error: "+err.Error(), + http.StatusInternalServerError, + ) + return + } + if !ok { + http.Error(w, "Server Error: the user went missing... huh", + http.StatusInternalServerError, + ) + return + } + + sub, ok := value.(*Subject) + if !ok { + http.Error( + w, + "Server Error: invalid data type", + http.StatusInternalServerError, + ) + return + } + + var emails []string + for k := range sub.Emails { + emails = append(emails, k) + } + jsonb, _ := json.MarshalIndent(struct { + Emails []string `json:"emails"` + }{ + Emails: emails, + }, "", " ") + + fmt.Fprintf(w, string(jsonb)+"\n") + }) + http.HandleFunc("/api/new-nonce", requireHashcash(func(w http.ResponseWriter, r *http.Request) { indexURL := getBaseURL(r) + "/api/directory" w.Header().Set("Link", "<"+indexURL+">;rel=\"index\"") @@ -501,93 +579,39 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler { if "" == token { token = r.URL.Query().Get("access_token") if "" == token { - http.Error(w, "Bad Format: missing Authorization header and 'access_token' query", http.StatusBadRequest) + http.Error( + w, + "Bad Format: missing Authorization header and 'access_token' query", + http.StatusBadRequest, + ) return } } else { parts := strings.Split(token, " ") if 2 != len(parts) { - http.Error(w, "Bad Format: expected Authorization header to be in the format of 'Bearer '", http.StatusBadRequest) + http.Error( + w, + "Bad Format: expected Authorization header to be in the format of 'Bearer '", + http.StatusBadRequest, + ) return } token = parts[1] } - parts := strings.Split(token, ".") - if 3 != len(parts) { - http.Error(w, "Bad Format: token should be in the format of ..", http.StatusBadRequest) - return - } - protected64 := parts[0] - payload64 := parts[1] - signature64 := parts[2] - - protectedB, err := base64.RawURLEncoding.DecodeString(protected64) + inspected, err := verifyToken(token) if nil != err { - http.Error(w, "Bad Format: token's header should be URL-safe base64 encoded", http.StatusBadRequest) + http.Error(w, "Invalid Token: "+err.Error(), http.StatusBadRequest) return } - payloadB, err := base64.RawURLEncoding.DecodeString(payload64) - if nil != err { - http.Error(w, "Bad Format: token's payload should be URL-safe base64 encoded", http.StatusBadRequest) - return - } - // TODO verify signature - sig, err := base64.RawURLEncoding.DecodeString(signature64) - if nil != err { - http.Error(w, "Bad Format: token's signature should be URL-safe base64 encoded", http.StatusBadRequest) - return - } - - errors := []string{} - - protected := map[string]interface{}{} - err = json.Unmarshal(protectedB, &protected) - if nil != err { - http.Error(w, "Bad Format: token's header should be URL-safe base64-encoded JSON", http.StatusBadRequest) - return - } - kid, kidOK := protected["kid"].(string) - // TODO parse jwkM - _, jwkOK := protected["jwk"] - if !kidOK && !jwkOK { - errors = append(errors, "must have either header.kid or header.jwk") - } - - data := map[string]interface{}{} - err = json.Unmarshal(payloadB, &data) - if nil != err { - http.Error(w, "Bad Format: token's payload should be URL-safe base64-encoded JSON", http.StatusBadRequest) - return - } - iss, issOK := data["iss"].(string) - if !jwkOK && !issOK { - errors = append(errors, "payload.iss must exist to complement header.kid") - } - - pub, err := keyfetch.OIDCJWK(kid, iss) - if nil != err { - fmt.Println("couldn't fetch pub key:") - fmt.Println(err) - } - fmt.Println("fetched pub key:") - fmt.Println(pub) - - hash := sha256.Sum256([]byte(fmt.Sprintf("%s.%s", protected64, payload64))) - verified := JOSEVerify(pub, hash[:], sig) - - inspected := &InspectableToken{ - Public: pub, - Protected: protected, - Payload: data, - Signature: signature64, - Verified: verified, - Errors: errors, - } tokenB, _ := json.MarshalIndent(inspected, "", " ") if nil != err { - http.Error(w, "Bad Format: malformed token, or malformed jwk at issuer url", http.StatusInternalServerError) + http.Error( + w, + "Bad Format: malformed token, or malformed jwk at issuer url", + http.StatusInternalServerError, + ) return } fmt.Fprintf(w, string(tokenB)+"\n") @@ -680,6 +704,76 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler { return http.DefaultServeMux } +func verifyToken(token string) (*InspectableToken, error) { + parts := strings.Split(token, ".") + if 3 != len(parts) { + return nil, errors.New("Bad Format: token should be in the format of ..") + } + protected64 := parts[0] + payload64 := parts[1] + signature64 := parts[2] + + protectedB, err := base64.RawURLEncoding.DecodeString(protected64) + if nil != err { + return nil, errors.New("Bad Format: token's header should be URL-safe base64 encoded") + } + payloadB, err := base64.RawURLEncoding.DecodeString(payload64) + if nil != err { + return nil, errors.New("Bad Format: token's payload should be URL-safe base64 encoded") + } + // TODO verify signature + sig, err := base64.RawURLEncoding.DecodeString(signature64) + if nil != err { + return nil, errors.New("Bad Format: token's signature should be URL-safe base64 encoded") + } + + errs := []string{} + + protected := map[string]interface{}{} + err = json.Unmarshal(protectedB, &protected) + if nil != err { + return nil, errors.New("Bad Format: token's header should be URL-safe base64-encoded JSON") + } + kid, kidOK := protected["kid"].(string) + // TODO parse jwkM + _, jwkOK := protected["jwk"] + if !kidOK && !jwkOK { + errs = append(errs, "must have either header.kid or header.jwk") + } + + data := map[string]interface{}{} + err = json.Unmarshal(payloadB, &data) + if nil != err { + return nil, errors.New("Bad Format: token's payload should be URL-safe base64-encoded JSON") + } + iss, issOK := data["iss"].(string) + if !jwkOK && !issOK { + errs = append(errs, "payload.iss must exist to complement header.kid") + } + + pub, err := keyfetch.OIDCJWK(kid, iss) + if nil != err { + fmt.Println("couldn't fetch pub key:") + fmt.Println(err) + } + fmt.Println("fetched pub key:") + fmt.Println(pub) + + hash := sha256.Sum256([]byte(fmt.Sprintf("%s.%s", protected64, payload64))) + verified := JOSEVerify(pub, hash[:], sig) + + inspected := &InspectableToken{ + Public: pub, + Protected: protected, + Payload: data, + Signature: signature64, + Verified: verified, + Errors: errs, + } + + return inspected, nil +} + type OTP struct { //Attempts int `json:"attempts"` CreatedAt time.Time `json:"created_at"`