diff --git a/jceks/jceks.go b/jceks/jceks.go index df91e38..becd4bd 100644 --- a/jceks/jceks.go +++ b/jceks/jceks.go @@ -23,7 +23,7 @@ package jceks import ( "bytes" - "crypto/rsa" + "crypto" "crypto/sha1" "crypto/subtle" "crypto/x509" @@ -46,6 +46,7 @@ const ( var ( oidKeyProtector = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 42, 2, 17, 1, 1} oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + oidPublicKeyEC = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} ) type encryptedPrivateKeyInfo struct { @@ -65,11 +66,19 @@ type privateKeyEntry struct { certs []*x509.Certificate } +// From https://golang.org/src/crypto/x509/sec1.go (also: see RFC 5915) +type ecPrivateKey struct { + Version int + PrivateKey []byte + NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` + PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` +} + func (e *privateKeyEntry) String() string { return fmt.Sprintf("private-key: %s", e.date) } -func (e *privateKeyEntry) Recover(password []byte) (*rsa.PrivateKey, error) { +func (e *privateKeyEntry) Recover(password []byte) (crypto.PrivateKey, error) { var eKey encryptedPrivateKeyInfo _, err := asn1.Unmarshal(e.encodedKey, &eKey) if err != nil { @@ -95,10 +104,32 @@ func (e *privateKeyEntry) Recover(password []byte) (*rsa.PrivateKey, error) { if _, err := asn1.Unmarshal(decryptedKey, &pKey); err != nil { return nil, err } - if !pKey.Algo.Algorithm.Equal(oidPublicKeyRSA) { - return nil, fmt.Errorf("unsupported private-key algorithm: %v", pKey.Algo.Algorithm) + if pKey.Algo.Algorithm.Equal(oidPublicKeyRSA) { + return x509.ParsePKCS1PrivateKey(pKey.PrivateKey) } - return x509.ParsePKCS1PrivateKey(pKey.PrivateKey) + if pKey.Algo.Algorithm.Equal(oidPublicKeyEC) { + // In a JCEKS file, the EC private key blob contains only the key itself, without the + // named curve OID. Instead, the named curve OID is in a separate field (in the algorithm + // indentifier for the keystore entry). However to parse the EC key properly we need the + // EC key blob to have the curve OID... + key := ecPrivateKey{} + oid := asn1.ObjectIdentifier{} + // Parse EC private key + _, err := asn1.Unmarshal(pKey.PrivateKey, &key) + if err != nil { + return nil, fmt.Errorf("problem parsing ec key asn.1 struct: %s", err) + } + // Parse named curve OID from algorithm identifier + _, err = asn1.Unmarshal(pKey.Algo.Parameters.FullBytes, &oid) + if err != nil { + return nil, fmt.Errorf("problem parsing ec key asn.1 struct: %s", err) + } + // Update key to add named curve info, re-marshal, and parse + key.NamedCurveOID = oid + raw, _ := asn1.Marshal(key) + return x509.ParseECPrivateKey(raw) + } + return nil, fmt.Errorf("unsupported private-key algorithm: %v", pKey.Algo.Algorithm) } type trustedCertEntry struct { @@ -338,7 +369,7 @@ func (ks *KeyStore) Parse(r io.Reader, password []byte) error { // nil if the private key does not exist or alias points to a non // private key entry. func (ks *KeyStore) GetPrivateKeyAndCerts(alias string, password []byte) ( - key *rsa.PrivateKey, certs []*x509.Certificate, err error) { + key crypto.PrivateKey, certs []*x509.Certificate, err error) { entry := ks.entries[alias] if entry == nil { diff --git a/jceks/jceks_test.go b/jceks/jceks_test.go index 4fda10c..378f063 100644 --- a/jceks/jceks_test.go +++ b/jceks/jceks_test.go @@ -175,7 +175,7 @@ func TestPrivateKey(t *testing.T) { if err != nil { t.Fatal(err) } - if !equalRSAPrivateKey(key, expected) { + if !equalRSAPrivateKey(key.(*rsa.PrivateKey), expected) { t.Fatalf("keys are not equal") } diff --git a/main.go b/main.go index d662adc..da13979 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,9 @@ package main import ( "bufio" + "crypto" + "crypto/ecdsa" + "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/binary" @@ -28,6 +31,7 @@ import ( "os" "path" "path/filepath" + "reflect" "strings" "sync" @@ -276,7 +280,6 @@ func readCertsFromFile(wg *sync.WaitGroup, reader io.Reader, filename string, fo case "PEM": scanner := pemScanner(reader) for scanner.Scan() { - fmt.Fprintf(os.Stderr, "found a block\n") block, _ := pem.Decode(scanner.Bytes()) if block.Type != "CERTIFICATE" { // Skip non-certificate PEM blocks @@ -290,7 +293,6 @@ func readCertsFromFile(wg *sync.WaitGroup, reader io.Reader, filename string, fo wg.Add(1) out <- certWithAlias{file: filename, cert: cert} } - fmt.Fprintf(os.Stderr, "done\n") case "DER": data, err := ioutil.ReadAll(reader) if err != nil { @@ -422,11 +424,7 @@ func convertToPem(wg *sync.WaitGroup, reader io.Reader, filename string, format os.Exit(1) } wg.Add(1) - out <- &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(key), - Headers: map[string]string{"alias": alias}, - } + out <- keyToPem(key, map[string]string{"alias": alias}) for _, cert := range certs { wg.Add(1) out <- &pem.Block{ @@ -441,3 +439,30 @@ func convertToPem(wg *sync.WaitGroup, reader io.Reader, filename string, format os.Exit(1) } } + +// Convert a key into one or more PEM blocks for output +func keyToPem(key crypto.PrivateKey, headers map[string]string) *pem.Block { + switch k := key.(type) { + case *rsa.PrivateKey: + return &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(k), + Headers: headers, + } + case *ecdsa.PrivateKey: + raw, err := x509.MarshalECPrivateKey(k) + if err != nil { + fmt.Fprintf(os.Stderr, "error marshaling key: %s\n", reflect.TypeOf(key)) + os.Exit(1) + } + return &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: raw, + Headers: headers, + } + default: + fmt.Fprintf(os.Stderr, "unknown key type: %s\n", reflect.TypeOf(key)) + os.Exit(1) + } + return nil +} diff --git a/test-certs/Makefile b/test-certs/Makefile index 38d941d..1281c0c 100644 --- a/test-certs/Makefile +++ b/test-certs/Makefile @@ -64,7 +64,7 @@ example-expired.crt: example-expired.csr # Example ECC key example-elliptic-sha1.key: - openssl ecparam -genkey -name prime256v1 -out $@ + openssl ecparam -genkey -name secp521r1 -out $@ example-elliptic-sha1.crt: example-elliptic-sha1.csr openssl x509 -req -in $< -signkey $(@:.crt=.key) -out $@ -days 2500 -extfile openssl.ext -extensions leaf diff --git a/test-certs/example-elliptic-sha1.crt b/test-certs/example-elliptic-sha1.crt index b4753d2..91975de 100644 --- a/test-certs/example-elliptic-sha1.crt +++ b/test-certs/example-elliptic-sha1.crt @@ -1,13 +1,16 @@ -----BEGIN CERTIFICATE----- -MIIB/zCCAaegAwIBAgIJAJo7hMg5Z4KeMAkGByqGSM49BAEwXjELMAkGA1UEBhMC +MIICiDCCAeqgAwIBAgIJAJ/xjMI6FKOAMAkGByqGSM49BAEwXjELMAkGA1UEBhMC VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQKEwdjZXJ0aWdvMRAwDgYDVQQLEwdleGFt -cGxlMR4wHAYDVQQDExVleGFtcGxlLWVsbGlwdGljLXNoYTEwHhcNMTYwNjEwMjIx -NDEyWhcNMjMwNDE1MjIxNDEyWjBeMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex +cGxlMR4wHAYDVQQDExVleGFtcGxlLWVsbGlwdGljLXNoYTEwHhcNMTYwNjIyMDAy +MzIwWhcNMjMwNDI3MDAyMzIwWjBeMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex EDAOBgNVBAoTB2NlcnRpZ28xEDAOBgNVBAsTB2V4YW1wbGUxHjAcBgNVBAMTFWV4 -YW1wbGUtZWxsaXB0aWMtc2hhMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABM8V -3EeolNVmL+Hie/hVzYWucKMFH0LpJecnBaGnFRNz3WoL4fYe6aEKx2YRdTOyFSiC -DTZYyIU38sVDF3q5Hy2jTzBNMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD -ATAsBgNVHREEJTAjhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABgglsb2NhbGhvc3Qw -CQYHKoZIzj0EAQNHADBEAiBeoQ8fJeQ8NVSvVWsA1sSjZX2JeF1AieqAKC/AlusM -uQIgM0iXusIK2OmatfJepYfZCJrXNnLi9piROfX0jvstreM= +YW1wbGUtZWxsaXB0aWMtc2hhMTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEARBY +2NPz8x6YNLA9UkPpPSoNM+JsLO6giyTuIwRy1j2PN94bmPRZcvffFMHo0jBbof/u +EEazJm0WAojmcqqED6caAMu+QcYrPdugHCHO1lv5iwZ5sJyswQ8qygaroMWBg7Qa +XjAs1p4Oncuh37CxRj70mse0YEPt+neBD5BM+8ZDchFXo08wTTAdBgNVHSUEFjAU +BggrBgEFBQcDAgYIKwYBBQUHAwEwLAYDVR0RBCUwI4cEfwAAAYcQAAAAAAAAAAAA +AAAAAAAAAYIJbG9jYWxob3N0MAkGByqGSM49BAEDgYwAMIGIAkIByNx56XB0CyNR +3vKIRdn9uoo8Q9PqBlO8ZtldeF+MqI/UvcnbrVxn7d5+lNj6K2YY2GzwV+IdxRyP +JSsHT03pww0CQgE5ssxuHZXCw1zLFXPilEHKpx1B3umRwR9q0lVc63d9zFT/Dz5L +QqF02WtegKS3Kr6M7R8fn2QvsdzOPqlcN57E2Q== -----END CERTIFICATE----- diff --git a/test-certs/example-elliptic-sha1.csr b/test-certs/example-elliptic-sha1.csr index eff4caa..20c923c 100644 --- a/test-certs/example-elliptic-sha1.csr +++ b/test-certs/example-elliptic-sha1.csr @@ -1,8 +1,11 @@ -----BEGIN CERTIFICATE REQUEST----- -MIIBGDCBwAIBADBeMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEDAOBgNVBAoT -B2NlcnRpZ28xEDAOBgNVBAsTB2V4YW1wbGUxHjAcBgNVBAMTFWV4YW1wbGUtZWxs -aXB0aWMtc2hhMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABM8V3EeolNVmL+Hi -e/hVzYWucKMFH0LpJecnBaGnFRNz3WoL4fYe6aEKx2YRdTOyFSiCDTZYyIU38sVD -F3q5Hy2gADAJBgcqhkjOPQQBA0gAMEUCIQDy8yncmMV6lW+WKrOnGC5CErrlo1TL -HaI9p2weexenLgIgLiO+4pINprkogDRDqPBow5qzTkyydxtiA+1npjjcYqY= +MIIBoTCCAQMCAQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQK +EwdjZXJ0aWdvMRAwDgYDVQQLEwdleGFtcGxlMR4wHAYDVQQDExVleGFtcGxlLWVs +bGlwdGljLXNoYTEwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAEQWNjT8/MemDSw +PVJD6T0qDTPibCzuoIsk7iMEctY9jzfeG5j0WXL33xTB6NIwW6H/7hBGsyZtFgKI +5nKqhA+nGgDLvkHGKz3boBwhztZb+YsGebCcrMEPKsoGq6DFgYO0Gl4wLNaeDp3L +od+wsUY+9JrHtGBD7fp3gQ+QTPvGQ3IRV6AAMAkGByqGSM49BAEDgYwAMIGIAkIA +57b3rNqca+NzuU1bX/vWV5bdFFN11YTmwjoWp8MHVd5AgZlVjYvVPVY531K6BS0c +HRB//5mAe99/8IfH7jsKPjcCQgFyEFjtXJmUkG3hieCT7rjYloFweBjkZbp9u5S5 +J06NKwXNJO2kyhZYWBPQ/sTx+p6cPP1gk3GxTWIGUlg/W9siZg== -----END CERTIFICATE REQUEST----- diff --git a/test-certs/example-elliptic-sha1.jceks b/test-certs/example-elliptic-sha1.jceks index 5dc743c..8c43688 100644 Binary files a/test-certs/example-elliptic-sha1.jceks and b/test-certs/example-elliptic-sha1.jceks differ diff --git a/test-certs/example-elliptic-sha1.key b/test-certs/example-elliptic-sha1.key index b04c71c..b7eebe3 100644 --- a/test-certs/example-elliptic-sha1.key +++ b/test-certs/example-elliptic-sha1.key @@ -1,8 +1,10 @@ -----BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== +BgUrgQQAIw== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- -MHcCAQEEIJ2Y2TSl0hYXHzCwS8H8EbgG7QwF8yShSsJq5UOrfcgXoAoGCCqGSM49 -AwEHoUQDQgAEzxXcR6iU1WYv4eJ7+FXNha5wowUfQukl5ycFoacVE3Pdagvh9h7p -oQrHZhF1M7IVKIINNljIhTfyxUMXerkfLQ== +MIHcAgEBBEIBc4MT0rgcDEaimeP7Bxlhp3hAI2/KySFbOu6cg40taVXhXnaDAPqV +tKj3kru+1DhvxBCrUewzKo5u9g+OC7xdu8qgBwYFK4EEACOhgYkDgYYABAEQWNjT +8/MemDSwPVJD6T0qDTPibCzuoIsk7iMEctY9jzfeG5j0WXL33xTB6NIwW6H/7hBG +syZtFgKI5nKqhA+nGgDLvkHGKz3boBwhztZb+YsGebCcrMEPKsoGq6DFgYO0Gl4w +LNaeDp3Lod+wsUY+9JrHtGBD7fp3gQ+QTPvGQ3IRVw== -----END EC PRIVATE KEY----- diff --git a/test-certs/example-elliptic-sha1.p12 b/test-certs/example-elliptic-sha1.p12 index 4d7d685..162214d 100644 Binary files a/test-certs/example-elliptic-sha1.p12 and b/test-certs/example-elliptic-sha1.p12 differ