pkcs12: add a DecodeAll method

Addition of a DecodeAll function as it was mentioned in #14015.

This solves a need many people seem to have, where there is no effective
way loading PKCS12 files that contain more than one certificate and one
private key.

The utility functions used by Decode are all internal, which makes
implementing this on the user-side tedious, hence the suggestion of
providing a more liberal version of the function: DecodeAll.

Fixes golang/go#14015

Change-Id: I03c541553b6cb488c2c59d39575342a43136e592
GitHub-Last-Rev: 05f6847ff80ca34c92a01a688c7b81e874af3009
GitHub-Pull-Request: golang/crypto#38
Reviewed-on: https://go-review.googlesource.com/c/105876
Reviewed-by: Adam Shannon <adamkshannon@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/pkcs12/pkcs12.go b/pkcs12/pkcs12.go
index eff9ad3..29235e3 100644
--- a/pkcs12/pkcs12.go
+++ b/pkcs12/pkcs12.go
@@ -267,6 +267,45 @@
 	return
 }
 
+// DecodeAll extracts all certificate and private keys from pfxData.
+func DecodeAll(pfxData []byte, password string) (privateKeys []interface{}, certificates []*x509.Certificate, err error) {
+	encodedPassword, err := bmpString(password)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	for _, bag := range bags {
+		switch {
+		case bag.Id.Equal(oidCertBag):
+			certsData, err := decodeCertBag(bag.Value.Bytes)
+			if err != nil {
+				return nil, nil, err
+			}
+			certs, err := x509.ParseCertificates(certsData)
+			if err != nil {
+				return nil, nil, err
+			}
+			certificates = append(certificates, certs...)
+
+		case bag.Id.Equal(oidPKCS8ShroundedKeyBag):
+			privateKey, err := decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword)
+
+			if err != nil {
+				return nil, nil, err
+			}
+
+			privateKeys = append(privateKeys, privateKey)
+		}
+	}
+
+	return
+}
+
 func getSafeContents(p12Data, password []byte) (bags []safeBag, updatedPassword []byte, err error) {
 	pfx := new(pfxPdu)
 	if err := unmarshal(p12Data, pfx); err != nil {
diff --git a/pkcs12/pkcs12_test.go b/pkcs12/pkcs12_test.go
index 14dd2a6..cabaaeb 100644
--- a/pkcs12/pkcs12_test.go
+++ b/pkcs12/pkcs12_test.go
@@ -31,6 +31,34 @@
 	}
 }
 
+func TestPfxDecodeAll(t *testing.T) {
+	for commonName, base64P12 := range testdata {
+		p12, _ := base64.StdEncoding.DecodeString(base64P12)
+
+		privs, certs, err := DecodeAll(p12, "")
+
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if len(privs) != 1 {
+			t.Errorf("expected 1 private key, but got %d", len(privs))
+		}
+
+		if len(certs) != 1 {
+			t.Errorf("expected 1 certificate, but got %d", len(certs))
+		}
+
+		if err := privs[0].(*rsa.PrivateKey).Validate(); err != nil {
+			t.Errorf("error while validating private key: %v", err)
+		}
+
+		if certs[0].Subject.CommonName != commonName {
+			t.Errorf("expected common name to be %q, but found %q", commonName, certs[0].Subject.CommonName)
+		}
+	}
+}
+
 func TestPEM(t *testing.T) {
 	for commonName, base64P12 := range testdata {
 		p12, _ := base64.StdEncoding.DecodeString(base64P12)