...

Source file src/github.com/Microsoft/hcsshim/internal/tools/sign1util/main.go

Documentation: github.com/Microsoft/hcsshim/internal/tools/sign1util

     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  		// fixme(maksiman): instead of just printing the error, consider returning
   246  		// it right away and skipping cert writing.
   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