-
Notifications
You must be signed in to change notification settings - Fork 92
Added certificates generate command #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "crypto" | ||
| "crypto/rand" | ||
| "crypto/rsa" | ||
| "crypto/x509" | ||
| "crypto/x509/pkix" | ||
| "encoding/pem" | ||
| "errors" | ||
| "fmt" | ||
| "log" | ||
| "math/big" | ||
| "net" | ||
| "os" | ||
| "path" | ||
| "path/filepath" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/urfave/cli/v2" | ||
| ) | ||
|
|
||
| const ( | ||
| // This needs to be configurable. Once the location of the | ||
| // configuration is finalized this parameter and file locations | ||
| // should be exposed as options on the cli with mock tests. | ||
| KEYS_BASE_DIR = ".notation" | ||
| ) | ||
|
|
||
| var certificatesCommand = &cli.Command{ | ||
| Name: "certificates", | ||
| Usage: "Commands to manage certificates", | ||
| Subcommands: []*cli.Command{ | ||
| generateCertCommand, | ||
| }, | ||
| } | ||
|
|
||
| var generateCertCommand = &cli.Command{ | ||
| Name: "generate", | ||
| Usage: "Generates a test crt and key file.", | ||
| ArgsUsage: "[host]", | ||
| Flags: []cli.Flag{ | ||
| &cli.IntFlag{ | ||
| Name: "rsaBits", | ||
| Usage: "--rsaBits 3072", | ||
| Required: false, | ||
| Value: 3072, | ||
| }, | ||
| &cli.StringFlag{ | ||
| Name: "not-after", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will it be a better customer experience to take validity period
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| Usage: "--not-after 2006-01-02T15:04:05-07:00 (default is 1 year)", | ||
| Required: false, | ||
| }, | ||
| }, | ||
|
|
||
| Action: runGenerateCert, | ||
| } | ||
|
|
||
| func ensureKeysDir() (string, error) { | ||
|
|
||
| // Expected to ensure ~/.notation/keys | ||
| dirname, err := os.UserHomeDir() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed in #76 we should support XDG_Base_Directory.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Notation CLI Alpha honors XDG directories. |
||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
|
|
||
| keysDir := filepath.Join(dirname, KEYS_BASE_DIR, "keys") | ||
| fsStat, err := os.Stat(keysDir) | ||
| if os.IsNotExist(err) { | ||
| err = os.MkdirAll(keysDir, 0700) // Only user has permissions on this directory | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| } else if fsStat.IsDir() == false { | ||
| return "", fmt.Errorf("%s should be a directory", keysDir) | ||
| } | ||
|
|
||
| return keysDir, nil | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to ensure that
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated to use IsDir. Is that good enough or do we need to ensure more checks. I believe you handled this in ORAS a well. |
||
| } | ||
|
|
||
| //ref: https://golang.org/src/crypto/tls/generate_cert.go | ||
|
|
||
| func runGenerateCert(ctx *cli.Context) error { | ||
|
|
||
| host := ctx.Args().First() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not accept an argument list instead of a signle argument, making
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ok to come back to this once we have sorted out the UX for this CLI? |
||
| if len(host) == 0 { | ||
| return errors.New("Missing required [host] parameter") | ||
| } | ||
|
|
||
| // Set certificate validity | ||
| notBefore := time.Now() | ||
| notAfter := notBefore.Add(time.Duration(365 * 24 * time.Hour)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dead code? |
||
|
|
||
| expiry := ctx.String("not-after") | ||
| var err error | ||
| if len(expiry) != 0 { | ||
| notAfter, err = time.Parse(time.RFC3339, expiry) | ||
| if err != nil { | ||
| return fmt.Errorf("Invalid --not-after %s value specified %v", expiry, err) | ||
| } | ||
| } | ||
|
|
||
| if notAfter.Before(notBefore) { | ||
| return fmt.Errorf("Invalid --not-after that is earlier than not-before [%s] specified", notBefore.Format(time.RFC3339)) | ||
| } | ||
|
|
||
| // Generate RSA Bits | ||
| rsaBits := ctx.Int("rsaBits") | ||
| var priv crypto.Signer | ||
|
|
||
| fmt.Printf("Generating RSA Key with %d bits\n", rsaBits) | ||
| priv, err = rsa.GenerateKey(rand.Reader, rsaBits) | ||
|
|
||
| if err != nil { | ||
| return fmt.Errorf("Failed to generate private key: %v", err) | ||
| } | ||
|
|
||
| // ECDSA, ED25519 and RSA subject keys should have the DigitalSignature | ||
| // KeyUsage bits set in the x509.Certificate template | ||
| keyUsage := x509.KeyUsageDigitalSignature | ||
| extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning} | ||
|
|
||
| serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) | ||
| serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) | ||
| if err != nil { | ||
| return fmt.Errorf("Failed to generate serial number: %v", err) | ||
| } | ||
|
|
||
| template := x509.Certificate{ | ||
| SerialNumber: serialNumber, | ||
| NotBefore: notBefore, | ||
| NotAfter: notAfter, | ||
| KeyUsage: keyUsage, | ||
| ExtKeyUsage: extKeyUsage, | ||
| BasicConstraintsValid: true, | ||
| } | ||
|
|
||
| hosts := strings.Split(host, ",") | ||
| for _, h := range hosts { | ||
| if ip := net.ParseIP(h); ip != nil { | ||
| template.IPAddresses = append(template.IPAddresses, ip) | ||
| } else { | ||
| template.DNSNames = append(template.DNSNames, h) | ||
| } | ||
| } | ||
|
Comment on lines
+139
to
+146
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as this is codesigning not ssl certificate, we don't need to support DNSName or ip-address.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still need them as the |
||
|
|
||
| template.Subject = pkix.Name{ | ||
| Organization: []string{hosts[0]}, | ||
| CommonName: hosts[0], | ||
|
Comment on lines
+149
to
+150
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we take
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, priv.Public(), priv) | ||
| if err != nil { | ||
| return fmt.Errorf("Failed to create certificate: %v", err) | ||
| } | ||
|
|
||
| // Write the crt public key file | ||
| keysDir, err := ensureKeysDir() | ||
| if err != nil { | ||
| return fmt.Errorf("Could not access keys directory: %v", err) | ||
| } | ||
|
Comment on lines
+159
to
+162
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be done as first thing after validating arguments. |
||
|
|
||
| crtFileName := path.Join(keysDir, hosts[0]+".crt") | ||
| crtFilePath, err := filepath.Abs(crtFileName) | ||
| if err != nil { | ||
| return fmt.Errorf("Unable to get full path of the file: %v", err) | ||
| } | ||
|
|
||
| certOut, err := os.OpenFile(crtFileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) | ||
| if err != nil { | ||
| return fmt.Errorf("Failed to open %s for writing: %v", crtFilePath, err) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/open/create file? |
||
| } | ||
|
|
||
| if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { | ||
| return fmt.Errorf("Failed to write data to %s: %v", crtFilePath, err) | ||
| } | ||
| if err := certOut.Close(); err != nil { | ||
| return fmt.Errorf("Error closing %s: %v", crtFilePath, err) | ||
| } | ||
|
|
||
| fmt.Printf("Generated certificates expiring on %s\n", notAfter.Format(time.RFC3339)) | ||
| fmt.Printf("Wrote self-signed certificate file: %s\n", crtFilePath) | ||
|
|
||
| // Write the private key file | ||
| keyFileName := path.Join(keysDir, hosts[0]+".key") | ||
| keyFilePath, err := filepath.Abs(keyFileName) | ||
| if err != nil { | ||
| return fmt.Errorf("Unable to get full path of the file: %v", err) | ||
| } | ||
|
|
||
| keyOut, err := os.OpenFile(keyFilePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) | ||
| if err != nil { | ||
| return fmt.Errorf("Failed to open key.pem for writing: %v", err) | ||
| } | ||
|
|
||
| privBytes, err := x509.MarshalPKCS8PrivateKey(priv) | ||
| if err != nil { | ||
| return fmt.Errorf("Unable to marshal private key: %v", err) | ||
| } | ||
| if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { | ||
| return fmt.Errorf("Failed to write data to key.pem: %v", err) | ||
| } | ||
| if err := keyOut.Close(); err != nil { | ||
| return fmt.Errorf("Error closing %s: %v", keyFilePath, err) | ||
| } | ||
| fmt.Printf("Wrote private key file: %s\n", keyFilePath) | ||
| return nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.