github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runbits/auth/keypair.go (about) 1 package auth 2 3 import ( 4 "github.com/ActiveState/cli/internal/constants" 5 "github.com/ActiveState/cli/internal/errs" 6 "github.com/ActiveState/cli/internal/keypairs" 7 "github.com/ActiveState/cli/internal/locale" 8 "github.com/ActiveState/cli/internal/output" 9 "github.com/ActiveState/cli/internal/prompt" 10 secretsapi "github.com/ActiveState/cli/pkg/platform/api/secrets" 11 secretsModels "github.com/ActiveState/cli/pkg/platform/api/secrets/secrets_models" 12 "github.com/ActiveState/cli/pkg/platform/authentication" 13 ) 14 15 // ensureUserKeypair checks to see if the currently authenticated user has a Keypair. If not, one is generated 16 // and saved. 17 func ensureUserKeypair(passphrase string, cfg keypairs.Configurable, out output.Outputer, prompt prompt.Prompter, auth *authentication.Auth) error { 18 keypairRes, err := keypairs.FetchRaw(secretsapi.Get(auth), cfg, auth) 19 if err == nil { 20 err = processExistingKeypairForUser(keypairRes, passphrase, cfg, out, prompt, auth) 21 } else if errs.Matches(err, &keypairs.ErrKeypairNotFound{}) { 22 err = generateKeypairForUser(cfg, passphrase, auth) 23 } 24 25 if err != nil { 26 return locale.WrapError(err, "err_ensure_keypair", "Could not find keypair. Please login with '[ACTIONABLE]state auth --prompt[/RESET]'.") 27 } 28 29 return nil 30 } 31 32 // generateKeypairForUser attempts to generate and save a Keypair for the currently authenticated user. 33 func generateKeypairForUser(cfg keypairs.Configurable, passphrase string, auth *authentication.Auth) error { 34 _, err := keypairs.GenerateAndSaveEncodedKeypair(cfg, secretsapi.Get(auth), passphrase, constants.DefaultRSABitLength, auth) 35 if err != nil { 36 return errs.Wrap(err, "Could not generate and save encoded keypair.") 37 } 38 return nil 39 } 40 41 // processExistingKeypairForUser will attempt to ensure the stored private-key for the user is encrypted 42 // using the provided passphrase. If passphrase match fails, processExistingKeypairForUser will then try 43 // validate that the locally stored private-key has a public-key matching the one provided in the keypair. 44 // If public-keys match, the locally stored private-key will be encrypted with the provided passphrase 45 // and uploaded for the user. 46 // 47 // If the previous paths result in err, user is prompted for their previous passphrase in attempt to 48 // determine if the password has changed. If successful, private-key is encrypted with passphrase provided 49 // to this function and uploaded. 50 // 51 // If all paths err, user is prompted to regenerate their keypair which will be encrypted with the 52 // provided passphrase and then uploaded; unless the user declines, which results in err. 53 func processExistingKeypairForUser(keypairRes *secretsModels.Keypair, passphrase string, cfg keypairs.Configurable, out output.Outputer, prompt prompt.Prompter, auth *authentication.Auth) error { 54 keypair, err := keypairs.ParseEncryptedRSA(*keypairRes.EncryptedPrivateKey, passphrase) 55 if err == nil { 56 // yay, store keypair locally just in case it isn't 57 return keypairs.SaveWithDefaults(cfg, keypair) 58 } else if !errs.Matches(err, &keypairs.ErrKeypairPassphrase{}) { 59 // err did not involve an unmatched passphrase 60 return err 61 } 62 63 // failed to decrypt stored private-key with provided passphrase, try using a local private-key 64 var localKeypair keypairs.Keypair 65 localKeypair, err = keypairs.LoadWithDefaults(cfg) 66 if err == nil && localKeypair.MatchPublicKey(*keypairRes.PublicKey) { 67 // locally stored private-key has a matching public-key, encrypt that with new passphrase and upload 68 var encodedKeypair *keypairs.EncodedKeypair 69 if encodedKeypair, err = keypairs.EncodeKeypair(localKeypair, passphrase); err != nil { 70 return err 71 } 72 return keypairs.SaveEncodedKeypair(cfg, secretsapi.Get(auth), encodedKeypair, auth) 73 } 74 75 // failed to validate with local private-key, try using previous passphrase 76 err = recoverKeypairFromPreviousPassphrase(keypairRes, passphrase, cfg, out, prompt, auth) 77 if err != nil && errs.Matches(err, &keypairs.ErrKeypairPassphrase{}) { 78 // that failed, see if they want to regenerate their passphrase 79 err = promptUserToRegenerateKeypair(passphrase, cfg, out, prompt, auth) 80 } 81 return err 82 } 83 84 func recoverKeypairFromPreviousPassphrase(keypairRes *secretsModels.Keypair, passphrase string, cfg keypairs.Configurable, out output.Outputer, prompt prompt.Prompter, auth *authentication.Auth) error { 85 out.Notice(locale.T("previous_password_message")) 86 prevPassphrase, err := promptForPreviousPassphrase(prompt) 87 if err == nil { 88 var keypair keypairs.Keypair 89 keypair, err = keypairs.ParseEncryptedRSA(*keypairRes.EncryptedPrivateKey, prevPassphrase) 90 if err == nil { 91 // previous passphrase is valid, encrypt private-key with new passphrase and upload 92 encodedKeypair, err := keypairs.EncodeKeypair(keypair, passphrase) 93 if err == nil { 94 if saveErr := keypairs.SaveEncodedKeypair(cfg, secretsapi.Get(auth), encodedKeypair, auth); saveErr != nil { 95 return saveErr 96 } 97 } 98 } 99 } 100 return err 101 } 102 103 func promptForPreviousPassphrase(prompt prompt.Prompter) (string, error) { 104 passphrase, err := prompt.InputSecret("", locale.T("previous_password_prompt")) 105 if err != nil { 106 return "", locale.WrapInputError(err, "auth_err_password_prompt") 107 } 108 return passphrase, nil 109 } 110 111 func promptUserToRegenerateKeypair(passphrase string, cfg keypairs.Configurable, out output.Outputer, prompt prompt.Prompter, auth *authentication.Auth) error { 112 var err error 113 // previous passphrase is invalid, inform user and ask if they want to generate a new keypair 114 out.Notice(locale.T("auth_generate_new_keypair_message")) 115 yes, err := prompt.Confirm("", locale.T("auth_confirm_generate_new_keypair_prompt"), new(bool)) 116 if err != nil { 117 return err 118 } 119 if yes { 120 _, err = keypairs.GenerateAndSaveEncodedKeypair(cfg, secretsapi.Get(auth), passphrase, constants.DefaultRSABitLength, auth) 121 // TODO delete user's secrets 122 } else { 123 err = locale.NewError("auth_err_unrecoverable_keypair") 124 } 125 return err 126 }