128 lines
3.5 KiB
Go
128 lines
3.5 KiB
Go
package sharedsecret
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"database/sql"
|
|
"errors"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/crypto/ecies"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
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
|
|
}
|