From a7caa8960cdb31a0eeb871a808a16520a95cda69 Mon Sep 17 00:00:00 2001 From: Cedric Staub Date: Tue, 21 Jun 2016 17:47:31 -0700 Subject: [PATCH] Support elliptic curve keys --- jceks/jceks.go | 43 +++++++++++++++++++++---- jceks/jceks_test.go | 2 +- main.go | 39 ++++++++++++++++++---- test-certs/Makefile | 2 +- test-certs/example-elliptic-sha1.crt | 21 ++++++------ test-certs/example-elliptic-sha1.csr | 15 +++++---- test-certs/example-elliptic-sha1.jceks | Bin 779 -> 1021 bytes test-certs/example-elliptic-sha1.key | 10 +++--- test-certs/example-elliptic-sha1.p12 | Bin 1149 -> 1391 bytes 9 files changed, 98 insertions(+), 34 deletions(-) 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 5dc743c19247d3cc6cf68cb7b4d5a0d1a9b8f26b..8c436886a4a481c1c5dc02cb690350b1aa75e68a 100644 GIT binary patch delta 699 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 T18)QfBS0_IwE2}i0s;sCa&KXl delta 1065 zcmV+^1lIfS3jGK`FoFbm0s#Xsf&@PX2`Yw2hW8Bt2LYgh1TX}G1S~Lu1Sl|q0^bG+ zDuzgg_YDCD2B3lh&@h4m%mM)bFoFWbkw6`Po>LGKl!N6J0s;sCfPw;;?=0Vg+}dr@ zzu2ui1y55lElMfRk!7MCcMo&g`Dd+wi~`N;c{=Q&M>CzSrmGKPATgyy0b5cnaHNU1 z_4%LRORmu1sV4IeYOCN7f0Pq$Uxp!%-x8wD#x_BrZ4=385nF1k9K5Tzz6r%k1xy`( z7C!{6wP*Rh)-r@m-YpWxY{(^*N}%WHfzEIy&H`YHRJOW!KP*RWB*kj7z+8TN#>r(} zgV?|{P)|kjfd0p$>h-V59rc6VkdMukwgWq~Crj_-I2SAZSj3#iphdQi2R=R^JlNJ9 zLfga(1oxKM+E;hN;|I_b!&&dz(+eGc;hv{$d92))PZml|gVvh?Shp5mQ!=2_`Y+bw zH7JvW0S=c-lYa<^6Yj+^Ixkl($?X6EC4pY@iTlsE%E(}DaIqZv;=hUn!iJZs%R)+jPX^v1 zn!ul0h;~wRpE93H+5YV)A5VU+{uw6uY7~}pnhLSc<;_XBxVqQE9JUmmy*(;Sunf~E zyKMy)qqukn;-U6@iW}$UtD9XjVo^Z+AOgYu3>A-PqzMy@Yg;cwnKxrO5rXN%U5Bkb zlKY_nmjh~*4YMnPkDsAA-jA(+KmC%mphCnj1>qsW(}maA_#*TmC56LOEUkNDg~dG( z4n6{7p&}AzHQ^^5EV7ner-OBK)0YCRi^2F>Wy>pd33Ba(Wr5{Y2@1Hs6&8NIaN< zs9#BkLmBGjn3wbHhmN>9*?tQ#x)o=mE!0iez}_@;j79Bonj@3R3sxA6{FxeRn|r_3<9x&!-4;41vcvINu<36tSkSx<3}b zagl}sF9$JTFe3&DDuzgg_YDCF6)_eB6oLu(`^?g&$G1#KAX?N1Nt;vy-ID