1 package main
2
3 import (
4 "fmt"
5 "os"
6 "strings"
7
8 "github.com/Microsoft/hcsshim/internal/cosesign1"
9 didx509resolver "github.com/Microsoft/hcsshim/internal/did-x509-resolver"
10 "github.com/urfave/cli"
11 )
12
13 func checkCoseSign1(inputFilename string, chainFilename string, didString string, verbose bool) (*cosesign1.UnpackedCoseSign1, error) {
14 coseBlob, err := os.ReadFile(inputFilename)
15 if err != nil {
16 return nil, err
17 }
18
19 var chainPEM []byte
20 var chainPEMString string
21 if chainFilename != "" {
22 chainPEM, err = os.ReadFile(chainFilename)
23 if err != nil {
24 return nil, err
25 }
26 chainPEMString = string(chainPEM[:])
27 }
28
29 unpacked, err := cosesign1.UnpackAndValidateCOSE1CertChain(coseBlob)
30 if err != nil {
31 fmt.Fprintf(os.Stdout, "checkCoseSign1 failed - %s\n", err)
32 return nil, err
33 }
34
35 fmt.Fprint(os.Stdout, "checkCoseSign1 passed\n")
36 if verbose {
37 fmt.Fprintf(os.Stdout, "iss: %s\n", unpacked.Issuer)
38 fmt.Fprintf(os.Stdout, "feed: %s\n", unpacked.Feed)
39 fmt.Fprintf(os.Stdout, "cty: %s\n", unpacked.ContentType)
40 fmt.Fprintf(os.Stdout, "pubkey: %s\n", unpacked.Pubkey)
41 fmt.Fprintf(os.Stdout, "pubcert: %s\n", unpacked.Pubcert)
42 fmt.Fprintf(os.Stdout, "payload:\n%s\n", string(unpacked.Payload[:]))
43 }
44 if len(didString) > 0 {
45 if len(chainPEMString) == 0 {
46 chainPEMString = unpacked.ChainPem
47 }
48 didDoc, err := didx509resolver.Resolve(chainPEMString, didString, true)
49 if err == nil {
50 fmt.Fprintf(os.Stdout, "DID resolvers passed:\n%s\n", didDoc)
51 } else {
52 fmt.Fprintf(os.Stdout, "DID resolvers failed: err: %s doc:\n%s\n", err.Error(), didDoc)
53 }
54 }
55 return unpacked, err
56 }
57
58 var createCmd = cli.Command{
59 Name: "create",
60 Usage: "",
61 Flags: []cli.Flag{
62 cli.StringFlag{
63 Name: "claims",
64 Usage: "filename of payload",
65 Value: "fragment.rego",
66 },
67 cli.StringFlag{
68 Name: "content-type",
69 Usage: "payload content type",
70 Value: "application/unknown+json",
71 },
72 cli.StringFlag{
73 Name: "chain",
74 Usage: "key or cert file to use (pem)",
75 Value: "chain.pem",
76 },
77 cli.StringFlag{
78 Name: "key",
79 Usage: "key to sign with - private key of the leaf of the chain",
80 Value: "key.pem",
81 },
82 cli.StringFlag{
83 Name: "algo",
84 Usage: "PS256, PS384 etc (required)",
85 Required: true,
86 },
87 cli.StringFlag{
88 Name: "out",
89 Usage: "output file (default: out.cose)",
90 Value: "out.cose",
91 },
92 cli.StringFlag{
93 Name: "salt",
94 Usage: "salt type [rand|zero] (default: rand)",
95 Value: "rand",
96 },
97 cli.StringFlag{
98 Name: "issuer",
99 Usage: "the party making the claims (optional). See https://ietf-scitt.github." +
100 "io/draft-birkholz-scitt-architecture/draft-birkholz-scitt-architecture.html#name-terminology",
101 },
102 cli.StringFlag{
103 Name: "feed",
104 Usage: "identifier for an artifact within the scope of an issuer (optional)",
105 },
106 cli.BoolFlag{
107 Name: "verbose,v",
108 Usage: "verbose output (optional)",
109 },
110 },
111 Action: func(ctx *cli.Context) error {
112 payloadBlob, err := os.ReadFile(ctx.String("claims"))
113 if err != nil {
114 return err
115 }
116 keyPem, err := os.ReadFile(ctx.String("key"))
117 if err != nil {
118 return err
119 }
120 chainPem, err := os.ReadFile(ctx.String("chain"))
121 if err != nil {
122 return err
123 }
124 algo, err := cosesign1.StringToAlgorithm(ctx.String("algo"))
125 if err != nil {
126 return err
127 }
128
129 raw, err := cosesign1.CreateCoseSign1(
130 payloadBlob,
131 ctx.String("issuer"),
132 ctx.String("feed"),
133 ctx.String("content-type"),
134 chainPem,
135 keyPem,
136 ctx.String("salt"),
137 algo,
138 )
139 if err != nil {
140 return fmt.Errorf("create failed: %w", err)
141 }
142
143 err = cosesign1.WriteBlob(ctx.String("out"), raw)
144 if err != nil {
145 return fmt.Errorf("failed to write output file: %w", err)
146 }
147 fmt.Fprint(os.Stdout, "create completed\n")
148 return nil
149 },
150 }
151
152 var checkCmd = cli.Command{
153 Name: "check",
154 Usage: "",
155 Flags: []cli.Flag{
156 cli.StringFlag{
157 Name: "in",
158 Usage: "input COSE Sign1 file (default: input.cose)",
159 Value: "input.cose",
160 },
161 cli.StringFlag{
162 Name: "chain",
163 Usage: "key or cert file to use (pem) (optional)",
164 },
165 cli.StringFlag{
166 Name: "did",
167 Usage: "DID x509 string to resolve against cert chain (optional)",
168 },
169 cli.BoolFlag{
170 Name: "verbose",
171 Usage: "verbose output (optional)",
172 },
173 },
174 Action: func(ctx *cli.Context) error {
175 _, err := checkCoseSign1(
176 ctx.String("in"),
177 ctx.String("chain"),
178 ctx.String("did"),
179 ctx.Bool("verbose"),
180 )
181 if err != nil {
182 return fmt.Errorf("failed check: %w", err)
183 }
184 return nil
185 },
186 }
187
188 var printCmd = cli.Command{
189 Name: "print",
190 Usage: "",
191 Flags: []cli.Flag{
192 cli.StringFlag{
193 Name: "in",
194 Usage: "input COSE Sign1 file",
195 Value: "input.cose",
196 },
197 },
198 Action: func(ctx *cli.Context) error {
199 _, err := checkCoseSign1(ctx.String("in"), "", "", true)
200 if err != nil {
201 return fmt.Errorf("failed verbose checkCoseSign1: %w", err)
202 }
203 return nil
204 },
205 }
206
207 var leafCmd = cli.Command{
208 Name: "leaf",
209 Usage: "",
210 Flags: []cli.Flag{
211 cli.StringFlag{
212 Name: "in",
213 Usage: "input COSE Sign1 file",
214 Value: "input.cose",
215 },
216 cli.StringFlag{
217 Name: "keyout",
218 Usage: "leaf key output file",
219 Value: "leafkey.pem",
220 },
221 cli.StringFlag{
222 Name: "certout",
223 Usage: "leaf cert output file",
224 Value: "leafcert.pem",
225 },
226 cli.BoolFlag{
227 Name: "verbose",
228 Usage: "print information about COSE Sign1 document",
229 },
230 },
231 Action: func(ctx *cli.Context) error {
232 inputFilename := ctx.String("in")
233 outputKeyFilename := ctx.String("keyout")
234 outputCertFilename := ctx.String("certout")
235 unpacked, err := checkCoseSign1(
236 inputFilename,
237 "",
238 "",
239 ctx.Bool("verbose"),
240 )
241 if err != nil {
242 return fmt.Errorf("reading the COSE Sign1 from %s failed: %w", inputFilename, err)
243 }
244
245
246
247 keyWriteErr := cosesign1.WriteString(outputKeyFilename, unpacked.Pubkey)
248 if keyWriteErr != nil {
249 fmt.Fprintf(os.Stderr, "writing the leaf pub key to %s failed: %s\n", outputKeyFilename, keyWriteErr)
250 }
251 certWriteErr := cosesign1.WriteString(outputCertFilename, unpacked.Pubcert)
252 if certWriteErr != nil {
253 fmt.Fprintf(os.Stderr, "writing the leaf cert to %s failed: %s", outputCertFilename, certWriteErr)
254 }
255
256 var retErr error
257 if keyWriteErr != nil {
258 retErr = fmt.Errorf("key write failed: %s", retErr)
259 }
260 if certWriteErr != nil {
261 if retErr != nil {
262 return fmt.Errorf("cert write failed: %s: %s", certWriteErr, retErr)
263 }
264 return fmt.Errorf("cert write failed: %s", certWriteErr)
265 }
266 return nil
267 },
268 }
269
270 var didX509Cmd = cli.Command{
271 Name: "did-x509",
272 Usage: "",
273 Flags: []cli.Flag{
274 cli.StringFlag{
275 Name: "in",
276 Usage: "input file",
277 },
278 cli.StringFlag{
279 Name: "fingerprint-algorithm",
280 Usage: "hash algorithm for certificate fingerprints",
281 Value: "sha256",
282 },
283 cli.StringFlag{
284 Name: "chain",
285 Usage: "certificate chain to use (pem)",
286 },
287 cli.IntFlag{
288 Name: "index, i",
289 Usage: "index of the certificate fingerprint in the chain",
290 Value: 1,
291 },
292 cli.StringFlag{
293 Name: "policy",
294 Usage: "did:509 policy, can be one of [cn|eku|custom]",
295 Value: "cn",
296 },
297 },
298 Action: func(ctx *cli.Context) error {
299 chainFilename := ctx.String("chain")
300 inputFilename := ctx.String("in")
301 if len(chainFilename) > 0 && len(inputFilename) > 0 {
302 return fmt.Errorf("cannot specify chain with cose file - it comes from the chain in the file")
303 }
304 var chainPEM string
305 if len(chainFilename) > 0 {
306 chainPEMBytes, err := os.ReadFile(chainFilename)
307 if err != nil {
308 return err
309 }
310 chainPEM = string(chainPEMBytes)
311 }
312 if len(inputFilename) > 0 {
313 unpacked, err := checkCoseSign1(inputFilename, "", "", true)
314 if err != nil {
315 return err
316 }
317 chainPEM = unpacked.ChainPem
318 }
319 r, err := cosesign1.MakeDidX509(
320 ctx.String("fingerprint-algorithm"),
321 ctx.Int("index"),
322 chainPEM,
323 ctx.String("policy"),
324 ctx.Bool("verbose"),
325 )
326 if err != nil {
327 return fmt.Errorf("failed make DID: %w", err)
328 }
329 fmt.Fprint(os.Stdout, r)
330 return nil
331 },
332 }
333
334 var chainCmd = cli.Command{
335 Name: "chain",
336 Usage: "",
337 Flags: []cli.Flag{
338 cli.StringFlag{
339 Name: "in",
340 Usage: "input COSE Sign1 file",
341 Value: "input.cose",
342 },
343 cli.StringFlag{
344 Name: "out",
345 Usage: "output chain PEM text file",
346 },
347 },
348 Action: func(ctx *cli.Context) error {
349 pems, err := cosesign1.ParsePemChain(ctx.String("in"))
350 if err != nil {
351 return err
352 }
353 if len(ctx.String("out")) > 0 {
354 return cosesign1.WriteString(ctx.String("out"), strings.Join(pems, "\n"))
355 } else {
356 fmt.Fprintf(os.Stdout, "%s\n", strings.Join(pems, "\n"))
357 return nil
358 }
359 },
360 }
361
362 func main() {
363 app := cli.NewApp()
364 app.Name = "sign1util"
365 app.Commands = []cli.Command{
366 createCmd,
367 checkCmd,
368 printCmd,
369 leafCmd,
370 didX509Cmd,
371 chainCmd,
372 }
373
374 if err := app.Run(os.Args); err != nil {
375 _, _ = fmt.Fprintln(os.Stderr, err)
376 os.Exit(1)
377 }
378 }
379
View as plain text