From 0ec65ac4d67bc82e0382a9478699a9d4d4affaba Mon Sep 17 00:00:00 2001 From: Taylor Date: Tue, 11 Aug 2020 14:59:33 -0600 Subject: [PATCH] Fix signing csr --- README.md | 21 ++-- cmd/certool/main.go | 245 +++++++++++++++++++++++++++++--------------- config.go | 40 +++----- 3 files changed, 188 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index 5acf0df..c63030b 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ Using `""` as the key will prompt for the password during the command, this is t "caName": "ca.journey", "caKey": "/home/user/.config/certool/ca.journey.key", "caCrt": "/home/user/.config/certool/ca.journey.crt", - "caPassword": "hunter2" } ``` @@ -82,19 +81,19 @@ Usage of certool: ## Generating a Certificate Authority ``` -$ certool -s ed25519 -gen +$ certool -gen ``` ## Create CSR ``` -$ certool -w -s ed25519 -dns test.denver.journey +$ certool -w -csr test.denver.journey ``` ## Create and sign request ``` -$ certool -w -s ed25519 -sign -dns test.denver.journey +$ certool -w -sign -f ./test.denver.journey.csr ``` ## Validate certificate @@ -102,7 +101,7 @@ $ certool -w -s ed25519 -sign -dns test.denver.journey **System roots** ``` -$ certool -verify -f ./test.denver.journey.crt +$ certool -verify -system ./test.denver.journey.crt DNSNames: [test.denver.journey] SerialNumber: 33402702424818636287940487352184976883 @@ -132,7 +131,7 @@ exit status 1 **Certool CA** ``` -$ certool -verify -custom -f ./test.denver.journey.crt +$ certool -verify ./test.denver.journey.crt DNSNames: [test.denver.journey] SerialNumber: 33402702424818636287940487352184976883 @@ -163,7 +162,7 @@ Certificate valid **System roots** ``` -$ certool -verify -remote -dns example.com +$ certool -verify example.com:443 DNSNames: [www.example.org example.com example.edu example.net example.org www.example.com www.example.edu www.example.net] SerialNumber: 21020869104500376438182461249190639870 @@ -204,7 +203,7 @@ System check valid Removing `-w` will output results to stdout. ``` -$ certool -s ed25519 -sign -dns test.denver.journey +$ certool -sign ./test.denver.journey.csr -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIJPb+/pcWV/jbB0UBk6HpDhXVjTzm0ltnbefPxQmfrqi -----END PRIVATE KEY----- @@ -235,8 +234,8 @@ MYQBgNVqhkgGEUFIkg5eVpBIHB5x38MLAw== ## Print certificate info ``` -$ certool -dns test.denver.journey -sign -w -$ certool -p -f test.denver.journey.crt +$ certool -sign test.denver.journey -w +$ certool -output ./test.denver.journey.crt DNSNames: [test.denver.journey] SerialNumber: 33402702424818636287940487352184976883 @@ -263,7 +262,7 @@ Signature: ## Print remote certificate chain ``` -$ certool -remote -dns example.com +$ certool -remote example.com:443 DNSNames: [www.example.org example.com example.edu example.net example.org www.example.com www.example.edu www.example.net] SerialNumber: 21020869104500376438182461249190639870 diff --git a/cmd/certool/main.go b/cmd/certool/main.go index b0f4ecd..0203657 100644 --- a/cmd/certool/main.go +++ b/cmd/certool/main.go @@ -6,7 +6,9 @@ import ( "encoding/pem" "flag" "fmt" + "io/ioutil" "os" + "regexp" "strings" "github.com/journeyai/certool" @@ -16,35 +18,37 @@ var ( configLocation string scheme string - dns string - - file string - port string systemCA bool - verify bool - remote bool - write bool - sign bool - csr bool - genCA bool + verify string + output string + + write bool + + csrhost string + + genCA bool + signCA bool + sign bool + file string ) func init() { flag.StringVar(&configLocation, "c", certool.DefaultConfig.Dir, "Config file location") - flag.StringVar(&scheme, "s", "ed25519", "Cryptographic scheme for certs [ed25519, rsa2048, rsa4096]") - flag.StringVar(&dns, "dns", "", "DNS for certificate") - flag.StringVar(&file, "f", "", "Certificate file") - - flag.BoolVar(&remote, "remote", false, "Check remote peer cert") - flag.StringVar(&port, "p", "443", "Port of remote server") + flag.StringVar(&scheme, "scheme", "ed25519", "Cryptographic scheme for certs [ed25519, rsa2048, rsa4096]") - flag.BoolVar(&verify, "verify", false, "Check cert validity") + flag.StringVar(&verify, "verify", "", "Check cert validity") flag.BoolVar(&systemCA, "system", false, "Validate using certool CA") + flag.StringVar(&output, "p", "", "Print certificate contents") + flag.BoolVar(&genCA, "gen", false, "Generate new CA") - flag.BoolVar(&sign, "sign", false, "sign request") - flag.BoolVar(&csr, "csr", false, "generate csr") + flag.BoolVar(&signCA, "signca", false, "Sign request as CA") + flag.BoolVar(&sign, "sign", false, "Sign request") + flag.StringVar(&file, "f", "", "File to sign") + + flag.StringVar(&csrhost, "csr", "", "Generate CSR") + flag.BoolVar(&write, "w", false, "Write values to file") } @@ -79,82 +83,127 @@ func run() error { return err } - if csr { + if csrhost != "" { _, err = createCSR() - if err != nil { - return err - } + return err } - if sign { - err = createSignedCert() - if err != nil { - return err - } + if sign || signCA { + err = signRequest() + return err } - if verify { - if remote { - chain, err := certool.GetPeerServerCertificateChain(fmt.Sprintf("%s:%s", dns, port)) + if verify != "" { + if isPath(verify) { + cert, err := certool.LoadCertificate(verify) if err != nil { - err = fmt.Errorf("Issue grabbing remote cert %w", err) + err = fmt.Errorf("Issue loading cert %w", err) return err } - err = certool.Verify(chain[1:], chain[0], dns) + fmt.Printf("%s\n\n", certool.HumanReadable(cert)) + + var dns string + if len(cert.DNSNames) > 0 { + dns = cert.DNSNames[0] // TODO pick which name to test + } else if cert.Subject.CommonName != "" { + dns = cert.Subject.CommonName + fmt.Println("WARNING: Using common name for verification, this is not recommended") + } else { + return fmt.Errorf("No dns detected in cert") + } + + if systemCA { + err := certool.VerifySystemRoots(cert, nil, cert.DNSNames[0]) + if err != nil { + err = fmt.Errorf("Certificate invalid %w", err) + return err + } + fmt.Println("Certificate valid") + return nil + } + + ca, err := certool.LoadCA() if err != nil { - err = fmt.Errorf("Certificate invalid %w", err) + err = fmt.Errorf("Issue loading certool ca %w", err) return err } - fmt.Printf("%v\n\n", certool.HumanReadable(chain[0])) - fmt.Println("Remote chain valid") - intermediate := []*x509.Certificate{} - if len(chain) > 2 { - intermediate = chain[1 : len(chain)-1] - } - err = certool.VerifySystemRoots(chain[0], intermediate, dns) + + err = certool.Verify([]*x509.Certificate{ca.Cert}, cert, dns) if err != nil { - fmt.Println(certool.HumanReadable(chain[0])) err = fmt.Errorf("Certificate invalid %w", err) return err } - fmt.Println("System check valid") - return err + fmt.Println("Certificate valid") + return nil + } - } else if file == "" { - err = fmt.Errorf("Please add -f [filename]") + re := regexp.MustCompile(`([[:alnum:]\.]+):([[:digit:]]+)`) + matches := re.FindAllStringSubmatch(verify, -1) + if err != nil || len(matches) == 0 { + return fmt.Errorf("Issue parsing remote dns to check") + } + host := matches[0][1] + port := matches[0][2] + chain, err := certool.GetPeerServerCertificateChain(fmt.Sprintf("%s:%s", host, port)) + if err != nil { + err = fmt.Errorf("Issue grabbing remote cert %w", err) return err } - cert, err := certool.LoadCertificate(file) + fmt.Println(host) + err = certool.Verify(chain[1:], chain[0], host) if err != nil { - err = fmt.Errorf("Issue loading cert %w", err) + err = fmt.Errorf("Certificate chain invalid %w", err) return err } - fmt.Printf("%s\n\n", certool.HumanReadable(cert)) - if !systemCA { - ca, err := certool.LoadCA() - if err != nil { - err = fmt.Errorf("Issue loading certool ca %w", err) - return err - } - err = certool.Verify([]*x509.Certificate{ca.Cert}, cert, dns) - if err != nil { - err = fmt.Errorf("Certificate invalid %w", err) - return err - } - } else { - err := certool.VerifySystemRoots(cert, nil, dns) + + fmt.Printf("%v\n\n", certool.HumanReadable(chain[0])) + fmt.Println("Remote chain valid") + + if systemCA { + err = certool.VerifySystemRoots(chain[0], chain[1:], host) if err != nil { err = fmt.Errorf("Certificate invalid %w", err) return err } + fmt.Println("System check valid") + return nil + } + + ca, err := certool.LoadCA() + if err != nil { + err = fmt.Errorf("Issue loading certool ca %w", err) + return err + } + + err = certool.Verify([]*x509.Certificate{ca.Cert}, chain[0], host) + if err != nil { + err = fmt.Errorf("Certificate invalid %w", err) + return err } fmt.Println("Certificate valid") return nil } - if remote { - chain, err := certool.GetPeerServerCertificateChain(fmt.Sprintf("%s:%s", dns, port)) + if output != "" { + if isPath(output) { + cert, err := certool.LoadCertificate(output) + if err != nil { + err = fmt.Errorf("Issue loading cert %w", err) + return err + } + fmt.Println(certool.HumanReadable(cert)) + return nil + } + + re := regexp.MustCompile(`([[:alnum:]\.]+):([[:digit:]]+)`) + matches := re.FindAllStringSubmatch(output, -1) + if err != nil || len(matches) == 0 { + return fmt.Errorf("Issue parsing remote dns to check") + } + host := matches[0][1] + port := matches[0][2] + chain, err := certool.GetPeerServerCertificateChain(fmt.Sprintf("%s:%s", host, port)) if err != nil { err = fmt.Errorf("Issue grabbing remote cert %w", err) return err @@ -164,18 +213,15 @@ func run() error { } return nil } - if file != "" { - cert, err := certool.LoadCertificate(file) - if err != nil { - err = fmt.Errorf("Issue loading cert %w", err) - return err - } - fmt.Println(certool.HumanReadable(cert)) - } + flag.Usage() return nil } func createCSR() (csr *x509.CertificateRequest, err error) { + if csrhost == "" { + err = fmt.Errorf("No host specified for csr") + return + } if scheme == "" { scheme, err = read("scheme", []string{"ed25519", "rsa2048", "rsa4096"}) if err != nil { @@ -190,7 +236,7 @@ func createCSR() (csr *x509.CertificateRequest, err error) { if err != nil { return } - csr, err = s.GenerateCSR(dns) + csr, err = s.GenerateCSR(csrhost) if err != nil { return } @@ -200,7 +246,7 @@ func createCSR() (csr *x509.CertificateRequest, err error) { if err != nil { return } - err = s.MarshalPrivateKeyToPem(dns) + err = s.MarshalPrivateKeyToPem(csrhost) } else { var skbytes []byte skbytes, err = x509.MarshalPKCS8PrivateKey(sk) @@ -212,26 +258,55 @@ func createCSR() (csr *x509.CertificateRequest, err error) { } return } -func createSignedCert() (err error) { - csr, err := createCSR() - if err != nil { - return + +func signRequest() (err error) { + var csr *x509.CertificateRequest + if file == "" { + csr, err = createCSR() + if err != nil { + return + } + } else { + var b []byte + b, err = ioutil.ReadFile(file) + if err != nil { + return + } + block, _ := pem.Decode(b) + csr, err = x509.ParseCertificateRequest(block.Bytes) + if err != nil { + return + } } ca, err := certool.LoadCA() if err != nil { return } + var certbytes []byte + if signCA { + certbytes, err = ca.SignCARequest(csr.Raw) + if err != nil { + return + } + } - certbytes, err := ca.SignRequest(csr.Raw) - if err != nil { - return + if sign { + certbytes, err = ca.SignRequest(csr.Raw) + if err != nil { + return + } + } + + if certbytes == nil { + return fmt.Errorf("issue signing request") } + cert, err := x509.ParseCertificate(certbytes) if err != nil { return } - err = certool.Verify([]*x509.Certificate{ca.Cert}, cert, dns) + err = certool.Verify([]*x509.Certificate{ca.Cert}, cert, csrhost) if err != nil { return } @@ -257,3 +332,9 @@ func read(name string, valid []string) (val string, err error) { val = strings.TrimSuffix(val, "\n") return } + +// isValidUrl tests a string to determine if it is a well-structured url or not. +func isPath(toTest string) bool { + _, err := os.Stat(toTest) + return err == nil +} diff --git a/config.go b/config.go index e21f342..b15cf78 100644 --- a/config.go +++ b/config.go @@ -10,19 +10,17 @@ import ( ) type Config struct { - Dir string `json:"-"` - CAName string `json:"caName"` - CAKey string `json:"caKey"` - CACrt string `json:"caCrt"` - CAPassword string `json:"caPassword"` + Dir string `json:"-"` + CAName string `json:"caName"` + CAKey string `json:"caKey"` + CACrt string `json:"caCrt"` } var DefaultConfig = Config{ - Dir: fmt.Sprintf("%s/.config/certool", os.Getenv("HOME")), - CAName: "ca.journey", - CAKey: fmt.Sprintf("%s/.config/certool/%s.key", os.Getenv("HOME"), "ca.journey"), - CACrt: fmt.Sprintf("%s/.config/certool/%s.crt", os.Getenv("HOME"), "ca.journey"), - CAPassword: "_", + Dir: fmt.Sprintf("%s/.config/certool", os.Getenv("HOME")), + CAName: "ca.journey", + CAKey: fmt.Sprintf("%s/.config/certool/%s.key", os.Getenv("HOME"), "ca.journey"), + CACrt: fmt.Sprintf("%s/.config/certool/%s.crt", os.Getenv("HOME"), "ca.journey"), } var config = DefaultConfig @@ -72,25 +70,17 @@ func LoadConfigFromFile(location string) (err error) { err = fmt.Errorf("issue marshalling config %w", err) return } - if config.CAPassword == DefaultConfig.CAPassword { - fmt.Println("WARNING") - } return } func (c *Config) GetCAPassword() string { - if c.CAPassword == "" { - fmt.Printf("CAPassword: ") - bytePassword, err := terminal.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - fmt.Println(err) - } - c.CAPassword = string(bytePassword) - return c.CAPassword + fmt.Printf("CA Password: ") + bytePassword, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + fmt.Println(err) } - if c.CAPassword == "_" { - c.CAPassword = "" - } - return c.CAPassword + fmt.Printf("\n") + return string(bytePassword) + }