forked from smallstep/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
verify.go
184 lines (166 loc) · 5.7 KB
/
verify.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
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
180
181
182
183
184
package jws
import (
"os"
"strings"
"github.com/pkg/errors"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/jose"
"github.com/smallstep/cli/utils"
"github.com/urfave/cli"
)
func verifyCommand() cli.Command {
return cli.Command{
Name: "verify",
Action: cli.ActionFunc(verifyAction),
Usage: "verify a signed JWS data structure and return the payload",
UsageText: `**step crypto jws verify**
[**--alg**=<algorithm>] [**--key**=<path>] [**--jwks**=<jwks>] [**--kid**=<kid>]`,
Description: `**step crypto jws verify** reads a JWS data structure from STDIN; checks that
the algorithm are in agreement with expectations; verifies the digital
signature or message authentication code as appropriate; and outputs the
decoded payload of the JWS on STDOUT. If verification fails a non-zero failure
code is returned. If verification succeeds the command returns 0.
For a JWS to be verified successfully:
* The JWS must be well formed (no errors during deserialization)
* The <algorithm> must match the **"alg"** member in the JWS header
* The <kid> must match the **"kid"** member in the JWS header (if both are
present) and must match the **"kid"** in the JWK or the **"kid"** of one of the
JWKs in JWKS
* The JWS signature must be successfully verified
For examples, see **step help crypto jws**.`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "alg, algorithm",
Usage: `The signature or MAC <algorithm> to use. Algorithms are case-sensitive strings
defined in RFC7518. If the key used do verify the JWS is not a JWK, or if it
is a JWK but does not have an **"alg"** member indicating its the intended
algorithm for use with the key, then the **--alg** flag is required to prevent
algorithm downgrade attacks. To disable this protection you can pass the
**--insecure** flag and omit the **--alg** flag.`,
},
cli.StringFlag{
Name: "key",
Usage: `The <path> to the key with which to verify the JWS.
The contents of the file can be a public or private JWK (or a JWK
encrypted as a JWE payload) or a public or private PEM (or a private key
encrypted using the modes described on RFC 1423 or with PBES2+PBKDF2 described
in RFC 2898).`,
},
cli.StringFlag{
Name: "jwks",
Usage: `The JWK Set containing the key to use to verify the JWS. The <jwks> argument
should be the name of a file. The file contents should be a JWK Set or a JWE
with a JWK Set payload. The JWS being verified should have a "kid" member that
matches the "kid" of one of the JWKs in the JWK Set. If the JWS does not have
a "kid" member the '--kid' flag can be used.`,
},
cli.StringFlag{
Name: "kid",
Usage: `The ID of the key used to sign the JWK, used to select a JWK from a JWK Set.
The KID argument is a case-sensitive string. If the input JWS has a "kid"
member its value must match <kid> or verification will fail.`,
},
cli.BoolFlag{
Name: "json",
Usage: `Displays the header, payload and signature as a JSON object. The payload will
be encoded using Base64.`,
},
cli.BoolFlag{
Name: "subtle",
Hidden: true,
},
cli.BoolFlag{
Name: "insecure",
Hidden: true,
},
},
}
}
// Get the public key for a JWK.
func publicKey(jwk *jose.JSONWebKey) interface{} {
if jose.IsSymmetric(jwk) {
return jwk.Key
}
return jwk.Public().Key
}
func verifyAction(ctx *cli.Context) error {
token, err := utils.ReadString(os.Stdin)
if err != nil {
return errors.Wrap(err, "error reading token")
}
tok, err := jose.ParseJWS(token)
if err != nil {
return errors.Errorf("error parsing token: %s", strings.TrimPrefix(err.Error(), "square/go-jose: "))
}
// We don't support multiple signatures
if len(tok.Signatures) > 1 {
return errors.New("validation failed: multiple signatures are not supported")
}
// Validate key, jwks and kid
key := ctx.String("key")
jwks := ctx.String("jwks")
kid := ctx.String("kid")
alg := ctx.String("alg")
switch {
case key == "" && jwks == "":
return errs.RequiredOrFlag(ctx, "key", "jwks")
case key != "" && jwks != "":
return errs.MutuallyExclusiveFlags(ctx, "key", "jwks")
case jwks != "" && kid == "":
if tok.Signatures[0].Header.KeyID == "" {
return errs.RequiredWithFlag(ctx, "kid", "jwks")
}
kid = tok.Signatures[0].Header.KeyID
}
// Add parse options
var options []jose.Option
options = append(options, jose.WithUse("sig"))
if len(alg) > 0 {
options = append(options, jose.WithAlg(alg))
}
if len(kid) > 0 {
options = append(options, jose.WithKid(kid))
}
if !ctx.Bool("insecure") {
options = append(options, jose.WithNoDefaults(true))
}
// Read key from --key or --jwks
var jwk *jose.JSONWebKey
switch {
case key != "":
jwk, err = jose.ParseKey(key, options...)
case jwks != "":
jwk, err = jose.ParseKeySet(jwks, options...)
default:
return errs.RequiredOrFlag(ctx, "key", "jwks")
}
if err != nil {
return err
}
// At this moment jwk.Algorithm should have an alg from:
// * alg parameter
// * jwk or jwkset
// * guessed for ecdsa and ed25519 keys
if jwk.Algorithm == "" {
return errors.New("flag '--alg' is required with the given key")
}
if err = jose.ValidateJWK(jwk); err != nil {
return err
}
// We don't support any critical headers
if _, ok := tok.Signatures[0].Header.ExtraHeaders["crit"]; ok {
return errors.New("validation failed: unrecognized critical headers (crit)")
}
if alg != "" && tok.Signatures[0].Header.Algorithm != "" && alg != tok.Signatures[0].Header.Algorithm {
return errors.Errorf("alg %s does not match the alg on JWS (%s)", alg, tok.Signatures[0].Header.Algorithm)
}
payload, err := tok.Verify(publicKey(jwk))
if err != nil {
return errors.New("validation failed: invalid signature")
}
if ctx.Bool("json") {
return printToken(tok)
}
os.Stdout.Write(payload)
return nil
}