Added KDF for shared secret to shared ecdsa key, and added signature to AuthRespJSON

graph-rework-2
noah metz 2023-07-19 21:28:48 -06:00
parent 374fd6e487
commit 2fde6ae282
2 changed files with 108 additions and 24 deletions

118
gql.go

@ -23,6 +23,7 @@ import (
"crypto/sha512" "crypto/sha512"
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"bytes"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -66,7 +67,7 @@ type AuthRespJSON struct {
Signature []byte `json:"signature"` Signature []byte `json:"signature"`
} }
func NewAuthRespJSON(thread *GQLThread, req AuthReqJSON) (AuthRespJSON, *ecdsa.PublicKey, []byte, error) { func NewAuthRespJSON(thread *GQLThread, req AuthReqJSON) (AuthRespJSON, *ecdsa.PublicKey, *ecdsa.PrivateKey, error) {
// Check if req.Time is within +- 1 second of now // Check if req.Time is within +- 1 second of now
now := time.Now() now := time.Now()
earliest := now.Add(-1 * time.Second) earliest := now.Add(-1 * time.Second)
@ -131,17 +132,49 @@ func NewAuthRespJSON(thread *GQLThread, req AuthReqJSON) (AuthRespJSON, *ecdsa.P
return AuthRespJSON{}, nil, nil, err return AuthRespJSON{}, nil, nil, err
} }
secret_hash := sha512.Sum512(shared_secret)
buf := bytes.NewReader(secret_hash[:])
shared_key, err := ecdsa.GenerateKey(thread.Key.Curve, buf)
if err != nil {
return AuthRespJSON{}, nil, nil, err
}
return AuthRespJSON{ return AuthRespJSON{
Granted: granted, Granted: granted,
ECDHPubkey: ec_key_pub, ECDHPubkey: ec_key_pub,
Signature: resp_sig, Signature: resp_sig,
}, remote_key, shared_secret, nil }, remote_key, shared_key, nil
}
func ParseAuthRespJSON(resp AuthRespJSON, ecdsa_curve elliptic.Curve, ecdh_curve ecdh.Curve, ec_key *ecdh.PrivateKey) (*ecdsa.PrivateKey, 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
}
secret_hash := sha512.Sum512(shared_secret)
buf := bytes.NewReader(secret_hash[:])
shared_key, err := ecdsa.GenerateKey(ecdsa_curve, buf)
if err != nil {
return nil, err
}
return shared_key, nil
} }
type AuthData struct { type AuthData struct {
Granted time.Time Granted time.Time
Pubkey *ecdsa.PublicKey Pubkey *ecdsa.PublicKey
Shared []byte Shared *ecdsa.PrivateKey
}
func (data AuthData) String() string {
return fmt.Sprintf("{Granted: %+v, Pubkey: %s, Shared: %s}", data.Granted, KeyID(data.Pubkey).String(), KeyID(&data.Shared.PublicKey).String())
} }
type AuthDataJSON struct { type AuthDataJSON struct {
@ -174,9 +207,9 @@ func AuthHandler(ctx *Context, server *GQLThread) func(http.ResponseWriter, *htt
return return
} }
resp, remote_id, _, err := NewAuthRespJSON(server, req) resp, remote_id, shared_key, err := NewAuthRespJSON(server, req)
if err != nil { if err != nil {
ctx.Log.Logf("gql", "GQL_AUTH_VERIFY_ERROR: %e", err) ctx.Log.Logf("gql", "GQL_AUTH_VERIFY_ERROR: %s", err)
return return
} }
@ -195,17 +228,22 @@ func AuthHandler(ctx *Context, server *GQLThread) func(http.ResponseWriter, *htt
return return
} }
ctx.Log.Logf("gql", "GQL_AUTH_VERIFY_SUCCESS: %s", str) key_id := KeyID(remote_id)
key_hash := KeyID(remote_id) new_auth := AuthData{
Granted: time.Now(),
Pubkey: remote_id,
Shared: shared_key,
}
_, exists := server.AuthMap[key_hash] _, exists := server.AuthMap[key_id]
if exists { if exists {
ctx.Log.Logf("gql", "REFRESHING AUTH FOR %+s", req.Pubkey) ctx.Log.Logf("gql", "REFRESHING AUTH FOR %s - %s", key_id, new_auth)
} else { } else {
ctx.Log.Logf("gql", "AUTHORIZING NEW USER %+s", req.Pubkey) ctx.Log.Logf("gql", "AUTHORIZING NEW USER %s - %s", key_id, new_auth)
} }
server.AuthMap[key_id] = new_auth
} }
} }
@ -356,13 +394,29 @@ func GQLHandler(ctx * Context, server * GQLThread) func(http.ResponseWriter, *ht
header_map[header] = value header_map[header] = value
} }
ctx.Log.Logm("gql", header_map, "REQUEST_HEADERS") ctx.Log.Logm("gql", header_map, "REQUEST_HEADERS")
auth, ok := checkForAuthHeader(r.Header) username, password, ok := r.BasicAuth()
if ok == false { if ok == false {
ctx.Log.Logf("gql", "GQL_REQUEST_ERR: no auth header included in request header") ctx.Log.Logf("gql", "GQL_REQUEST_ERR: no auth header included in request header")
json.NewEncoder(w).Encode(GQLUnauthorized("No TM Auth header provided")) json.NewEncoder(w).Encode(GQLUnauthorized("No Auth header provided"))
return
}
auth_id, err := ParseID(username)
if err != nil {
ctx.Log.Logf("gql", "GQL_REQUEST_ERR: failed to parse ID from auth username: %s", username)
json.NewEncoder(w).Encode(GQLUnauthorized("Failed to parse ID from username"))
return return
} }
ctx.Log.Logf("gql", "GQL_AUTH: %s", auth)
auth, exists := server.AuthMap[auth_id]
if exists == false {
ctx.Log.Logf("gql", "GQL_REQUEST_ERR: no existing authorization for client %s", auth_id)
json.NewEncoder(w).Encode(GQLUnauthorized("No matching authorization for client"))
return
}
req_ctx := context.WithValue(gql_ctx, "auth", auth)
ctx.Log.Logf("gql", "GQL_AUTH: %+v - %s", auth, password)
str, err := io.ReadAll(r.Body) str, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
@ -372,8 +426,6 @@ func GQLHandler(ctx * Context, server * GQLThread) func(http.ResponseWriter, *ht
query := GQLWSPayload{} query := GQLWSPayload{}
json.Unmarshal(str, &query) json.Unmarshal(str, &query)
req_ctx := context.WithValue(gql_ctx, "auth", auth)
params := graphql.Params{ params := graphql.Params{
Schema: ctx.GQL.Schema, Schema: ctx.GQL.Schema,
Context: req_ctx, Context: req_ctx,
@ -457,13 +509,29 @@ func GQLWSHandler(ctx * Context, server * GQLThread) func(http.ResponseWriter, *
} }
ctx.Log.Logm("gql", header_map, "REQUEST_HEADERS") ctx.Log.Logm("gql", header_map, "REQUEST_HEADERS")
auth, ok := checkForAuthHeader(r.Header) username, password, ok := r.BasicAuth()
if ok == false { if ok == false {
ctx.Log.Logf("gql", "GQL_REQUEST_ERR: no auth header included in request header") ctx.Log.Logf("gql", "GQL_REQUEST_ERR: no auth header included in request header")
json.NewEncoder(w).Encode(GQLUnauthorized("No TM Auth header provided")) json.NewEncoder(w).Encode(GQLUnauthorized("No Auth header provided"))
return
}
auth_id, err := ParseID(username)
if err != nil {
ctx.Log.Logf("gql", "GQL_REQUEST_ERR: failed to parse ID from auth username: %s", username)
json.NewEncoder(w).Encode(GQLUnauthorized("Failed to parse ID from username"))
return return
} }
ctx.Log.Logf("gql", "GQL_AUTH: %s", auth)
auth, exists := server.AuthMap[auth_id]
if exists == false {
ctx.Log.Logf("gql", "GQL_REQUEST_ERR: no existing authorization for client %s", auth_id)
json.NewEncoder(w).Encode(GQLUnauthorized("No matching authorization for client"))
return
}
req_ctx := context.WithValue(gql_ctx, "auth", auth)
ctx.Log.Logf("gql", "GQL_AUTH: %+v - %s", auth, password)
u := ws.HTTPUpgrader{ u := ws.HTTPUpgrader{
Protocol: func(protocol string) bool { Protocol: func(protocol string) bool {
@ -508,7 +576,7 @@ func GQLWSHandler(ctx * Context, server * GQLThread) func(http.ResponseWriter, *
ctx.Log.Logf("gqlws", "SUBSCRIBE: %+v", msg.Payload) ctx.Log.Logf("gqlws", "SUBSCRIBE: %+v", msg.Payload)
params := graphql.Params{ params := graphql.Params{
Schema: ctx.GQL.Schema, Schema: ctx.GQL.Schema,
Context: gql_ctx, Context: req_ctx,
RequestString: msg.Payload.Query, RequestString: msg.Payload.Query,
} }
if msg.Payload.OperationName != "" { if msg.Payload.OperationName != "" {
@ -640,10 +708,14 @@ func NewGQLThreadJSON(thread *GQLThread) GQLThreadJSON {
auth_map := map[string]AuthDataJSON{} auth_map := map[string]AuthDataJSON{}
for id, data := range(thread.AuthMap) { for id, data := range(thread.AuthMap) {
shared, err := x509.MarshalECPrivateKey(data.Shared)
if err != nil {
panic(err)
}
auth_map[id.String()] = AuthDataJSON{ auth_map[id.String()] = AuthDataJSON{
Granted: data.Granted, Granted: data.Granted,
Pubkey: elliptic.Marshal(data.Pubkey.Curve, data.Pubkey.X, data.Pubkey.Y), Pubkey: elliptic.Marshal(data.Pubkey.Curve, data.Pubkey.X, data.Pubkey.Y),
Shared: thread.AuthMap[id].Shared, Shared: shared,
} }
} }
@ -684,6 +756,10 @@ func LoadGQLThread(ctx *Context, id NodeID, data []byte, nodes NodeMap) (Node, e
if x == nil { if x == nil {
return nil, fmt.Errorf("Failed to load public key for curve %+v from %+v", key.Curve, auth_json.Pubkey) return nil, fmt.Errorf("Failed to load public key for curve %+v from %+v", key.Curve, auth_json.Pubkey)
} }
shared, err := x509.ParseECPrivateKey(auth_json.Shared)
if err != nil {
return nil, err
}
thread.AuthMap[id] = AuthData{ thread.AuthMap[id] = AuthData{
Granted: auth_json.Granted, Granted: auth_json.Granted,
Pubkey: &ecdsa.PublicKey{ Pubkey: &ecdsa.PublicKey{
@ -691,7 +767,7 @@ func LoadGQLThread(ctx *Context, id NodeID, data []byte, nodes NodeMap) (Node, e
X: x, X: x,
Y: y, Y: y,
}, },
Shared: auth_json.Shared, Shared: shared,
} }
} }
nodes[id] = &thread nodes[id] = &thread

@ -53,7 +53,7 @@ func TestGQLThread(t * testing.T) {
} }
func TestGQLDBLoad(t * testing.T) { func TestGQLDBLoad(t * testing.T) {
ctx := logTestContext(t, []string{"test"}) ctx := logTestContext(t, []string{})
l1_r := NewSimpleLockable(RandID(), "Test Lockable 1") l1_r := NewSimpleLockable(RandID(), "Test Lockable 1")
l1 := &l1_r l1 := &l1_r
@ -172,7 +172,7 @@ func TestGQLAuth(t * testing.T) {
id, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) id, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
fatalErr(t, err) fatalErr(t, err)
auth_req, _, err := NewAuthReqJSON(ecdh.P256(), id) auth_req, ec_key, err := NewAuthReqJSON(ecdh.P256(), id)
fatalErr(t, err) fatalErr(t, err)
str, err := json.Marshal(auth_req) str, err := json.Marshal(auth_req)
@ -186,7 +186,15 @@ func TestGQLAuth(t * testing.T) {
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
resp.Body.Close() resp.Body.Close()
fatalErr(t, err) fatalErr(t, err)
ctx.Log.Logf("test", "RESP_BODY: %s", body)
var j AuthRespJSON
err = json.Unmarshal(body, &j)
fatalErr(t, err)
shared_key, err := ParseAuthRespJSON(j, elliptic.P256(), ecdh.P256(), ec_key)
fatalErr(t, err)
ctx.Log.Logf("test", "TEST_SHARED_SECRET: %s", KeyID(&shared_key.PublicKey).String())
done <- nil done <- nil
}(gql_t) }(gql_t)