File: change-pass.go

package info (click to toggle)
golang-github-smallstep-cli 0.15.16%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 4,404 kB
  • sloc: sh: 512; makefile: 99
file content (179 lines) | stat: -rw-r--r-- 4,676 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package crypto

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"

	"github.com/pkg/errors"
	"github.com/urfave/cli"

	"github.com/smallstep/cli/command"
	"github.com/smallstep/cli/crypto/pemutil"
	"github.com/smallstep/cli/errs"
	"github.com/smallstep/cli/flags"
	"github.com/smallstep/cli/jose"
	"github.com/smallstep/cli/ui"
	"github.com/smallstep/cli/utils"
)

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**=<path>] [**--password-file**=<path>] [**--new-password-file**=<path>]
[**--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, "insecure", "no-password")
	}

	keyPath := ctx.Args().Get(0)
	newKeyPath := ctx.String("out")
	if len(newKeyPath) == 0 {
		newKeyPath = keyPath
	}

	b, err := ioutil.ReadFile(keyPath)
	if err != nil {
		return errs.FileError(err, keyPath)
	}

	if bytes.HasPrefix(b, []byte("-----BEGIN ")) {
		opts := []pemutil.Options{pemutil.WithFilename(keyPath)}
		if len(decryptPassFile) > 0 {
			opts = append(opts, pemutil.WithPasswordFile(decryptPassFile))
		}
		key, err := pemutil.Parse(b, opts...)
		if err != nil {
			return err
		}
		opts = []pemutil.Options{}
		if !noPass {
			if len(encryptPassFile) > 0 {
				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, 0644))
		if _, err := pemutil.Serialize(key, opts...); err != nil {
			return err
		}
	} else {
		opts := []jose.Option{}
		if len(decryptPassFile) > 0 {
			opts = append(opts, jose.WithPasswordFile(decryptPassFile))
		}
		jwk, err := jose.ParseKey(keyPath, opts...)
		if err != nil {
			return err
		}
		var b []byte
		if noPass {
			b, err = jwk.MarshalJSON()
			if err != nil {
				return err
			}
		} else {
			opts = []jose.Option{}
			if len(encryptPassFile) > 0 {
				opts = append(opts, jose.WithPasswordFile(encryptPassFile))
			}
			jwe, err := jose.EncryptJWK(jwk, 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 := utils.WriteFile(newKeyPath, out.Bytes(), 0600); err != nil {
			return errs.FileError(err, newKeyPath)
		}
	}

	ui.Printf("Your key has been saved in %s.\n", newKeyPath)
	return nil
}