|
|
|
@ -10,7 +10,6 @@ import (
|
|
|
|
|
"github.com/graphql-go/graphql/language/ast"
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"io"
|
|
|
|
|
"reflect"
|
|
|
|
|
"fmt"
|
|
|
|
@ -21,7 +20,6 @@ import (
|
|
|
|
|
"crypto/ecdh"
|
|
|
|
|
"crypto/ecdsa"
|
|
|
|
|
"crypto/elliptic"
|
|
|
|
|
"crypto/sha512"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/x509"
|
|
|
|
|
"crypto/tls"
|
|
|
|
@ -30,173 +28,6 @@ import (
|
|
|
|
|
"encoding/pem"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type AuthReqJSON struct {
|
|
|
|
|
Time time.Time `json:"time"`
|
|
|
|
|
Pubkey []byte `json:"pubkey"`
|
|
|
|
|
ECDHPubkey []byte `json:"ecdh_client"`
|
|
|
|
|
Signature []byte `json:"signature"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewAuthReqJSON(curve ecdh.Curve, id *ecdsa.PrivateKey) (AuthReqJSON, *ecdh.PrivateKey, error) {
|
|
|
|
|
ec_key, err := curve.GenerateKey(rand.Reader)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return AuthReqJSON{}, nil, err
|
|
|
|
|
}
|
|
|
|
|
now := time.Now()
|
|
|
|
|
time_bytes, err := now.MarshalJSON()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return AuthReqJSON{}, nil, err
|
|
|
|
|
}
|
|
|
|
|
sig_data := append(ec_key.PublicKey().Bytes(), time_bytes...)
|
|
|
|
|
sig_hash := sha512.Sum512(sig_data)
|
|
|
|
|
sig, err := ecdsa.SignASN1(rand.Reader, id, sig_hash[:])
|
|
|
|
|
|
|
|
|
|
id_ecdh, err := id.ECDH()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return AuthReqJSON{}, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return AuthReqJSON{
|
|
|
|
|
Time: now,
|
|
|
|
|
Pubkey: id_ecdh.PublicKey().Bytes(),
|
|
|
|
|
ECDHPubkey: ec_key.PublicKey().Bytes(),
|
|
|
|
|
Signature: sig,
|
|
|
|
|
}, ec_key, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AuthRespJSON struct {
|
|
|
|
|
Granted time.Time `json:"granted"`
|
|
|
|
|
ECDHPubkey []byte `json:"echd_server"`
|
|
|
|
|
Signature []byte `json:"signature"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewAuthRespJSON(gql_ext *GQLExt, req AuthReqJSON) (AuthRespJSON, *ecdsa.PublicKey, []byte, error) {
|
|
|
|
|
// Check if req.Time is within +- 1 second of now
|
|
|
|
|
now := time.Now()
|
|
|
|
|
earliest := now.Add(-1 * time.Second)
|
|
|
|
|
latest := now.Add(1 * time.Second)
|
|
|
|
|
// If req.Time is before the earliest acceptable time, or after the latest acceptible time
|
|
|
|
|
if req.Time.Compare(earliest) == -1 {
|
|
|
|
|
return AuthRespJSON{}, nil, nil, fmt.Errorf("GQL_AUTH_TIME_TOO_LATE: %s", req.Time)
|
|
|
|
|
} else if req.Time.Compare(latest) == 1 {
|
|
|
|
|
return AuthRespJSON{}, nil, nil, fmt.Errorf("GQL_AUTH_TIME_TOO_EARLY: %s", req.Time)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x, y := elliptic.Unmarshal(gql_ext.Key.Curve, req.Pubkey)
|
|
|
|
|
if x == nil {
|
|
|
|
|
return AuthRespJSON{}, nil, nil, fmt.Errorf("GQL_AUTH_UNMARSHAL_FAIL: %+v", req.Pubkey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remote, err := gql_ext.ECDH.NewPublicKey(req.ECDHPubkey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return AuthRespJSON{}, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the signature
|
|
|
|
|
time_bytes, _ := req.Time.MarshalJSON()
|
|
|
|
|
sig_data := append(req.ECDHPubkey, time_bytes...)
|
|
|
|
|
sig_hash := sha512.Sum512(sig_data)
|
|
|
|
|
|
|
|
|
|
remote_key := &ecdsa.PublicKey{
|
|
|
|
|
Curve: gql_ext.Key.Curve,
|
|
|
|
|
X: x,
|
|
|
|
|
Y: y,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
verified := ecdsa.VerifyASN1(
|
|
|
|
|
remote_key,
|
|
|
|
|
sig_hash[:],
|
|
|
|
|
req.Signature,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if verified == false {
|
|
|
|
|
return AuthRespJSON{}, nil, nil, fmt.Errorf("GQL_AUTH_VERIFY_FAIL: %+v", req)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ec_key, err := gql_ext.ECDH.GenerateKey(rand.Reader)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return AuthRespJSON{}, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ec_key_pub := ec_key.PublicKey().Bytes()
|
|
|
|
|
|
|
|
|
|
granted := time.Now()
|
|
|
|
|
time_ser, _ := granted.MarshalJSON()
|
|
|
|
|
resp_sig_data := append(ec_key_pub, time_ser...)
|
|
|
|
|
resp_sig_hash := sha512.Sum512(resp_sig_data)
|
|
|
|
|
|
|
|
|
|
resp_sig, err := ecdsa.SignASN1(rand.Reader, gql_ext.Key, resp_sig_hash[:])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return AuthRespJSON{}, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shared_secret, err := ec_key.ECDH(remote)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return AuthRespJSON{}, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return AuthRespJSON{
|
|
|
|
|
Granted: granted,
|
|
|
|
|
ECDHPubkey: ec_key_pub,
|
|
|
|
|
Signature: resp_sig,
|
|
|
|
|
}, remote_key, shared_secret, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ParseAuthRespJSON(resp AuthRespJSON, ecdsa_curve elliptic.Curve, ecdh_curve ecdh.Curve, ec_key *ecdh.PrivateKey) ([]byte, error) {
|
|
|
|
|
remote, err := ecdh_curve.NewPublicKey(resp.ECDHPubkey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shared_secret, err := ec_key.ECDH(remote)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shared_secret, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func AuthHandler(ctx *Context, server *Node, gql_ext *GQLExt) func(http.ResponseWriter, *http.Request) {
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
ctx.Log.Logf("gql", "GQL_AUTH_REQUEST: %s", r.RemoteAddr)
|
|
|
|
|
enableCORS(&w)
|
|
|
|
|
|
|
|
|
|
str, err := io.ReadAll(r.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.Log.Logf("gql", "GQL_AUTH_READ_ERR: %e", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var req AuthReqJSON
|
|
|
|
|
err = json.Unmarshal([]byte(str), &req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.Log.Logf("gql", "GQL_AUTH_UNMARHSHAL_ERR: %e", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp, _, _, err := NewAuthRespJSON(gql_ext, req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.Log.Logf("gql", "GQL_AUTH_VERIFY_ERROR: %s", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ser, err := json.Marshal(resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.Log.Logf("gql", "GQL_AUTH_RESP_MARSHAL_ERR: %e", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wrote, err := w.Write(ser)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.Log.Logf("gql", "GQL_AUTH_RESP_ERR: %e", err)
|
|
|
|
|
return
|
|
|
|
|
} else if wrote != len(ser) {
|
|
|
|
|
ctx.Log.Logf("gql", "GQL_AUTH_RESP_BAD_LENGTH: %d/%d", wrote, len(ser))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GraphiQLHandler() func(http.ResponseWriter, *http.Request) {
|
|
|
|
|
return func(w http.ResponseWriter, r * http.Request) {
|
|
|
|
|
graphiql_string := fmt.Sprintf(`
|
|
|
|
@ -340,7 +171,7 @@ type ResolveContext struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewResolveContext(ctx *Context, server *Node, gql_ext *GQLExt, r *http.Request) (*ResolveContext, error) {
|
|
|
|
|
username, password, ok := r.BasicAuth()
|
|
|
|
|
username, _, ok := r.BasicAuth()
|
|
|
|
|
if ok == false {
|
|
|
|
|
return nil, fmt.Errorf("GQL_REQUEST_ERR: no auth header included in request header")
|
|
|
|
|
}
|
|
|
|
@ -355,15 +186,6 @@ func NewResolveContext(ctx *Context, server *Node, gql_ext *GQLExt, r *http.Requ
|
|
|
|
|
return nil, fmt.Errorf("GQL_REQUEST_ERR: no existing authorization for client %s", auth_id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user_ext, err := GetExt[*ECDHExt](user)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if base64.StdEncoding.EncodeToString(user_ext.Shared) != password {
|
|
|
|
|
return nil, fmt.Errorf("GQL_AUTH_FAIL")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &ResolveContext{
|
|
|
|
|
Context: ctx,
|
|
|
|
|
GQLContext: ctx.Extensions[Hash(GQLExtType)].Data.(*GQLExtContext),
|
|
|
|
@ -942,7 +764,6 @@ func NewGQLExt(listen string, ecdh_curve ecdh.Curve, key *ecdsa.PrivateKey, tls_
|
|
|
|
|
|
|
|
|
|
func StartGQLServer(ctx *Context, node *Node, gql_ext *GQLExt) error {
|
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
mux.HandleFunc("/auth", AuthHandler(ctx, node, gql_ext))
|
|
|
|
|
mux.HandleFunc("/gql", GQLHandler(ctx, node, gql_ext))
|
|
|
|
|
mux.HandleFunc("/gqlws", GQLWSHandler(ctx, node, gql_ext))
|
|
|
|
|
|
|
|
|
|