From 9b5179a6ef5268571e611c97036c46ffcb5f83ab Mon Sep 17 00:00:00 2001 From: Sajay Antony Date: Fri, 13 Aug 2021 15:54:12 -0700 Subject: [PATCH 1/3] Added generate-certificates command. The command will generate an 2084 bits rsa based crt and key file. The files will be placed in $HOME/.notary/keys Signed-off-by: Sajay Antony --- cmd/nv2/gencert.go | 184 +++++++++++++++++++++++++++++++++++++++++++++ cmd/nv2/main.go | 1 + 2 files changed, 185 insertions(+) create mode 100644 cmd/nv2/gencert.go diff --git a/cmd/nv2/gencert.go b/cmd/nv2/gencert.go new file mode 100644 index 000000000..ea576d428 --- /dev/null +++ b/cmd/nv2/gencert.go @@ -0,0 +1,184 @@ +package main + +import ( + "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 ( + KEYS_BASE_DIR = ".notary" +) + +var generateCertCommand = &cli.Command{ + Name: "generate-certificates", + Usage: "Generates a test crt and key file.", + ArgsUsage: "[host]", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "rsaBits", + Usage: "rsaBits 2048", + Required: false, + Value: 2048, + }, + }, + Action: runGenerateCert, +} + +//ref: https://golang.org/src/crypto/tls/generate_cert.go +func publicKey(priv interface{}) interface{} { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + default: + return nil + } +} + +func ensureKeysDir() (string, error) { + dirname, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } + + keysDir := filepath.Join(dirname, KEYS_BASE_DIR, "keys") + _, 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 + } + } + + return keysDir, nil +} + +func runGenerateCert(ctx *cli.Context) error { + + host := ctx.Args().First() + if len(host) == 0 { + return errors.New("Missing required [host] parameter") + } + + rsaBits := ctx.Int("rsaBits") + var priv interface{} + var err error + + 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 + // Only RSA subject keys should have the KeyEncipherment KeyUsage bits set. In + // the context of TLS this KeyUsage is particular to RSA key exchange andgit st + // authentication. + if _, isRSA := priv.(*rsa.PrivateKey); isRSA { + keyUsage |= x509.KeyUsageKeyEncipherment + } + + // Set certificate validity to one year from now. + notBefore := time.Now() + notAfter := notBefore.Add(time.Duration(365 * 24 * time.Hour)) + + 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, + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + 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) + } + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), 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) + } + + 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) + } + + 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 cert.pem: %v", err) + } + fmt.Printf("Writing public key 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("Writing private key file: %s\n", keyFilePath) + return nil +} diff --git a/cmd/nv2/main.go b/cmd/nv2/main.go index 2f4a7b4af..47d2ab577 100644 --- a/cmd/nv2/main.go +++ b/cmd/nv2/main.go @@ -23,6 +23,7 @@ func main() { verifyCommand, pushCommand, pullCommand, + generateCertCommand, }, } if err := app.Run(os.Args); err != nil { From f12c23bf16e2e981d1d02bff6d9033494861d9a2 Mon Sep 17 00:00:00 2001 From: Sajay Antony Date: Sun, 15 Aug 2021 13:32:33 -0700 Subject: [PATCH 2/3] Fix PR feedback for test certificate generation - Default to 3047 for RSA Bits - Added --not-after flag for supporting expiry - Remove organization defaults - Remove TLS capabilities Signed-off-by: Sajay Antony --- cmd/nv2/gencert.go | 59 +++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/cmd/nv2/gencert.go b/cmd/nv2/gencert.go index ea576d428..7b9005242 100644 --- a/cmd/nv2/gencert.go +++ b/cmd/nv2/gencert.go @@ -21,6 +21,9 @@ import ( ) 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 = ".notary" ) @@ -31,11 +34,17 @@ var generateCertCommand = &cli.Command{ Flags: []cli.Flag{ &cli.IntFlag{ Name: "rsaBits", - Usage: "rsaBits 2048", + Usage: "--rsaBits 3072", + Required: false, + Value: 3072, + }, + &cli.StringFlag{ + Name: "not-after", + Usage: "--not-after 2006-01-02T15:04:05-07:00 (default is 1 year)", Required: false, - Value: 2048, }, }, + Action: runGenerateCert, } @@ -74,9 +83,26 @@ func runGenerateCert(ctx *cli.Context) error { return errors.New("Missing required [host] parameter") } + // Set certificate validity + notBefore := time.Now() + notAfter := time.Now().Add(time.Duration(365 * 24 * time.Hour)) + + 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 interface{} - var err error fmt.Printf("Generating RSA Key with %d bits\n", rsaBits) priv, err = rsa.GenerateKey(rand.Reader, rsaBits) @@ -88,16 +114,6 @@ func runGenerateCert(ctx *cli.Context) error { // ECDSA, ED25519 and RSA subject keys should have the DigitalSignature // KeyUsage bits set in the x509.Certificate template keyUsage := x509.KeyUsageDigitalSignature - // Only RSA subject keys should have the KeyEncipherment KeyUsage bits set. In - // the context of TLS this KeyUsage is particular to RSA key exchange andgit st - // authentication. - if _, isRSA := priv.(*rsa.PrivateKey); isRSA { - keyUsage |= x509.KeyUsageKeyEncipherment - } - - // Set certificate validity to one year from now. - notBefore := time.Now() - notAfter := notBefore.Add(time.Duration(365 * 24 * time.Hour)) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) @@ -106,15 +122,10 @@ func runGenerateCert(ctx *cli.Context) error { } template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{"Acme Co"}, - }, - NotBefore: notBefore, - NotAfter: notAfter, - + SerialNumber: serialNumber, + NotBefore: notBefore, + NotAfter: notAfter, KeyUsage: keyUsage, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } @@ -127,6 +138,10 @@ func runGenerateCert(ctx *cli.Context) error { } } + template.Subject = pkix.Name{ + Organization: []string{hosts[0]}, + } + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) if err != nil { return fmt.Errorf("Failed to create certificate: %v", err) @@ -155,6 +170,8 @@ func runGenerateCert(ctx *cli.Context) error { if err := certOut.Close(); err != nil { return fmt.Errorf("Error closing cert.pem: %v", err) } + + fmt.Printf("Generating certificates expiring on %s\n", notAfter.Format(time.RFC3339)) fmt.Printf("Writing public key file: %s\n", crtFilePath) // Write the private key file From e846844872d40727a22ed19c794952896fcff68f Mon Sep 17 00:00:00 2001 From: Sajay Antony Date: Wed, 25 Aug 2021 12:12:59 -0700 Subject: [PATCH 3/3] Moved to subcommand and other PR comments Signed-off-by: Sajay Antony --- cmd/nv2/gencert.go | 48 +++++++++++++++++++++++++++------------------- cmd/nv2/main.go | 2 +- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/cmd/nv2/gencert.go b/cmd/nv2/gencert.go index 7b9005242..e37afe894 100644 --- a/cmd/nv2/gencert.go +++ b/cmd/nv2/gencert.go @@ -1,6 +1,7 @@ package main import ( + "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -24,11 +25,19 @@ 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 = ".notary" + 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-certificates", + Name: "generate", Usage: "Generates a test crt and key file.", ArgsUsage: "[host]", Flags: []cli.Flag{ @@ -48,34 +57,30 @@ var generateCertCommand = &cli.Command{ Action: runGenerateCert, } -//ref: https://golang.org/src/crypto/tls/generate_cert.go -func publicKey(priv interface{}) interface{} { - switch k := priv.(type) { - case *rsa.PrivateKey: - return &k.PublicKey - default: - return nil - } -} - func ensureKeysDir() (string, error) { + + // Expected to ensure ~/.notation/keys dirname, err := os.UserHomeDir() if err != nil { log.Fatal(err) } keysDir := filepath.Join(dirname, KEYS_BASE_DIR, "keys") - _, err = os.Stat(keysDir) + 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 } +//ref: https://golang.org/src/crypto/tls/generate_cert.go + func runGenerateCert(ctx *cli.Context) error { host := ctx.Args().First() @@ -85,7 +90,7 @@ func runGenerateCert(ctx *cli.Context) error { // Set certificate validity notBefore := time.Now() - notAfter := time.Now().Add(time.Duration(365 * 24 * time.Hour)) + notAfter := notBefore.Add(time.Duration(365 * 24 * time.Hour)) expiry := ctx.String("not-after") var err error @@ -102,7 +107,7 @@ func runGenerateCert(ctx *cli.Context) error { // Generate RSA Bits rsaBits := ctx.Int("rsaBits") - var priv interface{} + var priv crypto.Signer fmt.Printf("Generating RSA Key with %d bits\n", rsaBits) priv, err = rsa.GenerateKey(rand.Reader, rsaBits) @@ -114,6 +119,7 @@ func runGenerateCert(ctx *cli.Context) error { // 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) @@ -126,6 +132,7 @@ func runGenerateCert(ctx *cli.Context) error { NotBefore: notBefore, NotAfter: notAfter, KeyUsage: keyUsage, + ExtKeyUsage: extKeyUsage, BasicConstraintsValid: true, } @@ -140,9 +147,10 @@ func runGenerateCert(ctx *cli.Context) error { template.Subject = pkix.Name{ Organization: []string{hosts[0]}, + CommonName: hosts[0], } - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, priv.Public(), priv) if err != nil { return fmt.Errorf("Failed to create certificate: %v", err) } @@ -168,11 +176,11 @@ func runGenerateCert(ctx *cli.Context) error { return fmt.Errorf("Failed to write data to %s: %v", crtFilePath, err) } if err := certOut.Close(); err != nil { - return fmt.Errorf("Error closing cert.pem: %v", err) + return fmt.Errorf("Error closing %s: %v", crtFilePath, err) } - fmt.Printf("Generating certificates expiring on %s\n", notAfter.Format(time.RFC3339)) - fmt.Printf("Writing public key file: %s\n", crtFilePath) + 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") @@ -196,6 +204,6 @@ func runGenerateCert(ctx *cli.Context) error { if err := keyOut.Close(); err != nil { return fmt.Errorf("Error closing %s: %v", keyFilePath, err) } - fmt.Printf("Writing private key file: %s\n", keyFilePath) + fmt.Printf("Wrote private key file: %s\n", keyFilePath) return nil } diff --git a/cmd/nv2/main.go b/cmd/nv2/main.go index 47d2ab577..12e1dde51 100644 --- a/cmd/nv2/main.go +++ b/cmd/nv2/main.go @@ -23,7 +23,7 @@ func main() { verifyCommand, pushCommand, pullCommand, - generateCertCommand, + certificatesCommand, }, } if err := app.Run(os.Args); err != nil {