diff --git a/README.md b/README.md index 736d849..683ff64 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Don't get the two mixed up! (furthermore, [just because you _do_ know someone doesn't make them _not_ smart](https://www.humancondition.com/asid-prophets-without-honour-in-their-own-home/)) -Although I would not want to invent my own cryptographic algorithm, +Although I would **not** want to invent my own cryptographic algorithm, I've read enough source code to know that, for standards I know well, I feel much more confident in the security, extensibility, and documentation of tooling that I've write myself. diff --git a/fixtures/pub-ec-p256.jwk.json b/fixtures/pub-ec-p256.jwk.json new file mode 100644 index 0000000..22556ab --- /dev/null +++ b/fixtures/pub-ec-p256.jwk.json @@ -0,0 +1,6 @@ +{ + "kty": "EC", + "crv": "P-256", + "x": "IT1SWLxsacPiE5Z16jkopAn8_-85rMjgyCokrnjDft4", + "y": "mP2JwOAOdMmXuwpxbKng3KZz27mz-nKWIlXJ3rzSGMo" +} diff --git a/go.mod b/go.mod index 9dc4597..312114d 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/big-squid/go-keypairs + +go 1.12 diff --git a/keypairs.go b/keypairs.go index cd39388..7194bc3 100644 --- a/keypairs.go +++ b/keypairs.go @@ -1,6 +1,7 @@ package keypairs import ( + "bytes" "crypto" "crypto/dsa" "crypto/ecdsa" @@ -16,6 +17,7 @@ import ( "io" "log" "math/big" + "strings" "time" ) @@ -26,6 +28,8 @@ var ErrParsePrivateKey = errors.New("PrivateKey bytes could not be parsed as PEM var ErrParseJWK = errors.New("JWK is missing required base64-encoded JSON fields") var ErrInvalidKeyType = errors.New("The JWK's 'kty' must be either 'RSA' or 'EC'") var ErrInvalidCurve = errors.New("The JWK's 'crv' must be either of the NIST standards 'P-256' or 'P-384'") +var ErrUnexpectedPublicKey = errors.New("PrivateKey was given where PublicKey was expected") +var ErrUnexpectedPrivateKey = errors.New("PublicKey was given where PrivateKey was expected") const ErrDevSwapPrivatePublic = "[Developer Error] You passed either crypto.PrivateKey or crypto.PublicKey where the other was expected." @@ -239,6 +243,13 @@ func ParsePrivateKey(block []byte) (PrivateKey, error) { } } + for i := range blocks { + block = blocks[i] + if _, err := parsePublicKey(block); nil == err { + return nil, ErrUnexpectedPublicKey + } + } + // If we didn't parse a key arleady, we failed return nil, ErrParsePrivateKey } @@ -331,6 +342,13 @@ func ParsePublicKey(block []byte) (PublicKey, error) { } } + for i := range blocks { + block = blocks[i] + if _, err := parsePrivateKey(block); nil == err { + return nil, ErrUnexpectedPrivateKey + } + } + // If we didn't parse a key arleady, we failed return nil, ErrParsePublicKey } @@ -399,17 +417,31 @@ func NewJWKPublicKey(m map[string]string) (PublicKey, error) { // ParseJWKPublicKey parses a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message func ParseJWKPublicKey(b []byte) (PublicKey, error) { + // RSA and EC have "d" as a private part + if bytes.Contains(b, []byte(`"d"`)) { + return nil, ErrUnexpectedPrivateKey + } return newJWKPublicKey(b) } // ParseJWKPublicKeyString calls ParseJWKPublicKey([]byte(key)) for all you lazy folk. func ParseJWKPublicKeyString(s string) (PublicKey, error) { + if strings.Contains(s, `"d"`) { + return nil, ErrUnexpectedPrivateKey + } return newJWKPublicKey(s) } // DecodeJWKPublicKey stream-decodes a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message func DecodeJWKPublicKey(r io.Reader) (PublicKey, error) { - return newJWKPublicKey(r) + m := make(map[string]string) + if err := json.NewDecoder(r).Decode(&m); nil != err { + return nil, err + } + if d := m["d"]; "" != d { + return nil, ErrUnexpectedPrivateKey + } + return newJWKPublicKey(m) } // the underpinnings of the parser as used by the typesafe wrappers @@ -419,11 +451,6 @@ func newJWKPublicKey(data interface{}) (PublicKey, error) { switch d := data.(type) { case map[string]string: m = d - case io.Reader: - m = make(map[string]string) - if err := json.NewDecoder(d).Decode(&m); nil != err { - return nil, err - } case string: if err := json.Unmarshal([]byte(d), &m); nil != err { return nil, err diff --git a/keypairs_test.go b/keypairs_test.go index 5c6136f..7b3ffdf 100644 --- a/keypairs_test.go +++ b/keypairs_test.go @@ -43,6 +43,53 @@ func TestParsePrivateKeyEC(t *testing.T) { } } +func TestParseUnexpectedPrivateKey(t *testing.T) { + keypaths := []string{ + "fixtures/privkey-ec-p256.jwk.json", + "fixtures/privkey-ec-p256.sec1.pem", + "fixtures/privkey-ec-p256.pkcs8.pem", + "fixtures/privkey-rsa-2048.jwk.json", + "fixtures/privkey-rsa-2048.pkcs1.pem", + "fixtures/privkey-rsa-2048.pkcs8.pem", + } + for i := range keypaths { + path := keypaths[i] + b, err := ioutil.ReadFile(path) + if nil != err { + t.Fatal(path, err) + } + + _, err = ParsePublicKey(b) + switch err { + case ErrUnexpectedPrivateKey: + continue + default: + t.Fatal(path, err) + } + } +} + +func TestParseUnexpectedPublicKey(t *testing.T) { + keypaths := []string{ + "fixtures/pub-ec-p256.jwk.json", + } + for i := range keypaths { + path := keypaths[i] + b, err := ioutil.ReadFile(path) + if nil != err { + t.Fatal(path, err) + } + + _, err = ParsePrivateKey(b) + switch err { + case ErrUnexpectedPublicKey: + continue + default: + t.Fatal(path, err) + } + } +} + func TestParsePrivateKeyRSA(t *testing.T) { keypaths := []string{ "fixtures/privkey-rsa-2048.jwk.json", @@ -71,7 +118,7 @@ func TestParsePrivateKeyRSA(t *testing.T) { } func TestParseCertificate(t *testing.T) { - resp, err := http.Get("http://bigsquid.auth0.com/pem") + resp, err := http.Get("https://example.auth0.com/pem") if nil != err { log.Fatal(err) }