mirror of https://github.com/docker/docs.git
223 lines
6.3 KiB
Go
223 lines
6.3 KiB
Go
// A CryptoService client wrapper around a remote wrapper service.
|
|
|
|
package client
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
pb "github.com/docker/notary/proto"
|
|
"github.com/docker/notary/tuf/data"
|
|
"golang.org/x/net/context"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/credentials"
|
|
)
|
|
|
|
// The only thing needed from grpc.ClientConn is it's state.
|
|
type checkableConnectionState interface {
|
|
State() grpc.ConnectivityState
|
|
}
|
|
|
|
// RemotePrivateKey is a key that is on a remote service, so no private
|
|
// key bytes are available
|
|
type RemotePrivateKey struct {
|
|
data.PublicKey
|
|
sClient pb.SignerClient
|
|
}
|
|
|
|
// RemoteSigner wraps a RemotePrivateKey and implements the crypto.Signer
|
|
// interface
|
|
type RemoteSigner struct {
|
|
RemotePrivateKey
|
|
}
|
|
|
|
// Public method of a crypto.Signer needs to return a crypto public key.
|
|
func (rs *RemoteSigner) Public() crypto.PublicKey {
|
|
publicKey, err := x509.ParsePKIXPublicKey(rs.RemotePrivateKey.Public())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return publicKey
|
|
}
|
|
|
|
// NewRemotePrivateKey returns RemotePrivateKey, a data.PrivateKey that is only
|
|
// good for signing. (You can't get the private bytes out for instance.)
|
|
func NewRemotePrivateKey(pubKey data.PublicKey, sClient pb.SignerClient) *RemotePrivateKey {
|
|
return &RemotePrivateKey{
|
|
PublicKey: pubKey,
|
|
sClient: sClient,
|
|
}
|
|
}
|
|
|
|
// Private returns nil bytes
|
|
func (pk *RemotePrivateKey) Private() []byte {
|
|
return nil
|
|
}
|
|
|
|
// Sign calls a remote service to sign a message.
|
|
func (pk *RemotePrivateKey) Sign(rand io.Reader, msg []byte,
|
|
opts crypto.SignerOpts) ([]byte, error) {
|
|
|
|
keyID := pb.KeyID{ID: pk.ID()}
|
|
sr := &pb.SignatureRequest{
|
|
Content: msg,
|
|
KeyID: &keyID,
|
|
}
|
|
sig, err := pk.sClient.Sign(context.Background(), sr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sig.Content, nil
|
|
}
|
|
|
|
// SignatureAlgorithm returns the signing algorithm based on the type of
|
|
// PublicKey algorithm.
|
|
func (pk *RemotePrivateKey) SignatureAlgorithm() data.SigAlgorithm {
|
|
switch pk.PublicKey.Algorithm() {
|
|
case data.ECDSAKey, data.ECDSAx509Key:
|
|
return data.ECDSASignature
|
|
case data.RSAKey, data.RSAx509Key:
|
|
return data.RSAPSSSignature
|
|
case data.ED25519Key:
|
|
return data.EDDSASignature
|
|
default: // unknown
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// CryptoSigner returns a crypto.Signer tha wraps the RemotePrivateKey. Needed
|
|
// for implementing the interface.
|
|
func (pk *RemotePrivateKey) CryptoSigner() crypto.Signer {
|
|
return &RemoteSigner{RemotePrivateKey: *pk}
|
|
}
|
|
|
|
// NotarySigner implements a RPC based Trust service that calls the Notary-signer Service
|
|
type NotarySigner struct {
|
|
kmClient pb.KeyManagementClient
|
|
sClient pb.SignerClient
|
|
clientConn checkableConnectionState
|
|
}
|
|
|
|
// NewNotarySigner is a convinience method that returns NotarySigner
|
|
func NewNotarySigner(hostname string, port string, tlsConfig *tls.Config) *NotarySigner {
|
|
var opts []grpc.DialOption
|
|
netAddr := net.JoinHostPort(hostname, port)
|
|
creds := credentials.NewTLS(tlsConfig)
|
|
opts = append(opts, grpc.WithTransportCredentials(creds))
|
|
conn, err := grpc.Dial(netAddr, opts...)
|
|
|
|
if err != nil {
|
|
logrus.Fatal("fail to dial: ", err)
|
|
}
|
|
kmClient := pb.NewKeyManagementClient(conn)
|
|
sClient := pb.NewSignerClient(conn)
|
|
return &NotarySigner{
|
|
kmClient: kmClient,
|
|
sClient: sClient,
|
|
clientConn: conn,
|
|
}
|
|
}
|
|
|
|
// Sign signs a byte string with a number of KeyIDs
|
|
func (trust *NotarySigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature, error) {
|
|
signatures := make([]data.Signature, 0, len(keyIDs))
|
|
for _, ID := range keyIDs {
|
|
keyID := pb.KeyID{ID: ID}
|
|
sr := &pb.SignatureRequest{
|
|
Content: toSign,
|
|
KeyID: &keyID,
|
|
}
|
|
sig, err := trust.sClient.Sign(context.Background(), sr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
signatures = append(signatures, data.Signature{
|
|
KeyID: sig.KeyInfo.KeyID.ID,
|
|
Method: data.SigAlgorithm(sig.Algorithm.Algorithm),
|
|
Signature: sig.Content,
|
|
})
|
|
}
|
|
return signatures, nil
|
|
}
|
|
|
|
// Create creates a remote key and returns the PublicKey associated with the remote private key
|
|
func (trust *NotarySigner) Create(role, algorithm string) (data.PublicKey, error) {
|
|
publicKey, err := trust.kmClient.CreateKey(context.Background(), &pb.Algorithm{Algorithm: algorithm})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
public := data.NewPublicKey(publicKey.KeyInfo.Algorithm.Algorithm, publicKey.PublicKey)
|
|
return public, nil
|
|
}
|
|
|
|
// RemoveKey deletes a key
|
|
func (trust *NotarySigner) RemoveKey(keyid string) error {
|
|
_, err := trust.kmClient.DeleteKey(context.Background(), &pb.KeyID{ID: keyid})
|
|
return err
|
|
}
|
|
|
|
// GetKey retrieves a key
|
|
func (trust *NotarySigner) GetKey(keyid string) data.PublicKey {
|
|
publicKey, err := trust.kmClient.GetKeyInfo(context.Background(), &pb.KeyID{ID: keyid})
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return data.NewPublicKey(publicKey.KeyInfo.Algorithm.Algorithm, publicKey.PublicKey)
|
|
}
|
|
|
|
// GetPrivateKey errors in all cases
|
|
func (trust *NotarySigner) GetPrivateKey(keyid string) (data.PrivateKey, string, error) {
|
|
pubKey := trust.GetKey(keyid)
|
|
if pubKey == nil {
|
|
return nil, "", nil
|
|
}
|
|
return NewRemotePrivateKey(pubKey, trust.sClient), "", nil
|
|
}
|
|
|
|
// ListKeys not supported for NotarySigner
|
|
func (trust *NotarySigner) ListKeys(role string) []string {
|
|
return []string{}
|
|
}
|
|
|
|
// ListAllKeys not supported for NotarySigner
|
|
func (trust *NotarySigner) ListAllKeys() map[string]string {
|
|
return map[string]string{}
|
|
}
|
|
|
|
// CheckHealth checks the health of one of the clients, since both clients run
|
|
// from the same GRPC server.
|
|
func (trust *NotarySigner) CheckHealth(timeout time.Duration) error {
|
|
|
|
// Do not bother starting checking at all if the connection is broken.
|
|
if trust.clientConn.State() != grpc.Idle &&
|
|
trust.clientConn.State() != grpc.Ready {
|
|
return fmt.Errorf("Not currently connected to trust server.")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
status, err := trust.kmClient.CheckHealth(ctx, &pb.Void{})
|
|
defer cancel()
|
|
if err == nil && len(status.Status) > 0 {
|
|
return fmt.Errorf("Trust is not healthy")
|
|
} else if err != nil && grpc.Code(err) == codes.DeadlineExceeded {
|
|
return fmt.Errorf(
|
|
"Timed out reaching trust service after %s.", timeout)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ImportRootKey satisfies the CryptoService interface. It should not be implemented
|
|
// for a NotarySigner.
|
|
func (trust *NotarySigner) ImportRootKey(r io.Reader) error {
|
|
return errors.New("Importing a root key to NotarySigner is not supported")
|
|
}
|