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  }