From 39482c23979cec68acd89a336b1ea14b1368ff45 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Sat, 11 Jul 2015 19:44:14 -0700 Subject: [PATCH] Working ECDSA implementation Signed-off-by: Diogo Monica --- client/cli_crypto_service.go | 84 +++++++++++++++++++------ client/client.go | 71 +++++++++++++++------ client/client_test.go | 14 +++-- cmd/notary/tuf.go | 2 +- trustmanager/x509utils.go | 119 +++++++++++++++++++++++++---------- 5 files changed, 212 insertions(+), 78 deletions(-) diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index 393e1eed8b..84f19921eb 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -11,6 +11,7 @@ import ( "path/filepath" "strings" + "github.com/Sirupsen/logrus" "github.com/docker/notary/trustmanager" "github.com/endophage/gotuf/data" ) @@ -46,32 +47,41 @@ func (ccs *RSACryptoService) Create(role string) (*data.PublicKey, error) { } // Store the private key into our keystore with the name being: /GUN/ID.key - ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey) + err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey) + if err != nil { + return nil, fmt.Errorf("failed to add key to filestore: %v", err) + } + + logrus.Debugf("generated new RSA key for role: %s and keyID: %s", role, privKey.ID()) return data.PublicKeyFromPrivate(*privKey), nil } -// Sign returns the signatures for data with the given root Key ID, falling back -// if not rootKeyID is found +// Sign returns the signatures for the payload with a set of keyIDs. It ignores +// errors to sign and expects the called to validate if the number of returned +// signatures is adequate. func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { // Create hasher and hash data hash := crypto.SHA256 hashed := sha256.Sum256(payload) signatures := make([]data.Signature, 0, len(keyIDs)) - for _, fingerprint := range keyIDs { + for _, keyid := range keyIDs { // ccs.gun will be empty if this is the root key - keyName := filepath.Join(ccs.gun, fingerprint) + keyName := filepath.Join(ccs.gun, keyid) var privKey *data.PrivateKey var err error var method string // Read PrivateKey from file. - // This assumes root keys always have to have a non-empty passphrase. + // TODO(diogo): This assumes both that only root keys are encrypted and + // that encrypted keys are always X509 encoded if ccs.passphrase != "" { // This is a root key privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase) + // This method is added to the signature so verifiers can decode + // the X509 certificate before trying to parse the public materials method = "RSASSA-PSS-X509" } else { privKey, err = ccs.keyStore.GetKey(keyName) @@ -82,17 +92,26 @@ func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa // InitRepo gets a signer that doesn't have access to // the root keys. Continuing here is safe because we // end up not returning any signatures. + logrus.Debugf("ignoring error attempting to retrieve RSA key ID: %s, %v", privKey.ID(), err) continue } sig, err := rsaSign(privKey, hash, hashed[:]) if err != nil { - return nil, err + // If the rsaSign method got called with a non RSA private key, + // we ignore this call. + // This might happen when root is ECDSA, targets and snapshots RSA, and + // gotuf still attempts to sign root with this cryptoserver + // return nil, err + logrus.Debugf("ignoring error attempting to RSA sign with keyID: %s, %v", keyid, err) + continue } + logrus.Debugf("appending RSA signature with Key ID: %s and method %s", privKey.ID(), method) + // Append signatures to result array signatures = append(signatures, data.Signature{ - KeyID: fingerprint, + KeyID: keyid, Method: method, Signature: sig[:], }) @@ -102,7 +121,7 @@ func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa } func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { - if strings.ToLower(privKey.Cipher()) != "rsa" { + if strings.ToUpper(privKey.Cipher()) != "RSA" { return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher()) } @@ -112,7 +131,7 @@ func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, return nil, err } - // Use the RSA key to sign the data + // Use the RSA key to RSASSA-PSS sign the data sig, err := rsa.SignPSS(rand.Reader, rsaPrivKey, hash, hashed[:], &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) if err != nil { return nil, err @@ -134,51 +153,77 @@ func (ccs *ECDSACryptoService) Create(role string) (*data.PublicKey, error) { } // Store the private key into our keystore with the name being: /GUN/ID.key - ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey) + err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey) + if err != nil { + return nil, fmt.Errorf("failed to add key to filestore: %v", err) + } + + logrus.Debugf("generated new ECDSA key for role: %s with keyID: %s", role, privKey.ID()) return data.PublicKeyFromPrivate(*privKey), nil } -// Sign returns the signatures for data with the given root Key ID, falling back -// if not rootKeyID is found +// Sign returns the signatures for the payload with a set of keyIDs. It ignores +// errors to sign and expects the called to validate if the number of returned +// signatures is adequate. func (ccs *ECDSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { // Create hasher and hash data hash := crypto.SHA256 hashed := sha256.Sum256(payload) signatures := make([]data.Signature, 0, len(keyIDs)) - for _, fingerprint := range keyIDs { + for _, keyid := range keyIDs { // ccs.gun will be empty if this is the root key - keyName := filepath.Join(ccs.gun, fingerprint) + keyName := filepath.Join(ccs.gun, keyid) var privKey *data.PrivateKey var err error - // var method string + var method string // Read PrivateKey from file + // TODO(diogo): This assumes both that only root keys are encrypted and + // that encrypted keys are always X509 encoded if ccs.passphrase != "" { // This is a root key privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase) + // This method is added to the signature so verifiers can decode + // the X509 certificate before trying to parse the public materials + method = "ECDSA-X509" } else { privKey, err = ccs.keyStore.GetKey(keyName) + method = "ECDSA" } if err != nil { // Note that GetDecryptedKey always fails on InitRepo. // InitRepo gets a signer that doesn't have access to // the root keys. Continuing here is safe because we // end up not returning any signatures. + // TODO(diogo): figure out if there are any specific error types to + // check. We're swallowing all errors. + logrus.Debugf("Ignoring error attempting to retrieve ECDSA key ID: %s, %v", keyid, err) continue } + if err != nil { + fmt.Println("ERROR: ", err.Error()) + } sig, err := ecdsaSign(privKey, hash, hashed[:]) if err != nil { - return nil, err + // If the ecdsaSign method got called with a non ECDSA private key, + // we ignore this call. + // This might happen when root is RSA, targets and snapshots ECDSA, and + // gotuf still attempts to sign root with this cryptoserver + // return nil, err + logrus.Debugf("ignoring error attempting to ECDSA sign with keyID: %s, %v", privKey.ID(), err) + continue } + logrus.Debugf("appending ECDSA signature with Key ID: %s and method: %s", privKey.ID(), method) + // Append signatures to result array signatures = append(signatures, data.Signature{ - KeyID: fingerprint, - Method: "ECDSA", + KeyID: keyid, + Method: method, Signature: sig[:], }) } @@ -205,6 +250,7 @@ func ecdsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byt rBytes, sBytes := r.Bytes(), s.Bytes() octetLength := (ecdsaPrivKey.Params().BitSize + 7) >> 3 + // MUST include leading zeros in the output rBuf := make([]byte, octetLength-len(rBytes), octetLength) sBuf := make([]byte, octetLength-len(sBytes), octetLength) diff --git a/client/client.go b/client/client.go index 09acfbc4c8..8f958b0326 100644 --- a/client/client.go +++ b/client/client.go @@ -2,7 +2,10 @@ package client import ( "bytes" + "crypto" + "crypto/ecdsa" "crypto/rand" + "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" @@ -111,8 +114,8 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N return nil, err } - fmt.Println("creating non-root cryptoservice") - signer := signed.NewSigner(NewRSACryptoService(gun, privKeyStore, "")) + // TODO(diogo): This hardcodes snapshots and targets to using EC. Change it. + signer := signed.NewSigner(NewECDSACryptoService(gun, privKeyStore, "")) nRepo := &NotaryRepository{ gun: gun, @@ -139,7 +142,9 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error { return err } r.certificateStore.AddCert(rootCert) - rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert)) + logrus.Debugf("Adding certificate with fingerprint: %s to the certificate store.", trustmanager.FingerprintCert(rootCert)) + rootKey := data.NewPublicKey(uSigner.privKey.Cipher(), trustmanager.CertToPEM(rootCert)) + logrus.Debugf("Linking %s to %s.", rootKey.ID(), uSigner.ID()) err = r.rootKeyStore.Link(uSigner.ID(), rootKey.ID()) if err != nil { return err @@ -538,8 +543,6 @@ func (r *NotaryRepository) validateRoot(root *data.Signed) error { // TODO(dlaw): currently assuming only one cert contained in // public key entry. Need to fix when we want to pass in chains. k, _ := pem.Decode([]byte(rootSigned.Keys[fingerprint].Public())) - logrus.Debug("Root PEM: ", k) - logrus.Debug("Root ID: ", fingerprint) decodedCerts, err := x509.ParseCertificates(k.Bytes) if err != nil { continue @@ -619,10 +622,21 @@ func (r *NotaryRepository) ListRootKeys() []string { } // GenRootKey generates a new root key protected by a given passphrase -func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) { - privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize) +func (r *NotaryRepository) GenRootKey(algorithm, passphrase string) (string, error) { + var err error + var privKey *data.PrivateKey + + switch algorithm { + case "RSA": + privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize) + case "ECDSA": + privKey, err = trustmanager.GenerateECDSAKey(rand.Reader) + default: + return "", fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm) + + } if err != nil { - return "", fmt.Errorf("failed to convert private key: %v", err) + return "", fmt.Errorf("failed to generate private key: %v", err) } r.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) @@ -634,16 +648,20 @@ func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) { func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) { privKey, err := r.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase) if err != nil { - return nil, fmt.Errorf("could not get decrypted root key: %v", err) + return nil, fmt.Errorf("could not get decrypted root key with keyID: %s, %v", rootKeyID, err) } - // This signer will be used for all of the normal TUF operations, except for - // when a root key is needed. - + var signer *signed.Signer + cipher := privKey.Cipher() // Passing an empty GUN because root keys aren't associated with a GUN. - fmt.Println("creating root cryptoservice with passphrase", passphrase) - ccs := NewRSACryptoService("", r.rootKeyStore, passphrase) - signer := signed.NewSigner(ccs) + switch cipher { + case "RSA": + signer = signed.NewSigner(NewRSACryptoService("", r.rootKeyStore, passphrase)) + case "ECDSA": + signer = signed.NewSigner(NewECDSACryptoService("", r.rootKeyStore, passphrase)) + default: + return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher) + } return &UnlockedSigner{ privKey: privKey, @@ -700,9 +718,26 @@ func (uk *UnlockedSigner) PublicKey() *data.PublicKey { // GenerateCertificate generates an X509 Certificate from a template, given a GUN func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) { - privKey, err := x509.ParsePKCS1PrivateKey(uk.privKey.Private()) + cipher := uk.privKey.Cipher() + var publicKey crypto.PublicKey + var privateKey crypto.PrivateKey + var err error + switch cipher { + case "RSA": + var rsaPrivateKey *rsa.PrivateKey + rsaPrivateKey, err = x509.ParsePKCS1PrivateKey(uk.privKey.Private()) + privateKey = rsaPrivateKey + publicKey = rsaPrivateKey.Public() + case "ECDSA": + var ecdsaPrivateKey *ecdsa.PrivateKey + ecdsaPrivateKey, err = x509.ParseECPrivateKey(uk.privKey.Private()) + privateKey = ecdsaPrivateKey + publicKey = ecdsaPrivateKey.Public() + default: + return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher) + } if err != nil { - return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error()) + return nil, fmt.Errorf("failed to parse root key: %s (%v)", gun, err) } template, err := trustmanager.NewCertificate(gun) @@ -710,7 +745,7 @@ func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, er return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err) } - derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey) + derBytes, err := x509.CreateCertificate(rand.Reader, template, template, publicKey, privateKey) if err != nil { return nil, fmt.Errorf("failed to create the certificate for: %s (%v)", gun, err) } diff --git a/client/client_test.go b/client/client_test.go index 7183534b93..ef5ff287fb 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -18,6 +18,8 @@ import ( ) const timestampKeyJSON = `{"keytype":"RSA","keyval":{"public":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQAB","private":"MIIEpAIBAAKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQABAoIBAHar8FFxrE1gAGTeUpOF8fG8LIQMRwO4U6eVY7V9GpWiv6gOJTHXYFxU/aL0Ty3eQRxwy9tyVRo8EJz5pRex+e6ws1M+jLOviYqW4VocxQ8dZYd+zBvQfWmRfah7XXJ/HPUx2I05zrmR7VbGX6Bu4g5w3KnyIO61gfyQNKF2bm2Q3yblfupx3URvX0bl180R/+QN2Aslr4zxULFE6b+qJqBydrztq+AAP3WmskRxGa6irFnKxkspJqUpQN1mFselj6iQrzAcwkRPoCw0RwCCMq1/OOYvQtgxTJcO4zDVlbw54PvnxPZtcCWw7fO8oZ2Fvo2SDo75CDOATOGaT4Y9iqECgYEAzWZSpFbN9ZHmvq1lJQg//jFAyjsXRNn/nSvyLQILXltz6EHatImnXo3v+SivG91tfzBI1GfDvGUGaJpvKHoomB+qmhd8KIQhO5MBdAKZMf9fZqZofOPTD9xRXECCwdi+XqHBmL+l1OWz+O9Bh+Qobs2as/hQVgHaoXhQpE0NkTcCgYEA/Tjf6JBGl1+WxQDoGZDJrXoejzG9OFW19RjMdmPrg3t4fnbDtqTpZtCzXxPTCSeMrvplKbqAqZglWyq227ksKw4p7O6YfyhdtvC58oJmivlLr6sFaTsER7mDcYce8sQpqm+XQ8IPbnOk0Z1l6g56euTwTnew49uy25M6U1xL0P8CgYEAxEXv2Kw+OVhHV5PX4BBHHj6we88FiDyMfwM8cvfOJ0datekf9X7ImZkmZEAVPJpWBMD+B0J0jzU2b4SLjfFVkzBHVOH2Ob0xCH2MWPAWtekin7OKizUlPbW5ZV8b0+Kq30DQ/4a7D3rEhK8UPqeuX1tHZox1MAqrgbq3zJj4yvcCgYEAktYPKPm4pYCdmgFrlZ+bA0iEPf7Wvbsd91F5BtHsOOM5PQQ7e0bnvWIaEXEad/2CG9lBHlBy2WVLjDEZthILpa/h6e11ao8KwNGY0iKBuebT17rxOVMqqTjPGt8CuD2994IcEgOPFTpkAdUmyvG4XlkxbB8F6St17NPUB5DGuhsCgYA//Lfytk0FflXEeRQ16LT1YXgV7pcR2jsha4+4O5pxSFw/kTsOfJaYHg8StmROoyFnyE3sg76dCgLn0LENRCe5BvDhJnp5bMpQldG3XwcAxH8FGFNY4LtV/2ZKnJhxcONkfmzQPOmTyedOzrKQ+bNURsqLukCypP7/by6afBY4dA=="}}` +const timestampECDSAKeyJSON = ` +{"keytype":"ECDSA","keyval":{"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw==","private":"MHcCAQEEIDqtcdzU7H3AbIPSQaxHl9+xYECt7NpK7B1+6ep5cv9CoAoGCCqGSM49AwEHoUQDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw=="}}` func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) { mux := http.NewServeMux() @@ -26,7 +28,7 @@ func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) { mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.key", func(w http.ResponseWriter, r *http.Request) { // Also contains the private key, but for the purpose of this // test, we don't care - fmt.Fprint(w, timestampKeyJSON) + fmt.Fprint(w, timestampECDSAKeyJSON) }) ts := httptest.NewServer(mux) @@ -50,11 +52,11 @@ func TestInitRepo(t *testing.T) { repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) assert.NoError(t, err, "error creating repo: %s", err) - rootKeyID, err := repo.GenRootKey("passphrase") + rootKeyID, err := repo.GenRootKey("ECDSA", "passphrase") assert.NoError(t, err, "error generating root key: %s", err) rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase") - assert.NoError(t, err, "error retreiving root key: %s", err) + assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootSigner) assert.NoError(t, err, "error creating repository: %s", err) @@ -185,7 +187,7 @@ func TestAddListTarget(t *testing.T) { repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) assert.NoError(t, err, "error creating repository: %s", err) - rootKeyID, err := repo.GenRootKey("passphrase") + rootKeyID, err := repo.GenRootKey("ECDSA", "passphrase") assert.NoError(t, err, "error generating root key: %s", err) rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase") @@ -285,7 +287,7 @@ func TestAddListTarget(t *testing.T) { assert.NoError(t, err, "could not apply changelist") var tempKey data.PrivateKey - json.Unmarshal([]byte(timestampKeyJSON), &tempKey) + json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey) repo.privKeyStore.AddKey(filepath.Join(gun, tempKey.ID()), &tempKey) @@ -363,7 +365,7 @@ func TestValidateRootKey(t *testing.T) { repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) assert.NoError(t, err, "error creating repository: %s", err) - rootKeyID, err := repo.GenRootKey("passphrase") + rootKeyID, err := repo.GenRootKey("ECDSA", "passphrase") assert.NoError(t, err, "error generating root key: %s", err) rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase") diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index fb64eb5a5e..8c8e64495e 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -116,7 +116,7 @@ func tufInit(cmd *cobra.Command, args []string) { if err != nil { fatalf(err.Error()) } - rootKeyID, err = nRepo.GenRootKey(passphrase) + rootKeyID, err = nRepo.GenRootKey("ECDSA", passphrase) if err != nil { fatalf(err.Error()) } diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index 01efd4162c..ed1a07fedc 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -17,6 +17,7 @@ import ( "net/url" "time" + "github.com/Sirupsen/logrus" "github.com/endophage/gotuf/data" ) @@ -61,39 +62,6 @@ func CertToPEM(cert *x509.Certificate) []byte { return pemCert } -// KeyToPEM returns a PEM encoded key from a Private Key -func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) { - if privKey.Cipher() != "RSA" { - return nil, errors.New("only RSA keys are currently supported") - } - - return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privKey.Private()}), nil -} - -// EncryptPrivateKey returns an encrypted PEM key given a Privatekey -// and a passphrase -func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) { - // TODO(diogo): Currently only supports RSA Private keys - if key.Cipher() != "RSA" { - return nil, errors.New("only RSA keys are currently supported") - } - - password := []byte(passphrase) - cipherType := x509.PEMCipherAES256 - blockType := "RSA PRIVATE KEY" - - encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, - blockType, - key.Private(), - password, - cipherType) - if err != nil { - return nil, err - } - - return pem.EncodeToMemory(encryptedPEMBlock), nil -} - // LoadCertFromPEM returns the first certificate found in a bunch of bytes or error // if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85. func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) { @@ -127,8 +95,19 @@ func fingerprintCert(cert *x509.Certificate) CertID { block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw} pemdata := pem.EncodeToMemory(&block) + keyType := "" + switch cert.PublicKeyAlgorithm { + case x509.RSA: + keyType = "RSA" + case x509.ECDSA: + keyType = "ECDSA" + default: + logrus.Debug("error while fingerprinting certificate. Got Unknown key type.") + } + + logrus.Debugf("certificate fingerprint of key type: %s", keyType) // Create new TUF Key so we can compute the TUF-compliant CertID - tufKey := data.NewTUFKey("RSA", pemdata, nil) + tufKey := data.NewTUFKey(keyType, pemdata, nil) return CertID(tufKey.ID()) } @@ -195,6 +174,31 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (*data.PrivateKey, e } return tufRSAPrivateKey, nil + case "EC PRIVATE KEY": + var privKeyBytes []byte + var err error + + if x509.IsEncryptedPEMBlock(block) { + privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase)) + if err != nil { + return nil, errors.New("could not decrypt private key") + } + } else { + privKeyBytes = block.Bytes + } + + rsaPrivKey, err := x509.ParseECPrivateKey(privKeyBytes) + if err != nil { + return nil, fmt.Errorf("could not parse DER encoded private key: %v", err) + } + + tufRSAPrivateKey, err := ECDSAToPrivateKey(rsaPrivKey) + if err != nil { + return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err) + } + + return tufRSAPrivateKey, nil + default: return nil, fmt.Errorf("unsupported key type %q", block.Type) } @@ -253,6 +257,53 @@ func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey) (*data.PrivateKey, error) return data.NewPrivateKey("ECDSA", ecdsaPubBytes, ecdsaPrivKeyBytes), nil } +// KeyToPEM returns a PEM encoded key from a Private Key +func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) { + var pemType string + cipher := privKey.Cipher() + + switch cipher { + case "RSA": + pemType = "RSA PRIVATE KEY" + case "ECDSA": + pemType = "EC PRIVATE KEY" + default: + return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher) + } + + return pem.EncodeToMemory(&pem.Block{Type: pemType, Bytes: privKey.Private()}), nil +} + +// EncryptPrivateKey returns an encrypted PEM key given a Privatekey +// and a passphrase +func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) { + var blockType string + cipher := key.Cipher() + + switch cipher { + case "RSA": + blockType = "RSA PRIVATE KEY" + case "ECDSA": + blockType = "EC PRIVATE KEY" + default: + return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher) + } + + password := []byte(passphrase) + cipherType := x509.PEMCipherAES256 + + encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, + blockType, + key.Private(), + password, + cipherType) + if err != nil { + return nil, err + } + + return pem.EncodeToMemory(encryptedPEMBlock), nil +} + // NewCertificate returns an X509 Certificate following a template, given a GUN. func NewCertificate(gun string) (*x509.Certificate, error) { notBefore := time.Now()