pax_global_header00006660000000000000000000000064141460533360014517gustar00rootroot0000000000000052 comment=33db19a27ed9c33281373d2e8802452c4f3a208e golang-github-mhilton-openid-0.0~git20181012.aeae87e/000077500000000000000000000000001414605333600217705ustar00rootroot00000000000000golang-github-mhilton-openid-0.0~git20181012.aeae87e/LICENSE000066400000000000000000000020711414605333600227750ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Martin Hilton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-mhilton-openid-0.0~git20181012.aeae87e/README.md000066400000000000000000000001701414605333600232450ustar00rootroot00000000000000# openid openid provides a go implementation of the openid protocol, at the moment only the OP endpoint is implemented. golang-github-mhilton-openid-0.0~git20181012.aeae87e/go.mod000066400000000000000000000000411414605333600230710ustar00rootroot00000000000000module github.com/mhilton/openid golang-github-mhilton-openid-0.0~git20181012.aeae87e/openid2/000077500000000000000000000000001414605333600233305ustar00rootroot00000000000000golang-github-mhilton-openid-0.0~git20181012.aeae87e/openid2/association.go000066400000000000000000000141661414605333600262030ustar00rootroot00000000000000package openid2 import ( "crypto/hmac" "crypto/rand" "crypto/sha1" "crypto/sha256" "encoding/ascii85" "encoding/base64" "errors" "fmt" "hash" "strings" "time" ) const ( hmacSHA1 = "HMAC-SHA1" hmacSHA256 = "HMAC-SHA256" ) var ErrDuplicateAssociation = errors.New("duplicate association") // Association represents an openid association. type Association struct { // Endpoint is the OP Endpoint for which this association is valid. It might be blank. Endpoint string // Handle is used to identify the association with the OP Endpoint. Handle string // Secret is the secret established with the OP Endpoint. Secret []byte // Type is the type of this association. Type string // Expires holds the expiration time of the association. Expires time.Time } func (a Association) sign(params map[string]string, signed []string) (string, error) { var h hash.Hash switch a.Type { case hmacSHA1: h = hmac.New(sha1.New, a.Secret) case hmacSHA256: h = hmac.New(sha256.New, a.Secret) default: return "", fmt.Errorf("unsupported association type %q", a.Type) } for _, k := range signed { WriteKeyValuePair(h, k, params[k]) } return base64.URLEncoding.EncodeToString(h.Sum(nil)), nil } // AssociationStore is used to store associations in both the server and client. type AssociationStore interface { // Add stores a new Association. If the specified Association is already // present in the store then ErrDuplicateAssociation should be returned. Add(a *Association) error // Get retrieves the Association with the specified endpoint and handle. // if there is no matching association in the store then ErrAssociationNotFound // should be returned. Get(endpoint, handle string) (*Association, error) // Find retrieves all Associations for the specified endpoint. Find(endpoint string) ([]*Association, error) // Delete removes the Association with the specified endpoint and handle. Delete(endpoint, handle string) error } // MemoryAssociationStore is an in memory implementation of AssociationStore. type MemoryAssociationStore struct { m map[string]map[string]Association } // NewMemoryAssociationStore creates a new in memory AssocationStore. func NewMemoryAssociationStore() *MemoryAssociationStore { return &MemoryAssociationStore{map[string]map[string]Association{}} } // Add implements AssociationStore.Add. func (s *MemoryAssociationStore) Add(a *Association) error { ass, err := s.Get(a.Endpoint, a.Handle) if err != nil { return err } if ass != nil { return ErrDuplicateAssociation } m := s.m[a.Endpoint] if m == nil { m = make(map[string]Association) } m[a.Handle] = *a s.m[a.Endpoint] = m return nil } // Find implements AssociationStore.Find. func (s *MemoryAssociationStore) Find(endpoint string) ([]*Association, error) { var assocs []*Association for _, a := range s.m[endpoint] { assocs = append(assocs, &a) } return assocs, nil } // Get implements AssociationStore.Get. func (s *MemoryAssociationStore) Get(endpoint, handle string) (*Association, error) { if s.m[endpoint] == nil { return nil, nil } a, ok := s.m[endpoint][handle] if !ok { return nil, nil } return &a, nil } // Delete implements AssociationStore.Delete. func (s *MemoryAssociationStore) Delete(endpoint, handle string) error { a, err := s.Get(endpoint, handle) if err != nil { return err } if a == nil { return nil } delete(s.m[endpoint], handle) return nil } // DefaultAssociationStore is the AssociationStore that will be used if no AssociationStore // is specified. var DefaultAssociationStore AssociationStore = NewMemoryAssociationStore() func (h *Handler) getAssociation(requestHandle, nonce string) (a *Association, err error) { store := h.Associations if store == nil { store = DefaultAssociationStore } if requestHandle != "" { a, err = store.Get("", requestHandle) if err != nil { return } if a != nil { if time.Now().Before(a.Expires) { return } store.Delete("", requestHandle) } } secret := make([]byte, 128) if _, err = rand.Read(secret); err != nil { return } a = &Association{ Secret: secret, Type: hmacSHA256, Expires: time.Now().Add(time.Minute), } err = saveAssociation(store, a) if err != nil { a = nil } return } func (h *Handler) associate(params map[string]string) (map[string]string, error) { // store := h.Associations // if store == nil { // assocs = DefaultAssociationStore // } switch params["session_type"] { // case "DH-SHA1": // case "DH-SHA256": // case "no-encryption": // return h.associateNoEncryption(params) default: return nil, unsupportedSessionTypeError(params["session_type"]) } } func (h *Handler) checkAuthentication(params map[string]string) (map[string]string, error) { store := h.Associations if store == nil { store = DefaultAssociationStore } assoc, err := store.Get("", params["assoc_handle"]) if err != nil { return nil, err } if assoc == nil { return map[string]string{ "ns": Namespace, "is_valid": "false", }, nil } signed := strings.Split(params["signed"], ",") sig, err := assoc.sign(params, signed) if err != nil { return nil, err } if params["sig"] != sig { return map[string]string{ "ns": Namespace, "is_valid": "false", }, nil } rparams := map[string]string{ "ns": Namespace, "is_valid": "true", } // TODO: deal with invalid_handle store.Delete("", assoc.Handle) return rparams, nil } func saveAssociation(store AssociationStore, a *Association) error { for i := 0; i < 10; i++ { var handle [16]byte if _, err := rand.Read(handle[:]); err != nil { return err } ehandle := make([]byte, ascii85.MaxEncodedLen(len(handle))) n := ascii85.Encode(ehandle, handle[:]) a.Handle = string(ehandle[:n]) err := store.Add(a) if err == nil { return nil } if err != ErrDuplicateAssociation { return err } } return errors.New("cannot store association") } type unsupportedSessionTypeError string func (e unsupportedSessionTypeError) Error() string { return fmt.Sprintf("session type %q not supported", string(e)) } func (e unsupportedSessionTypeError) errorParams() map[string]string { return map[string]string{ "error-code": "unsupported-type", } } golang-github-mhilton-openid-0.0~git20181012.aeae87e/openid2/encoding.go000066400000000000000000000023121414605333600254430ustar00rootroot00000000000000package openid2 import ( "bytes" "fmt" "io" "net/url" "strings" ) const Namespace = "http://specs.openid.net/auth/2.0" // ParseHTTP parses openid values from the parameters in a url.Values. func ParseHTTP(v url.Values) map[string]string { p := make(map[string]string) for k, v := range v { if strings.HasPrefix(k, "openid.") && len(v) > 0 { p[strings.TrimPrefix(k, "openid.")] = v[0] } } return p } // EncodeHTTP updates v with the encoding of p. func EncodeHTTP(v url.Values, p map[string]string) { for k, pv := range p { v.Set("openid."+k, pv) } } // ParseKeyValue func ParseKeyValue(body []byte) (map[string]string, error) { p := make(map[string]string) for _, b := range bytes.Split(body, []byte("\n")) { parts := bytes.SplitN(b, []byte(":"), 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid key-value line %q", b) } p[string(parts[0])] = string(parts[1]) } return p, nil } func EncodeKeyValue(w io.Writer, p map[string]string) error { for k, v := range p { if err := WriteKeyValuePair(w, k, v); err != nil { return err } } return nil } func WriteKeyValuePair(w io.Writer, key, value string) error { _, err := fmt.Fprintf(w, "%s:%s\n", key, value) return err } golang-github-mhilton-openid-0.0~git20181012.aeae87e/openid2/extensions.go000066400000000000000000000051521414605333600260610ustar00rootroot00000000000000package openid2 import ( "fmt" "strings" ) type Extension struct { Namespace string Prefix string Params map[string]string } func parseExtensions(params map[string]string) ([]Extension, error) { prefixes := make(map[string]string) namespaces := make(map[string]string) for k, v := range params { parts := strings.SplitN(k, ".", 2) if len(parts) < 2 { continue } if parts[0] != "ns" { continue } prefix := parts[1] if bannedPrefixes[prefix] { return nil, fmt.Errorf("namespace prefix %q not allowed", prefix) } if ns, ok := prefixes[prefix]; ok && ns != v { return nil, fmt.Errorf("namespace prefix %q assigned to multiple namespaces", prefix) } ns := v if p, ok := namespaces[ns]; ok && p != prefix { return nil, fmt.Errorf("namespace %q assigned to multiple prefixes", ns) } namespaces[ns] = prefix prefixes[prefix] = ns } extensions := make([]Extension, len(prefixes)) positions := make(map[string]int) i := 0 for p, ns := range prefixes { extensions[i] = Extension{ Namespace: ns, Prefix: p, Params: map[string]string{}, } positions[p] = i i++ } for k, v := range params { parts := strings.SplitN(k, ".", 2) if len(parts) < 2 { continue } if parts[0] == "ns" { continue } prefix := parts[0] key := parts[1] pos, ok := positions[prefix] if !ok { continue } extensions[pos].Params[key] = v } return extensions, nil } func encodeExtensions(params map[string]string, extensions []Extension) (signed []string) { var i int used := map[string]bool{} for _, ext := range extensions { prefix := ext.Prefix for bannedPrefixes[prefix] || used[prefix] { prefix = fmt.Sprintf("ext%d", i) i++ } used[prefix] = true params["ns."+prefix] = ext.Namespace for k, v := range ext.Params { key := fmt.Sprintf("%s.%s", prefix, k) params[key] = v signed = append(signed, key) } } return } var bannedPrefixes = map[string]bool{ "assoc_handle": true, "assoc_type": true, "claimed_id": true, "contact": true, "delegate": true, "dh_consumer_public": true, "dh_gen": true, "dh_modulus": true, "error": true, "identity": true, "invalidate_handle": true, "mode": true, "ns": true, "op_endpoint": true, "openid": true, "realm": true, "reference": true, "response_nonce": true, "return_to": true, "server": true, "session_type": true, "sig": true, "signed": true, "trust_root": true, } golang-github-mhilton-openid-0.0~git20181012.aeae87e/openid2/login.go000066400000000000000000000067631414605333600250030ustar00rootroot00000000000000package openid2 import ( "errors" "fmt" "net/http" "strings" ) var ErrUnauthenticated = errors.New("authentication failed") // LoginRequest represents an openid login request. type LoginRequest struct { ClaimedID string Identity string ReturnTo string Realm string Extensions []Extension } func parseLoginRequest(params map[string]string) (*LoginRequest, error) { extensions, err := parseExtensions(params) if err != nil { return nil, err } req := &LoginRequest{ ClaimedID: params["claimed_id"], Identity: params["identity"], ReturnTo: params["return_to"], Realm: params["realm"], Extensions: extensions, } return req, nil } // LoginResponse represents the response to an openid login request. type LoginResponse struct { ClaimedID string Identity string OPEndpoint string Extensions []Extension } // LoginHandler provides server-side handling of a LoginRequest. type LoginHandler interface { Login(http.ResponseWriter, *http.Request, *LoginRequest) (*LoginResponse, error) } func (h *Handler) login(w http.ResponseWriter, r *http.Request, params map[string]string) { req, err := parseLoginRequest(params) if err != nil { indirect(w, params["return_to"]).respond(nil, err) return } var resp *LoginResponse switch params["mode"] { case "checkid_immediate": if h.Login != nil { resp, err = h.Login.Login(nil, r, req) } if err != nil && err != ErrUnauthenticated { indirect(w, params["return_to"]).respond(nil, err) return } if resp != nil { break } indirect(w, params["return_to"]).respond(map[string]string{ "ns": Namespace, "mode": "setup_needed", }, nil) return case "checkid_setup": if h.Login != nil { resp, err = h.Login.Login(w, r, req) } if err != nil && err != ErrUnauthenticated { indirect(w, params["return_to"]).respond(nil, err) return } if resp != nil { break } if err == nil { return } indirect(w, params["return_to"]).respond(map[string]string{ "ns": Namespace, "mode": "cancel", }, nil) return default: panic(fmt.Sprintf("login called with unexpected mode %q", params["mode"])) } if params["return_to"] == "" { direct(w).respond(nil, fmt.Errorf("cannot send id_res message, no return_to parameter")) return } nonce, err := h.getNonce() if err != nil { indirect(w, params["return_to"]).respond(nil, err) return } assoc, err := h.getAssociation(params["assoc_handle"], nonce) if err != nil { indirect(w, params["return_to"]).respond(nil, err) return } // encode the response signed := []string{ "op_endpoint", "return_to", "response_nonce", "assoc_handle", } rparams := map[string]string{ "ns": Namespace, "mode": "id_res", "return_to": params["return_to"], "op_endpoint": resp.OPEndpoint, "response_nonce": nonce, "assoc_handle": assoc.Handle, } if resp.ClaimedID != "" { signed = append(signed, "claimed_id") rparams["claimed_id"] = resp.ClaimedID } if resp.Identity != "" { signed = append(signed, "identity") rparams["identity"] = resp.Identity } if params["assoc_handle"] != "" && params["assoc_handle"] != assoc.Handle { rparams["invalidate_handle"] = params["assoc_handle"] } signed = append(signed, encodeExtensions(rparams, resp.Extensions)...) rparams["signed"] = strings.Join(signed, ",") sig, err := assoc.sign(rparams, signed) if err != nil { indirect(w, params["return_to"]).respond(nil, err) return } rparams["sig"] = sig indirect(w, params["return_to"]).respond(rparams, nil) } golang-github-mhilton-openid-0.0~git20181012.aeae87e/openid2/server.go000066400000000000000000000047611414605333600251750ustar00rootroot00000000000000package openid2 import ( "crypto/rand" "encoding/ascii85" "fmt" "net/http" "net/url" "time" ) type Handler struct { Login LoginHandler Associations AssociationStore } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { r.ParseForm() var params map[string]string switch r.Method { case "GET": params = ParseHTTP(r.URL.Query()) case "POST": params = ParseHTTP(r.PostForm) } switch params["ns"] { case Namespace: break default: indirect(w, params["return_to"]).respond(nil, fmt.Errorf("unknown ns %q", params["ns"])) } switch params["mode"] { case "associate": direct(w).respond(h.associate(params)) case "checkid_immediate", "checkid_setup": h.login(w, r, params) case "check_authentication": direct(w).respond(h.checkAuthentication(params)) default: indirect(w, params["return_to"]).respond(nil, fmt.Errorf("unknown mode %q", params["mode"])) } return } func (h *Handler) getNonce() (string, error) { var nonce [16]byte if _, err := rand.Read(nonce[:]); err != nil { return "", err } enonce := make([]byte, ascii85.MaxEncodedLen(len(nonce))) n := ascii85.Encode(enonce, nonce[:]) return fmt.Sprintf("%s%s", time.Now().UTC().Format(time.RFC3339), enonce[:n]), nil } type responder interface { respond(map[string]string, error) } func direct(w http.ResponseWriter) responder { return directResponder{w} } type directResponder struct { w http.ResponseWriter } func (d directResponder) respond(params map[string]string, err error) { if err != nil { d.w.WriteHeader(http.StatusBadRequest) params = makeError(err) } EncodeKeyValue(d.w, params) } func indirect(w http.ResponseWriter, returnTo string) responder { if returnTo == "" { return direct(w) } u, err := url.Parse(returnTo) if err != nil { return direct(w) } return &indirectResponder{w, u} } type indirectResponder struct { w http.ResponseWriter returnTo *url.URL } func (i *indirectResponder) respond(params map[string]string, err error) { v := i.returnTo.Query() if err != nil { params = makeError(err) } EncodeHTTP(v, params) i.returnTo.RawQuery = v.Encode() i.w.Header().Set("Location", i.returnTo.String()) i.w.WriteHeader(http.StatusSeeOther) } func makeError(err error) map[string]string { e := map[string]string{ "ns": Namespace, "mode": "error", "error": err.Error(), } if err, ok := err.(errorParamser); ok { for k, v := range err.errorParams() { e[k] = v } } return e } type errorParamser interface { errorParams() map[string]string }