1
0
mirror of https://github.com/smallstep/cli.git synced 2025-08-04 17:42:08 +03:00
Files
step-ca-cli/command/crypto/change-pass.go
Max 60a4eef3de Fix linter warnings (#1450)
* Remove unnecessary utils files in favor of cli-utils package
* Remove nolint:revive directive from bcrypt-pbkdf file
2025-07-10 11:15:28 -07:00

183 lines
4.7 KiB
Go

package crypto
import (
"bytes"
"encoding/json"
"fmt"
"os"
"github.com/pkg/errors"
"github.com/urfave/cli"
"github.com/smallstep/cli-utils/command"
"github.com/smallstep/cli-utils/errs"
"github.com/smallstep/cli-utils/fileutil"
"github.com/smallstep/cli-utils/ui"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/flags"
)
func changePassCommand() cli.Command {
return cli.Command{
Name: "change-pass",
Action: command.ActionFunc(changePassAction),
Usage: "change password of an encrypted private key (PEM or JWK format)",
UsageText: `**step crypto change-pass** <key-file>
[**--out**=<file>] [**--password-file**=<file>] [**--new-password-file**=<file>]
[**--insecure**] [**--no-password**]`,
Description: `**step crypto change-pass** extracts and decrypts
the private key from a file and encrypts and serializes the key to disk using a
new password.
## POSITIONAL ARGUMENTS
<key-file>
: The PEM or JWK file with the encrypted key.
## EXAMPLES
Change password for PEM formatted key:
'''
$ step crypto change-pass key.pem
'''
Remove password for PEM formatted key:
'''
$ step crypto change-pass key.pem --no-password --insecure
'''
Change password for PEM formatted key and write encrypted key to different file:
'''
$ step crypto change-pass key.pem --out new-key.pem
'''
Change password for JWK formatted key:
'''
$ step crypto change-pass key.jwk
'''
Removed password for JWK formatted key:
'''
$ step crypto change-pass key.jwk --no-password --insecure
'''
Change password for JWK formatted key:
'''
$ step crypto change-pass key.jwk --out new-key.jwk
'''`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "password-file",
Usage: `The path to the <file> containing the password to decrypt the private key.`,
},
cli.StringFlag{
Name: "new-password-file",
Usage: `The path to the <file> containing the password to encrypt the private key.`,
},
cli.StringFlag{
Name: "out,output-file",
Usage: "The <file> new encrypted key path. Default to overwriting the <key> positional argument",
},
flags.Force,
flags.Insecure,
flags.NoPassword,
},
}
}
// changePassAction does the following:
// 1. decrypts a private key (if necessary)
// 2. encrypts the key using a new password
// 3. writes the encrypted key to the original file
func changePassAction(ctx *cli.Context) error {
if err := errs.NumberOfArguments(ctx, 1); err != nil {
return err
}
insecure := ctx.Bool("insecure")
noPass := ctx.Bool("no-password")
decryptPassFile := ctx.String("password-file")
encryptPassFile := ctx.String("new-password-file")
if noPass && !insecure {
return errs.RequiredWithFlag(ctx, "no-password", "insecure")
}
keyPath := ctx.Args().Get(0)
newKeyPath := ctx.String("out")
if newKeyPath == "" {
newKeyPath = keyPath
}
b, err := os.ReadFile(keyPath)
if err != nil {
return errs.FileError(err, keyPath)
}
if bytes.HasPrefix(b, []byte("-----BEGIN ")) {
opts := []pemutil.Options{pemutil.WithFilename(keyPath)}
if decryptPassFile != "" {
opts = append(opts, pemutil.WithPasswordFile(decryptPassFile))
}
key, err := pemutil.Parse(b, opts...)
if err != nil {
return err
}
opts = []pemutil.Options{}
if !noPass {
if encryptPassFile != "" {
opts = append(opts, pemutil.WithPasswordFile(encryptPassFile))
} else {
pass, err := ui.PromptPassword(fmt.Sprintf("Please enter the password to encrypt %s", newKeyPath))
if err != nil {
return errors.Wrap(err, "error reading password")
}
opts = append(opts, pemutil.WithPassword(pass))
}
}
opts = append(opts, pemutil.ToFile(newKeyPath, 0o644))
if _, err := pemutil.Serialize(key, opts...); err != nil {
return err
}
} else {
opts := []jose.Option{}
if decryptPassFile != "" {
opts = append(opts, jose.WithPasswordFile(decryptPassFile))
}
jwk, err := jose.ReadKey(keyPath, opts...)
if err != nil {
return err
}
b, err := json.Marshal(jwk)
if err != nil {
return err
}
if !noPass {
opts = []jose.Option{
jose.WithPasswordPrompter("Please enter the password to encrypt the private JWK", func(s string) ([]byte, error) {
return ui.PromptPassword(s)
}),
}
if encryptPassFile != "" {
opts = append(opts, jose.WithPasswordFile(encryptPassFile))
}
jwe, err := jose.Encrypt(b, opts...)
if err != nil {
return err
}
b = []byte(jwe.FullSerialize())
}
var out bytes.Buffer
if err := json.Indent(&out, b, "", " "); err != nil {
return errors.Wrap(err, "error formatting JSON")
}
if err := fileutil.WriteFile(newKeyPath, out.Bytes(), 0o600); err != nil {
return errs.FileError(err, newKeyPath)
}
}
ui.Printf("Your key has been saved in %s.\n", newKeyPath)
return nil
}