diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65eef93 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +db diff --git a/config/config.go b/config/config.go index c0afc22..fdcb08c 100644 --- a/config/config.go +++ b/config/config.go @@ -1,13 +1,39 @@ package config import ( + "crypto/x509" + "encoding/pem" + "log" + "os" + "github.com/Netflix/go-env" ) type Config struct { - GrpcListenAddress string `env:"GRPC_LISTEN_ADDRESS,default=0.0.0.0:8080"` - SQLiteDirPath string `env:"SQLITE_DIR_PATH,default=db"` - PgDatabaseUrl string `env:"DATABASE_URL"` + GrpcListenAddress string `env:"GRPC_LISTEN_ADDRESS,default=0.0.0.0:8080"` + SQLiteDirPath string `env:"SQLITE_DIR_PATH,default=db"` + PgDatabaseUrl string `env:"DATABASE_URL"` + CACertPath *string `env:"CA_CERT_PATH"` + CACert *x509.Certificate +} + +func initializeCACert(certPath string) *x509.Certificate { + certData, err := os.ReadFile(certPath) + if err != nil { + log.Fatal("CA certificate not found") + } + + CACertBlock, _ := pem.Decode(certData) + if CACertBlock == nil { + log.Fatal("CA certificate is invalid") + } + + CACert, err := x509.ParseCertificate(CACertBlock.Bytes) + if err != nil { + log.Fatal("Could not parse CA cert:", err) + } + + return CACert } func NewConfig() (*Config, error) { @@ -15,6 +41,9 @@ func NewConfig() (*Config, error) { if _, err := env.UnmarshalFromEnviron(&config); err != nil { return nil, err } + if config.CACertPath != nil { + config.CACert = initializeCACert(*config.CACertPath) + } return &config, nil } diff --git a/middleware/auth.go b/middleware/auth.go index 7430ce5..9591191 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -3,8 +3,11 @@ package middleware import ( "context" "crypto/sha256" + "crypto/x509" + "encoding/base64" "encoding/hex" "fmt" + "strings" "github.com/breez/data-sync/config" "github.com/breez/data-sync/proto" @@ -12,6 +15,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/tv42/zbase32" + "google.golang.org/grpc/metadata" ) const ( @@ -22,7 +26,53 @@ var ErrInternalError = fmt.Errorf("internal error") var ErrInvalidSignature = fmt.Errorf("invalid signature") var SignedMsgPrefix = []byte("realtimesync:") +func checkApiKey(config *config.Config, ctx context.Context, req interface{}) error { + if config.CACert == nil { + return nil + } + + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return fmt.Errorf("Could not read request metadata") + } + + authHeader := md.Get("Authorization")[0] + if len(authHeader) <= 7 || !strings.HasPrefix(authHeader, "Bearer ") { + return fmt.Errorf("Invalid auth header") + } + + apiKey := authHeader[7:] + block, err := base64.StdEncoding.DecodeString(apiKey) + if err != nil { + return fmt.Errorf("Could not decode auth header: %v", err) + } + + cert, err := x509.ParseCertificate(block) + if err != nil { + return fmt.Errorf("Could not parse certificate: %v", err) + } + + rootPool := x509.NewCertPool() + rootPool.AddCert(config.CACert) + + chains, err := cert.Verify(x509.VerifyOptions{ + Roots: rootPool, + }) + if err != nil { + return fmt.Errorf("Certificate verification error: %v", err) + } + if len(chains) != 1 || len(chains[0]) != 2 || !chains[0][0].Equal(cert) || !chains[0][1].Equal(config.CACert) { + return fmt.Errorf("Certificate verification error: invalid chain of trust") + } + + return nil +} + func Authenticate(config *config.Config, ctx context.Context, req interface{}) (context.Context, error) { + if err := checkApiKey(config, ctx, req); err != nil { + return nil, err + } + var toVerify string var signature string setRecordReq, ok := req.(*proto.SetRecordRequest)