Skip to content

Commit a71ac72

Browse files
committed
Add support for plain TSS2 PEM files
1 parent 6759ce2 commit a71ac72

File tree

2 files changed

+81
-22
lines changed

2 files changed

+81
-22
lines changed

command/api/token/create.go

+77-22
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package token
22

33
import (
44
"bytes"
5+
"context"
56
"crypto"
67
"crypto/tls"
78
"encoding/json"
9+
"encoding/pem"
810
"errors"
911
"fmt"
1012
"net/http"
1113
"net/url"
14+
"os"
1215
"path"
1316

1417
"github.com/google/uuid"
@@ -19,7 +22,10 @@ import (
1922
"github.com/smallstep/cli-utils/ui"
2023
"go.step.sm/crypto/pemutil"
2124
"go.step.sm/crypto/randutil"
25+
"go.step.sm/crypto/tpm"
26+
"go.step.sm/crypto/tpm/tss2"
2227

28+
"github.com/smallstep/cli/flags"
2329
"github.com/smallstep/cli/internal/cryptoutil"
2430
"github.com/smallstep/cli/internal/httptransport"
2531
)
@@ -35,6 +41,8 @@ func createCommand() cli.Command {
3541
Flags: []cli.Flag{
3642
apiURLFlag,
3743
audienceFlag,
44+
flags.PasswordFile,
45+
tpmDeviceFlag,
3846
},
3947
Description: `**step ca api token create** creates a new token for connecting to the Smallstep API.
4048
@@ -81,12 +89,14 @@ func createAction(ctx *cli.Context) (err error) {
8189
}
8290

8391
var (
84-
args = ctx.Args()
85-
teamID = args.Get(0)
86-
crtFile = args.Get(1)
87-
keyFile = args.Get(2)
88-
apiURLFlag = ctx.String("api-url")
89-
audience = ctx.String("audience")
92+
args = ctx.Args()
93+
teamID = args.Get(0)
94+
crtFile = args.Get(1)
95+
keyFile = args.Get(2)
96+
passwordFile = ctx.String("password-file")
97+
apiURLFlag = ctx.String("api-url")
98+
audience = ctx.String("audience")
99+
tpmDevice = ctx.String("tpm-device")
90100
)
91101

92102
parsedURL, err := url.Parse(apiURLFlag)
@@ -96,7 +106,7 @@ func createAction(ctx *cli.Context) (err error) {
96106
parsedURL.Path = path.Join(parsedURL.Path, "api/auth")
97107
apiURL := parsedURL.String()
98108

99-
clientCert, err := createClientCertificate(crtFile, keyFile)
109+
clientCert, err := createClientCertificate(crtFile, keyFile, passwordFile, tpmDevice)
100110
if err != nil {
101111
return err
102112
}
@@ -175,7 +185,7 @@ func newRequestID() string {
175185
return requestID
176186
}
177187

178-
func createClientCertificate(crtFile, keyFile string) (*tls.Certificate, error) {
188+
func createClientCertificate(crtFile, keyFile, passwordFile, tpmDevice string) (*tls.Certificate, error) {
179189
certs, err := pemutil.ReadCertificateBundle(crtFile)
180190
if err != nil {
181191
return nil, fmt.Errorf("failed reading %q: %w", crtFile, err)
@@ -186,26 +196,71 @@ func createClientCertificate(crtFile, keyFile string) (*tls.Certificate, error)
186196
certificates[i] = c.Raw
187197
}
188198

189-
var (
190-
v any
191-
signer crypto.Signer
192-
)
199+
pk, err := getPrivateKey(keyFile, passwordFile, tpmDevice)
200+
if err != nil {
201+
return nil, fmt.Errorf("failed reading key from %q: %w", keyFile, err)
202+
}
203+
204+
if _, ok := pk.(crypto.Signer); !ok {
205+
return nil, fmt.Errorf("private key type %T read from %q cannot be used as a signer", pk, keyFile)
206+
}
207+
208+
return &tls.Certificate{
209+
Certificate: certificates,
210+
Leaf: certs[0],
211+
PrivateKey: pk,
212+
}, nil
213+
}
214+
215+
func getPrivateKey(keyFile, passwordFile, tpmDevice string) (crypto.PrivateKey, error) {
193216
if cryptoutil.IsKMS(keyFile) {
194-
signer, err = cryptoutil.CreateSigner(keyFile, keyFile)
217+
signer, err := cryptoutil.CreateSigner(keyFile, keyFile)
195218
if err != nil {
196219
return nil, fmt.Errorf("failed creating signer: %w", err)
197220
}
198-
v = signer
199-
} else {
200-
v, err = pemutil.Read(keyFile)
221+
222+
return signer, nil
223+
}
224+
225+
b, err := os.ReadFile(keyFile)
226+
if err != nil {
227+
return nil, err
228+
}
229+
230+
// detect the type of the PEM file. if it's a TSS2 PEM file, pemutil
231+
// can't be used to create a private key, as it does not support this
232+
// type. Support could be added, but it could require some additional
233+
// options, such as specifying the TPM device that backs the TSS2
234+
// signer.
235+
p, _ := pem.Decode(b)
236+
if p.Type != "TSS2 PRIVATE KEY" {
237+
pk, err := pemutil.Parse(b, pemutil.WithPasswordFile(passwordFile))
201238
if err != nil {
202-
return nil, fmt.Errorf("failed reading %q: %w", keyFile, err)
239+
return nil, fmt.Errorf("failed parsing PEM: %w", err)
203240
}
241+
242+
return pk, nil
204243
}
205244

206-
return &tls.Certificate{
207-
Certificate: certificates,
208-
Leaf: certs[0],
209-
PrivateKey: v,
210-
}, nil
245+
key, err := tss2.ParsePrivateKey(p.Bytes)
246+
if err != nil {
247+
return nil, fmt.Errorf("failed creating TSS2 private key: %w", err)
248+
}
249+
250+
var tpmOpts = []tpm.NewTPMOption{}
251+
if tpmDevice != "" {
252+
tpmOpts = append(tpmOpts, tpm.WithDeviceName(tpmDevice))
253+
}
254+
255+
t, err := tpm.New(tpmOpts...)
256+
if err != nil {
257+
return nil, fmt.Errorf("failed initializing TPM: %w", err)
258+
}
259+
260+
signer, err := tpm.CreateTSS2Signer(context.Background(), t, key)
261+
if err != nil {
262+
return nil, fmt.Errorf("failed creating TSS2 signer: %w", err)
263+
}
264+
265+
return signer, nil
211266
}

command/api/token/token.go

+4
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@ var (
3030
Name: "audience",
3131
Usage: "Request a token for an audience other than the API Gateway",
3232
}
33+
tpmDeviceFlag = cli.StringFlag{
34+
Name: "tpm-device",
35+
Usage: "(Optional) path to TPM device (e.g. /dev/tpmrm0)",
36+
}
3337
)

0 commit comments

Comments
 (0)