mirror of https://github.com/docker/docs.git
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:
parent
0e0605c6e2
commit
43d0ec8a75
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue