Merge pull request #138 from square/cs/better-errors

Better error handling in certigo dump command
This commit is contained in:
Cedric Staub
2017-05-09 15:34:29 -07:00
committed by GitHub
3 changed files with 89 additions and 38 deletions
+67 -31
View File
@@ -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"
@@ -65,21 +67,42 @@ 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
@@ -87,6 +110,7 @@ func ReadAsPEMFromFiles(files []*os.File, format string, password func(string) s
// 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)
@@ -94,34 +118,41 @@ func ReadAsPEM(readers []io.Reader, format string, password func(string) string,
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)
@@ -129,26 +160,28 @@ func ReadAsX509(readers []io.Reader, format string, password func(string) string
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)
}
}
}
@@ -161,7 +194,7 @@ 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() {
@@ -169,30 +202,31 @@ func readCertsFromStream(reader io.Reader, filename string, format string, passw
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 {
@@ -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)
@@ -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) {
+21 -6
View File
@@ -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 {
@@ -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)
@@ -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)
+1 -1
View File
@@ -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"`