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 (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
@ -14,21 +15,31 @@ import (
|
||||||
"github.com/endophage/gotuf/data"
|
"github.com/endophage/gotuf/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CryptoService implements Sign and Create, holding a specific GUN and keystore to
|
type genericCryptoService struct {
|
||||||
// operate on
|
|
||||||
type CryptoService struct {
|
|
||||||
gun string
|
gun string
|
||||||
passphrase string
|
passphrase string
|
||||||
keyStore *trustmanager.KeyFileStore
|
keyStore *trustmanager.KeyFileStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCryptoService returns an instance of CryptoService
|
// RSACryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||||
func NewCryptoService(gun string, keyStore *trustmanager.KeyFileStore) *CryptoService {
|
// operate on
|
||||||
return &CryptoService{gun: gun, keyStore: keyStore}
|
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
|
// 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)
|
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
|
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
|
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
|
// Sign returns the signatures for data with the given root Key ID, falling back
|
||||||
// if not rootKeyID is found
|
// 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
|
// Create hasher and hash data
|
||||||
hash := crypto.SHA256
|
hash := crypto.SHA256
|
||||||
hashed := sha256.Sum256(payload)
|
hashed := sha256.Sum256(payload)
|
||||||
|
@ -62,7 +67,8 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur
|
||||||
var err error
|
var err error
|
||||||
var method string
|
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 != "" {
|
if ccs.passphrase != "" {
|
||||||
// This is a root key
|
// This is a root key
|
||||||
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
|
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
|
||||||
|
@ -79,7 +85,7 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, err := sign(privKey, hash, hashed[:])
|
sig, err := rsaSign(privKey, hash, hashed[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -95,8 +101,7 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur
|
||||||
return signatures, nil
|
return signatures, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
|
func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
|
||||||
// TODO(diogo): Implement support for ECDSA.
|
|
||||||
if strings.ToLower(privKey.Cipher()) != "rsa" {
|
if strings.ToLower(privKey.Cipher()) != "rsa" {
|
||||||
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
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
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("creating non-root cryptoservice")
|
fmt.Println("creating non-root cryptoservice")
|
||||||
signer := signed.NewSigner(NewCryptoService(gun, privKeyStore))
|
signer := signed.NewSigner(NewRSACryptoService(gun, privKeyStore, ""))
|
||||||
|
|
||||||
nRepo := &NotaryRepository{
|
nRepo := &NotaryRepository{
|
||||||
gun: gun,
|
gun: gun,
|
||||||
|
@ -641,8 +641,8 @@ func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*Unlocke
|
||||||
// when a root key is needed.
|
// when a root key is needed.
|
||||||
|
|
||||||
// Passing an empty GUN because root keys aren't associated with a GUN.
|
// Passing an empty GUN because root keys aren't associated with a GUN.
|
||||||
ccs := NewCryptoService("", r.rootKeyStore)
|
fmt.Println("creating root cryptoservice with passphrase", passphrase)
|
||||||
ccs.SetPassphrase(passphrase)
|
ccs := NewRSACryptoService("", r.rootKeyStore, passphrase)
|
||||||
signer := signed.NewSigner(ccs)
|
signer := signed.NewSigner(ccs)
|
||||||
|
|
||||||
return &UnlockedSigner{
|
return &UnlockedSigner{
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package trustmanager
|
package trustmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -213,7 +215,7 @@ func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) {
|
||||||
// Get a DER-encoded representation of the PublicKey
|
// Get a DER-encoded representation of the PublicKey
|
||||||
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
|
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
|
||||||
if err != nil {
|
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
|
// 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
|
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.
|
// NewCertificate returns an X509 Certificate following a template, given a GUN.
|
||||||
func NewCertificate(gun string) (*x509.Certificate, error) {
|
func NewCertificate(gun string) (*x509.Certificate, error) {
|
||||||
notBefore := time.Now()
|
notBefore := time.Now()
|
||||||
|
|
Loading…
Reference in New Issue