|
|
@ -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 <Token>'", |
|
|
|
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 <Token>'", http.StatusBadRequest) |
|
|
|
http.Error( |
|
|
|
w, |
|
|
|
"Bad Format: expected Authorization header to be in the format of 'Bearer <Token>'", |
|
|
|
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 <protected-header>.<payload>.<signature>", http.StatusBadRequest) |
|
|
|
return |
|
|
|
} |
|
|
|
protected64 := parts[0] |
|
|
|
payload64 := parts[1] |
|
|
|
signature64 := parts[2] |
|
|
|
|
|
|
|
protectedB, err := base64.RawURLEncoding.DecodeString(protected64) |
|
|
|
if nil != err { |
|
|
|
http.Error(w, "Bad Format: token's header should be URL-safe base64 encoded", 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) |
|
|
|
inspected, err := verifyToken(token) |
|
|
|
if nil != err { |
|
|
|
http.Error(w, "Bad Format: token's signature should be URL-safe base64 encoded", http.StatusBadRequest) |
|
|
|
http.Error(w, "Invalid Token: "+err.Error(), 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 <protected-header>.<payload>.<signature>") |
|
|
|
} |
|
|
|
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"` |
|
|
|