File: p12.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 (170 lines) | stat: -rw-r--r-- 4,746 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
package certificate

import (
	"crypto/rand"
	"crypto/x509"

	"github.com/pkg/errors"
	"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/ui"
	"github.com/smallstep/cli/utils"
	"github.com/urfave/cli"

	"software.sslmate.com/src/go-pkcs12"
)

func p12Command() cli.Command {
	return cli.Command{
		Name:   "p12",
		Action: command.ActionFunc(p12Action),
		Usage:  `package a certificate and keys into a .p12 file`,
		UsageText: `step certificate p12 <p12-path> [<crt-path>] [<key-path>]
[**--ca**=<file>] [**--password-file**=<file>]`,
		Description: `**step certificate p12** creates a .p12 (PFX / PKCS12)
file containing certificates and keys. This can then be used to import
into Windows / Firefox / Java applications.

## EXIT CODES

This command returns 0 on success and \>0 if any error occurs.

## EXAMPLES

Package a certificate and private key together:

'''
$ step certificate p12 foo.p12 foo.crt foo.key
'''

Package a certificate and private key together, and include an intermediate certificate:

'''
$ step certificate p12 foo.p12 foo.crt foo.key --ca intermediate.crt
'''

Package a CA certificate into a "trust store" for Java applications:

'''
$ step certificate p12 trust.p12 --ca ca.crt
'''

Package a certificate and private key with an empty password:

'''
$ step certificate p12 --no-password --insecure foo.p12 foo.crt foo.key
'''`,
		Flags: []cli.Flag{
			cli.StringSliceFlag{
				Name: "ca",
				Usage: `The path to the <file> containing a CA or intermediate certificate to
add to the .p12 file. Use the '--ca' flag multiple times to add
multiple CAs or intermediates.`,
			},
			cli.StringFlag{
				Name:  "password-file",
				Usage: `The path to the <file> containing the password to encrypt the .p12 file.`,
			},
			flags.NoPassword,
			flags.Force,
			flags.Insecure,
		},
	}
}

func p12Action(ctx *cli.Context) error {
	if err := errs.MinMaxNumberOfArguments(ctx, 1, 3); err != nil {
		return err
	}

	p12File := ctx.Args().Get(0)
	crtFile := ctx.Args().Get(1)
	keyFile := ctx.Args().Get(2)
	caFiles := ctx.StringSlice("ca")
	hasKeyAndCert := crtFile != "" && keyFile != ""

	//If either key or cert are provided, both must be provided
	if !hasKeyAndCert && (crtFile != "" || keyFile != "") {
		return errs.MissingArguments(ctx, "key_file")
	}

	//If no key and cert are provided, ca files must be provided
	if !hasKeyAndCert && len(caFiles) == 0 {
		return errors.Errorf("flag '--%s' must be provided when no <crt_path> and <key_path> are present", "ca")
	}

	// Validate flags
	switch {
	case ctx.String("password-file") != "" && ctx.Bool("no-password"):
		return errs.IncompatibleFlagWithFlag(ctx, "no-password", "password-file")
	case ctx.Bool("no-password") && !ctx.Bool("insecure"):
		return errs.RequiredInsecureFlag(ctx, "no-password")
	}

	x509CAs := []*x509.Certificate{}
	for _, caFile := range caFiles {
		x509Bundle, err := pemutil.ReadCertificateBundle(caFile)
		if err != nil {
			return errors.Wrap(err, "error reading CA certificate")
		}
		x509CAs = append(x509CAs, x509Bundle...)
	}

	var err error
	var password string
	if !ctx.Bool("no-password") {
		if passwordFile := ctx.String("password-file"); passwordFile != "" {
			password, err = utils.ReadStringPasswordFromFile(passwordFile)
			if err != nil {
				return err
			}
		}

		if password == "" {
			pass, err := ui.PromptPassword("Please enter a password to encrypt the .p12 file")
			if err != nil {
				return errors.Wrap(err, "error reading password")
			}
			password = string(pass)
		}
	}

	var pkcs12Data []byte
	if hasKeyAndCert {
		//If we have a key and certificate, we're making an identity store
		x509CertBundle, err := pemutil.ReadCertificateBundle(crtFile)
		if err != nil {
			return errors.Wrap(err, "error reading certificate")
		}

		key, err := pemutil.Read(keyFile)
		if err != nil {
			return errors.Wrap(err, "error reading key")
		}

		//The first certificate in the bundle will be our server cert
		x509Cert := x509CertBundle[0]
		//Any remaning certs will be intermediates for the server
		x509CAs = append(x509CAs, x509CertBundle[1:]...)

		pkcs12Data, err = pkcs12.Encode(rand.Reader, key, x509Cert, x509CAs, password)
		if err != nil {
			return errs.Wrap(err, "failed to encode PKCS12 data")
		}
	} else {
		//If we have only --ca flags, we're making a trust store
		pkcs12Data, err = pkcs12.EncodeTrustStore(rand.Reader, x509CAs, password)
		if err != nil {
			return errs.Wrap(err, "failed to encode PKCS12 data")
		}
	}

	if err := utils.WriteFile(p12File, pkcs12Data, 0600); err != nil {
		return err
	}

	ui.Printf("Your .p12 bundle has been saved as %s.\n", p12File)
	return nil
}