github.com/status-im/status-go@v1.1.0/protocol/encryption/x3dh.go (about)

     1  package encryption
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"errors"
     6  	"sort"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/status-im/status-go/eth-node/crypto"
    11  	"github.com/status-im/status-go/eth-node/crypto/ecies"
    12  )
    13  
    14  const (
    15  	// Shared secret key length
    16  	sskLen = 16
    17  )
    18  
    19  func buildSignatureMaterial(bundle *Bundle) []byte {
    20  	signedPreKeys := bundle.GetSignedPreKeys()
    21  	timestamp := bundle.GetTimestamp()
    22  	var keys []string
    23  
    24  	for k := range signedPreKeys {
    25  		keys = append(keys, k)
    26  	}
    27  	var signatureMaterial []byte
    28  
    29  	sort.Strings(keys)
    30  
    31  	for _, installationID := range keys {
    32  		signedPreKey := signedPreKeys[installationID]
    33  		signatureMaterial = append(signatureMaterial, []byte(installationID)...)
    34  		signatureMaterial = append(signatureMaterial, signedPreKey.SignedPreKey...)
    35  		signatureMaterial = append(signatureMaterial, []byte(strconv.FormatUint(uint64(signedPreKey.Version), 10))...)
    36  		// We don't use timestamp in the signature if it's 0, for backward compatibility
    37  	}
    38  
    39  	if timestamp != 0 {
    40  		signatureMaterial = append(signatureMaterial, []byte(strconv.FormatInt(timestamp, 10))...)
    41  	}
    42  
    43  	return signatureMaterial
    44  
    45  }
    46  
    47  // SignBundle signs the bundle and refreshes the timestamps
    48  func SignBundle(identity *ecdsa.PrivateKey, bundleContainer *BundleContainer) error {
    49  	bundleContainer.Bundle.Timestamp = time.Now().UnixNano()
    50  	signatureMaterial := buildSignatureMaterial(bundleContainer.GetBundle())
    51  
    52  	signature, err := crypto.Sign(crypto.Keccak256(signatureMaterial), identity)
    53  	if err != nil {
    54  		return err
    55  	}
    56  	bundleContainer.Bundle.Signature = signature
    57  	return nil
    58  }
    59  
    60  // NewBundleContainer creates a new BundleContainer from an identity private key
    61  func NewBundleContainer(identity *ecdsa.PrivateKey, installationID string) (*BundleContainer, error) {
    62  	preKey, err := crypto.GenerateKey()
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	compressedPreKey := crypto.CompressPubkey(&preKey.PublicKey)
    68  	compressedIdentityKey := crypto.CompressPubkey(&identity.PublicKey)
    69  
    70  	encodedPreKey := crypto.FromECDSA(preKey)
    71  	signedPreKeys := make(map[string]*SignedPreKey)
    72  	signedPreKeys[installationID] = &SignedPreKey{
    73  		ProtocolVersion: protocolVersion,
    74  		SignedPreKey:    compressedPreKey,
    75  	}
    76  
    77  	bundle := Bundle{
    78  		Timestamp:     time.Now().UnixNano(),
    79  		Identity:      compressedIdentityKey,
    80  		SignedPreKeys: signedPreKeys,
    81  	}
    82  
    83  	return &BundleContainer{
    84  		Bundle:              &bundle,
    85  		PrivateSignedPreKey: encodedPreKey,
    86  	}, nil
    87  }
    88  
    89  // VerifyBundle checks that a bundle is valid
    90  func VerifyBundle(bundle *Bundle) error {
    91  	_, err := ExtractIdentity(bundle)
    92  	return err
    93  }
    94  
    95  // ExtractIdentity extracts the identity key from a given bundle
    96  func ExtractIdentity(bundle *Bundle) (*ecdsa.PublicKey, error) {
    97  	bundleIdentityKey, err := crypto.DecompressPubkey(bundle.GetIdentity())
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	signatureMaterial := buildSignatureMaterial(bundle)
   103  
   104  	recoveredKey, err := crypto.SigToPub(
   105  		crypto.Keccak256(signatureMaterial),
   106  		bundle.GetSignature(),
   107  	)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	if crypto.PubkeyToAddress(*recoveredKey) != crypto.PubkeyToAddress(*bundleIdentityKey) {
   113  		return nil, errors.New("identity key and signature mismatch")
   114  	}
   115  
   116  	return recoveredKey, nil
   117  }
   118  
   119  // PerformDH generates a shared key given a private and a public key
   120  func PerformDH(privateKey *ecies.PrivateKey, publicKey *ecies.PublicKey) ([]byte, error) {
   121  	return privateKey.GenerateShared(
   122  		publicKey,
   123  		sskLen,
   124  		sskLen,
   125  	)
   126  }
   127  
   128  func getSharedSecret(dh1 []byte, dh2 []byte, dh3 []byte) []byte {
   129  	secretInput := append(append(dh1, dh2...), dh3...)
   130  
   131  	return crypto.Keccak256(secretInput)
   132  }
   133  
   134  // x3dhActive handles initiating an X3DH session
   135  func x3dhActive(
   136  	myIdentityKey *ecies.PrivateKey,
   137  	theirSignedPreKey *ecies.PublicKey,
   138  	myEphemeralKey *ecies.PrivateKey,
   139  	theirIdentityKey *ecies.PublicKey,
   140  ) ([]byte, error) {
   141  	var dh1, dh2, dh3 []byte
   142  	var err error
   143  
   144  	if dh1, err = PerformDH(myIdentityKey, theirSignedPreKey); err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	if dh2, err = PerformDH(myEphemeralKey, theirIdentityKey); err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	if dh3, err = PerformDH(myEphemeralKey, theirSignedPreKey); err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	return getSharedSecret(dh1, dh2, dh3), nil
   157  }
   158  
   159  // x3dhPassive handles the response to an initiated X3DH session
   160  func x3dhPassive(
   161  	theirIdentityKey *ecies.PublicKey,
   162  	mySignedPreKey *ecies.PrivateKey,
   163  	theirEphemeralKey *ecies.PublicKey,
   164  	myIdentityKey *ecies.PrivateKey,
   165  ) ([]byte, error) {
   166  	var dh1, dh2, dh3 []byte
   167  	var err error
   168  
   169  	if dh1, err = PerformDH(mySignedPreKey, theirIdentityKey); err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	if dh2, err = PerformDH(myIdentityKey, theirEphemeralKey); err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	if dh3, err = PerformDH(mySignedPreKey, theirEphemeralKey); err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	return getSharedSecret(dh1, dh2, dh3), nil
   182  }
   183  
   184  // PerformActiveDH performs a Diffie-Hellman exchange using a public key and a generated ephemeral key.
   185  // Returns the key resulting from the DH exchange as well as the ephemeral public key.
   186  func PerformActiveDH(publicKey *ecdsa.PublicKey) ([]byte, *ecdsa.PublicKey, error) {
   187  	ephemeralKey, err := crypto.GenerateKey()
   188  	if err != nil {
   189  		return nil, nil, err
   190  	}
   191  
   192  	key, err := PerformDH(
   193  		ecies.ImportECDSA(ephemeralKey),
   194  		ecies.ImportECDSAPublic(publicKey),
   195  	)
   196  	if err != nil {
   197  		return nil, nil, err
   198  	}
   199  
   200  	return key, &ephemeralKey.PublicKey, err
   201  }
   202  
   203  // PerformActiveX3DH takes someone else's bundle and calculates shared secret.
   204  // Returns the shared secret and the ephemeral key used.
   205  func PerformActiveX3DH(identity []byte, signedPreKey []byte, prv *ecdsa.PrivateKey) ([]byte, *ecdsa.PublicKey, error) {
   206  	bundleIdentityKey, err := crypto.DecompressPubkey(identity)
   207  	if err != nil {
   208  		return nil, nil, err
   209  	}
   210  
   211  	bundleSignedPreKey, err := crypto.DecompressPubkey(signedPreKey)
   212  	if err != nil {
   213  		return nil, nil, err
   214  	}
   215  
   216  	ephemeralKey, err := crypto.GenerateKey()
   217  	if err != nil {
   218  		return nil, nil, err
   219  	}
   220  
   221  	sharedSecret, err := x3dhActive(
   222  		ecies.ImportECDSA(prv),
   223  		ecies.ImportECDSAPublic(bundleSignedPreKey),
   224  		ecies.ImportECDSA(ephemeralKey),
   225  		ecies.ImportECDSAPublic(bundleIdentityKey),
   226  	)
   227  	if err != nil {
   228  		return nil, nil, err
   229  	}
   230  
   231  	return sharedSecret, &ephemeralKey.PublicKey, nil
   232  }
   233  
   234  // PerformPassiveX3DH handles the part of the protocol where
   235  // our interlocutor used our bundle, with ID of the signedPreKey,
   236  // we loaded our identity key and the correct signedPreKey and we perform X3DH
   237  func PerformPassiveX3DH(theirIdentityKey *ecdsa.PublicKey, mySignedPreKey *ecdsa.PrivateKey, theirEphemeralKey *ecdsa.PublicKey, myPrivateKey *ecdsa.PrivateKey) ([]byte, error) {
   238  	sharedSecret, err := x3dhPassive(
   239  		ecies.ImportECDSAPublic(theirIdentityKey),
   240  		ecies.ImportECDSA(mySignedPreKey),
   241  		ecies.ImportECDSAPublic(theirEphemeralKey),
   242  		ecies.ImportECDSA(myPrivateKey),
   243  	)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	return sharedSecret, nil
   249  }