/
sshcert.go
144 lines (127 loc) · 3.57 KB
/
sshcert.go
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
package main
import (
"crypto/rand"
"fmt"
"io/ioutil"
"math"
"os"
"github.com/folbricht/tpmk"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
"github.com/spf13/cobra"
)
type sshCertOptions struct {
cakey string
id string
serial uint64
options []string
extensions []string
host bool
outFormat string
}
func newSSHCertCommand() *cobra.Command {
var opt sshCertOptions
cmd := &cobra.Command{
Use: "certificate <keyfile> <certfile>",
Short: "Generate a certificate",
Long: `Generate an SSH certificate using the provided public key and
sign it with a CA key.
The certificat format can be 'openssh' or 'wire' which is smaller
and more suitable for storage in NV indexes.
Use '-' to read the key from STDIN, or to output the certificate
to STDOUT.`,
Example: ` tpmk ssh certificate publickey.pem tpm-cert.pub
tpmk ssh certificate -O force-command=ls --serial 123 - -`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return runSSHCert(opt, args)
},
SilenceUsage: true,
}
flags := cmd.Flags()
flags.StringVarP(&opt.cakey, "ca-key", "k", "", "SSH CA key file")
flags.StringVar(&opt.id, "id", "", "Certificate identifier")
flags.Uint64Var(&opt.serial, "serial", 0, "Serial")
flags.StringSliceVarP(&opt.options, "option", "O", nil, "Certificate option in the form <key>=[<value>]")
flags.StringSliceVarP(&opt.extensions, "extension", "E", nil, "Certificate extension in the form <key>=[<value>]")
flags.BoolVarP(&opt.host, "host", "H", false, "Generate a host certificate instead of a user certificate")
flags.StringVarP(&opt.outFormat, "out-format", "f", "openssh", "Output format")
cmd.MarkFlagRequired("ca-key")
return cmd
}
func runSSHCert(opt sshCertOptions, args []string) error {
keyfile := args[0]
crtfile := args[1]
format, err := parseSSHFormat(opt.outFormat)
if err != nil {
return err
}
// Parse certificate options and extensions
criticalOptions := parseOptionsMap(opt.options)
extensions := parseOptionsMap(opt.extensions)
// Read the CA
k, err := ioutil.ReadFile(opt.cakey)
if err != nil {
return errors.Wrap(err, "opening CA")
}
caKey, err := ssh.ParsePrivateKey(k)
if err != nil {
return errors.Wrap(err, "reading CA")
}
// Read the public key from file or stdin and turn it into an SSH public key
var pk []byte
if keyfile == "-" {
pk, err = ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
} else {
pk, err = ioutil.ReadFile(keyfile)
if err != nil {
return err
}
}
public, err := tpmk.PEMToPubKey(pk)
if err != nil {
return errors.Wrap(err, "decode public key")
}
sshPublic, err := ssh.NewPublicKey(public)
if err != nil {
return errors.Wrap(err, "read public key")
}
// Build a cert
cert := ssh.Certificate{
Key: sshPublic,
CertType: ssh.UserCert,
KeyId: opt.id,
ValidAfter: 0, // 0 - MaxUint64 = "forever"
ValidBefore: math.MaxUint64,
Permissions: ssh.Permissions{
CriticalOptions: criticalOptions,
Extensions: extensions,
},
}
if opt.host {
cert.CertType = ssh.HostCert
}
// Sign the cert
if err := cert.SignCert(rand.Reader, caKey); err != nil {
return errors.Wrap(err, "signing certificate")
}
// Marshal the cert into the desired format
var b []byte
switch format {
case formatOpenSSH:
b = tpmk.MarshalSSHPublic(&cert, cert.KeyId)
case formatWire:
b = cert.Marshal()
default:
return fmt.Errorf("unsupported output format %d", format)
}
// Write it to file or STDOUT
if crtfile == "-" {
_, err = os.Stdout.Write(b)
return err
}
return ioutil.WriteFile(crtfile, b, 0755)
}