diff --git a/ciphersuite/ciphersuite.go b/ciphersuite/ciphersuite.go new file mode 100644 index 00000000..56c9ce4f --- /dev/null +++ b/ciphersuite/ciphersuite.go @@ -0,0 +1,306 @@ +// Package ciphersuite defines the interfaces that Onet needs to setup +// a secure channel between the conodes. It is built around a cipher suite +// interface that provides the cryptographic primitives. +// +// The package also provides a cipher suite implementation that is using the +// Ed25519 signature scheme. +// +// As a server could use multiple cipher suites, the package implements a +// cipher registry that takes an implementation of a cipher suite and +// registered using the name of the suite. +// +// Public keys and signatures may need to be transmitted over the network and +// interfaces cannot be used as is. That is why every the different elements +// can be packed as CipherData. The registry provides functions to unpack +// them as the structure is self-contained. +package ciphersuite + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "io" + + "golang.org/x/xerrors" +) + +// encodedNameLengthSize defines the size in bytes of the name length +// when marshaling cipher data. +const encodedNameLengthSize = 32 / 8 + +// Name is the type that can differentiate multiple ciphers. +type Name = string + +// Nameable binds a structure to a cipher. +type Nameable interface { + Name() Name +} + +// CipherData is a self-contained message type that can be used +// over the network in the contrary of the interfaces. +type CipherData struct { + Data []byte + CipherName Name +} + +// Name returns the name of the cipher suite compatible with the data +// contained in the raw structure. +func (d *CipherData) Name() Name { + return d.CipherName +} + +func (d *CipherData) String() string { + buf := append([]byte(d.Name()), d.Data...) + return hex.EncodeToString(buf) +} + +// Equal verifies if both self and other are deeply equal. +func (d *CipherData) Equal(other *CipherData) bool { + return d.Name() == other.Name() && bytes.Equal(d.Data, other.Data) +} + +// Clone returns a clone of the cipher data. +func (d *CipherData) Clone() *CipherData { + data := make([]byte, len(d.Data)) + copy(data, d.Data) + return &CipherData{ + CipherName: d.Name(), + Data: data, + } +} + +// WriteTo implements the io.WriteTo interface so that the cipher +// data can be written into any standard writer (e.g. hash). +func (d *CipherData) WriteTo(w io.Writer) (n int64, err error) { + var size int + size, err = w.Write([]byte(d.Name())) + n += int64(size) + if err != nil { + return n, xerrors.Errorf("writing name: %v", err) + } + + size, err = w.Write(d.Data) + n += int64(size) + if err != nil { + return n, xerrors.Errorf("writing data: %v", err) + } + + return n, nil +} + +// MarshalText implements the encoding interface TextMarshaler so that +// it can be serialized in format such as TOML. +func (d *CipherData) MarshalText() ([]byte, error) { + name := []byte(d.Name()) + size := make([]byte, encodedNameLengthSize) + binary.LittleEndian.PutUint32(size, uint32(len(name))) + + // Buffer starts with the size of the cipher suite name, then the name + // and finally the data. + data := append(append(size, name...), d.Data...) + + buf := make([]byte, hex.EncodedLen(len(data))) + hex.Encode(buf, data) + return buf, nil +} + +// UnmarshalText implements the encoding interface TextUnmarshaler so that +// format such as TOML can deserialize the data. +func (d *CipherData) UnmarshalText(text []byte) error { + buf := make([]byte, hex.DecodedLen(len(text))) + _, err := hex.Decode(buf, text) + if err != nil { + return xerrors.Errorf("decoding hex: %v", err) + } + + if len(buf) < encodedNameLengthSize { + return xerrors.Errorf("data is too small") + } + + size := int(binary.LittleEndian.Uint32(buf[:encodedNameLengthSize])) + if len(buf) < encodedNameLengthSize+size { + return xerrors.Errorf("data is too small") + } + + d.CipherName = string(buf[encodedNameLengthSize : encodedNameLengthSize+size]) + d.Data = buf[encodedNameLengthSize+size:] + return nil +} + +// RawPublicKey is a raw data structure of a public key implementation. +type RawPublicKey struct { + *CipherData +} + +// NewRawPublicKey returns an instance of a public key. +func NewRawPublicKey(name Name, data []byte) *RawPublicKey { + return &RawPublicKey{ + CipherData: &CipherData{ + Data: data, + CipherName: name, + }, + } +} + +// Raw returns the raw data of a public key. It is implemented to allow +// a raw public key to be compatible with the interface. +func (raw *RawPublicKey) Raw() *RawPublicKey { + return raw +} + +// Equal returns true when the two data structure contains the same public +// key. +func (raw *RawPublicKey) Equal(other PublicKey) bool { + data := other.Raw() + return data.CipherData.Equal(raw.CipherData) +} + +// Clone returns a clone of the raw public key. +func (raw *RawPublicKey) Clone() *RawPublicKey { + return &RawPublicKey{CipherData: raw.CipherData.Clone()} +} + +// UnmarshalText converts the raw public key back from a text marshaling. +func (raw *RawPublicKey) UnmarshalText(text []byte) error { + raw.CipherData = &CipherData{} + err := raw.CipherData.UnmarshalText(text) + if err != nil { + return xerrors.Errorf("unmarshaling cipher data: %v", err) + } + + return nil +} + +// RawSecretKey is a raw data structure of a secret key implementation. +type RawSecretKey struct { + *CipherData +} + +// NewRawSecretKey returns an instance of a raw secret key. +func NewRawSecretKey(name Name, data []byte) *RawSecretKey { + return &RawSecretKey{ + CipherData: &CipherData{ + CipherName: name, + Data: data, + }, + } +} + +// Raw returns the raw data of a secret key. It is implemented to allow +// a raw secret key to be compatible with the interface. +func (raw *RawSecretKey) Raw() *RawSecretKey { + return raw +} + +// Clone makes a clone of the secret key. +func (raw *RawSecretKey) Clone() *RawSecretKey { + return &RawSecretKey{CipherData: raw.CipherData.Clone()} +} + +// UnmarshalText converts the raw secret key back from a text marshaling. +func (raw *RawSecretKey) UnmarshalText(text []byte) error { + raw.CipherData = &CipherData{} + err := raw.CipherData.UnmarshalText(text) + if err != nil { + return xerrors.Errorf("unmarshaling cipher data: %v", err) + } + + return nil +} + +// RawSignature is a raw data structure of a signature implementation. +type RawSignature struct { + *CipherData +} + +// NewRawSignature returns an instance of a raw signature. +func NewRawSignature(name Name, data []byte) *RawSignature { + return &RawSignature{ + CipherData: &CipherData{ + CipherName: name, + Data: data, + }, + } +} + +// Raw returns the raw data of a signature. It is implemented to allow +// a raw signature to be compatible with the interface. +func (raw *RawSignature) Raw() *RawSignature { + return raw +} + +// Clone returns a clone of a raw signature. +func (raw *RawSignature) Clone() *RawSignature { + return &RawSignature{CipherData: raw.CipherData.Clone()} +} + +// UnmarshalText converts the raw signature back from a text marshaling. +func (raw *RawSignature) UnmarshalText(text []byte) error { + raw.CipherData = &CipherData{} + err := raw.CipherData.UnmarshalText(text) + if err != nil { + return xerrors.Errorf("unmarshaling cipher data: %v", err) + } + + return nil +} + +// PublicKey represents one of the two sides of an asymmetric key pair +// which can be safely shared publicly. +type PublicKey interface { + Nameable + + fmt.Stringer + + Raw() *RawPublicKey + + Equal(other PublicKey) bool +} + +// SecretKey represents one of the two sides of an asymmetric key pair +// which must remain private. +type SecretKey interface { + Nameable + + fmt.Stringer + + Raw() *RawSecretKey +} + +// Signature represents a signature produced using a secret key and +// that can be verified with the associated public key. +type Signature interface { + Nameable + + fmt.Stringer + + Raw() *RawSignature +} + +// CipherSuite provides the primitive needed to create and verify +// signatures using an asymmetric key pair. +type CipherSuite interface { + Nameable + + // PublicKey must return an implementation of a public key. + PublicKey(raw *RawPublicKey) (PublicKey, error) + + // SecretKey must return an implementation of a secret key. + SecretKey(raw *RawSecretKey) (SecretKey, error) + + // Signature must return an implementation of a signature. + Signature(raw *RawSignature) (Signature, error) + + // GenerateKeyPair must return a random secret key and its associated public key. + GenerateKeyPair(reader io.Reader) (PublicKey, SecretKey, error) + + // Sign must produce a signature that can be validated by the + // associated public key of the secret key. + Sign(sk SecretKey, msg []byte) (Signature, error) + + // Verify must return nil when the signature is valid for the + // message and the public key. Otherwise it should return the + // reason of the invalidity. + Verify(pk PublicKey, signature Signature, msg []byte) error +} diff --git a/ciphersuite/ciphersuite_test.go b/ciphersuite/ciphersuite_test.go new file mode 100644 index 00000000..0edecb61 --- /dev/null +++ b/ciphersuite/ciphersuite_test.go @@ -0,0 +1,176 @@ +package ciphersuite + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" +) + +func TestCipherData_String(t *testing.T) { + data := &CipherData{ + CipherName: "A", + Data: []byte{255}, + } + + require.Equal(t, "41ff", data.String()) +} + +func TestCipherData_Equal(t *testing.T) { + a := &CipherData{CipherName: "abc", Data: []byte{1, 2, 3}} + b := &CipherData{CipherName: "abc", Data: []byte{1, 2, 3}} + + require.True(t, a.Equal(b)) + require.True(t, b.Equal(a)) + + b.CipherName = "oops" + require.False(t, a.Equal(b)) + + b.CipherName = a.CipherName + b.Data = []byte{} + require.False(t, a.Equal(b)) +} + +func TestCipherData_Clone(t *testing.T) { + a := &CipherData{CipherName: "abc", Data: []byte{1, 2, 3}} + b := a.Clone() + + require.True(t, a.Equal(b)) + + b.Data[0] = 4 + require.False(t, a.Equal(b)) +} + +type badWriter struct{} + +func (w *badWriter) Write(b []byte) (int, error) { + if len(b) == 0 { + return 0, xerrors.New("this is a bad writer") + } + return 0, nil +} + +func TestCipherData_WriteTo(t *testing.T) { + data := &CipherData{ + CipherName: "abc", + Data: []byte{1, 2, 3}, + } + + buf := new(bytes.Buffer) + n, err := data.WriteTo(buf) + + require.NoError(t, err) + require.Equal(t, len(data.CipherName)+len(data.Data), int(n)) + require.Equal(t, []byte{0x61, 0x62, 0x63, 0x1, 0x2, 0x3}, buf.Bytes()) + + data.Data = []byte{} + n, err = data.WriteTo(new(badWriter)) + require.Error(t, err) + + data.CipherName = "" + n, err = data.WriteTo(new(badWriter)) + require.Error(t, err) +} + +func TestCipherData_MarshalText(t *testing.T) { + data := &CipherData{ + CipherName: "abc", + Data: []byte{1, 2, 3}, + } + + buf, err := data.MarshalText() + require.NoError(t, err) + + data2 := &CipherData{} + err = data2.UnmarshalText(buf) + require.NoError(t, err) + require.Equal(t, data, data2) +} + +func TestCipherData_UnmarshalText(t *testing.T) { + data := &CipherData{CipherName: "abc", Data: []byte{1, 2, 3}} + err := data.UnmarshalText([]byte{255}) + require.Error(t, err) + require.Contains(t, err.Error(), "decoding hex:") + + buf, err := data.MarshalText() + require.NoError(t, err) + + err = data.UnmarshalText(buf[:2]) + require.Error(t, err) + require.Contains(t, err.Error(), "data is too small") + + err = data.UnmarshalText(buf[:encodedNameLengthSize*2+2]) + require.Error(t, err) + require.Contains(t, err.Error(), "data is too small") +} + +func TestRawPublicKey_Equal(t *testing.T) { + raw1 := NewRawPublicKey("abc", []byte{1}) + raw2 := NewRawPublicKey("abc", []byte{2}) + + require.False(t, raw1.Equal(raw2)) + require.True(t, raw1.Equal(raw1)) + require.True(t, raw1.Raw().Equal(raw1)) + require.True(t, raw2.Clone().Equal(raw2)) +} + +func TestRawPublicKey_TextMarshaling(t *testing.T) { + raw := NewRawPublicKey("abc", []byte{1}) + + buf, err := raw.MarshalText() + require.NoError(t, err) + + decoded := NewRawPublicKey("", []byte{}) + require.NoError(t, decoded.UnmarshalText(buf)) + require.True(t, raw.Equal(decoded)) + + require.Error(t, decoded.UnmarshalText([]byte{})) +} + +func TestRawSecretKey_Clone(t *testing.T) { + raw := NewRawSecretKey("abc", []byte{1}) + raw2 := raw.Clone() + + require.Equal(t, raw, raw2) + + raw2.Data[0] = 5 + require.NotEqual(t, raw, raw2) +} + +func TestRawSecretKey_TextMarshaling(t *testing.T) { + raw := NewRawSecretKey("abc", []byte{1}) + + buf, err := raw.Raw().MarshalText() + require.NoError(t, err) + + decoded := NewRawSecretKey("", []byte{}) + require.NoError(t, decoded.UnmarshalText(buf)) + require.Equal(t, raw, decoded) + + require.Error(t, decoded.UnmarshalText([]byte{})) +} + +func TestRawSignature_Clone(t *testing.T) { + raw := NewRawSignature("abc", []byte{1}) + raw2 := raw.Clone() + + require.Equal(t, raw, raw2) + + raw2.Data[0] = 5 + require.NotEqual(t, raw, raw2) +} + +func TestRawSignature_TextMarshaling(t *testing.T) { + raw := NewRawSignature("abc", []byte{1}) + + buf, err := raw.Raw().MarshalText() + require.NoError(t, err) + + decoded := NewRawSignature("", []byte{}) + require.NoError(t, decoded.UnmarshalText(buf)) + require.Equal(t, raw, decoded) + + require.Error(t, decoded.UnmarshalText([]byte{})) +} diff --git a/ciphersuite/ed25519.go b/ciphersuite/ed25519.go new file mode 100644 index 00000000..48851a8b --- /dev/null +++ b/ciphersuite/ed25519.go @@ -0,0 +1,220 @@ +package ciphersuite + +import ( + "io" + + "golang.org/x/crypto/ed25519" + "golang.org/x/xerrors" +) + +const errNotEd25519CipherSuite = "invalid cipher suite" +const errInvalidBufferSize = "invalid buffer size" + +// Ed25519CipherSuiteName is the name of the cipher suite that is using Ed25519 as +// the signature algorithm. +const Ed25519CipherSuiteName = "ED25519_CIPHER_SUITE" + +// Ed25519PublicKey is the public key implementation for the Ed25519 cipher suite. +type Ed25519PublicKey struct { + data ed25519.PublicKey +} + +// Name returns the name of the cipher suite. +func (pk *Ed25519PublicKey) Name() Name { + return Ed25519CipherSuiteName +} + +func (pk *Ed25519PublicKey) String() string { + return pk.Raw().String() +} + +// Raw returns the raw public key. +func (pk *Ed25519PublicKey) Raw() *RawPublicKey { + return NewRawPublicKey(pk.Name(), pk.data) +} + +// Equal returns true when both public key are equal. +func (pk *Ed25519PublicKey) Equal(other PublicKey) bool { + return pk.Raw().Equal(other.Raw()) +} + +// Ed25519SecretKey is the secret key implementation for the Ed25519 cipher suite. +type Ed25519SecretKey struct { + data ed25519.PrivateKey +} + +// Name returns the cipher suite name. +func (sk *Ed25519SecretKey) Name() Name { + return Ed25519CipherSuiteName +} + +func (sk *Ed25519SecretKey) String() string { + return sk.Raw().String() +} + +// Raw returns the raw secret key. +func (sk *Ed25519SecretKey) Raw() *RawSecretKey { + return NewRawSecretKey(sk.Name(), sk.data) +} + +// Ed25519Signature is the signature implementation for the Ed25519 cipher suite. +type Ed25519Signature struct { + data []byte +} + +// Name returns the name of the cipher suite. +func (sig *Ed25519Signature) Name() Name { + return Ed25519CipherSuiteName +} + +func (sig *Ed25519Signature) String() string { + return sig.Raw().String() +} + +// Raw returns the raw signature. +func (sig *Ed25519Signature) Raw() *RawSignature { + return NewRawSignature(sig.Name(), sig.data) +} + +// Ed25519CipherSuite is a cipher suite implementation using the Ed25519 scheme. +type Ed25519CipherSuite struct{} + +// NewEd25519CipherSuite returns an instance of the cipher suite. +func NewEd25519CipherSuite() *Ed25519CipherSuite { + return &Ed25519CipherSuite{} +} + +// Name returns the name of the suite. +func (s *Ed25519CipherSuite) Name() Name { + return Ed25519CipherSuiteName +} + +// PublicKey takes a raw public key and converts it to a public key. +func (s *Ed25519CipherSuite) PublicKey(raw *RawPublicKey) (PublicKey, error) { + if raw.Name() != s.Name() { + return nil, xerrors.New(errNotEd25519CipherSuite) + } + + if len(raw.Data) != ed25519.PublicKeySize { + return nil, xerrors.New(errInvalidBufferSize) + } + + return &Ed25519PublicKey{data: raw.Data}, nil +} + +// SecretKey takes a raw secret key and converts it to a secret key. +func (s *Ed25519CipherSuite) SecretKey(raw *RawSecretKey) (SecretKey, error) { + if raw.Name() != s.Name() { + return nil, xerrors.New(errNotEd25519CipherSuite) + } + + if len(raw.Data) != ed25519.PrivateKeySize { + return nil, xerrors.New(errInvalidBufferSize) + } + + return &Ed25519SecretKey{data: raw.Data}, nil +} + +// Signature takes a raw signature and converts it to a signature. +func (s *Ed25519CipherSuite) Signature(raw *RawSignature) (Signature, error) { + if raw.Name() != s.Name() { + return nil, xerrors.New(errNotEd25519CipherSuite) + } + + if len(raw.Data) != ed25519.SignatureSize { + return nil, xerrors.New(errInvalidBufferSize) + } + + return &Ed25519Signature{data: raw.Data}, nil +} + +// GenerateKeyPair generates a secret key and its associated public key. When +// reader is nil, it will use the default randomness source. +func (s *Ed25519CipherSuite) GenerateKeyPair(reader io.Reader) (PublicKey, SecretKey, error) { + pk, sk, err := ed25519.GenerateKey(reader) + if err != nil { + return nil, nil, xerrors.Errorf("generate ed25519 key: %v", err) + } + + return &Ed25519PublicKey{data: pk}, &Ed25519SecretKey{data: sk}, nil +} + +// Sign signs the message with the secret key and returns the signature that +// can be verified with the public key. +func (s *Ed25519CipherSuite) Sign(sk SecretKey, msg []byte) (Signature, error) { + secretKey, err := s.unpackSecretKey(sk) + if err != nil { + return nil, xerrors.Errorf("unpacking secret key: %v", err) + } + sigbuf := ed25519.Sign(secretKey.data, msg) + + return &Ed25519Signature{data: sigbuf}, nil +} + +// Verify returns nil when the signature of the message can be verified by +// the public key. +func (s *Ed25519CipherSuite) Verify(pk PublicKey, sig Signature, msg []byte) error { + publicKey, err := s.unpackPublicKey(pk) + if err != nil { + return xerrors.Errorf("unpacking public key: %v", err) + } + + signature, err := s.unpackSignature(sig) + if err != nil { + return xerrors.Errorf("unpacking signature: %v", err) + } + + if !ed25519.Verify(publicKey.data, msg, signature.data) { + return xerrors.New("signature not verified") + } + + return nil +} + +func (s *Ed25519CipherSuite) unpackPublicKey(pk PublicKey) (*Ed25519PublicKey, error) { + if data, ok := pk.(*RawPublicKey); ok { + var err error + pk, err = s.PublicKey(data) + if err != nil { + return nil, xerrors.Errorf("unmarshaling raw public key: %v", err) + } + } + + if publicKey, ok := pk.(*Ed25519PublicKey); ok { + return publicKey, nil + } + + return nil, xerrors.New("wrong type of public key") +} + +func (s *Ed25519CipherSuite) unpackSecretKey(sk SecretKey) (*Ed25519SecretKey, error) { + if data, ok := sk.(*RawSecretKey); ok { + var err error + sk, err = s.SecretKey(data) + if err != nil { + return nil, xerrors.Errorf("unmarshaling raw secret key: %v", err) + } + } + + if secretKey, ok := sk.(*Ed25519SecretKey); ok { + return secretKey, nil + } + + return nil, xerrors.New("wrong type of secret key") +} + +func (s *Ed25519CipherSuite) unpackSignature(sig Signature) (*Ed25519Signature, error) { + if data, ok := sig.(*RawSignature); ok { + var err error + sig, err = s.Signature(data) + if err != nil { + return nil, xerrors.Errorf("unmarshaling raw signature: %v", err) + } + } + + if signature, ok := sig.(*Ed25519Signature); ok { + return signature, nil + } + + return nil, xerrors.New("wrong type of signature") +} diff --git a/ciphersuite/ed25519_test.go b/ciphersuite/ed25519_test.go new file mode 100644 index 00000000..d00bae7f --- /dev/null +++ b/ciphersuite/ed25519_test.go @@ -0,0 +1,194 @@ +package ciphersuite + +import ( + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/ed25519" + "golang.org/x/xerrors" +) + +func TestEd25519PublicKey(t *testing.T) { + pk, _, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + + publicKey := &Ed25519PublicKey{data: pk} + require.Equal(t, Ed25519CipherSuiteName, publicKey.Name()) + require.NotNil(t, publicKey.Raw()) + require.NotNil(t, publicKey.String()) + + suite := NewEd25519CipherSuite() + publicKey2, err := suite.PublicKey(publicKey.Raw()) + require.Equal(t, publicKey, publicKey2) +} + +func TestEd25519PublicKey_Equal(t *testing.T) { + pk, _, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + publicKey := &Ed25519PublicKey{data: pk} + + pk2, _, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + publicKey2 := &Ed25519PublicKey{data: pk2} + + require.True(t, publicKey.Equal(publicKey)) + require.False(t, publicKey.Equal(publicKey2)) +} + +func TestEd25519SecretKey(t *testing.T) { + _, sk, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + + secretKey := &Ed25519SecretKey{data: sk} + + require.Equal(t, Ed25519CipherSuiteName, secretKey.Name()) + require.NotNil(t, secretKey.Raw()) + require.NotNil(t, secretKey.String()) +} + +func TestEd25519Signature(t *testing.T) { + _, sk, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + sig := ed25519.Sign(sk, []byte{}) + signature := &Ed25519Signature{data: sig} + + require.Equal(t, Ed25519CipherSuiteName, signature.Name()) + require.NotNil(t, signature.Raw()) + require.NotNil(t, signature.String()) +} + +func TestEd25519CipherSuite_BasicUsage(t *testing.T) { + suite := NewEd25519CipherSuite() + + pk, sk, err := suite.GenerateKeyPair(nil) + require.NoError(t, err) + msg := []byte("deadbeef") + + sig, err := suite.Sign(sk, msg) + require.NoError(t, err) + + err = suite.Verify(pk, sig, msg) + require.NoError(t, err) +} + +type testPublicKey struct { + *Ed25519PublicKey +} + +type testSecretKey struct { + *Ed25519SecretKey +} + +type testSignature struct { + *Ed25519Signature +} + +func TestEd25519CipherSuite_Unpacking(t *testing.T) { + suite := NewEd25519CipherSuite() + + // Public keys + rawPk := &RawPublicKey{CipherData: &CipherData{CipherName: "abc"}} + _, err := suite.PublicKey(rawPk) + require.Error(t, err) + require.Contains(t, err.Error(), errNotEd25519CipherSuite) + + _, err = suite.unpackPublicKey(&testPublicKey{}) + require.Error(t, err) + require.Contains(t, err.Error(), "wrong type of public key") + + rawPk.CipherName = Ed25519CipherSuiteName + rawPk.Data = []byte{} + _, err = suite.PublicKey(rawPk) + require.Error(t, err) + require.Contains(t, err.Error(), errInvalidBufferSize) + + // Secret Keys + rawSk := &RawSecretKey{CipherData: &CipherData{CipherName: "abc"}} + _, err = suite.SecretKey(rawSk) + require.Error(t, err) + require.Contains(t, err.Error(), errNotEd25519CipherSuite) + + _, err = suite.unpackSecretKey(&testSecretKey{}) + require.Error(t, err) + require.Contains(t, err.Error(), "wrong type of secret key") + + rawSk.CipherName = Ed25519CipherSuiteName + rawSk.Data = []byte{} + _, err = suite.SecretKey(rawSk) + require.Error(t, err) + require.Contains(t, err.Error(), errInvalidBufferSize) + + // Signatures + rawSig := &RawSignature{CipherData: &CipherData{CipherName: "abc"}} + _, err = suite.Signature(rawSig) + require.Error(t, err) + require.Contains(t, err.Error(), errNotEd25519CipherSuite) + + _, err = suite.unpackSignature(&testSignature{}) + require.Error(t, err) + require.Contains(t, err.Error(), "wrong type of signature") + + rawSig.CipherName = Ed25519CipherSuiteName + rawSig.Data = []byte{} + _, err = suite.Signature(rawSig) + require.Error(t, err) + require.Contains(t, err.Error(), errInvalidBufferSize) +} + +type badReader struct{} + +func (br *badReader) Read(p []byte) (int, error) { + return 0, xerrors.New("oops") +} + +func TestEd25519CipherSuite_GenerateKey(t *testing.T) { + suite := NewEd25519CipherSuite() + + _, _, err := suite.GenerateKeyPair(&badReader{}) + require.Error(t, err) + + pk, sk, err := suite.GenerateKeyPair(nil) + require.NoError(t, err) + require.NotNil(t, pk) + require.NotNil(t, sk) +} + +func TestEd25519CipherSuite_Sign(t *testing.T) { + suite := NewEd25519CipherSuite() + + _, sk, err := suite.GenerateKeyPair(nil) + require.NoError(t, err) + + sig, err := suite.Sign(sk, []byte{}) + require.NoError(t, err) + require.NotNil(t, sig) + + rawSk := sk.Raw() + rawSk.CipherName = "abc" + + _, err = suite.Sign(rawSk, []byte{}) + require.Error(t, err) +} + +func TestEd25519CipherSuite_Verify(t *testing.T) { + suite := NewEd25519CipherSuite() + + pk, sk, err := suite.GenerateKeyPair(nil) + require.NoError(t, err) + + sig, err := suite.Sign(sk, []byte{}) + require.NoError(t, err) + + err = suite.Verify(pk, sig, []byte{}) + require.NoError(t, err) + + rawSig := sig.Raw() + rawSig.CipherName = "abc" + err = suite.Verify(pk, rawSig, []byte{}) + require.Error(t, err) + + rawPk := pk.Raw() + rawPk.CipherName = "abc" + err = suite.Verify(rawPk, sig, []byte{}) + require.Error(t, err) +} diff --git a/ciphersuite/registry.go b/ciphersuite/registry.go new file mode 100644 index 00000000..c31e9b37 --- /dev/null +++ b/ciphersuite/registry.go @@ -0,0 +1,160 @@ +package ciphersuite + +import ( + "io" + + "golang.org/x/xerrors" +) + +// Registry stores the cipher suites by name and provides the functions +// to unpack elements and use the cipher suite primitives. +type Registry struct { + ciphers map[Name]CipherSuite +} + +// NewRegistry creates a new empty registry. +func NewRegistry() *Registry { + return &Registry{ + ciphers: make(map[Name]CipherSuite), + } +} + +// RegisterCipherSuite stores the cipher if it does not exist. It returns the +// the suite stored for this name if it already exists, or it returns the +// provided suite. +func (cr *Registry) RegisterCipherSuite(suite CipherSuite) CipherSuite { + name := suite.Name() + if suite := cr.ciphers[name]; suite != nil { + // Cipher suite already registered so we return it so it can be reused. + return suite + } + + cr.ciphers[name] = suite + + return suite +} + +func (cr *Registry) get(name Name) (CipherSuite, error) { + c, _ := cr.ciphers[name] + if c == nil { + return nil, xerrors.New("cipher not found") + } + return c, nil +} + +// UnpackPublicKey takes generic cipher data and tries to convert it +// into a public key of the associated implementation. The cipher suite +// must be registered beforehand. +func (cr *Registry) UnpackPublicKey(raw *RawPublicKey) (PublicKey, error) { + c, err := cr.get(raw.Name()) + if err != nil { + return nil, xerrors.Errorf("cipher suite: %v", err) + } + + pk, err := c.PublicKey(raw) + if err != nil { + return nil, xerrors.Errorf("unpacking: %v", err) + } + + return pk, nil +} + +// UnpackSecretKey takes generic cipher data and tries to convert it +// into a secret key of the associated implementation. The cipher suite +// must be registered beforehand. +func (cr *Registry) UnpackSecretKey(raw *RawSecretKey) (SecretKey, error) { + c, err := cr.get(raw.Name()) + if err != nil { + return nil, xerrors.Errorf("cipher suite: %v", err) + } + + sk, err := c.SecretKey(raw) + if err != nil { + return nil, xerrors.Errorf("unpacking: %v", err) + } + + return sk, nil +} + +// UnpackSignature takes generic cipher data and tries to convert it +// into a signature of the associated implementation. The cipher suite +// must be registered beforehand. +func (cr *Registry) UnpackSignature(raw *RawSignature) (Signature, error) { + c, err := cr.get(raw.Name()) + if err != nil { + return nil, xerrors.Errorf("cipher suite: %v", err) + } + + sig, err := c.Signature(raw) + if err != nil { + return nil, xerrors.Errorf("unpacking: %v", err) + } + + return sig, nil +} + +// WithContext executes the fn by passing the cipher suite that is assigned +// to the nameable parameter. +func (cr *Registry) WithContext(n Nameable, fn func(CipherSuite) error) error { + suite, err := cr.get(n.Name()) + if err != nil { + return xerrors.Errorf("looking up cipher suite: %v", err) + } + + return fn(suite) +} + +// GenerateKeyPair returns a random secret key and its associated public key. This +// function will panic in case of error which means the cipher suite should +// be known and the default randomness should not trigger an error. If it +// happens, that means something is wrong with the configuration. +func (cr *Registry) GenerateKeyPair(name Name, reader io.Reader) (PublicKey, SecretKey, error) { + c, err := cr.get(name) + if err != nil { + return nil, nil, xerrors.Errorf("searching for cipher suite: %v", err) + } + + pk, sk, err := c.GenerateKeyPair(reader) + if err != nil { + return nil, nil, xerrors.Errorf("generating key pair: %v", err) + } + + return pk, sk, nil +} + +// Sign takes a secret key and a message and produces a signature. It will +// return an error if the signature is not known. +func (cr *Registry) Sign(sk SecretKey, msg []byte) (Signature, error) { + c, err := cr.get(sk.Name()) + if err != nil { + return nil, xerrors.Errorf("cipher suite: %v", err) + } + + sig, err := c.Sign(sk, msg) + if err != nil { + return nil, xerrors.Errorf("signing: %v", err) + } + + return sig, nil +} + +// Verify takes a public key, a signature and a message and performs a verification +// that will return an error if the signature does not match the message. It +// will also return an error if the cipher suite is unknown. +func (cr *Registry) Verify(pk PublicKey, sig Signature, msg []byte) error { + if pk.Name() != sig.Name() { + return xerrors.New("mismatching cipher names") + } + + c, err := cr.get(pk.Name()) + if err != nil { + return xerrors.Errorf("cipher suite: %v", err) + } + + err = c.Verify(pk, sig, msg) + if err != nil { + return xerrors.Errorf("verifying signature: %v", err) + } + + return nil +} diff --git a/ciphersuite/registry_test.go b/ciphersuite/registry_test.go new file mode 100644 index 00000000..8ade9103 --- /dev/null +++ b/ciphersuite/registry_test.go @@ -0,0 +1,204 @@ +package ciphersuite + +import ( + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" +) + +var anotherCipherSuiteName = "another_cipher_suite" + +type anotherCipherSuite struct { + *Ed25519CipherSuite +} + +func (a *anotherCipherSuite) Name() Name { + return anotherCipherSuiteName +} + +func (a *anotherCipherSuite) Sign(sk SecretKey, msg []byte) (Signature, error) { + return nil, xerrors.New("test error") +} + +type anotherSignature struct { + *Ed25519Signature +} + +func (s *anotherSignature) Name() Name { + return anotherCipherSuiteName +} + +// Test the basic usage of the registry. +func TestCipherRegistry_BasicUsage(t *testing.T) { + r := NewRegistry() + r.RegisterCipherSuite(NewEd25519CipherSuite()) + + pk, sk, err := r.GenerateKeyPair(Ed25519CipherSuiteName, nil) + require.NoError(t, err) + + sig, err := r.Sign(sk, []byte{1, 2, 3}) + require.NoError(t, err) + + err = r.Verify(pk.Raw(), sig, []byte{}) + require.Error(t, err) + + rawPk := pk.Raw() + rawPk.CipherName = anotherCipherSuiteName + rawSig := sig.Raw() + rawSig.CipherName = anotherCipherSuiteName + err = r.Verify(rawPk, rawSig, []byte{}) + require.Error(t, err) + require.Contains(t, err.Error(), "cipher suite:") + + err = r.Verify(pk, sig, []byte{1, 2, 3}) + require.NoError(t, err) +} + +func TestCipherRegistry_WithContext(t *testing.T) { + r := NewRegistry() + r.RegisterCipherSuite(NewEd25519CipherSuite()) + + pk, _, err := r.GenerateKeyPair(Ed25519CipherSuiteName, nil) + require.NoError(t, err) + + err = r.WithContext(pk, func(suite CipherSuite) error { + return nil + }) + require.NoError(t, err) + + errExample := xerrors.New("oops") + err = r.WithContext(pk, func(suite CipherSuite) error { + return errExample + }) + require.True(t, xerrors.Is(err, errExample)) + + err = r.WithContext(&anotherCipherSuite{}, func(suite CipherSuite) error { + return nil + }) + require.Error(t, err) +} + +func TestCipherRegistry_GenerateKeyPair(t *testing.T) { + r := NewRegistry() + + _, _, err := r.GenerateKeyPair(anotherCipherSuiteName, nil) + require.Error(t, err) + + r.RegisterCipherSuite(NewEd25519CipherSuite()) + _, _, err = r.GenerateKeyPair(Ed25519CipherSuiteName, &badReader{}) + require.Error(t, err) + + pk, sk, err := r.GenerateKeyPair(Ed25519CipherSuiteName, nil) + require.NoError(t, err) + require.NotNil(t, pk) + require.NotNil(t, sk) +} + +func TestCipherRegistry_FailingSignature(t *testing.T) { + r := NewRegistry() + r.RegisterCipherSuite(&anotherCipherSuite{}) + + _, sk, err := r.GenerateKeyPair(anotherCipherSuiteName, nil) + require.NoError(t, err) + + rawSk := sk.Raw() + rawSk.CipherName = anotherCipherSuiteName + + _, err = r.Sign(rawSk, []byte{}) + require.Error(t, err) + require.Contains(t, err.Error(), "signing:") +} + +func TestCipherRegistry_SuiteNotFound(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expect a panic") + } + }() + + r := NewRegistry() + r.RegisterCipherSuite(&anotherCipherSuite{}) + + ed := NewEd25519CipherSuite() + pk, sk, err := ed.GenerateKeyPair(nil) + require.NoError(t, err) + + sig, err := r.Sign(sk, []byte{}) + require.Error(t, err) + + err = r.Verify(pk, sig, []byte{}) + require.Error(t, err) + + r.GenerateKeyPair(Ed25519CipherSuiteName, nil) +} + +func TestCipherRegistry_InvalidType(t *testing.T) { + r := NewRegistry() + r.RegisterCipherSuite(NewEd25519CipherSuite()) + + ed := NewEd25519CipherSuite() + pk, _, err := ed.GenerateKeyPair(nil) + require.NoError(t, err) + + sig := &anotherSignature{} + err = r.Verify(pk, sig, []byte{}) + require.Error(t, err) + require.Contains(t, err.Error(), "mismatch") +} + +func TestCipherRegistry_Registration(t *testing.T) { + r := NewRegistry() + + suite := NewEd25519CipherSuite() + + r.RegisterCipherSuite(suite) + require.Equal(t, suite, r.RegisterCipherSuite(NewEd25519CipherSuite())) + require.NotEqual(t, suite, r.RegisterCipherSuite(&anotherCipherSuite{})) + + require.Equal(t, 2, len(r.ciphers)) +} + +func TestCipherRegistry_Unpack(t *testing.T) { + r := NewRegistry() + + pk := (&Ed25519PublicKey{data: []byte{}}).Raw() + _, err := r.UnpackPublicKey(pk) + require.Error(t, err) + sk := (&Ed25519SecretKey{data: []byte{}}).Raw() + _, err = r.UnpackSecretKey(sk) + require.Error(t, err) + sig := (&Ed25519Signature{data: []byte{}}).Raw() + _, err = r.UnpackSignature(sig) + require.Error(t, err) + + r.RegisterCipherSuite(NewEd25519CipherSuite()) + + pk.Data = []byte{} + _, err = r.UnpackPublicKey(pk) + require.Error(t, err) + + sk.Data = []byte{} + _, err = r.UnpackSecretKey(sk) + require.Error(t, err) + + sig.Data = []byte{} + _, err = r.UnpackSignature(sig) + require.Error(t, err) + + pk2, sk2, err := r.GenerateKeyPair(Ed25519CipherSuiteName, nil) + require.NoError(t, err) + pk2, err = r.UnpackPublicKey(pk2.Raw()) + require.NoError(t, err) + require.NotNil(t, pk2) + + sk2, err = r.UnpackSecretKey(sk2.Raw()) + require.NoError(t, err) + require.NotNil(t, sk2) + + sig2, err := r.Sign(sk2, []byte{}) + require.NoError(t, err) + sig2, err = r.UnpackSignature(sig2.Raw()) + require.NoError(t, err) + require.NotNil(t, sig2) +} diff --git a/go.mod b/go.mod index 9df87034..9b3268c6 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( go.dedis.ch/kyber/v4 v4.0.0-pre1 go.dedis.ch/protobuf v1.0.8 go.etcd.io/bbolt v1.3.3 + golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect golang.org/x/sys v0.0.0-20190124100055-b90733256f2e golang.org/x/text v0.3.2 // indirect