status-go/protocol/encryption/sharedsecret/sharedsecret.go

129 lines
3.5 KiB
Go

package sharedsecret
import (
"bytes"
"crypto/ecdsa"
"database/sql"
"errors"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/crypto/ecies"
)
const sskLen = 16
type Secret struct {
Identity *ecdsa.PublicKey
Key []byte
}
// SharedSecret generates and manages negotiated secrets.
// Identities (public keys) stored by SharedSecret
// are compressed.
// TODO: make compression of public keys a responsibility of sqlitePersistence instead of SharedSecret.
type SharedSecret struct {
persistence *sqlitePersistence
logger *zap.Logger
}
func New(db *sql.DB, logger *zap.Logger) *SharedSecret {
if logger == nil {
logger = zap.NewNop()
}
return &SharedSecret{
persistence: newSQLitePersistence(db),
logger: logger.With(zap.Namespace("SharedSecret")),
}
}
func (s *SharedSecret) generate(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
sharedKey, err := ecies.ImportECDSA(myPrivateKey).GenerateShared(
ecies.ImportECDSAPublic(theirPublicKey),
sskLen,
sskLen,
)
if err != nil {
return nil, err
}
logger := s.logger.With(zap.String("site", "generate"))
logger.Debug(
"saving a shared key",
zap.Binary("their-public-key", crypto.FromECDSAPub(theirPublicKey)),
zap.String("installation-id", installationID),
)
theirIdentity := crypto.CompressPubkey(theirPublicKey)
if err = s.persistence.Add(theirIdentity, sharedKey, installationID); err != nil {
return nil, err
}
return &Secret{Key: sharedKey, Identity: theirPublicKey}, err
}
// Generate will generate a shared secret for a given identity, and return it.
func (s *SharedSecret) Generate(myPrivateKey *ecdsa.PrivateKey, theirPublicKey *ecdsa.PublicKey, installationID string) (*Secret, error) {
return s.generate(myPrivateKey, theirPublicKey, installationID)
}
// Agreed returns true if a secret has been acknowledged by all the installationIDs.
func (s *SharedSecret) Agreed(myPrivateKey *ecdsa.PrivateKey, myInstallationID string, theirPublicKey *ecdsa.PublicKey, theirInstallationIDs []string) (*Secret, bool, error) {
logger := s.logger.With(zap.String("site", "Agreed"))
logger.Debug(
"checking if shared secret is acknowledged",
zap.Binary("their-public-key", crypto.FromECDSAPub(theirPublicKey)),
zap.Strings("their-installation-ids", theirInstallationIDs),
)
secret, err := s.generate(myPrivateKey, theirPublicKey, myInstallationID)
if err != nil {
return nil, false, err
}
if len(theirInstallationIDs) == 0 {
return secret, false, nil
}
theirIdentity := crypto.CompressPubkey(theirPublicKey)
response, err := s.persistence.Get(theirIdentity, theirInstallationIDs)
if err != nil {
return nil, false, err
}
for _, installationID := range theirInstallationIDs {
if !response.installationIDs[installationID] {
logger.Debug("no shared secret for installation", zap.String("installation-id", installationID))
return secret, false, nil
}
}
if !bytes.Equal(secret.Key, response.secret) {
return nil, false, errors.New("computed and saved secrets are different for a given identity")
}
return secret, true, nil
}
func (s *SharedSecret) All() ([]*Secret, error) {
var secrets []*Secret
tuples, err := s.persistence.All()
if err != nil {
return nil, err
}
for _, tuple := range tuples {
key, err := crypto.DecompressPubkey(tuple[0])
if err != nil {
return nil, err
}
secrets = append(secrets, &Secret{Identity: key, Key: tuple[1]})
}
return secrets, nil
}