From 9a4c2dc74408644aa1ecc4507945ee4c985753ae Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Mon, 13 Jul 2015 20:51:25 -0700 Subject: [PATCH] Initial copy of notary-signer Signed-off-by: Diogo Monica --- Makefile | 8 +- cmd/notary-signer/main.go | 178 ++++++++++++ cmd/notary-signer/main_test.go | 1 + fixtures/rufus.key | 27 ++ fixtures/rufus.pem | 30 ++ proto/rufus.pb.go | 294 ++++++++++++++++++++ proto/rufus.proto | 55 ++++ signer/Dockerfile | 27 ++ signer/api/api.go | 176 ++++++++++++ signer/api/api_test.go | 307 +++++++++++++++++++++ signer/api/ed25519_signer.go | 30 ++ signer/api/ed25519_signer_test.go | 61 ++++ signer/api/ed25519_signing_service.go | 68 +++++ signer/api/ed25519_signing_service_test.go | 98 +++++++ signer/api/rpc_api.go | 101 +++++++ signer/api/rpc_api_test.go | 136 +++++++++ signer/api/rsa_signing_service.go | 225 +++++++++++++++ signer/keys/hex_bytes.go | 36 +++ signer/keys/key_db.go | 59 ++++ signer/keys/keys.go | 33 +++ signer/rufus_trust.go | 20 +- signer/softhsm2.conf | 5 + 22 files changed, 1963 insertions(+), 12 deletions(-) create mode 100644 cmd/notary-signer/main.go create mode 100644 cmd/notary-signer/main_test.go create mode 100644 fixtures/rufus.key create mode 100644 fixtures/rufus.pem create mode 100644 proto/rufus.pb.go create mode 100644 proto/rufus.proto create mode 100644 signer/Dockerfile create mode 100644 signer/api/api.go create mode 100644 signer/api/api_test.go create mode 100644 signer/api/ed25519_signer.go create mode 100644 signer/api/ed25519_signer_test.go create mode 100644 signer/api/ed25519_signing_service.go create mode 100644 signer/api/ed25519_signing_service_test.go create mode 100644 signer/api/rpc_api.go create mode 100644 signer/api/rpc_api_test.go create mode 100644 signer/api/rsa_signing_service.go create mode 100644 signer/keys/hex_bytes.go create mode 100644 signer/keys/key_db.go create mode 100644 signer/keys/keys.go create mode 100644 signer/softhsm2.conf diff --git a/Makefile b/Makefile index 32dec4112c..019de6abb4 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,10 @@ ${PREFIX}/bin/notary: version/version.go $(shell find . -type f -name '*.go') @echo "+ $@" @godep go build -o $@ ${GO_LDFLAGS} ./cmd/notary +${PREFIX}/bin/notary-signer: version/version.go $(shell find . -type f -name '*.go') + @echo "+ $@" + @godep go build -o $@ ${GO_LDFLAGS} ./cmd/notary-signer + vet: @echo "+ $@" @test -z "$$(go tool vet -printf=false . 2>&1 | grep -v Godeps/_workspace/src/ | tee /dev/stderr)" @@ -53,9 +57,9 @@ protos: clean-protos: @rm proto/*.pb.go -binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary +binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer @echo "+ $@" clean: @echo "+ $@" - @rm -rf "${PREFIX}/bin/notary-server" "${PREFIX}/bin/notary" + @rm -rf "${PREFIX}/bin/notary-server" "${PREFIX}/bin/notary" "${PREFIX}/bin/notary-signer" diff --git a/cmd/notary-signer/main.go b/cmd/notary-signer/main.go new file mode 100644 index 0000000000..047df20fc7 --- /dev/null +++ b/cmd/notary-signer/main.go @@ -0,0 +1,178 @@ +package main + +import ( + "crypto/rand" + "crypto/tls" + _ "expvar" + "flag" + "log" + "net" + "net/http" + "os" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + _ "github.com/docker/distribution/health" + "github.com/docker/notary/signer/api" + "github.com/docker/notary/signer/keys" + "github.com/miekg/pkcs11" + + pb "github.com/docker/notary/proto" +) + +const ( + _Addr = ":4444" + _RpcAddr = ":7899" + _DebugAddr = "localhost:8080" +) + +var debug, yubikey bool +var certFile, keyFile, pkcs11Lib, pin string + +func init() { + flag.StringVar(&certFile, "cert", "", "Intermediate certificates") + flag.StringVar(&keyFile, "key", "", "Private key file") + flag.StringVar(&pkcs11Lib, "pkcs11", "", "enables HSM mode and uses the provided pkcs11 library path") + flag.StringVar(&pin, "pin", "", "the PIN to use for the HSM") + flag.BoolVar(&yubikey, "yubikey", false, "enables yubikey support mode. should be used with --pkcs11") + flag.BoolVar(&debug, "debug", false, "show the version and exit") +} + +func main() { + flag.Usage = usage + flag.Parse() + + if _DebugAddr != "" { + go debugServer(_DebugAddr) + } + + if certFile == "" || keyFile == "" { + usage() + log.Fatalf("Certificate and key are mandatory") + } + + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + PreferServerCipherSuites: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA}, + } + tlsConfig.Rand = rand.Reader + + sigServices := make(signer.SigningServiceIndex) + + if pkcs11Lib != "" { + if pin == "" { + log.Fatalf("Using PIN is mandatory with pkcs11") + } + + ctx, session := SetupHSMEnv(pkcs11Lib) + + defer cleanup(ctx, session) + + sigServices[api.RSAAlgorithm] = api.NewRSASigningService(ctx, session) + } + + sigServices[api.ED25519] = api.EdDSASigningService{KeyDB: keys.NewKeyDB()} + + //RPC server setup + kms := &api.KeyManagementServer{SigServices: sigServices} + ss := &api.SignerServer{SigServices: sigServices} + + grpcServer := grpc.NewServer() + pb.RegisterKeyManagementServer(grpcServer, kms) + pb.RegisterSignerServer(grpcServer, ss) + + lis, err := net.Listen("tcp", _RpcAddr) + if err != nil { + log.Fatalf("failed to listen %v", err) + } + creds, err := credentials.NewServerTLSFromFile(certFile, keyFile) + if err != nil { + log.Fatalf("failed to generate credentials %v", err) + } + go grpcServer.Serve(creds.NewListener(lis)) + + //HTTP server setup + server := http.Server{ + Addr: _Addr, + Handler: api.Handlers(sigServices), + TLSConfig: tlsConfig, + } + + if debug { + log.Println("[Notary-signer RPC Server] : Listening on", _RpcAddr) + log.Println("[Notary-signer Server] : Listening on", _Addr) + } + + err = server.ListenAndServeTLS(certFile, keyFile) + if err != nil { + log.Fatalf("[Notary-signer Server] : Failed to start %s", err) + } +} + +func usage() { + log.Println("usage:", os.Args[0], "") + flag.PrintDefaults() +} + +// debugServer starts the debug server with pprof, expvar among other +// endpoints. The addr should not be exposed externally. For most of these to +// work, tls cannot be enabled on the endpoint, so it is generally separate. +func debugServer(addr string) { + log.Println("[Notary-signer Debug Server] server listening on", addr) + if err := http.ListenAndServe(addr, nil); err != nil { + log.Fatalf("[Notary-signer Debug Server] error listening on debug interface: %v", err) + } +} + +// SetupHSMEnv is a method that depends on the existences +func SetupHSMEnv(libraryPath string) (*pkcs11.Ctx, pkcs11.SessionHandle) { + p := pkcs11.New(libraryPath) + + if p == nil { + log.Fatalf("Failed to init library") + } + + if err := p.Initialize(); err != nil { + log.Fatalf("Initialize error %s\n", err.Error()) + } + + slots, err := p.GetSlotList(true) + if err != nil { + log.Fatalf("Failed to list HSM slots %s", err) + } + // Check to see if we got any slots from the HSM. + if len(slots) < 1 { + log.Fatalln("No HSM Slots found") + } + + // CKF_SERIAL_SESSION: TRUE if cryptographic functions are performed in serial with the application; FALSE if the functions may be performed in parallel with the application. + // CKF_RW_SESSION: TRUE if the session is read/write; FALSE if the session is read-only + session, err := p.OpenSession(slots[0], pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) + if err != nil { + log.Fatalf("Failed to Start Session with HSM %s", err) + } + + // (diogo): Configure PIN from config file + if err = p.Login(session, pkcs11.CKU_USER, pin); err != nil { + log.Fatalf("User PIN %s\n", err.Error()) + } + + return p, session +} + +func cleanup(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) { + ctx.Destroy() + ctx.Finalize() + ctx.CloseSession(session) + ctx.Logout(session) +} diff --git a/cmd/notary-signer/main_test.go b/cmd/notary-signer/main_test.go new file mode 100644 index 0000000000..06ab7d0f9a --- /dev/null +++ b/cmd/notary-signer/main_test.go @@ -0,0 +1 @@ +package main diff --git a/fixtures/rufus.key b/fixtures/rufus.key new file mode 100644 index 0000000000..9b3a032f73 --- /dev/null +++ b/fixtures/rufus.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtjV1orRtFazywT7ueDSVB3KsgFaAOUfhtyk2upTmvF8vg+6C +47zCzgkPLiELBdMi93jP/86Osnud3zGZxLcb+rdP1SWfGzd5QeLHwlM17NTYLhFc +6nFzQozKWSZlZGQeCPxpynSSuGHS9qIH+bgqmrWQSc7nz2qsqqDxO2yX9XFJ669O +OIK3zIroCzi0ElOluerWnaW0430IWk4ykPWUyGYtwsdW+vHIqjXMgx0dPkQParpH +kDsCSbUYmnsK0PUmuuToIaTX0yR99FJZdnPOprwHWTqOe239Xbnmb6QX8qidXjVT +J+FFJwWBMHWd2Z5wIs8+7oTKC8r3tWea66IhaQIDAQABAoIBACNLkcD1wFe9i7yh +SyJf1Sp/LSkjGG2AHhoT0rUr2NJOge7FifdBfl7Y5GbkIbV8I77aWThNM9khFwDA +I/A2JCZZ0g4Q3pQwF74KhvV+luiMhl/OHziOx2vxx9SjGmrq9eJ5uqhsEmvDD1GV +K4RVB3O2jf+uI7tLB615xaDkNT9m4d/cDLW1drP3RKXO9LJt290NfMEoyP1018Gi +rEAnkyG9JHDeXGOCKCDDw0+tgNrhjiky8hmakpd+/TmCP8QEeTYC0n7W5vDn1jFb +1nLW9x9dQxWoRVW9kfQDrOeGlOvmbvVIgoU9aFmtOF0ys2l23iIS/fkFOVtSmxO1 +/d7TAY0CgYEA3PYdIau1sfLOzCRUNiW0O/EubZPWDWCcX8bua+YDtZHU04ZWaj1x +AqJPFCCuZS5PXrEvIjgoTRB47KJqw/sb5AXtZXYB3jBrhdWaxKMEmOh/abwm6zvS +jhVyp/hYs7trROZ9ZF178CndbJMMG29w4NA6hRTe9FEbF/dko2J+zE8CgYEA0xox +Y4FxucckktsvNvl/SKd0ueN8aJHxy3jtuQTtKuhmrcf9ZFLlyhsZ1osHe0F+dapL +3FqzgMWdb2mLha+CZ0wgS3wJyGps18AkA8MsOJ7ANMZy8IrTewbo1MZkxU10a5Hs +kYfUy2YGgRiumqFN+UsEMxB0FEV3V56IMCn1sMcCgYEAmHRvUSYKbUccjBmcyOdm +3dzgrdZM4FoCSssVAfloIcUG1RNhWFggOzMF4NtTuJglRy51WOTqWZG9/XdqtuHx +MnksfM7ZJxa9eVASQw4/Q8lW7/tT1wYllscRnvdpLElqD9YAOlmOA/y+vAURW3Se +q17AocXutx+m0/hrxZdV6V0CgYA+45q7DiEXO2CF745NTl7BAkIf7sltTa0+9LO9 +Xt5Y3gtY0i+G57tVTmWQKLL94TEPVMBs0QiQ4E5alpLeLL8ojuFAG5++eRYb4D3a +cRaHd6PDFSvAxVrjV9edK81xifWY5kwXvuOCBM8DPpslrdBQ6CxEGi66q0c2byAb +WSS8mQKBgDtkAAKNTFlSAXxoqqPxESFJTFVHKRZ6QVTwf2s3oNWmCAlxx5oh4W6t +Yy4dZP7dkCYp6KSwZ0lF8bUGc54hoUZFR+SewLiKgbRq+mgWmnHbMvMoUxGhaHGV +P4cieXBWZrCa4eq2UPyYv8RvXlEvKht5zvIc2wc2aupOu/0dMG52 +-----END RSA PRIVATE KEY----- diff --git a/fixtures/rufus.pem b/fixtures/rufus.pem new file mode 100644 index 0000000000..3b0eaa9587 --- /dev/null +++ b/fixtures/rufus.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQsFADBZMRMwEQYDVQQDEwpFeGFt +cGxlIENBMQswCQYDVQQGEwJVUzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEQMA4G +A1UEChMHRXhhbXBsZTELMAkGA1UECBMCQ0EwHhcNMTUwNDI4MDEyNTU0WhcNMTYw +NDI3MDEyNTU0WjBUMQ4wDAYDVQQDEwVydWZ1czELMAkGA1UEBhMCVVMxFjAUBgNV +BAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB0V4YW1wbGUxCzAJBgNVBAgTAkNB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtjV1orRtFazywT7ueDSV +B3KsgFaAOUfhtyk2upTmvF8vg+6C47zCzgkPLiELBdMi93jP/86Osnud3zGZxLcb ++rdP1SWfGzd5QeLHwlM17NTYLhFc6nFzQozKWSZlZGQeCPxpynSSuGHS9qIH+bgq +mrWQSc7nz2qsqqDxO2yX9XFJ669OOIK3zIroCzi0ElOluerWnaW0430IWk4ykPWU +yGYtwsdW+vHIqjXMgx0dPkQParpHkDsCSbUYmnsK0PUmuuToIaTX0yR99FJZdnPO +prwHWTqOe239Xbnmb6QX8qidXjVTJ+FFJwWBMHWd2Z5wIs8+7oTKC8r3tWea66Ih +aQIDAQABo4H1MIHyMIGBBgNVHSMEejB4gBR233eeV1v6NtTVfho+G5C9LAyys6Fd +pFswWTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJh +bmNpc2NvMRAwDgYDVQQKEwdFeGFtcGxlMRMwEQYDVQQDEwpFeGFtcGxlIENBggEB +MAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4G +A1UdDwEB/wQEAwIFoDAQBgNVHREECTAHggVydWZ1czAdBgNVHQ4EFgQUj0GbM9xU +MHPV4NhlA3a8EoYKtWIwDQYJKoZIhvcNAQELBQADggIBACs49/6/X1piMMuIYGhT +2TL1bjs1ew1kSoqzT36cVKv8hTpuy5O/4uYZnIXzyEZ8Fa3jtcs0AHdTxdmbwg/V +Txzhma8Zl4Asumd36fWMgFRWmM5kBRRhkkT86Zgsi9/DOaZ8QTuxDdhJ7W9VX1gp +Gbl/vvhKu5QEldT/G+ZmWITBG6vspioPQr/GmEew9bLEQ3X90Ogo+3XFxxBSvlMu +ueaUKUQOlotwZ/feSnd3N8m9CqsMahGDehPBdq9gPuFNdiwmAq0zvLA+KupPa63x +bEHjgEJSRwVUvjrST08rSoUtfEL8SzbV95VCffASOuVZM5GN1e8czwxckhKyk6dr +9knCQS5EsgtluR4jbW2o76t4zEHYxsLQ3KtQ/uAxQ9h5Q6T4z+PA2/TScMOjlm3W +fWpGaQcWmfyz/T4T2P7GWu0mNzzkVkUls8sz70a7b6xQdf1+l/TRUEO5JUvvYhcF +f4DB6hnLeaocPeVvReuGX51nSZM2/0eAwCoF8RMfsk6L+dnb8XDv4td0fY6FIVqM +0zQ/tdVPl2iEH1cRZLFzHJ+AQRxaR665tOS6mR/VFb0mNumvFWHyLYgz/GeGRcEO +8tQL96ZlYR/Iz98zJBuaJM3hfraYR5PQ6K+F8LJZn4SBdBUy3rZOY80BM2PwZsQw +yUmpY7ItUcmo5C05DCmUDgnW +-----END CERTIFICATE----- diff --git a/proto/rufus.pb.go b/proto/rufus.pb.go new file mode 100644 index 0000000000..37e188aa10 --- /dev/null +++ b/proto/rufus.pb.go @@ -0,0 +1,294 @@ +// Code generated by protoc-gen-go. +// source: proto/rufus.proto +// DO NOT EDIT! + +/* +Package proto is a generated protocol buffer package. + +It is generated from these files: + proto/rufus.proto + +It has these top-level messages: + KeyInfo + Algorithm + PublicKey + Signature + SignatureRequest + Void +*/ +package proto + +import proto1 "github.com/golang/protobuf/proto" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto1.Marshal + +// KeyInfo holds an ID that is used to reference the key and it's algorithm +type KeyInfo struct { + ID string `protobuf:"bytes,1,opt" json:"ID,omitempty"` + Algorithm *Algorithm `protobuf:"bytes,2,opt,name=algorithm" json:"algorithm,omitempty"` +} + +func (m *KeyInfo) Reset() { *m = KeyInfo{} } +func (m *KeyInfo) String() string { return proto1.CompactTextString(m) } +func (*KeyInfo) ProtoMessage() {} + +func (m *KeyInfo) GetAlgorithm() *Algorithm { + if m != nil { + return m.Algorithm + } + return nil +} + +// Type holds the type of crypto algorithm used +type Algorithm struct { + Algorithm string `protobuf:"bytes,1,opt,name=algorithm" json:"algorithm,omitempty"` +} + +func (m *Algorithm) Reset() { *m = Algorithm{} } +func (m *Algorithm) String() string { return proto1.CompactTextString(m) } +func (*Algorithm) ProtoMessage() {} + +// PublicKey has a KeyInfo that is used to reference the key, and opaque bytes of a publicKey +type PublicKey struct { + KeyInfo *KeyInfo `protobuf:"bytes,1,opt,name=keyInfo" json:"keyInfo,omitempty"` + PublicKey []byte `protobuf:"bytes,2,opt,name=publicKey,proto3" json:"publicKey,omitempty"` +} + +func (m *PublicKey) Reset() { *m = PublicKey{} } +func (m *PublicKey) String() string { return proto1.CompactTextString(m) } +func (*PublicKey) ProtoMessage() {} + +func (m *PublicKey) GetKeyInfo() *KeyInfo { + if m != nil { + return m.KeyInfo + } + return nil +} + +// Signature specifies a KeyInfo that was used for signing and signed content +type Signature struct { + KeyInfo *KeyInfo `protobuf:"bytes,1,opt,name=keyInfo" json:"keyInfo,omitempty"` + Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` +} + +func (m *Signature) Reset() { *m = Signature{} } +func (m *Signature) String() string { return proto1.CompactTextString(m) } +func (*Signature) ProtoMessage() {} + +func (m *Signature) GetKeyInfo() *KeyInfo { + if m != nil { + return m.KeyInfo + } + return nil +} + +// SignatureRequests specifies a KeyInfo, and content to be signed +type SignatureRequest struct { + KeyInfo *KeyInfo `protobuf:"bytes,1,opt,name=keyInfo" json:"keyInfo,omitempty"` + Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` +} + +func (m *SignatureRequest) Reset() { *m = SignatureRequest{} } +func (m *SignatureRequest) String() string { return proto1.CompactTextString(m) } +func (*SignatureRequest) ProtoMessage() {} + +func (m *SignatureRequest) GetKeyInfo() *KeyInfo { + if m != nil { + return m.KeyInfo + } + return nil +} + +// Void represents an empty message type +type Void struct { +} + +func (m *Void) Reset() { *m = Void{} } +func (m *Void) String() string { return proto1.CompactTextString(m) } +func (*Void) ProtoMessage() {} + +// Client API for KeyManagement service + +type KeyManagementClient interface { + // CreateKey creates as asymmetric key pair and returns the PublicKey + CreateKey(ctx context.Context, in *Algorithm, opts ...grpc.CallOption) (*PublicKey, error) + // DeleteKey deletes the key associated with a KeyInfo + DeleteKey(ctx context.Context, in *KeyInfo, opts ...grpc.CallOption) (*Void, error) + // GetKeyInfo returns the PublicKey associated with a KeyInfo + GetKeyInfo(ctx context.Context, in *KeyInfo, opts ...grpc.CallOption) (*PublicKey, error) +} + +type keyManagementClient struct { + cc *grpc.ClientConn +} + +func NewKeyManagementClient(cc *grpc.ClientConn) KeyManagementClient { + return &keyManagementClient{cc} +} + +func (c *keyManagementClient) CreateKey(ctx context.Context, in *Algorithm, opts ...grpc.CallOption) (*PublicKey, error) { + out := new(PublicKey) + err := grpc.Invoke(ctx, "/proto.KeyManagement/CreateKey", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *keyManagementClient) DeleteKey(ctx context.Context, in *KeyInfo, opts ...grpc.CallOption) (*Void, error) { + out := new(Void) + err := grpc.Invoke(ctx, "/proto.KeyManagement/DeleteKey", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *keyManagementClient) GetKeyInfo(ctx context.Context, in *KeyInfo, opts ...grpc.CallOption) (*PublicKey, error) { + out := new(PublicKey) + err := grpc.Invoke(ctx, "/proto.KeyManagement/GetKeyInfo", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for KeyManagement service + +type KeyManagementServer interface { + // CreateKey creates as asymmetric key pair and returns the PublicKey + CreateKey(context.Context, *Algorithm) (*PublicKey, error) + // DeleteKey deletes the key associated with a KeyInfo + DeleteKey(context.Context, *KeyInfo) (*Void, error) + // GetKeyInfo returns the PublicKey associated with a KeyInfo + GetKeyInfo(context.Context, *KeyInfo) (*PublicKey, error) +} + +func RegisterKeyManagementServer(s *grpc.Server, srv KeyManagementServer) { + s.RegisterService(&_KeyManagement_serviceDesc, srv) +} + +func _KeyManagement_CreateKey_Handler(srv interface{}, ctx context.Context, codec grpc.Codec, buf []byte) (interface{}, error) { + in := new(Algorithm) + if err := codec.Unmarshal(buf, in); err != nil { + return nil, err + } + out, err := srv.(KeyManagementServer).CreateKey(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _KeyManagement_DeleteKey_Handler(srv interface{}, ctx context.Context, codec grpc.Codec, buf []byte) (interface{}, error) { + in := new(KeyInfo) + if err := codec.Unmarshal(buf, in); err != nil { + return nil, err + } + out, err := srv.(KeyManagementServer).DeleteKey(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _KeyManagement_GetKeyInfo_Handler(srv interface{}, ctx context.Context, codec grpc.Codec, buf []byte) (interface{}, error) { + in := new(KeyInfo) + if err := codec.Unmarshal(buf, in); err != nil { + return nil, err + } + out, err := srv.(KeyManagementServer).GetKeyInfo(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _KeyManagement_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.KeyManagement", + HandlerType: (*KeyManagementServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateKey", + Handler: _KeyManagement_CreateKey_Handler, + }, + { + MethodName: "DeleteKey", + Handler: _KeyManagement_DeleteKey_Handler, + }, + { + MethodName: "GetKeyInfo", + Handler: _KeyManagement_GetKeyInfo_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +// Client API for Signer service + +type SignerClient interface { + // Sign calculates a cryptographic signature using the Key associated with a KeyInfo and returns the signature + Sign(ctx context.Context, in *SignatureRequest, opts ...grpc.CallOption) (*Signature, error) +} + +type signerClient struct { + cc *grpc.ClientConn +} + +func NewSignerClient(cc *grpc.ClientConn) SignerClient { + return &signerClient{cc} +} + +func (c *signerClient) Sign(ctx context.Context, in *SignatureRequest, opts ...grpc.CallOption) (*Signature, error) { + out := new(Signature) + err := grpc.Invoke(ctx, "/proto.Signer/Sign", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Signer service + +type SignerServer interface { + // Sign calculates a cryptographic signature using the Key associated with a KeyInfo and returns the signature + Sign(context.Context, *SignatureRequest) (*Signature, error) +} + +func RegisterSignerServer(s *grpc.Server, srv SignerServer) { + s.RegisterService(&_Signer_serviceDesc, srv) +} + +func _Signer_Sign_Handler(srv interface{}, ctx context.Context, codec grpc.Codec, buf []byte) (interface{}, error) { + in := new(SignatureRequest) + if err := codec.Unmarshal(buf, in); err != nil { + return nil, err + } + out, err := srv.(SignerServer).Sign(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Signer_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Signer", + HandlerType: (*SignerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Sign", + Handler: _Signer_Sign_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} diff --git a/proto/rufus.proto b/proto/rufus.proto new file mode 100644 index 0000000000..e499363a1b --- /dev/null +++ b/proto/rufus.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package proto; + +// KeyManagement Interface +service KeyManagement { + + // CreateKey creates as asymmetric key pair and returns the PublicKey + rpc CreateKey(Algorithm) returns (PublicKey) {} + + // DeleteKey deletes the key associated with a KeyInfo + rpc DeleteKey(KeyInfo) returns (Void) {} + + // GetKeyInfo returns the PublicKey associated with a KeyInfo + rpc GetKeyInfo(KeyInfo) returns (PublicKey) {} +} + +// Signer Interface +service Signer { + // Sign calculates a cryptographic signature using the Key associated with a KeyInfo and returns the signature + rpc Sign(SignatureRequest) returns (Signature) {} +} + +// KeyInfo holds an ID that is used to reference the key and it's algorithm +message KeyInfo { + string ID = 1; + Algorithm algorithm = 2; +} + +// Type holds the type of crypto algorithm used +message Algorithm { + string algorithm = 1; +} + +// PublicKey has a KeyInfo that is used to reference the key, and opaque bytes of a publicKey +message PublicKey { + KeyInfo keyInfo = 1; + bytes publicKey = 2; +} + +// Signature specifies a KeyInfo that was used for signing and signed content +message Signature { + KeyInfo keyInfo = 1; + bytes content = 2; +} + +// SignatureRequests specifies a KeyInfo, and content to be signed +message SignatureRequest { + KeyInfo keyInfo = 1; + bytes content = 2; +} + +// Void represents an empty message type +message Void { +} diff --git a/signer/Dockerfile b/signer/Dockerfile new file mode 100644 index 0000000000..dc680d87e4 --- /dev/null +++ b/signer/Dockerfile @@ -0,0 +1,27 @@ +FROM diogomonica/golang-softhsm2 + +MAINTAINER Diogo Monica "diogo@docker.com" + +# CHANGE-ME: Default values for SoftHSM2 PIN and SOPIN, used to initialize the first token +ENV PIN="1234" +ENV SOPIN="1234" +ENV LIBDIR="/usr/local/lib/softhsm/" + +# Install openSC and dependencies +RUN apt-get update + +RUN apt-get install -y build-essential autoconf automake libtool gtk-doc-tools gengetopt help2man libpcsclite-dev libzip-dev opensc libssl-dev usbutils vim + +# Initialize the SoftHSM2 token on slod 0, using PIN and SOPIN varaibles +RUN softhsm2-util --init-token --slot 0 --label "test_token" --pin $PIN --so-pin $SOPIN + +# Copy the local repo to the expected go path +COPY . /go/src/github.com/docker/rufus + +# Install rufus +RUN go get github.com/docker/rufus/cmd/rufus + +EXPOSE 4443 + +#ENTRYPOINT rufus -cert /go/src/github.com/docker/rufus/fixtures/rufus.pem -key /go/src/github.com/docker/rufus/fixtures/rufus.key -debug -pkcs11 /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so -pin 123456 -yubikey +ENTRYPOINT rufus -cert /go/src/github.com/docker/rufus/fixtures/rufus.pem -key /go/src/github.com/docker/rufus/fixtures/rufus.key -debug -pkcs11 $LIBDIR/libsofthsm2.so -pin 1234 diff --git a/signer/api/api.go b/signer/api/api.go new file mode 100644 index 0000000000..40fb53e02c --- /dev/null +++ b/signer/api/api.go @@ -0,0 +1,176 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/docker/rufus" + "github.com/docker/rufus/keys" + "github.com/gorilla/mux" + + pb "github.com/docker/rufus/proto" +) + +// Handlers sets up all the handers for the routes, injecting a specific SigningService object for them to use +func Handlers(sigServices rufus.SigningServiceIndex) *mux.Router { + r := mux.NewRouter() + + r.Methods("GET").Path("/{Algorithm}/{ID}").Handler(KeyInfo(sigServices)) + r.Methods("POST").Path("/new/{Algorithm}").Handler(CreateKey(sigServices)) + r.Methods("POST").Path("/delete").Handler(DeleteKey(sigServices)) + r.Methods("POST").Path("/sign").Handler(Sign(sigServices)) + return r +} + +// getSigningService handles looking up the correct signing service, given the +// algorithm specified in the HTTP request. If the algorithm isn't specified +// or isn't supported, an error is returned to the client and this function +// returns a nil SigningService +func getSigningService(w http.ResponseWriter, algorithm string, sigServices rufus.SigningServiceIndex) rufus.SigningService { + if algorithm == "" { + http.Error(w, "algorithm not specified", http.StatusBadRequest) + return nil + } + + service := sigServices[algorithm] + + if service == nil { + http.Error(w, "algorithm "+algorithm+" not supported", http.StatusBadRequest) + return nil + } + + return service +} + +// KeyInfo returns a Handler that given a specific Key ID param, returns the public key bits of that key +func KeyInfo(sigServices rufus.SigningServiceIndex) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + algorithm := vars["Algorithm"] + + sigService := getSigningService(w, algorithm, sigServices) + if sigService == nil { + // Error handled inside getSigningService + return + } + + keyInfo := &pb.KeyInfo{ID: vars["ID"], Algorithm: &pb.Algorithm{Algorithm: algorithm}} + key, err := sigService.KeyInfo(keyInfo) + if err != nil { + switch err { + // If we received an ErrInvalidKeyID, the key doesn't exist, return 404 + case keys.ErrInvalidKeyID: + w.WriteHeader(http.StatusNotFound) + w.Write([]byte(err.Error())) + return + // If we received anything else, it is unexpected, and we return a 500 + default: + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + } + json.NewEncoder(w).Encode(key) + return + }) +} + +// CreateKey returns a handler that generates a new +func CreateKey(sigServices rufus.SigningServiceIndex) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + sigService := getSigningService(w, vars["Algorithm"], sigServices) + if sigService == nil { + // Error handled inside getSigningService + return + } + + key, err := sigService.CreateKey() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + json.NewEncoder(w).Encode(key) + return + }) +} + +// DeleteKey returns a handler that delete a specific KeyID +func DeleteKey(sigServices rufus.SigningServiceIndex) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var keyInfo *pb.KeyInfo + err := json.NewDecoder(r.Body).Decode(&keyInfo) + defer r.Body.Close() + if err != nil || keyInfo.ID == "" || keyInfo.Algorithm == nil || keyInfo.Algorithm.Algorithm == "" { + w.WriteHeader(http.StatusBadRequest) + jsonErr, _ := json.Marshal("Malformed request") + w.Write([]byte(jsonErr)) + return + } + + sigService := getSigningService(w, keyInfo.Algorithm.Algorithm, sigServices) + if sigService == nil { + // Error handled inside getSigningService + return + } + + _, err = sigService.DeleteKey(keyInfo) + + if err != nil { + switch err { + // If we received an ErrInvalidKeyID, the key doesn't exist, return 404 + case keys.ErrInvalidKeyID: + w.WriteHeader(http.StatusNotFound) + w.Write([]byte(err.Error())) + return + // If we received anything else, it is unexpected, and we return a 500 + default: + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + } + // In case we successfully delete this key, return 200 + return + }) +} + +// Sign returns a handler that is able to perform signatures on a given blob +func Sign(sigServices rufus.SigningServiceIndex) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var sigRequest *pb.SignatureRequest + err := json.NewDecoder(r.Body).Decode(&sigRequest) + defer r.Body.Close() + if err != nil || sigRequest.Content == nil || + sigRequest.KeyInfo == nil || sigRequest.KeyInfo.Algorithm == nil { + w.WriteHeader(http.StatusBadRequest) + jsonErr, _ := json.Marshal("Malformed request") + w.Write([]byte(jsonErr)) + return + } + + sigService := getSigningService(w, sigRequest.KeyInfo.Algorithm.Algorithm, sigServices) + if sigService == nil { + // Error handled inside getSigningService + return + } + + signer, err := sigService.Signer(sigRequest.KeyInfo) + if err == keys.ErrInvalidKeyID { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte(err.Error())) + return + } + + signature, err := signer.Sign(sigRequest) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + json.NewEncoder(w).Encode(signature) + return + }) +} diff --git a/signer/api/api_test.go b/signer/api/api_test.go new file mode 100644 index 0000000000..f1a3177823 --- /dev/null +++ b/signer/api/api_test.go @@ -0,0 +1,307 @@ +package api_test + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/docker/rufus" + "github.com/docker/rufus/api" + "github.com/docker/rufus/keys" + "github.com/miekg/pkcs11" + "github.com/stretchr/testify/assert" + + pb "github.com/docker/rufus/proto" +) + +var ( + server *httptest.Server + reader io.Reader + hsmSigService *api.RSASigningService + softwareSigService *api.EdDSASigningService + deleteKeyBaseURL string + createKeyBaseURL string + keyInfoBaseURL string + signBaseURL string +) + +func SetupHSMEnv(t *testing.T) (*pkcs11.Ctx, pkcs11.SessionHandle) { + var libPath = "/usr/local/lib/softhsm/libsofthsm2.so" + if _, err := os.Stat(libPath); err != nil { + t.Skipf("Skipping test. Library path: %s does not exist", libPath) + } + + p := pkcs11.New(libPath) + + if p == nil { + t.Fatalf("Failed to init library") + } + + if err := p.Initialize(); err != nil { + t.Fatalf("Initialize error %s\n", err.Error()) + } + + slots, err := p.GetSlotList(true) + if err != nil { + t.Fatalf("Failed to list HSM slots %s", err) + } + + session, err := p.OpenSession(slots[0], pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) + if err != nil { + t.Fatalf("Failed to Start Session with HSM %s", err) + } + + if err = p.Login(session, pkcs11.CKU_USER, "1234"); err != nil { + t.Fatalf("User PIN %s\n", err.Error()) + } + + return p, session +} + +func setup(sigServices rufus.SigningServiceIndex) { + server = httptest.NewServer(api.Handlers(sigServices)) + deleteKeyBaseURL = fmt.Sprintf("%s/delete", server.URL) + createKeyBaseURL = fmt.Sprintf("%s/new", server.URL) + keyInfoBaseURL = fmt.Sprintf("%s", server.URL) + signBaseURL = fmt.Sprintf("%s/sign", server.URL) +} + +func TestDeleteKeyHandlerReturns404WithNonexistentKey(t *testing.T) { + setup(rufus.SigningServiceIndex{api.ED25519: api.NewEdDSASigningService(keys.NewKeyDB())}) + + fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" + + keyInfo := &pb.KeyInfo{ID: fakeID, Algorithm: &pb.Algorithm{Algorithm: api.ED25519}} + requestJson, _ := json.Marshal(keyInfo) + reader = strings.NewReader(string(requestJson)) + + request, err := http.NewRequest("POST", deleteKeyBaseURL, reader) + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + assert.Equal(t, 404, res.StatusCode) +} + +func TestDeleteKeyHandler(t *testing.T) { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + setup(rufus.SigningServiceIndex{api.ED25519: sigService}) + + key, _ := sigService.CreateKey() + + requestJson, _ := json.Marshal(key.KeyInfo) + reader = strings.NewReader(string(requestJson)) + + request, err := http.NewRequest("POST", deleteKeyBaseURL, reader) + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + assert.Equal(t, 200, res.StatusCode) +} + +func TestKeyInfoHandler(t *testing.T) { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + setup(rufus.SigningServiceIndex{api.ED25519: sigService}) + + key, _ := sigService.CreateKey() + + keyInfoURL := fmt.Sprintf("%s/%s/%s", keyInfoBaseURL, api.ED25519, key.KeyInfo.ID) + + request, err := http.NewRequest("GET", keyInfoURL, nil) + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + jsonBlob, err := ioutil.ReadAll(res.Body) + assert.Nil(t, err) + + var keyInfo *pb.PublicKey + err = json.Unmarshal(jsonBlob, &keyInfo) + assert.Nil(t, err) + + assert.Equal(t, key.KeyInfo.ID, keyInfo.KeyInfo.ID) + assert.Equal(t, 200, res.StatusCode) +} + +func TestKeyInfoHandlerReturns404WithNonexistentKey(t *testing.T) { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + setup(rufus.SigningServiceIndex{api.ED25519: sigService}) + + fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" + keyInfoURL := fmt.Sprintf("%s/%s/%s", keyInfoBaseURL, api.ED25519, fakeID) + + request, err := http.NewRequest("GET", keyInfoURL, nil) + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + assert.Equal(t, 404, res.StatusCode) +} + +func TestHSMCreateKeyHandler(t *testing.T) { + ctx, session := SetupHSMEnv(t) + defer ctx.Destroy() + defer ctx.Finalize() + defer ctx.CloseSession(session) + defer ctx.Logout(session) + + setup(rufus.SigningServiceIndex{api.RSAAlgorithm: api.NewRSASigningService(ctx, session)}) + + createKeyURL := fmt.Sprintf("%s/%s", createKeyBaseURL, api.RSAAlgorithm) + + request, err := http.NewRequest("POST", createKeyURL, nil) + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + jsonBlob, err := ioutil.ReadAll(res.Body) + assert.Nil(t, err) + + var keyInfo *pb.PublicKey + err = json.Unmarshal(jsonBlob, &keyInfo) + assert.Nil(t, err) + + assert.Equal(t, 200, res.StatusCode) +} + +func TestSoftwareCreateKeyHandler(t *testing.T) { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + setup(rufus.SigningServiceIndex{api.ED25519: sigService}) + + createKeyURL := fmt.Sprintf("%s/%s", createKeyBaseURL, api.ED25519) + + request, err := http.NewRequest("POST", createKeyURL, nil) + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + assert.Equal(t, 200, res.StatusCode) + + jsonBlob, err := ioutil.ReadAll(res.Body) + assert.Nil(t, err) + + var keyInfo *pb.PublicKey + err = json.Unmarshal(jsonBlob, &keyInfo) + assert.Nil(t, err) +} + +func TestHSMSignHandler(t *testing.T) { + ctx, session := SetupHSMEnv(t) + defer ctx.Destroy() + defer ctx.Finalize() + defer ctx.CloseSession(session) + defer ctx.Logout(session) + + sigService := api.NewRSASigningService(ctx, session) + setup(rufus.SigningServiceIndex{api.RSAAlgorithm: sigService}) + key, _ := sigService.CreateKey() + + sigRequest := &pb.SignatureRequest{KeyInfo: &pb.KeyInfo{ID: key.KeyInfo.ID, Algorithm: &pb.Algorithm{Algorithm: "RSA"}}, Content: make([]byte, 10)} + requestJson, _ := json.Marshal(sigRequest) + + reader = strings.NewReader(string(requestJson)) + + request, err := http.NewRequest("POST", signBaseURL, reader) + + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + jsonBlob, err := ioutil.ReadAll(res.Body) + assert.Nil(t, err) + + var sig *pb.Signature + err = json.Unmarshal(jsonBlob, &sig) + assert.Nil(t, err) + + assert.Equal(t, key.KeyInfo.ID, sig.KeyInfo.ID) + assert.Equal(t, 200, res.StatusCode) +} + +func TestSoftwareSignHandler(t *testing.T) { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + setup(rufus.SigningServiceIndex{api.ED25519: sigService}) + key, _ := sigService.CreateKey() + + sigRequest := &pb.SignatureRequest{KeyInfo: &pb.KeyInfo{ID: key.KeyInfo.ID, Algorithm: &pb.Algorithm{Algorithm: api.ED25519}}, Content: make([]byte, 10)} + requestJson, _ := json.Marshal(sigRequest) + + reader = strings.NewReader(string(requestJson)) + + request, err := http.NewRequest("POST", signBaseURL, reader) + + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + assert.Equal(t, 200, res.StatusCode) + + jsonBlob, err := ioutil.ReadAll(res.Body) + assert.Nil(t, err) + + var sig *pb.Signature + err = json.Unmarshal(jsonBlob, &sig) + assert.Nil(t, err) + + assert.Equal(t, key.KeyInfo.ID, sig.KeyInfo.ID) +} + +func TestSoftwareSignWithInvalidRequestHandler(t *testing.T) { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + setup(rufus.SigningServiceIndex{api.ED25519: sigService}) + + requestJson := "{\"blob\":\"7d16f1d0b95310a7bc557747fc4f20fcd41c1c5095ae42f189df0717e7d7f4a0a2b55debce630f43c4ac099769c612965e3fda3cd4c0078ee6a460f14fa19307\"}" + reader = strings.NewReader(requestJson) + + request, err := http.NewRequest("POST", signBaseURL, reader) + + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + jsonBlob, err := ioutil.ReadAll(res.Body) + assert.Nil(t, err) + + var sig *pb.Signature + err = json.Unmarshal(jsonBlob, &sig) + + assert.Equal(t, 400, res.StatusCode) +} + +func TestSignHandlerReturns404WithNonexistentKey(t *testing.T) { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + setup(rufus.SigningServiceIndex{api.ED25519: sigService}) + + fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" + + sigService.CreateKey() + + sigRequest := &pb.SignatureRequest{KeyInfo: &pb.KeyInfo{ID: fakeID, Algorithm: &pb.Algorithm{Algorithm: api.ED25519}}, Content: make([]byte, 10)} + requestJson, _ := json.Marshal(sigRequest) + + reader = strings.NewReader(string(requestJson)) + + request, err := http.NewRequest("POST", signBaseURL, reader) + assert.Nil(t, err) + + res, err := http.DefaultClient.Do(request) + assert.Nil(t, err) + + assert.Equal(t, 404, res.StatusCode) +} diff --git a/signer/api/ed25519_signer.go b/signer/api/ed25519_signer.go new file mode 100644 index 0000000000..5a7d8e5c9e --- /dev/null +++ b/signer/api/ed25519_signer.go @@ -0,0 +1,30 @@ +package api + +import ( + "github.com/agl/ed25519" + "github.com/docker/rufus/keys" + + pb "github.com/docker/rufus/proto" +) + +// ED25519 represents an ed25519 algorithm +const ED25519 string = "ed25519" + +// Ed25519Signer implements the Signer interface for Ed25519 keys +type Ed25519Signer struct { + privateKey *keys.Key +} + +// Sign returns a signature for a given blob +func (s *Ed25519Signer) Sign(request *pb.SignatureRequest) (*pb.Signature, error) { + priv := [ed25519.PrivateKeySize]byte{} + copy(priv[:], s.privateKey.Private[:]) + sig := ed25519.Sign(&priv, request.Content) + + return &pb.Signature{KeyInfo: &pb.KeyInfo{ID: s.privateKey.ID, Algorithm: &pb.Algorithm{Algorithm: ED25519}}, Content: sig[:]}, nil +} + +// NewEd25519Signer returns a Ed25519Signer, given a private key +func NewEd25519Signer(key *keys.Key) *Ed25519Signer { + return &Ed25519Signer{privateKey: key} +} diff --git a/signer/api/ed25519_signer_test.go b/signer/api/ed25519_signer_test.go new file mode 100644 index 0000000000..07228463cb --- /dev/null +++ b/signer/api/ed25519_signer_test.go @@ -0,0 +1,61 @@ +package api_test + +import ( + "encoding/hex" + "fmt" + "testing" + + "github.com/agl/ed25519" + "github.com/docker/rufus/api" + "github.com/docker/rufus/keys" + "github.com/stretchr/testify/assert" + + pb "github.com/docker/rufus/proto" +) + +type zeroReader struct{} + +func (zeroReader) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = 0 + } + return len(buf), nil +} + +func TestSign(t *testing.T) { + var zero zeroReader + public, private, _ := ed25519.GenerateKey(zero) + + blob := []byte("test message") + + directSig := ed25519.Sign(private, blob) + directSigHex := hex.EncodeToString(directSig[:]) + + signer := api.NewEd25519Signer(&keys.Key{Private: private, Public: *public, ID: "fake_id"}) + + sigRequest := &pb.SignatureRequest{KeyInfo: &pb.KeyInfo{ID: "fake_id", Algorithm: &pb.Algorithm{Algorithm: "ed25519"}}, Content: blob} + + sig, err := signer.Sign(sigRequest) + assert.Nil(t, err) + signatureHex := fmt.Sprintf("%x", sig.Content) + + assert.Equal(t, directSigHex, signatureHex) + assert.Equal(t, sig.KeyInfo.ID, "fake_id") +} + +func BenchmarkSign(b *testing.B) { + blob := []byte("7d16f1d0b95310a7bc557747fc4f20fcd41c1c5095ae42f189df") + + keyDB := keys.NewKeyDB() + var sigService = api.NewEdDSASigningService(keyDB) + + key, _ := sigService.CreateKey() + privkey, _ := keyDB.GetKey(key.KeyInfo) + + signer := api.NewEd25519Signer(privkey) + sigRequest := &pb.SignatureRequest{KeyInfo: &pb.KeyInfo{ID: key.KeyInfo.ID, Algorithm: &pb.Algorithm{Algorithm: "ed25519"}}, Content: blob} + + for n := 0; n < b.N; n++ { + _, _ = signer.Sign(sigRequest) + } +} diff --git a/signer/api/ed25519_signing_service.go b/signer/api/ed25519_signing_service.go new file mode 100644 index 0000000000..f7663bba63 --- /dev/null +++ b/signer/api/ed25519_signing_service.go @@ -0,0 +1,68 @@ +package api + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/hex" + + "github.com/agl/ed25519" + "github.com/docker/rufus" + "github.com/docker/rufus/keys" + + pb "github.com/docker/rufus/proto" +) + +// EdDSASigningService is an implementation of SigningService +type EdDSASigningService struct { + KeyDB rufus.KeyDatabase +} + +// CreateKey creates a key and returns its public components +func (s EdDSASigningService) CreateKey() (*pb.PublicKey, error) { + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + k := &keys.Key{ + Algorithm: ED25519, + Public: *pub, + Private: priv, + } + digest := sha256.Sum256(k.Public[:]) + k.ID = hex.EncodeToString(digest[:]) + + err = s.KeyDB.AddKey(k) + if err != nil { + return nil, err + } + + pubKey := &pb.PublicKey{KeyInfo: &pb.KeyInfo{ID: k.ID, Algorithm: &pb.Algorithm{Algorithm: k.Algorithm}}, PublicKey: k.Public[:]} + + return pubKey, nil +} + +// DeleteKey removes a key from the key database +func (s EdDSASigningService) DeleteKey(keyInfo *pb.KeyInfo) (*pb.Void, error) { + return s.KeyDB.DeleteKey(keyInfo) +} + +// KeyInfo returns the public components of a particular key +func (s EdDSASigningService) KeyInfo(keyInfo *pb.KeyInfo) (*pb.PublicKey, error) { + return s.KeyDB.KeyInfo(keyInfo) +} + +// Signer returns a Signer for a specific KeyID +func (s EdDSASigningService) Signer(keyInfo *pb.KeyInfo) (rufus.Signer, error) { + key, err := s.KeyDB.GetKey(keyInfo) + if err != nil { + return nil, keys.ErrInvalidKeyID + } + return &Ed25519Signer{privateKey: key}, nil +} + +// NewEdDSASigningService returns an instance of KeyDB +func NewEdDSASigningService(keyDB rufus.KeyDatabase) *EdDSASigningService { + return &EdDSASigningService{ + KeyDB: keyDB, + } +} diff --git a/signer/api/ed25519_signing_service_test.go b/signer/api/ed25519_signing_service_test.go new file mode 100644 index 0000000000..e7b4815fb7 --- /dev/null +++ b/signer/api/ed25519_signing_service_test.go @@ -0,0 +1,98 @@ +package api_test + +import ( + "testing" + + "github.com/docker/rufus/api" + "github.com/docker/rufus/keys" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + pb "github.com/docker/rufus/proto" +) + +type FakeKeyDB struct { + mock.Mock +} + +func (m *FakeKeyDB) CreateKey() (*pb.PublicKey, error) { + args := m.Mock.Called() + return args.Get(0).(*pb.PublicKey), args.Error(1) +} + +func (m *FakeKeyDB) AddKey(key *keys.Key) error { + args := m.Mock.Called() + return args.Error(0) +} + +func (m *FakeKeyDB) DeleteKey(keyInfo *pb.KeyInfo) (*pb.Void, error) { + args := m.Mock.Called(keyInfo.ID) + return nil, args.Error(0) +} + +func (m *FakeKeyDB) KeyInfo(keyInfo *pb.KeyInfo) (*pb.PublicKey, error) { + args := m.Mock.Called(keyInfo.ID) + return args.Get(0).(*pb.PublicKey), args.Error(1) +} + +func (m *FakeKeyDB) GetKey(keyInfo *pb.KeyInfo) (*keys.Key, error) { + args := m.Mock.Called(keyInfo.ID) + return args.Get(0).(*keys.Key), args.Error(1) +} + +func TestDeleteKey(t *testing.T) { + fakeKeyID := "830158bb5a4af00a3f689a8f29120f0fa7f8ae57cf00ce1fede8ae8652b5181a" + + m := FakeKeyDB{} + sigService := api.NewEdDSASigningService(&m) + + m.On("DeleteKey", fakeKeyID).Return(nil).Once() + _, err := sigService.DeleteKey(&pb.KeyInfo{ID: fakeKeyID}) + + m.Mock.AssertExpectations(t) + assert.Nil(t, err) +} + +func TestKeyInfo(t *testing.T) { + fakeKeyID := "830158bb5a4af00a3f689a8f29120f0fa7f8ae57cf00ce1fede8ae8652b5181a" + + m := FakeKeyDB{} + sigService := api.NewEdDSASigningService(&m) + + m.On("KeyInfo", fakeKeyID).Return(&pb.PublicKey{}, nil).Once() + _, err := sigService.KeyInfo(&pb.KeyInfo{ID: fakeKeyID}) + + m.Mock.AssertExpectations(t) + assert.Nil(t, err) +} + +func TestCreateKey(t *testing.T) { + m := FakeKeyDB{} + sigService := api.NewEdDSASigningService(&m) + + m.On("AddKey").Return(nil).Once() + + _, err := sigService.CreateKey() + + m.Mock.AssertExpectations(t) + assert.Nil(t, err) +} + +func TestSigner(t *testing.T) { + fakeKeyID := "830158bb5a4af00a3f689a8f29120f0fa7f8ae57cf00ce1fede8ae8652b5181a" + m := FakeKeyDB{} + sigService := api.NewEdDSASigningService(&m) + + m.On("GetKey", fakeKeyID).Return(&keys.Key{}, nil).Once() + _, err := sigService.Signer(&pb.KeyInfo{ID: fakeKeyID}) + + m.Mock.AssertExpectations(t) + assert.Nil(t, err) +} + +func BenchmarkCreateKey(b *testing.B) { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + for n := 0; n < b.N; n++ { + _, _ = sigService.CreateKey() + } +} diff --git a/signer/api/rpc_api.go b/signer/api/rpc_api.go new file mode 100644 index 0000000000..999a9569ea --- /dev/null +++ b/signer/api/rpc_api.go @@ -0,0 +1,101 @@ +package api + +import ( + "fmt" + "log" + + "github.com/docker/rufus" + "github.com/docker/rufus/keys" + "golang.org/x/net/context" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + pb "github.com/docker/rufus/proto" +) + +//KeyManagementServer implements the KeyManagementServer grpc interface +type KeyManagementServer struct { + SigServices rufus.SigningServiceIndex +} + +//SignerServer implements the SignerServer grpc interface +type SignerServer struct { + SigServices rufus.SigningServiceIndex +} + +//CreateKey returns a PublicKey created using KeyManagementServer's SigningService +func (s *KeyManagementServer) CreateKey(ctx context.Context, algorithm *pb.Algorithm) (*pb.PublicKey, error) { + service := s.SigServices[algorithm.Algorithm] + + if service == nil { + return nil, fmt.Errorf("algorithm %s not supported for create key", algorithm.Algorithm) + } + + key, err := service.CreateKey() + if err != nil { + return nil, grpc.Errorf(codes.Internal, "Key creation failed") + } + log.Println("[Rufus CreateKey] : Created KeyID ", key.KeyInfo.ID) + return key, nil +} + +//DeleteKey deletes they key associated with a KeyID +func (s *KeyManagementServer) DeleteKey(ctx context.Context, keyInfo *pb.KeyInfo) (*pb.Void, error) { + service := s.SigServices[keyInfo.Algorithm.Algorithm] + + if service == nil { + return nil, fmt.Errorf("algorithm %s not supported for delete key", keyInfo.Algorithm.Algorithm) + } + + _, err := service.DeleteKey(keyInfo) + log.Println("[Rufus DeleteKey] : Deleted KeyID ", keyInfo.ID) + if err != nil { + switch err { + case keys.ErrInvalidKeyID: + return nil, grpc.Errorf(codes.NotFound, "Invalid keyID: key %s not found", keyInfo.ID) + default: + return nil, grpc.Errorf(codes.Internal, "Key deletion for keyID %s failed", keyInfo.ID) + } + } + + return &pb.Void{}, nil +} + +//GetKeyInfo returns they PublicKey associated with a KeyID +func (s *KeyManagementServer) GetKeyInfo(ctx context.Context, keyInfo *pb.KeyInfo) (*pb.PublicKey, error) { + service := s.SigServices[keyInfo.Algorithm.Algorithm] + + if service == nil { + return nil, fmt.Errorf("algorithm %s not supported for get key info", keyInfo.Algorithm.Algorithm) + } + + key, err := service.KeyInfo(keyInfo) + if err != nil { + return nil, grpc.Errorf(codes.NotFound, "Invalid keyID: key %s not found", keyInfo.ID) + } + log.Println("[Rufus GetKeyInfo] : Returning PublicKey for KeyID ", keyInfo.ID) + return key, nil +} + +//Sign signs a message and returns the signature using a private key associate with the KeyID from the SignatureRequest +func (s *SignerServer) Sign(ctx context.Context, sr *pb.SignatureRequest) (*pb.Signature, error) { + service := s.SigServices[sr.KeyInfo.Algorithm.Algorithm] + + if service == nil { + return nil, fmt.Errorf("algorithm %s not supported for sign", sr.KeyInfo.Algorithm.Algorithm) + } + + log.Println("[Rufus Sign] : Signing ", string(sr.Content), " with KeyID ", sr.KeyInfo.ID) + signer, err := service.Signer(sr.KeyInfo) + if err == keys.ErrInvalidKeyID { + return nil, grpc.Errorf(codes.NotFound, "Invalid keyID: key not found") + } + + signature, err := signer.Sign(sr) + if err != nil { + return nil, grpc.Errorf(codes.Internal, "Signing failed for keyID %s on hash %s", sr.KeyInfo.ID, sr.Content) + } + + return signature, nil +} diff --git a/signer/api/rpc_api_test.go b/signer/api/rpc_api_test.go new file mode 100644 index 0000000000..1fdffb61fd --- /dev/null +++ b/signer/api/rpc_api_test.go @@ -0,0 +1,136 @@ +package api_test + +import ( + "fmt" + "log" + "net" + "testing" + + "github.com/docker/rufus" + "github.com/docker/rufus/api" + "github.com/docker/rufus/keys" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + pb "github.com/docker/rufus/proto" +) + +var ( + kmClient pb.KeyManagementClient + sClient pb.SignerClient + grpcServer *grpc.Server + void *pb.Void +) + +func init() { + sigService := api.NewEdDSASigningService(keys.NewKeyDB()) + sigServices := rufus.SigningServiceIndex{api.ED25519: sigService} + void = &pb.Void{} + //server setup + kms := &api.KeyManagementServer{SigServices: sigServices} + ss := &api.SignerServer{SigServices: sigServices} + grpcServer = grpc.NewServer() + pb.RegisterKeyManagementServer(grpcServer, kms) + pb.RegisterSignerServer(grpcServer, ss) + lis, err := net.Listen("tcp", "127.0.0.1:7899") + if err != nil { + log.Fatalf("failed to listen %v", err) + } + + go grpcServer.Serve(lis) + + //client setup + conn, err := grpc.Dial("127.0.0.1:7899") + if err != nil { + log.Fatalf("fail to dial: %v", err) + } + kmClient = pb.NewKeyManagementClient(conn) + sClient = pb.NewSignerClient(conn) +} + +func TestDeleteKeyHandlerReturnsNotFoundWithNonexistentKey(t *testing.T) { + fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" + keyInfo := &pb.KeyInfo{ID: fakeID, Algorithm: &pb.Algorithm{Algorithm: api.ED25519}} + + ret, err := kmClient.DeleteKey(context.Background(), keyInfo) + assert.NotNil(t, err) + assert.Equal(t, grpc.Code(err), codes.NotFound) + assert.Nil(t, ret) +} + +func TestCreateKeyHandlerCreatesKey(t *testing.T) { + publicKey, err := kmClient.CreateKey(context.Background(), &pb.Algorithm{Algorithm: api.ED25519}) + assert.NotNil(t, publicKey) + assert.NotEmpty(t, publicKey.PublicKey) + assert.NotEmpty(t, publicKey.KeyInfo) + assert.Nil(t, err) + assert.Equal(t, grpc.Code(err), codes.OK) +} + +func TestDeleteKeyHandlerDeletesCreatedKey(t *testing.T) { + publicKey, err := kmClient.CreateKey(context.Background(), &pb.Algorithm{Algorithm: api.ED25519}) + ret, err := kmClient.DeleteKey(context.Background(), publicKey.KeyInfo) + assert.Nil(t, err) + assert.Equal(t, ret, void) +} + +func TestKeyInfoReturnsCreatedKeys(t *testing.T) { + publicKey, err := kmClient.CreateKey(context.Background(), &pb.Algorithm{Algorithm: api.ED25519}) + fmt.Println("Pubkey ID: " + publicKey.GetKeyInfo().ID) + returnedPublicKey, err := kmClient.GetKeyInfo(context.Background(), publicKey.KeyInfo) + fmt.Println("returnedPublicKey ID: " + returnedPublicKey.GetKeyInfo().ID) + + assert.Nil(t, err) + assert.Equal(t, publicKey.KeyInfo, returnedPublicKey.KeyInfo) + assert.Equal(t, publicKey.PublicKey, returnedPublicKey.PublicKey) +} + +func TestCreateKeyCreatesNewKeys(t *testing.T) { + publicKey1, err := kmClient.CreateKey(context.Background(), &pb.Algorithm{Algorithm: api.ED25519}) + assert.Nil(t, err) + publicKey2, err := kmClient.CreateKey(context.Background(), &pb.Algorithm{Algorithm: api.ED25519}) + assert.Nil(t, err) + assert.NotEqual(t, publicKey1, publicKey2) + assert.NotEqual(t, publicKey1.KeyInfo, publicKey2.KeyInfo) + assert.NotEqual(t, publicKey1.PublicKey, publicKey2.PublicKey) +} + +func TestGetKeyInfoReturnsNotFoundOnNonexistKeys(t *testing.T) { + fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" + keyInfo := &pb.KeyInfo{ID: fakeID, Algorithm: &pb.Algorithm{Algorithm: api.ED25519}} + + ret, err := kmClient.GetKeyInfo(context.Background(), keyInfo) + assert.NotNil(t, err) + assert.Equal(t, grpc.Code(err), codes.NotFound) + assert.Nil(t, ret) +} + +func TestCreatedKeysCanBeUsedToSign(t *testing.T) { + message := []byte{0, 0, 0, 0} + + publicKey, err := kmClient.CreateKey(context.Background(), &pb.Algorithm{Algorithm: api.ED25519}) + assert.Nil(t, err) + assert.NotNil(t, publicKey) + + sr := &pb.SignatureRequest{Content: message, KeyInfo: publicKey.KeyInfo} + assert.NotNil(t, sr) + signature, err := sClient.Sign(context.Background(), sr) + assert.Nil(t, err) + assert.NotNil(t, signature) + assert.NotEmpty(t, signature.Content) + assert.Equal(t, publicKey.KeyInfo, signature.KeyInfo) +} + +func TestSignReturnsNotFoundOnNonexistKeys(t *testing.T) { + fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" + keyInfo := &pb.KeyInfo{ID: fakeID, Algorithm: &pb.Algorithm{Algorithm: api.ED25519}} + message := []byte{0, 0, 0, 0} + sr := &pb.SignatureRequest{Content: message, KeyInfo: keyInfo} + + ret, err := sClient.Sign(context.Background(), sr) + assert.NotNil(t, err) + assert.Equal(t, grpc.Code(err), codes.NotFound) + assert.Nil(t, ret) +} diff --git a/signer/api/rsa_signing_service.go b/signer/api/rsa_signing_service.go new file mode 100644 index 0000000000..893dab3b00 --- /dev/null +++ b/signer/api/rsa_signing_service.go @@ -0,0 +1,225 @@ +package api + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "errors" + "log" + "math/big" + + "github.com/docker/rufus" + "github.com/docker/rufus/keys" + "github.com/miekg/pkcs11" + + pb "github.com/docker/rufus/proto" +) + +// RSAAlgorithm represents the rsa signing algorithm +const RSAAlgorithm string = "rsa" + +// RSASigningService is an implementation of SigningService +type RSASigningService struct { + keys map[string]*keys.HSMRSAKey + context *pkcs11.Ctx + session pkcs11.SessionHandle +} + +// CreateKey creates a key and returns its public components +func (s RSASigningService) CreateKey() (*pb.PublicKey, error) { + + // For now generate random labels for keys + // (diogo): add link between keyID and label in database so we can support multiple keys + randomLabel := make([]byte, 32) + _, err := rand.Read(randomLabel) + if err != nil { + return nil, errors.New("Could not generate a random key label.") + } + + // Set the public key template + // CKA_TOKEN: Guarantees key persistence in hardware + // CKA_LABEL: Identifies this specific key inside of the HSM + publicKeyTemplate := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), + pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{3}), + pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, 2048), + pkcs11.NewAttribute(pkcs11.CKA_LABEL, string(randomLabel)), + } + privateKeyTemplate := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), + pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true), + pkcs11.NewAttribute(pkcs11.CKA_SIGN, true), + pkcs11.NewAttribute(pkcs11.CKA_LABEL, string(randomLabel)), + } + + // Generate a new RSA private/public keypair inside of the HSM + pub, priv, err := s.context.GenerateKeyPair(s.session, + []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_KEY_PAIR_GEN, nil)}, + publicKeyTemplate, privateKeyTemplate) + if err != nil { + return nil, errors.New("Could not generate a new key inside of the HSM.") + } + + // (diogo): This template is used for the GetAttribute + template := []*pkcs11.Attribute{ + pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, nil), + pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, nil), + pkcs11.NewAttribute(pkcs11.CKA_MODULUS, nil), + } + + // Retrieve the public-key material to be able to create a new HSMRSAKey + attr, err := s.context.GetAttributeValue(s.session, pub, template) + if err != nil { + return nil, errors.New("Failed to get Attribute value.") + } + + // We're going to store the elements of the RSA Public key, exponent and Modulus inside of exp and mod + var exp int + mod := big.NewInt(0) + + // Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues. + for _, a := range attr { + if a.Type == pkcs11.CKA_PUBLIC_EXPONENT { + exp, _ = readInt(a.Value) + } + + if a.Type == pkcs11.CKA_MODULUS { + mod.SetBytes(a.Value) + } + } + + rsaPublicKey := rsa.PublicKey{N: mod, E: exp} + // Using x509 to Marshal the Public key into der encoding + pubBytes, err := x509.MarshalPKIXPublicKey(&rsaPublicKey) + if err != nil { + return nil, errors.New("Failed to Marshal public key.") + } + + // (diogo): Ideally I would like to return base64 PEM encoded public keys to the client + k := &keys.HSMRSAKey{ + Algorithm: RSAAlgorithm, + Public: pubBytes, + Private: priv, + } + + // (diogo): Change this to be consistent with how TUF does (canonical JSON) + digest := sha256.Sum256(k.Public[:]) + k.ID = hex.EncodeToString(digest[:]) + + s.keys[k.ID] = k + + pubKey := &pb.PublicKey{KeyInfo: &pb.KeyInfo{ID: k.ID, Algorithm: &pb.Algorithm{Algorithm: k.Algorithm}}, PublicKey: k.Public[:]} + + return pubKey, nil +} + +// DeleteKey removes a key from the key database +func (s RSASigningService) DeleteKey(keyInfo *pb.KeyInfo) (*pb.Void, error) { + if _, ok := s.keys[keyInfo.ID]; !ok { + return nil, keys.ErrInvalidKeyID + } + + delete(s.keys, keyInfo.ID) + return nil, nil +} + +// KeyInfo returns the public components of a particular key +func (s RSASigningService) KeyInfo(keyInfo *pb.KeyInfo) (*pb.PublicKey, error) { + k, ok := s.keys[keyInfo.ID] + if !ok { + return nil, keys.ErrInvalidKeyID + } + + pubKey := &pb.PublicKey{KeyInfo: &pb.KeyInfo{ID: k.ID, Algorithm: &pb.Algorithm{Algorithm: k.Algorithm}}, PublicKey: k.Public[:]} + + return pubKey, nil +} + +// Signer returns a Signer for a specific KeyID +func (s RSASigningService) Signer(keyInfo *pb.KeyInfo) (rufus.Signer, error) { + key, ok := s.keys[keyInfo.ID] + if !ok { + return nil, keys.ErrInvalidKeyID + } + // (diogo): Investigate if caching is worth it. Is this object expensive to create? + return &RSASigner{privateKey: key, context: s.context, session: s.session}, nil +} + +// RSASigner implements the Signer interface for RSA keys +type RSASigner struct { + privateKey *keys.HSMRSAKey + context *pkcs11.Ctx + session pkcs11.SessionHandle +} + +// Sign returns a signature for a given signature request +func (s *RSASigner) Sign(request *pb.SignatureRequest) (*pb.Signature, error) { + priv := s.privateKey.Private + var sig []byte + var err error + for i := 0; i < 3; i++ { + //TODO(mccauley): move this to RSA OAEP + s.context.SignInit(s.session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_SHA256_RSA_PKCS, nil)}, priv) + + sig, err = s.context.Sign(s.session, request.Content) + if err != nil { + log.Printf("Error while signing: %s", err) + continue + } + + // (diogo): XXX: Remove this before shipping + digest := sha256.Sum256(request.Content) + pub, err := x509.ParsePKIXPublicKey(s.privateKey.Public) + if err != nil { + log.Printf("Failed to parse public key: %s\n", err) + return nil, err + } + + rsaPub, ok := pub.(*rsa.PublicKey) + if !ok { + log.Printf("Value returned from ParsePKIXPublicKey was not an RSA public key") + return nil, err + } + + err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, digest[:], sig) + if err != nil { + log.Printf("Failed verification. Retrying: %s", err) + continue + } + break + } + + // (diogo): XXX: END Area of removal + if sig == nil { + return nil, errors.New("Failed to create signature") + } + + returnSig := &pb.Signature{KeyInfo: &pb.KeyInfo{ID: s.privateKey.ID, Algorithm: &pb.Algorithm{Algorithm: RSAAlgorithm}}, Content: sig[:]} + log.Printf("[Rufus Server] Signature request JSON: %s , response: %s", string(request.Content), returnSig) + return returnSig, nil +} + +// NewRSASigningService returns an instance of KeyDB +func NewRSASigningService(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) *RSASigningService { + return &RSASigningService{ + keys: make(map[string]*keys.HSMRSAKey), + context: ctx, + session: session, + } +} + +// readInt converts a []byte into an int. It is used to convert the RSA Public key exponent into an int to create a crypto.PublicKey +func readInt(data []byte) (int, error) { + var ret int + if len(data) > 4 { + return 0, errors.New("Cannot convert byte array due to size") + } + + for i, a := range data { + ret |= (int(a) << uint(i*8)) + } + return ret, nil +} diff --git a/signer/keys/hex_bytes.go b/signer/keys/hex_bytes.go new file mode 100644 index 0000000000..00ec6086f8 --- /dev/null +++ b/signer/keys/hex_bytes.go @@ -0,0 +1,36 @@ +package keys + +import ( + "encoding/hex" + "errors" +) + +// HexBytes represents hexadecimal bytes +type HexBytes []byte + +// UnmarshalJSON allows the representation in JSON of hexbytes +func (b *HexBytes) UnmarshalJSON(data []byte) error { + if len(data) < 2 || len(data)%2 != 0 || data[0] != '"' || data[len(data)-1] != '"' { + return errors.New("tuf: invalid JSON hex bytes") + } + res := make([]byte, hex.DecodedLen(len(data)-2)) + _, err := hex.Decode(res, data[1:len(data)-1]) + if err != nil { + return err + } + *b = res + return nil +} + +// MarshalJSON allows the representation in JSON of hexbytes +func (b HexBytes) MarshalJSON() ([]byte, error) { + res := make([]byte, hex.EncodedLen(len(b))+2) + res[0] = '"' + res[len(res)-1] = '"' + hex.Encode(res[1:], b) + return res, nil +} + +func (b HexBytes) String() string { + return hex.EncodeToString(b) +} diff --git a/signer/keys/key_db.go b/signer/keys/key_db.go new file mode 100644 index 0000000000..ed460c3a93 --- /dev/null +++ b/signer/keys/key_db.go @@ -0,0 +1,59 @@ +package keys + +import pb "github.com/docker/rufus/proto" + +// KeyDB represents an in-memory key keystore +type KeyDB struct { + keys map[string]*Key +} + +// CreateKey is needed to implement KeyManager. Returns an empty key. +func (db *KeyDB) CreateKey() (*pb.PublicKey, error) { + k := &pb.PublicKey{} + + return k, nil +} + +// AddKey Adds a new key to the database +func (db *KeyDB) AddKey(key *Key) error { + if _, ok := db.keys[key.ID]; ok { + return ErrExists + } + db.keys[key.ID] = key + + return nil +} + +// GetKey returns the private bits of a key +func (db *KeyDB) GetKey(keyInfo *pb.KeyInfo) (*Key, error) { + if key, ok := db.keys[keyInfo.ID]; ok { + return key, nil + } + return nil, ErrInvalidKeyID +} + +// DeleteKey deletes the keyID from the database +func (db *KeyDB) DeleteKey(keyInfo *pb.KeyInfo) (*pb.Void, error) { + _, err := db.GetKey(keyInfo) + if err != nil { + return nil, err + } + delete(db.keys, keyInfo.ID) + return nil, nil +} + +// KeyInfo returns the public bits of a key, given a specific keyID +func (db *KeyDB) KeyInfo(keyInfo *pb.KeyInfo) (*pb.PublicKey, error) { + key, err := db.GetKey(keyInfo) + if err != nil { + return nil, err + } + return &pb.PublicKey{KeyInfo: &pb.KeyInfo{ID: keyInfo.ID, Algorithm: &pb.Algorithm{Algorithm: key.Algorithm}}, PublicKey: key.Public[:]}, nil +} + +// NewKeyDB returns an instance of KeyDB +func NewKeyDB() *KeyDB { + return &KeyDB{ + keys: make(map[string]*Key), + } +} diff --git a/signer/keys/keys.go b/signer/keys/keys.go new file mode 100644 index 0000000000..89bc8749d0 --- /dev/null +++ b/signer/keys/keys.go @@ -0,0 +1,33 @@ +package keys + +import ( + "errors" + + "github.com/agl/ed25519" + "github.com/miekg/pkcs11" +) + +var ( + // ErrExists happens when a Key already exists in a database + ErrExists = errors.New("rufus: key already in db") + // ErrInvalidKeyID error happens when a key isn't found + ErrInvalidKeyID = errors.New("rufus: invalid key id") + // ErrFailedKeyGeneration happens when there is a failure in generating a key + ErrFailedKeyGeneration = errors.New("rufus: failed to generate new key") +) + +// Key represents all the information of a key, including the private and public bits +type Key struct { + ID string + Algorithm string + Public [ed25519.PublicKeySize]byte + Private *[ed25519.PrivateKeySize]byte +} + +// HSMRSAKey represents the information for an HSMRSAKey with ObjectHandle for private portion +type HSMRSAKey struct { + ID string + Algorithm string + Public []byte + Private pkcs11.ObjectHandle +} diff --git a/signer/rufus_trust.go b/signer/rufus_trust.go index 9d3609711c..785383cccf 100644 --- a/signer/rufus_trust.go +++ b/signer/rufus_trust.go @@ -4,21 +4,21 @@ import ( "net" "github.com/Sirupsen/logrus" - pb "github.com/docker/rufus/proto" + pb "github.com/docker/notary/proto" "github.com/endophage/gotuf/data" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) -// RufusSigner implements a RPC based Trust service that calls the Rufus Service -type RufusSigner struct { +// NotarySigner implements a RPC based Trust service that calls the Notary-signer Service +type NotarySigner struct { kmClient pb.KeyManagementClient sClient pb.SignerClient } -// NewRufusSigner is a convinience method that returns RufusSigner -func NewRufusSigner(hostname string, port string, tlscafile string) *RufusSigner { +// NewNotarySigner is a convinience method that returns NotarySigner +func NewNotarySigner(hostname string, port string, tlscafile string) *NotarySigner { var opts []grpc.DialOption netAddr := net.JoinHostPort(hostname, port) creds, err := credentials.NewClientTLSFromFile(tlscafile, hostname) @@ -33,14 +33,14 @@ func NewRufusSigner(hostname string, port string, tlscafile string) *RufusSigner } kmClient := pb.NewKeyManagementClient(conn) sClient := pb.NewSignerClient(conn) - return &RufusSigner{ + return &NotarySigner{ kmClient: kmClient, sClient: sClient, } } // Sign signs a byte string with a number of KeyIDs -func (trust *RufusSigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature, error) { +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} @@ -62,8 +62,8 @@ func (trust *RufusSigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature } // Create creates a remote key and returns the PublicKey associated with the remote private key -// TODO(diogo): Ignoring algorithm for now until rufus supports it -func (trust *RufusSigner) Create(role string, _ data.KeyAlgorithm) (*data.PublicKey, error) { +// TODO(diogo): Ignoring algorithm for now until notary-signer supports it +func (trust *NotarySigner) Create(role string, _ data.KeyAlgorithm) (*data.PublicKey, error) { publicKey, err := trust.kmClient.CreateKey(context.Background(), &pb.Void{}) if err != nil { return nil, err @@ -74,7 +74,7 @@ func (trust *RufusSigner) Create(role string, _ data.KeyAlgorithm) (*data.Public } // PublicKeys returns the public key(s) associated with the passed in keyIDs -func (trust *RufusSigner) PublicKeys(keyIDs ...string) (map[string]*data.PublicKey, error) { +func (trust *NotarySigner) PublicKeys(keyIDs ...string) (map[string]*data.PublicKey, error) { publicKeys := make(map[string]*data.PublicKey) for _, ID := range keyIDs { keyID := pb.KeyID{ID: ID} diff --git a/signer/softhsm2.conf b/signer/softhsm2.conf new file mode 100644 index 0000000000..775101f967 --- /dev/null +++ b/signer/softhsm2.conf @@ -0,0 +1,5 @@ +# SoftHSM v2 configuration file + +directories.tokendir = /softhsm2/tokens +objectstore.backend = db +log.level = INFO