From 82089d129662f4fc2d4e92e22d25150d896bcddb Mon Sep 17 00:00:00 2001 From: Panos Chatzopoulos Date: Tue, 14 Nov 2023 13:29:34 +0100 Subject: [PATCH 1/2] Encrypt fetches pub key from auth Change encrypt module to fetch the public key from auth's endpoint info. Encrypt will now try to find a public key in the following order from: - provided as an argument - lookup in the .sda-cli-session from a previous login - fetch it from auth and store it as crypth4gh.pub --- README.md | 2 +- encrypt/encrypt.go | 41 +++++++++++++++++++++++++++++++++++------ encrypt/encrypt_test.go | 2 +- helpers/helpers.go | 21 +++++++++++++++++---- helpers/helpers_test.go | 4 ++-- 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b5b39b79..3fceb7ee 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ You can login to download the configuration file needed for some of the the tool ```bash ./sda-cli login ``` -where `login_target` is the URL can be the login endpoint for Big Picture (https://login.bp.nbis.se/), Federated EGA (https://login.test.fega.nbis.se/) or Genomic Data Infrastructure (https://login.gdi.nbis.se/) +where `login_target` is the URL can be the login endpoint for Big Picture (https://login.bp.nbis.se/), Federated EGA (https://login.fega.nbis.se/) or Genomic Data Infrastructure (https://login.gdi.nbis.se/) This will open a link for the user where they can go and log in. After the login is complete, a configuration file will be created in the tool's directory with the name of `.sda-cli-session` diff --git a/encrypt/encrypt.go b/encrypt/encrypt.go index c5c36452..356a5713 100644 --- a/encrypt/encrypt.go +++ b/encrypt/encrypt.go @@ -15,7 +15,7 @@ import ( "strings" "github.com/NBISweden/sda-cli/helpers" - + "github.com/NBISweden/sda-cli/login" "github.com/neicnordic/crypt4gh/keys" "github.com/neicnordic/crypt4gh/streaming" log "github.com/sirupsen/logrus" @@ -26,7 +26,12 @@ import ( // Usage text that will be displayed as command line help text when using the // `help encrypt` command var Usage = ` -USAGE: %s encrypt -key (-outdir ) (-continue=true) [file(s)] +USAGE: %s encrypt -key (-target ) (-outdir ) (-continue=true) [file(s)] + +The target can be one of the following: + bp.nbis.se + fega.nbis.se + gdc.nbis.se encrypt: Encrypts files according to the crypt4gh standard used in the @@ -54,6 +59,8 @@ var outDir = Args.String("outdir", "", var continueEncrypt = Args.Bool("continue", false, "Do not exit on file errors but skip and continue.") +var target = Args.String("target", "", "Client target for public key.") + var publicKeyFileList []string func init() { @@ -75,15 +82,37 @@ func Encrypt(args []string) error { return err } - // no key provided, check for one in the session file + var sesKey string if len(publicKeyFileList) == 0 { - - sesKey, err := helpers.GetPublicKey() + // check for public key in .sda-cli-session file from login + sesKey, err = helpers.GetPublicKeyFromSession() if err != nil { - return fmt.Errorf("public key not provided or %v", err) + log.Println("could not read key from previous login,", err) } + } + // key from session file found + if len(publicKeyFileList) == 0 && sesKey != "" { publicKeyFileList = append(publicKeyFileList, sesKey) } + if len(publicKeyFileList) == 0 && sesKey == "" && *target != "" { + // fetch info endpoint values + info, err := login.GetAuthInfo(*target) + if err != nil { + return err + } + // create pub file + pubFile, err := helpers.CreatePubFile(info.PublicKey, "crypt4gh_key.pub") + if err != nil { + return err + } + log.Println("fetching public key") + // no key provided, no key in session file, target provided + publicKeyFileList = append(publicKeyFileList, pubFile) + } + // no key provided, no key in session file, no target provided + if len(publicKeyFileList) == 0 && sesKey == "" && *target == "" { + return errors.New("no public key could be obtained") + } // Each filename is first read into a helper struct (sliced for combatibility with checkFiles) eachFile := make([]helpers.EncryptionFileSet, 1) diff --git a/encrypt/encrypt_test.go b/encrypt/encrypt_test.go index 2830782a..d15860ab 100644 --- a/encrypt/encrypt_test.go +++ b/encrypt/encrypt_test.go @@ -210,7 +210,7 @@ func (suite *EncryptTests) TestEncryptFunction() { // pub key not given os.Args = []string{"encrypt", suite.fileOk.Name()} err := Encrypt(os.Args) - assert.EqualError(suite.T(), err, "public key not provided or configuration file (.sda-cli-session) not found") + assert.EqualError(suite.T(), err, "no public key could be obtained") // no such pub key file msg := "open somekey: no such file or directory" diff --git a/helpers/helpers.go b/helpers/helpers.go index 7742c732..42cfe7b4 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -292,7 +292,8 @@ func GetAuth(path string) (*Config, error) { return nil, errors.New("failed to read the configuration file") } -func GetPublicKey() (string, error) { +// reads the .sda-cli-session file, creates the public key file and returns the name of the file +func GetPublicKeyFromSession() (string, error) { // Check if the ".sda-cli-session" file exists if !FileExists(".sda-cli-session") { return "", errors.New("configuration file (.sda-cli-session) not found") @@ -316,13 +317,25 @@ func GetPublicKey() (string, error) { return "", errors.New("public key not found in the configuration") } + pubFile, err := CreatePubFile(config.PublicKey, "key-from-oidc.pub.pem") + if err != nil { + return "", fmt.Errorf("failed to create public key file: %w", err) + } + + return pubFile, nil + +} + +// Create public key file +func CreatePubFile(publicKey string, filename string) (string, error) { + // Create a fixed-size array to hold the public key data var publicKeyData [32]byte - b := []byte(config.PublicKey) + b := []byte(publicKey) copy(publicKeyData[:], b) // Open or create a file named "key-from-oidc.pub.pem" in write-only mode with file permissions 0600 - pubFile, err := os.OpenFile(filepath.Clean("key-from-oidc.pub.pem"), os.O_WRONLY|os.O_CREATE, 0600) + pubFile, err := os.OpenFile(filepath.Clean(filename), os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return "", fmt.Errorf("failed to open or create the public key file: %w", err) } @@ -340,7 +353,7 @@ func GetPublicKey() (string, error) { } // If everything is successful, return the name of the generated public key file - return "key-from-oidc.pub.pem", nil + return filename, nil } // CheckTokenExpiration is used to determine whether the token is expiring in less than a day diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index dfd6ead9..f7fa2e2d 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -361,7 +361,7 @@ encrypt = False log.Printf("failed to write temp config file, %v", err) } - _, err = GetPublicKey() + _, err = GetPublicKeyFromSession() assert.EqualError(suite.T(), err, "public key not found in the configuration") } @@ -396,7 +396,7 @@ public_key = 27be42445fd9e39c9be39e6b36a55e61e3801fc845f63781a813d3fe9977e17a log.Printf("failed to write temp config file, %v", err) } - _, err = GetPublicKey() + _, err = GetPublicKeyFromSession() assert.NoError(suite.T(), err) if assert.FileExists(suite.T(), "key-from-oidc.pub.pem") { From ddaf14001e32e4eb101c066f36ed4f0ce1e1f646 Mon Sep 17 00:00:00 2001 From: Panos Chatzopoulos Date: Fri, 17 Nov 2023 13:52:23 +0100 Subject: [PATCH 2/2] Refactoring some functions, added tests --- README.md | 2 +- encrypt/encrypt.go | 39 +++++++++++++++++---------------------- encrypt/encrypt_test.go | 2 +- helpers/helpers.go | 14 +++++--------- helpers/helpers_test.go | 17 ++++++++++++++++- login/login.go | 7 ++----- main.go | 4 ++-- 7 files changed, 44 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 3fceb7ee..f77545de 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ You can login to download the configuration file needed for some of the the tool ```bash ./sda-cli login ``` -where `login_target` is the URL can be the login endpoint for Big Picture (https://login.bp.nbis.se/), Federated EGA (https://login.fega.nbis.se/) or Genomic Data Infrastructure (https://login.gdi.nbis.se/) +where `login_target` is the URL to the `sda-auth` service from the [sensitive-data-archive](https://github.com/neicnordic/sensitive-data-archive/) project. This will open a link for the user where they can go and log in. After the login is complete, a configuration file will be created in the tool's directory with the name of `.sda-cli-session` diff --git a/encrypt/encrypt.go b/encrypt/encrypt.go index 356a5713..3b504c56 100644 --- a/encrypt/encrypt.go +++ b/encrypt/encrypt.go @@ -28,11 +28,6 @@ import ( var Usage = ` USAGE: %s encrypt -key (-target ) (-outdir ) (-continue=true) [file(s)] -The target can be one of the following: - bp.nbis.se - fega.nbis.se - gdc.nbis.se - encrypt: Encrypts files according to the crypt4gh standard used in the Sensitive Data Archive (SDA). Each given file will be encrypted @@ -82,36 +77,36 @@ func Encrypt(args []string) error { return err } - var sesKey string - if len(publicKeyFileList) == 0 { - // check for public key in .sda-cli-session file from login - sesKey, err = helpers.GetPublicKeyFromSession() - if err != nil { - log.Println("could not read key from previous login,", err) - } + if publicKeyFileList != nil && *target != "" { + return errors.New("only one of -key or -target can be used") } - // key from session file found - if len(publicKeyFileList) == 0 && sesKey != "" { - publicKeyFileList = append(publicKeyFileList, sesKey) - } - if len(publicKeyFileList) == 0 && sesKey == "" && *target != "" { + + if *target != "" { // fetch info endpoint values + log.Println("fetching public key") info, err := login.GetAuthInfo(*target) if err != nil { return err } // create pub file - pubFile, err := helpers.CreatePubFile(info.PublicKey, "crypt4gh_key.pub") + pubKeyFile, err := helpers.CreatePubFile(info.PublicKey, "crypt4gh_key.pub") if err != nil { return err } - log.Println("fetching public key") // no key provided, no key in session file, target provided - publicKeyFileList = append(publicKeyFileList, pubFile) + publicKeyFileList = append(publicKeyFileList, pubKeyFile) } // no key provided, no key in session file, no target provided - if len(publicKeyFileList) == 0 && sesKey == "" && *target == "" { - return errors.New("no public key could be obtained") + if publicKeyFileList == nil && *target == "" { + // check for public key in .sda-cli-session file from login + pubKey, err := helpers.GetPublicKeyFromSession() + if err != nil { + return err + } + // key from session file found + if len(publicKeyFileList) == 0 && pubKey != "" { + publicKeyFileList = append(publicKeyFileList, pubKey) + } } // Each filename is first read into a helper struct (sliced for combatibility with checkFiles) diff --git a/encrypt/encrypt_test.go b/encrypt/encrypt_test.go index d15860ab..441e6343 100644 --- a/encrypt/encrypt_test.go +++ b/encrypt/encrypt_test.go @@ -210,7 +210,7 @@ func (suite *EncryptTests) TestEncryptFunction() { // pub key not given os.Args = []string{"encrypt", suite.fileOk.Name()} err := Encrypt(os.Args) - assert.EqualError(suite.T(), err, "no public key could be obtained") + assert.EqualError(suite.T(), err, "configuration file (.sda-cli-session) not found") // no such pub key file msg := "open somekey: no such file or directory" diff --git a/helpers/helpers.go b/helpers/helpers.go index 42cfe7b4..237008e8 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -299,11 +299,9 @@ func GetPublicKeyFromSession() (string, error) { return "", errors.New("configuration file (.sda-cli-session) not found") } - if FileExists(".sda-cli-session") { - file, err := os.Open(".sda-cli-session") - if err != nil { - fmt.Println("could not read file:", file) - } + _, err := os.Open(".sda-cli-session") + if err != nil { + return "", err } // Load the configuration file @@ -319,7 +317,7 @@ func GetPublicKeyFromSession() (string, error) { pubFile, err := CreatePubFile(config.PublicKey, "key-from-oidc.pub.pem") if err != nil { - return "", fmt.Errorf("failed to create public key file: %w", err) + return "", err } return pubFile, nil @@ -328,13 +326,12 @@ func GetPublicKeyFromSession() (string, error) { // Create public key file func CreatePubFile(publicKey string, filename string) (string, error) { - // Create a fixed-size array to hold the public key data var publicKeyData [32]byte b := []byte(publicKey) copy(publicKeyData[:], b) - // Open or create a file named "key-from-oidc.pub.pem" in write-only mode with file permissions 0600 + // Open or create a file in write-only mode with file permissions 0600 pubFile, err := os.OpenFile(filepath.Clean(filename), os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return "", fmt.Errorf("failed to open or create the public key file: %w", err) @@ -345,7 +342,6 @@ func CreatePubFile(publicKey string, filename string) (string, error) { log.Errorf("Error closing file: %s\n", cerr) } }() - // Write the publicKeyData array to the "key-from-oidc.pub.pem" file in Crypt4GHX25519 public key format err = keys.WriteCrypt4GHX25519PublicKey(pubFile, publicKeyData) if err != nil { diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index f7fa2e2d..06aa9717 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -365,7 +365,7 @@ encrypt = False assert.EqualError(suite.T(), err, "public key not found in the configuration") } -func (suite *HelperTests) TestGetPublicKey() { +func (suite *HelperTests) TestGetPublicKeyFromSession() { var confFile = ` access_token = someToken @@ -415,3 +415,18 @@ func (suite *HelperTests) TestInvalidCharacters() { assert.Equal(suite.T(), fmt.Sprintf("filepath %v contains disallowed characters: %+v", testfilepath, badchar), err.Error()) } } + +func (suite *HelperTests) TestCreatePubFile() { + var pubKeyContent = `339eb2a458fec5e23aa8b57cfcb35f10e7389025816e44d4234f814ed2aeed3f` + var expectedPubKey = `-----BEGIN CRYPT4GH PUBLIC KEY----- +MzM5ZWIyYTQ1OGZlYzVlMjNhYThiNTdjZmNiMzVmMTA= +-----END CRYPT4GH PUBLIC KEY----- +` + _, err := CreatePubFile(pubKeyContent, os.TempDir()+"/test_public_file.pub.pem") + assert.NoError(suite.T(), err) + + pubFile, _ := os.ReadFile(os.TempDir() + "/test_public_file.pub.pem") + s := string(pubFile) + assert.Equal(suite.T(), expectedPubKey, s) + defer os.Remove(os.TempDir() + "/test_public_file.pub.pem") +} diff --git a/login/login.go b/login/login.go index dd4de34f..308f518e 100755 --- a/login/login.go +++ b/login/login.go @@ -32,10 +32,7 @@ login: // the module help var ArgHelp = ` [login-target] - The login target can be one of the following: - https://login.bp.nbis.se/ - https://login.test.fega.nbis.se/ - https://login.gdi.nbis.se/` + The login target is the base URL of the service.` // Args is a flagset that needs to be exported so that it can be written to the // main program help @@ -168,7 +165,7 @@ func NewLogin(args []string) error { } err = deviceLogin.Login() if err != nil { - return fmt.Errorf("Login failed") + return err } fmt.Printf("Logged in as %v\n", deviceLogin.UserInfo.Name) diff --git a/main.go b/main.go index b3aa3a47..7a51f222 100644 --- a/main.go +++ b/main.go @@ -122,9 +122,9 @@ func ParseArgs() (string, []string) { if Help(subcommand) == nil { os.Exit(0) - } else { - os.Exit(1) } + os.Exit(1) + } // The "list" command can have no arguments since it can use the