-
Notifications
You must be signed in to change notification settings - Fork 295
Expand file tree
/
Copy pathverify.go
More file actions
104 lines (87 loc) · 2.76 KB
/
verify.go
File metadata and controls
104 lines (87 loc) · 2.76 KB
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
package ssh
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/pkg/errors"
"github.com/urfave/cli"
"golang.org/x/crypto/ssh"
"golang.org/x/exp/maps"
"go.step.sm/cli-utils/command"
"github.com/smallstep/cli/utils"
)
func verifyCommand() cli.Command {
return cli.Command{
Name: "verify",
Action: command.ActionFunc(verifyAction),
Usage: "verify an ssh certificate",
UsageText: `**step ssh verify** <crt-file> <ca-file>`,
Description: `**step ssh verify** command ...
format.
`,
}
}
func verifyAction(ctx *cli.Context) error {
// TODO: validation of args; allow caFile to be interpreted as directory
var (
certFile = ctx.Args().Get(0)
caFile = ctx.Args().Get(1)
)
certBytes, err := utils.ReadFile(certFile)
if err != nil {
return err
}
pub, _, _, _, err := ssh.ParseAuthorizedKey(certBytes)
if err != nil {
// Attempt to parse the key without the type.
certBytes = bytes.TrimSpace(certBytes)
keyBytes := make([]byte, base64.StdEncoding.DecodedLen(len(certBytes)))
n, err := base64.StdEncoding.Decode(keyBytes, certBytes)
if err != nil {
return errors.Wrap(err, "error parsing ssh certificate")
}
if pub, err = ssh.ParsePublicKey(keyBytes[:n]); err != nil {
return errors.Wrap(err, "error parsing ssh certificate")
}
}
cert, ok := pub.(*ssh.Certificate)
if !ok {
return errors.Errorf("error decoding ssh certificate: %T is not an *ssh.Certificate", pub)
}
// TODO: add additional config? This could include a revocation check.
checker := &ssh.CertChecker{
SupportedCriticalOptions: maps.Keys(cert.CriticalOptions), // allow any option in the certificate
}
var principal string
if len(cert.ValidPrincipals) > 0 {
principal = cert.ValidPrincipals[0]
}
// check critical options, principal, validity and signature
if err := checker.CheckCert(principal, cert); err != nil {
return fmt.Errorf("error verifying ssh certificate: %w", err)
}
caBytes, err := utils.ReadFile(caFile)
if err != nil {
return err
}
caPub, _, _, _, err := ssh.ParseAuthorizedKey(caBytes)
if err != nil {
// Attempt to parse the key without the type.
certBytes = bytes.TrimSpace(caBytes)
keyBytes := make([]byte, base64.StdEncoding.DecodedLen(len(certBytes)))
n, err := base64.StdEncoding.Decode(keyBytes, certBytes)
if err != nil {
return errors.Wrap(err, "error parsing ssh CA certificate")
}
if caPub, err = ssh.ParsePublicKey(keyBytes[:n]); err != nil {
return errors.Wrap(err, "error parsing ssh CA certificate")
}
}
// check the certificate was signed by the SSH CA provided
caFP := ssh.FingerprintSHA256(caPub)
certSignerFP := ssh.FingerprintSHA256(cert.SignatureKey)
if certSignerFP != caFP {
return fmt.Errorf("ssh certificate signed by %q does not equal ssh CA %q", certSignerFP, caFP)
}
return nil
}