Skip to content

Commit

Permalink
Add p12 parsing to pkg/cryptoinfo (#792)
Browse files Browse the repository at this point in the history
Add p12 parsing to `cryptoinfo`. Also supports passphrases.
  • Loading branch information
directionless authored Feb 14, 2022
1 parent 88882ff commit 8fd5171
Show file tree
Hide file tree
Showing 15 changed files with 284 additions and 96 deletions.
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ require (
github.com/theupdateframework/notary v0.6.1
go.etcd.io/bbolt v1.3.6
go.opencensus.io v0.22.1
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
golang.org/x/sync v0.0.0-20190423024810-112230192c58
Expand All @@ -74,8 +74,9 @@ require (
gopkg.in/fatih/pool.v2 v2.0.0 // indirect
gopkg.in/gorethink/gorethink.v3 v3.0.5 // indirect
gopkg.in/ini.v1 v1.61.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0
howett.net/plist v0.0.0-20181124034731-591f970eefbb
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
)

go 1.16
12 changes: 8 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
Expand Down Expand Up @@ -212,17 +213,16 @@ github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50=
go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
Expand Down Expand Up @@ -258,12 +258,14 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down Expand Up @@ -318,3 +320,5 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4=
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg=
42 changes: 21 additions & 21 deletions pkg/cryptoinfo/identify.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,31 @@
// with dataflatten, and may eventually it may replace pkg/keyidentifier
package cryptoinfo

import (
"bytes"
)
// identifierSignature is an internal type to denote the identification functions. It's
// used to add a small amount of clarity to the array of possible identifiers.
type identifierSignature func(data []byte, password string) (results []*KeyInfo, err error)

var (
certificateLeadingBytes = []byte{0x30} // used to detect raw DER certs
//pkcs1LeadingBytes = nil
//pkcs8LeadingBytes = nil
//pkcs12LeadingBytes = nil
)
var defaultIdentifiers = []identifierSignature{
tryP12,
tryDer,
tryPem,
}

// Identify examines a []byte and attempts to descern what
// cryptographic material is contained within.
func Identify(data []byte) ([]*KeyInfo, error) {
switch {
case bytes.HasPrefix(data, certificateLeadingBytes):
return []*KeyInfo{expandDer(data)}, nil
default:
return decodePem(data)
func Identify(data []byte, password string) ([]*KeyInfo, error) {

// Try the identifiers. Some future work might be to allow
// callers to specify identifier order, or to try to discern
// it from the file extension. But meanwhile, just try everything.
for _, fn := range defaultIdentifiers {
res, err := fn(data, password)
if err == nil {
return res, nil
}
}
}

func expandDer(data []byte) *KeyInfo {
ki := NewKeyInfo(kiDER, kiCertificate, nil)
ki.SetDataName("certificate")
ki.SetData(parseCertificate(data))
return ki
// If we can't parse anything, return nothing. It's not a fatal error, and it's
// somewhat obvious from context that nothing was parsed.
return nil, nil
}
39 changes: 33 additions & 6 deletions pkg/cryptoinfo/identify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -15,10 +16,12 @@ func TestIdentify(t *testing.T) {

var tests = []struct {
in []string
password string
expectedCount int
expectedError bool
expectedSubjects []string
}{

{
in: []string{filepath.Join("testdata", "test_crt.pem")},
expectedCount: 1,
Expand Down Expand Up @@ -49,18 +52,29 @@ func TestIdentify(t *testing.T) {
"Actalis Authentication Root CA",
},
},
{
in: []string{filepath.Join("testdata", "test-unenc.p12")},
expectedCount: 2,
expectedSubjects: []string{"www.example.com"},
},
{
in: []string{filepath.Join("testdata", "test-enc.p12")}, //password is test123
password: "test123",
expectedCount: 2,
expectedSubjects: []string{"www.example.com"},
},
}

for _, tt := range tests {
t.Run("", func(t *testing.T) {
t.Run(strings.Join(tt.in, ","), func(t *testing.T) {
in := []byte{}
for _, file := range tt.in {
fileBytes, err := os.ReadFile(file)
require.NoError(t, err, "reading input %s for setup", file)
in = bytes.Join([][]byte{in, fileBytes}, nil)
}

results, err := Identify(in)
results, err := Identify(in, tt.password)
if tt.expectedError {
require.Error(t, err)
return
Expand All @@ -69,10 +83,23 @@ func TestIdentify(t *testing.T) {
require.NoError(t, err)
assert.Len(t, results, tt.expectedCount)

for i, expectedSubject := range tt.expectedSubjects {
cert, ok := results[i].Data.(*certExtract)
require.True(t, ok, "type assert")
assert.Equal(t, expectedSubject, cert.Subject.CommonName)
// If we have expected subjects, do they match?
count := 0
for _, returnedCert := range results {
// Some things aren't certs, just skep them for the expectedSubject test
cert, ok := returnedCert.Data.(*certExtract)
if !ok {
continue
}

count++

// If we don't have any more expected subjects, just break
if count > len(tt.expectedSubjects) {
break
}

assert.Equal(t, tt.expectedSubjects[count-1], cert.Subject.CommonName)
}
})
}
Expand Down
71 changes: 59 additions & 12 deletions pkg/cryptoinfo/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,78 @@ package cryptoinfo
import "encoding/json"

type KeyInfo struct {
Type string
Encoding string
Type kiType
Encoding kiEncoding
Data interface{}
DataName string
DataName kiDataNames
Error error
Headers map[string]string
}

// Maybe make these types?
// kiDataNames is an internal type. It's used to help provide uniformity in the returned data.
type kiDataNames string

const (
kiPEM = "PEM"
kiDER = "DER"
kiCertificate = "CERTIFICATE"
kiCaCertificate kiDataNames = "certificate"
kiCertificate = "certificate"
kiKey = "key"
)

func NewKeyInfo(typ, encoding string, headers map[string]string) *KeyInfo {
// kiType is an internal type to denote what an indentified blob is. It is ultimately presented as a string
type kiType string

const (
kiCACERTIFICATE kiType = "CA-CERTIFICATE" // Not totally sure what the correct string is here
kiCERTIFICATE = "CERTIFICATE"
kiKEY = "KEY"
)

// kiType is an internal type to denote what encoding was used. It is ultimately presented as a string
type kiEncoding string

const (
kiPEM kiEncoding = "PEM"
kiDER = "DER"
kiP12 = "P12"
)

func NewKey(encoding kiEncoding) *KeyInfo {
return &KeyInfo{
DataName: kiKey,
Encoding: encoding,
Type: kiKEY,
}
}

func NewCertificate(encoding kiEncoding) *KeyInfo {
return &KeyInfo{
Type: typ,
DataName: kiCertificate,
Encoding: encoding,
Headers: headers,
Type: kiCERTIFICATE,
}
}

func NewCaCertificate(encoding kiEncoding) *KeyInfo {
return &KeyInfo{
DataName: kiCaCertificate,
Encoding: encoding,
Type: kiCACERTIFICATE,
}
}

func NewError(encoding kiEncoding, err error) *KeyInfo {
return &KeyInfo{
Encoding: encoding,
Error: err,
}
}

func (ki *KeyInfo) SetHeaders(headers map[string]string) *KeyInfo {
ki.Headers = headers
return ki
}

func (ki *KeyInfo) SetDataName(name string) *KeyInfo {
func (ki *KeyInfo) SetDataName(name kiDataNames) *KeyInfo {
ki.DataName = name
return ki
}
Expand Down Expand Up @@ -55,7 +102,7 @@ func (ki *KeyInfo) MarshalJSON() ([]byte, error) {
ret["error"] = ki.Error.Error()
} else {
if ki.DataName != "" {
ret[ki.DataName] = ki.Data
ret[string(ki.DataName)] = ki.Data
} else {
ret["error"] = "No data name"
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/cryptoinfo/parse_certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package cryptoinfo
import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"net"
"net/url"
"time"

"github.com/pkg/errors"
)

type certExtract struct {
Expand Down Expand Up @@ -43,12 +42,15 @@ type certExtract struct {
// parseCertificate parses a certificate from a stream of bytes. We use this, instead of a bare x509.ParseCertificate, to handle some
// string conversions, and bitfield enumerations.
func parseCertificate(certBytes []byte) (interface{}, error) {

c, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, errors.Wrap(err, "parsing cert")
return nil, fmt.Errorf("parsing certificate: %w", err)
}

return extractCert(c)
}

func extractCert(c *x509.Certificate) (interface{}, error) {
return &certExtract{
CRLDistributionPoints: c.CRLDistributionPoints,
DNSNames: c.DNSNames,
Expand All @@ -70,7 +72,6 @@ func parseCertificate(certBytes []byte) (interface{}, error) {
URIs: c.URIs,
Version: c.Version,
}, nil

}

var keyUsageBits = map[x509.KeyUsage]string{
Expand Down
Binary file added pkg/cryptoinfo/testdata/test-enc.p12
Binary file not shown.
Binary file added pkg/cryptoinfo/testdata/test-unenc.p12
Binary file not shown.
10 changes: 10 additions & 0 deletions pkg/cryptoinfo/try_der.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package cryptoinfo

func tryDer(data []byte, _password string) ([]*KeyInfo, error) {
cert, err := parseCertificate(data)
if err != nil {
return nil, err
}

return []*KeyInfo{NewCertificate(kiDER).SetData(cert, err)}, nil
}
28 changes: 28 additions & 0 deletions pkg/cryptoinfo/try_p12.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cryptoinfo

import (
p12 "software.sslmate.com/src/go-pkcs12"
)

func tryP12(data []byte, password string) ([]*KeyInfo, error) {
privateKey, cert, caCerts, err := p12.DecodeChain(data, password)
if err != nil {
return nil, err
}

results := []*KeyInfo{}

if privateKey != nil {
results = append(results, NewKey(kiP12))
}

if cert != nil {
results = append(results, NewCertificate(kiP12).SetData(extractCert(cert)))
}

for _, c := range caCerts {
results = append(results, NewCaCertificate(kiP12).SetData(extractCert(c)))
}

return results, nil
}
Loading

0 comments on commit 8fd5171

Please sign in to comment.