Skip to content

Commit

Permalink
Merge pull request #138 from square/cs/better-errors
Browse files Browse the repository at this point in the history
Better error handling in certigo dump command
  • Loading branch information
csstaub authored May 9, 2017
2 parents d4135d2 + e0eb43a commit 7d2dcf3
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 38 deletions.
98 changes: 67 additions & 31 deletions lib/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ package lib

import (
"bufio"
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -65,90 +67,121 @@ var badSignatureAlgorithms = [...]x509.SignatureAlgorithm{
x509.ECDSAWithSHA1,
}

func errorFromErrors(errs []error) error {
if len(errs) == 0 {
return nil
}
if len(errs) == 1 {
return errs[0]
}
buffer := new(bytes.Buffer)
buffer.WriteString("encountered multiple errors:\n")
for _, err := range errs {
buffer.WriteString("* ")
buffer.WriteString(strings.TrimSuffix(err.Error(), "\n"))
buffer.WriteString("\n")
}
return errors.New(buffer.String())
}

// ReadAsPEMFromFiles will read PEM blocks from the given set of inputs. Input
// data may be in plain-text PEM files, DER-encoded certificates or PKCS7
// envelopes, or PKCS12/JCEKS keystores. All inputs will be converted to PEM
// blocks and passed to the callback.
func ReadAsPEMFromFiles(files []*os.File, format string, password func(string) string, callback func(*pem.Block)) error {
errs := []error{}
for _, file := range files {
reader := bufio.NewReaderSize(file, 4)
format, err := formatForFile(reader, file.Name(), format)
if err != nil {
return fmt.Errorf("unable to guess file type (for file %s)\n", file.Name())
return fmt.Errorf("unable to guess file type for file %s, try adding --format flag", file.Name())
}

readCertsFromStream(reader, file.Name(), format, password, callback)
err = readCertsFromStream(reader, file.Name(), format, password, callback)
if err != nil {
errs = append(errs, err)
}
}
return nil
return errorFromErrors(errs)
}

// ReadAsPEM will read PEM blocks from the given set of inputs. Input data may
// be in plain-text PEM files, DER-encoded certificates or PKCS7 envelopes, or
// PKCS12/JCEKS keystores. All inputs will be converted to PEM blocks and
// passed to the callback.
func ReadAsPEM(readers []io.Reader, format string, password func(string) string, callback func(*pem.Block)) error {
errs := []error{}
for _, r := range readers {
reader := bufio.NewReaderSize(r, 4)
format, err := formatForFile(reader, "", format)
if err != nil {
return fmt.Errorf("unable to guess format for input stream")
}

readCertsFromStream(reader, "", format, password, callback)
err = readCertsFromStream(reader, "", format, password, callback)
if err != nil {
errs = append(errs, err)
}
}
return nil
return errorFromErrors(errs)
}

// ReadAsX509FromFiles will read X.509 certificates from the given set of
// inputs. Input data may be in plain-text PEM files, DER-encoded certificates
// or PKCS7 envelopes, or PKCS12/JCEKS keystores. All inputs will be converted
// to X.509 certificates (private keys are skipped) and passed to the callback.
func ReadAsX509FromFiles(files []*os.File, format string, password func(string) string, callback func(*x509.Certificate)) error {
func ReadAsX509FromFiles(files []*os.File, format string, password func(string) string, callback func(*x509.Certificate, error)) error {
errs := []error{}
for _, file := range files {
reader := bufio.NewReaderSize(file, 4)
format, err := formatForFile(reader, file.Name(), format)
if err != nil {
return fmt.Errorf("unable to guess file type (for file %s)", file.Name())
return fmt.Errorf("unable to guess file type for file %s, try adding --format flag", file.Name())
}

readCertsFromStream(reader, file.Name(), format, password, pemToX509(callback))
err = readCertsFromStream(reader, file.Name(), format, password, pemToX509(callback))
if err != nil {
errs = append(errs, err)
}
}

return nil
return errorFromErrors(errs)
}

// ReadAsX509 will read X.509 certificates from the given set of inputs. Input
// data may be in plain-text PEM files, DER-encoded certificates or PKCS7
// envelopes, or PKCS12/JCEKS keystores. All inputs will be converted to X.509
// certificates (private keys are skipped) and passed to the callback.
func ReadAsX509(readers []io.Reader, format string, password func(string) string, callback func(*x509.Certificate)) error {
func ReadAsX509(readers []io.Reader, format string, password func(string) string, callback func(*x509.Certificate, error)) error {
errs := []error{}
for _, r := range readers {
reader := bufio.NewReaderSize(r, 4)
format, err := formatForFile(reader, "", format)
if err != nil {
return fmt.Errorf("unable to guess format for input stream")
}

readCertsFromStream(reader, "", format, password, pemToX509(callback))
err = readCertsFromStream(reader, "", format, password, pemToX509(callback))
if err != nil {
errs = append(errs, err)
}
}

return nil
return errorFromErrors(errs)
}

func pemToX509(callback func(*x509.Certificate)) func(*pem.Block) {
func pemToX509(callback func(*x509.Certificate, error)) func(*pem.Block) {
return func(block *pem.Block) {
switch block.Type {
case "CERTIFICATE":
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil {
callback(cert)
}
callback(cert, err)
case "PKCS7":
certs, err := pkcs7.ExtractCertificates(block.Bytes)
if err == nil {
for _, cert := range certs {
callback(cert)
callback(cert, nil)
}
} else {
callback(nil, err)
}
}
}
Expand All @@ -161,38 +194,39 @@ func readCertsFromStream(reader io.Reader, filename string, format string, passw
headers[fileHeader] = filename
}

switch format {
switch strings.TrimSpace(format) {
case "PEM":
scanner := pemScanner(reader)
for scanner.Scan() {
block, _ := pem.Decode(scanner.Bytes())
block.Headers = mergeHeaders(block.Headers, headers)
callback(block)
}
return nil
case "DER":
data, err := ioutil.ReadAll(reader)
if err != nil {
return fmt.Errorf("error reading input: %s\n", err)
return fmt.Errorf("unable to read input: %s\n", err)
}
x509Certs, err := x509.ParseCertificates(data)
if err == nil {
x509Certs, err0 := x509.ParseCertificates(data)
if err0 == nil {
for _, cert := range x509Certs {
callback(EncodeX509ToPEM(cert, headers))
}
return nil
}
p7bBlocks, err := pkcs7.ParseSignedData(data)
if err == nil {
p7bBlocks, err1 := pkcs7.ParseSignedData(data)
if err1 == nil {
for _, block := range p7bBlocks {
callback(pkcs7ToPem(block, headers))
}
return nil
}
return fmt.Errorf("error parsing certificates from DER data\n")
return fmt.Errorf("unable to parse certificates from DER data\n* X.509 parser gave: %s\n* PKCS7 parser gave: %s\n", err0, err1)
case "PKCS12":
data, err := ioutil.ReadAll(reader)
if err != nil {
return fmt.Errorf("error reading input: %s\n", err)
return fmt.Errorf("unable to read input: %s\n", err)
}
blocks, err := pkcs12.ToPEM(data, password(""))
if err != nil || len(blocks) == 0 {
Expand All @@ -202,10 +236,11 @@ func readCertsFromStream(reader io.Reader, filename string, format string, passw
block.Headers = mergeHeaders(block.Headers, headers)
callback(block)
}
return nil
case "JCEKS":
keyStore, err := jceks.LoadFromReader(reader, []byte(password("")))
if err != nil {
return fmt.Errorf("error parsing keystore: %s\n", err)
return fmt.Errorf("unable to parse keystore: %s\n", err)
}
for _, alias := range keyStore.ListCerts() {
cert, _ := keyStore.GetCert(alias)
Expand All @@ -214,19 +249,20 @@ func readCertsFromStream(reader io.Reader, filename string, format string, passw
for _, alias := range keyStore.ListPrivateKeys() {
key, certs, err := keyStore.GetPrivateKeyAndCerts(alias, []byte(password(alias)))
if err != nil {
return fmt.Errorf("error parsing keystore: %s\n", err)
return fmt.Errorf("unable to parse keystore: %s\n", err)
}
block, err := keyToPem(key, mergeHeaders(headers, map[string]string{nameHeader: alias}))
if err != nil {
return fmt.Errorf("error reading key: %s\n", err)
return fmt.Errorf("problem reading key: %s\n", err)
}
callback(block)
for _, cert := range certs {
callback(EncodeX509ToPEM(cert, mergeHeaders(headers, map[string]string{nameHeader: alias})))
}
}
return nil
}
return fmt.Errorf("unknown file type: %s\n", format)
return fmt.Errorf("unknown file type '%s'\n", format)
}

func mergeHeaders(baseHeaders, extraHeaders map[string]string) (headers map[string]string) {
Expand Down
27 changes: 21 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,19 @@ func main() {
}
}()

var err error
if *dumpPem {
lib.ReadAsPEMFromFiles(files, *dumpType, readPassword, func(block *pem.Block) {
err = lib.ReadAsPEMFromFiles(files, *dumpType, readPassword, func(block *pem.Block) {
block.Headers = nil
pem.Encode(os.Stdout, block)
})
} else {
lib.ReadAsX509FromFiles(files, *dumpType, readPassword, func(cert *x509.Certificate) {
result.Certificates = append(result.Certificates, cert)
err = lib.ReadAsX509FromFiles(files, *dumpType, readPassword, func(cert *x509.Certificate, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "error parsing block: %s\n", strings.TrimSuffix(err.Error(), "\n"))
} else {
result.Certificates = append(result.Certificates, cert)
}
})

if *dumpJSON {
Expand All @@ -103,10 +108,16 @@ func main() {
}
}
}
if err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", strings.TrimSuffix(err.Error(), "\n"))
os.Exit(1)
} else if len(result.Certificates) == 0 && !*dumpPem {
fmt.Fprintf(os.Stderr, "warning: no certificates found in input\n")
}

case connect.FullCommand(): // Get certs by connecting to a server
if connectStartTLS == nil && connectIdentity != nil {
fmt.Fprintln(os.Stderr, "--identity can only be used with --start-tls")
fmt.Fprintln(os.Stderr, "error: --identity can only be used with --start-tls")
os.Exit(1)
}
connState, cri, err := starttls.GetConnectionState(*connectStartTLS, *connectName, *connectTo, *connectIdentity, *connectCert, *connectKey, *connectTimeout)
Expand Down Expand Up @@ -154,8 +165,12 @@ func main() {
defer file.Close()

chain := []*x509.Certificate{}
lib.ReadAsX509FromFiles([]*os.File{file}, *verifyType, readPassword, func(cert *x509.Certificate) {
chain = append(chain, cert)
lib.ReadAsX509FromFiles([]*os.File{file}, *verifyType, readPassword, func(cert *x509.Certificate, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "error parsing block: %s\n", strings.TrimSuffix(err.Error(), "\n"))
} else {
chain = append(chain, cert)
}
})

verifyResult := verifyChain(chain, *verifyName, *verifyCaPath)
Expand Down
2 changes: 1 addition & 1 deletion pkcs7/pkcs7.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type SignedDataEnvelope struct {
// Refer to RFC 2315, Section 9.1 for definition of this type.
type SignedData struct {
Version int
DigestAlgorithms []asn1.ObjectIdentifier `asn1:"set"`
DigestAlgorithms []asn1.RawValue `asn1:"set"`
ContentInfo asn1.RawValue
Certificates []asn1.RawValue `asn1:"tag:0,optional,set"`
RevocationLists []asn1.RawValue `asn1:"tag:1,optional,set"`
Expand Down

0 comments on commit 7d2dcf3

Please sign in to comment.