Initial ECDSA trustmanager methods

Signed-off-by: Diogo Monica <diogo@docker.com>

Splitting CryptoService into ECDSA and RSA cryptoservices

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Working ECDSA support

Signed-off-by: Diogo Monica <diogo@docker.com>
This commit is contained in:
Diogo Monica 2015-07-10 16:32:21 -07:00
parent 0e0605c6e2
commit 43d0ec8a75
3 changed files with 153 additions and 23 deletions

View File

@ -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
}

View File

@ -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{

View File

@ -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()