From 43d0ec8a75f89c153080cad615c146deb51a78cb Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Fri, 10 Jul 2015 16:32:21 -0700 Subject: [PATCH] Initial ECDSA trustmanager methods Signed-off-by: Diogo Monica Splitting CryptoService into ECDSA and RSA cryptoservices Signed-off-by: Aaron Lehmann Working ECDSA support Signed-off-by: Diogo Monica --- client/cli_crypto_service.go | 135 ++++++++++++++++++++++++++++++----- client/client.go | 8 +-- trustmanager/x509utils.go | 33 ++++++++- 3 files changed, 153 insertions(+), 23 deletions(-) diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index 41342375fc..393e1eed8b 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -2,6 +2,7 @@ package client import ( "crypto" + "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha256" @@ -14,21 +15,31 @@ import ( "github.com/endophage/gotuf/data" ) -// CryptoService implements Sign and Create, holding a specific GUN and keystore to -// operate on -type CryptoService struct { +type genericCryptoService struct { gun string passphrase string keyStore *trustmanager.KeyFileStore } -// NewCryptoService returns an instance of CryptoService -func NewCryptoService(gun string, keyStore *trustmanager.KeyFileStore) *CryptoService { - return &CryptoService{gun: gun, keyStore: keyStore} +// RSACryptoService implements Sign and Create, holding a specific GUN and keystore to +// operate on +type RSACryptoService struct { + genericCryptoService +} + +// ECDSACryptoService implements Sign and Create, holding a specific GUN and keystore to +// operate on +type ECDSACryptoService struct { + genericCryptoService +} + +// NewRSACryptoService returns an instance of CryptoService +func NewRSACryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *RSACryptoService { + return &RSACryptoService{genericCryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}} } // Create is used to generate keys for targets, snapshots and timestamps -func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) { +func (ccs *RSACryptoService) Create(role string) (*data.PublicKey, error) { privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize) if err != nil { return nil, fmt.Errorf("failed to generate RSA key: %v", err) @@ -40,15 +51,9 @@ func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) { return data.PublicKeyFromPrivate(*privKey), nil } -// SetPassphrase tells the cryptoservice the passphrase. Use only if the key needs -// to be decrypted. -func (ccs *CryptoService) SetPassphrase(passphrase string) { - ccs.passphrase = passphrase -} - // Sign returns the signatures for data with the given root Key ID, falling back // if not rootKeyID is found -func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { +func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { // Create hasher and hash data hash := crypto.SHA256 hashed := sha256.Sum256(payload) @@ -62,7 +67,8 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur var err error var method string - // Read PrivateKey from file + // Read PrivateKey from file. + // This assumes root keys always have to have a non-empty passphrase. if ccs.passphrase != "" { // This is a root key privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase) @@ -79,7 +85,7 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur continue } - sig, err := sign(privKey, hash, hashed[:]) + sig, err := rsaSign(privKey, hash, hashed[:]) if err != nil { return nil, err } @@ -95,8 +101,7 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur return signatures, nil } -func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { - // TODO(diogo): Implement support for ECDSA. +func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { if strings.ToLower(privKey.Cipher()) != "rsa" { return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher()) } @@ -115,3 +120,97 @@ func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, er return sig, nil } + +// NewECDSACryptoService returns an instance of CryptoService +func NewECDSACryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *ECDSACryptoService { + return &ECDSACryptoService{genericCryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}} +} + +// Create is used to generate keys for targets, snapshots and timestamps +func (ccs *ECDSACryptoService) Create(role string) (*data.PublicKey, error) { + privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate EC key: %v", err) + } + + // Store the private key into our keystore with the name being: /GUN/ID.key + ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey) + + return data.PublicKeyFromPrivate(*privKey), nil +} + +// Sign returns the signatures for data with the given root Key ID, falling back +// if not rootKeyID is found +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 { + // ccs.gun will be empty if this is the root key + keyName := filepath.Join(ccs.gun, fingerprint) + + var privKey *data.PrivateKey + var err error + // var method string + + // Read PrivateKey from file + if ccs.passphrase != "" { + // This is a root key + privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase) + } else { + privKey, err = ccs.keyStore.GetKey(keyName) + } + 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. + continue + } + + sig, err := ecdsaSign(privKey, hash, hashed[:]) + if err != nil { + return nil, err + } + + // Append signatures to result array + signatures = append(signatures, data.Signature{ + KeyID: fingerprint, + Method: "ECDSA", + Signature: sig[:], + }) + } + + return signatures, nil +} + +func ecdsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { + if strings.ToLower(privKey.Cipher()) != "ecdsa" { + return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher()) + } + + // Create an ecdsa.PrivateKey out of the private key bytes + ecdsaPrivKey, err := x509.ParseECPrivateKey(privKey.Private()) + if err != nil { + return nil, err + } + + // Use the ECDSA key to sign the data + r, s, err := ecdsa.Sign(rand.Reader, ecdsaPrivKey, hashed[:]) + if err != nil { + return nil, err + } + + 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) + + rBuf = append(rBuf, rBytes...) + sBuf = append(sBuf, sBytes...) + + return append(rBuf, sBuf...), nil +} diff --git a/client/client.go b/client/client.go index b3122a26c2..09acfbc4c8 100644 --- a/client/client.go +++ b/client/client.go @@ -111,8 +111,8 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N return nil, err } - logrus.Debugf("creating non-root cryptoservice") - signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) + fmt.Println("creating non-root cryptoservice") + signer := signed.NewSigner(NewRSACryptoService(gun, privKeyStore, "")) nRepo := &NotaryRepository{ gun: gun, @@ -641,8 +641,8 @@ func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*Unlocke // when a root key is needed. // Passing an empty GUN because root keys aren't associated with a GUN. - ccs := NewCryptoService("", r.rootKeyStore) - ccs.SetPassphrase(passphrase) + fmt.Println("creating root cryptoservice with passphrase", passphrase) + ccs := NewRSACryptoService("", r.rootKeyStore, passphrase) signer := signed.NewSigner(ccs) return &UnlockedSigner{ diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index 3bfd817d63..01efd4162c 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -1,6 +1,8 @@ package trustmanager import ( + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -213,7 +215,7 @@ func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) { // Get a DER-encoded representation of the PublicKey rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey) if err != nil { - return nil, fmt.Errorf("failed to marshal private key: %v", err) + return nil, fmt.Errorf("failed to marshal public key: %v", err) } // Get a DER-encoded representation of the PrivateKey @@ -222,6 +224,35 @@ func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) { return data.NewPrivateKey("RSA", rsaPubBytes, rsaPrivBytes), nil } +// GenerateECDSAKey generates an ECDSA Private key and returns a TUF PrivateKey +func GenerateECDSAKey(random io.Reader) (*data.PrivateKey, error) { + // TODO(diogo): For now hardcode P256. There were timming attacks on the other + // curves, but I can't seem to find the issue. + ecdsaPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), random) + if err != nil { + return nil, err + } + + return ECDSAToPrivateKey(ecdsaPrivKey) +} + +// ECDSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type +func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey) (*data.PrivateKey, error) { + // Get a DER-encoded representation of the PublicKey + ecdsaPubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPrivKey.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to marshal public key: %v", err) + } + + // Get a DER-encoded representation of the PrivateKey + ecdsaPrivKeyBytes, err := x509.MarshalECPrivateKey(ecdsaPrivKey) + if err != nil { + return nil, fmt.Errorf("failed to marshal private key: %v", err) + } + + return data.NewPrivateKey("ECDSA", ecdsaPubBytes, ecdsaPrivKeyBytes), nil +} + // NewCertificate returns an X509 Certificate following a template, given a GUN. func NewCertificate(gun string) (*x509.Certificate, error) { notBefore := time.Now()