Skip to content

Update authentication keys #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aggregator/aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const (
func RunWithConfig(configPath string) error {
nodeConfig, err := config.NewConfig(configPath)
if err != nil {
panic(fmt.Errorf("failed to parse config file: %w\nMake sure %s is exist and a valid yaml file %w.", configPath, err))
panic(fmt.Errorf("failed to parse config file: %s\nMake sure it is exist and a valid yaml file %w.", configPath, err))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s fix the capitalization now - the first letter of a sentence must be capitalized. Please fix other instances of fmt.Errorf() too.

Use Failed to, not failed to.

Do the below return return the message string to the gRPC client?

return fmt.Errorf("failed to parse config file: %s\nMake sure it is exist and a valid yaml file %w.", configPath, err)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the error message when we run the aggregator from the command line during start-up. It isn't use for any gRPC client API.

}

aggregator, err := NewAggregator(nodeConfig)
Expand Down
25 changes: 20 additions & 5 deletions aggregator/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ import (

const (
// We had old operators pre 1.3 where auth isn't enforced. upon all operators updated to 1.3.0 we will toggle this server side
enforceAuth = false
enforceAuth = false
authTemplate = `Please sign the below text for ownership verification.

URI: https://app.avaprotocol.org
Chain ID: %d
Version: 1
Issued At: %s
Expire At: %s
Wallet: %s`
)

// GetKey exchanges an api key or signature submit by an EOA with an API key that can manage
Expand All @@ -32,18 +40,19 @@ func (r *RpcServer) GetKey(ctx context.Context, payload *avsproto.GetKeyReq) (*a
r.config.Logger.Info("process getkey",
"owner", payload.Owner,
"expired", payload.ExpiredAt,
"issued", payload.IssuedAt,
"chainId", payload.ChainId,
)

if strings.Contains(payload.Signature, ".") {
// API key directly
authenticated, err := auth.VerifyJwtKeyForUser(r.config.JwtSecret, payload.Signature, submitAddress)
if err != nil || !authenticated {
return nil, status.Errorf(codes.Unauthenticated, "%s: %s", auth.AuthenticationError, auth.InvalidAPIKey)
}
} else {
// We need to have 3 things to verify the signature: the signature, the hash of the original data, and the public key of the signer. With this information we can determine if the private key holder of the public key pair did indeed sign the message
// The message format we need to sign
//
text := fmt.Sprintf("key request for %s expired at %d", payload.Owner, payload.ExpiredAt)
text := fmt.Sprintf(authTemplate, payload.ChainId, payload.IssuedAt, payload.ExpiredAt, payload.Owner)
data := []byte(text)
hash := accounts.TextHash(data)

Expand All @@ -69,8 +78,14 @@ func (r *RpcServer) GetKey(ctx context.Context, payload *avsproto.GetKeyReq) (*a
}
}

expiredAt, err := time.Parse(time.RFC3339, payload.ExpiredAt)

if err != nil {
return nil, status.Error(codes.InvalidArgument, auth.MalformedExpirationTime)
}

claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Unix(payload.ExpiredAt, 0)),
ExpiresAt: jwt.NewNumericDate(expiredAt),
Issuer: auth.Issuer,
Subject: payload.Owner,
}
Expand Down
73 changes: 73 additions & 0 deletions aggregator/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package aggregator

import (
"context"
"fmt"
"testing"

"github.com/AvaProtocol/ap-avs/core/auth"
"github.com/AvaProtocol/ap-avs/core/config"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/golang-jwt/jwt/v5"

"github.com/AvaProtocol/ap-avs/core/chainio/signer"
avsproto "github.com/AvaProtocol/ap-avs/protobuf"
sdklogging "github.com/Layr-Labs/eigensdk-go/logging"
)

func TestGetKeyWithSignature(t *testing.T) {
logger, _ := sdklogging.NewZapLogger("development")

r := RpcServer{
config: &config.Config{
JwtSecret: []byte("test123"),
Logger: logger,
},
}

owner := "0x578B110b0a7c06e66b7B1a33C39635304aaF733c"
chainID := int64(11155111)
issuedAt := "2025-01-01T00:00:00Z"
expiredAt := "2025-01-02T00:00:00Z"

text := fmt.Sprintf(authTemplate, chainID, issuedAt, expiredAt, owner)
privateKey, _ := crypto.HexToECDSA("e0502ddd5a0d05ec7b5c22614a01c8ce783810edaa98e44cc82f5fa5a819aaa9")

signature, _ := signer.SignMessage(privateKey, []byte(text))

payload := &avsproto.GetKeyReq{
ChainId: chainID,
IssuedAt: issuedAt,
ExpiredAt: expiredAt,
Owner: owner,
Signature: hexutil.Encode(signature),
}

// Run the test
resp, err := r.GetKey(context.Background(), payload)

if err != nil {
t.Errorf("expect GetKey succesfully but got error: %s", err)
}
if resp.Key == "" {
t.Errorf("expect jwt key but got no")
}

// Now let verify the key is valid
token, err := jwt.Parse(resp.Key, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("%s", auth.InvalidAuthenticationKey)
}

// hmacSampleSecret is a []byte containing your
// secret, e.g. []byte("my_secret_key")
return r.config.JwtSecret, nil
})

sub, _ := token.Claims.GetSubject()
if sub != "0x578B110b0a7c06e66b7B1a33C39635304aaF733c" {
t.Errorf("invalid subject. expected 0x578B110b0a7c06e66b7B1a33C39635304aaF733c but got %s", sub)
}
}
4 changes: 2 additions & 2 deletions aggregator/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ type CreateApiKeyOption struct {
func CreateAdminKey(configPath string, opt CreateApiKeyOption) error {
nodeConfig, err := config.NewConfig(configPath)
if err != nil {
panic(fmt.Errorf("failed to parse config file: %w\nMake sure %s is exist and a valid yaml file %w.", configPath, err))
return fmt.Errorf("failed to parse config file: %s\nMake sure it is exist and a valid yaml file %w.", configPath, err)
}

aggregator, err := NewAggregator(nodeConfig)
if err != nil {
panic(fmt.Errorf("cannot initialize aggregrator from config: %w", err))
return fmt.Errorf("cannot initialize aggregrator from config: %w", err)
}

if opt.Subject == "" {
Expand Down
1 change: 1 addition & 0 deletions core/auth/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ const (
InvalidSignatureFormat = "Invalid Signature Format"
InvalidAuthenticationKey = "User Auth key is invalid"
InvalidAPIKey = "API key is invalid"
MalformedExpirationTime = "Malformed Expired Time"
)
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2Gihuqh
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc=
github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ=
github.com/ginkgoch/godash/v2 v2.0.1 h1:VEpzpsZGTVFhYBXIrjh+5xpI8mP4ip4Sk3cZj43E3sg=
github.com/ginkgoch/godash/v2 v2.0.1/go.mod h1:lyQg4RqW9hWWkW1GzIWHuGvvqbZsm1akNliSiYcZYm8=
github.com/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
Expand Down Expand Up @@ -509,8 +507,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
Expand Down
Loading
Loading