mirror of https://github.com/docker/docs.git
				
				
				
			Initial copy of notary-signer
Signed-off-by: Diogo Monica <diogo@docker.com>
This commit is contained in:
		
							parent
							
								
									ead0224526
								
							
						
					
					
						commit
						9a4c2dc744
					
				
							
								
								
									
										8
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										8
									
								
								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" | ||||
|  |  | |||
|  | @ -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], "<config>") | ||||
| 	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) | ||||
| } | ||||
|  | @ -0,0 +1 @@ | |||
| package main | ||||
|  | @ -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----- | ||||
|  | @ -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----- | ||||
|  | @ -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{}, | ||||
| } | ||||
|  | @ -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 { | ||||
| } | ||||
|  | @ -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 | ||||
|  | @ -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 | ||||
| 	}) | ||||
| } | ||||
|  | @ -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) | ||||
| } | ||||
|  | @ -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} | ||||
| } | ||||
|  | @ -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) | ||||
| 	} | ||||
| } | ||||
|  | @ -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, | ||||
| 	} | ||||
| } | ||||
|  | @ -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() | ||||
| 	} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -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) | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -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) | ||||
| } | ||||
|  | @ -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), | ||||
| 	} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -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} | ||||
|  |  | |||
|  | @ -0,0 +1,5 @@ | |||
| # SoftHSM v2 configuration file | ||||
| 
 | ||||
| directories.tokendir = /softhsm2/tokens | ||||
| objectstore.backend = db | ||||
| log.level = INFO | ||||
		Loading…
	
		Reference in New Issue