ceremonyclient/node/crypto/doubleratchet.go
2023-08-20 23:07:43 -05:00

453 lines
13 KiB
Go

package crypto
// Uncomment with full release
// import (
// "crypto/aes"
// "crypto/cipher"
// "crypto/hmac"
// "crypto/rand"
// "crypto/sha512"
// "crypto/subtle"
// "encoding/binary"
// "github.com/pkg/errors"
// "golang.org/x/crypto/hkdf"
// "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
// "source.quilibrium.com/quilibrium/monorepo/node/keys"
// "source.quilibrium.com/quilibrium/monorepo/node/protobufs"
// )
// const PROTOCOL_VERSION = 1
// const PROTOCOL = 1<<8 + PROTOCOL_VERSION
// const CHAIN_KEY = 0x01
// const MESSAGE_KEY = 0x02
// const AEAD_KEY = 0x03
// // Note: If an HSM with raw primitive access becomes available, the raw crypto
// // mechanisms should be refactored into calls in KeyManager and implemented
// // through the driver
// type DoubleRatchetParticipant struct {
// sendingEphemeralPrivateKey curves.Scalar
// receivingEphemeralKey curves.Point
// curve curves.Curve
// keyManager keys.KeyManager
// rootKey []byte
// sendingChainKey []byte
// currentSendingHeaderKey []byte
// currentReceivingHeaderKey []byte
// nextSendingHeaderKey []byte
// nextReceivingHeaderKey []byte
// receivingChainKey []byte
// currentSendingChainLength uint32
// previousSendingChainLength uint32
// currentReceivingChainLength uint32
// previousReceivingChainLength uint32
// skippedKeysMap map[string]map[uint32][]byte
// }
// func NewDoubleRatchetParticipant(
// sessionKey []byte,
// sendingHeaderKey []byte,
// nextReceivingHeaderKey []byte,
// isSender bool,
// sendingEphemeralPrivateKey curves.Scalar,
// receivingEphemeralKey curves.Point,
// curve curves.Curve,
// keyManager keys.KeyManager,
// ) (*DoubleRatchetParticipant, error) {
// participant := &DoubleRatchetParticipant{}
// participant.sendingEphemeralPrivateKey = sendingEphemeralPrivateKey
// participant.skippedKeysMap = make(map[string]map[uint32][]byte)
// participant.keyManager = keyManager
// participant.currentSendingChainLength = 0
// participant.previousSendingChainLength = 0
// participant.currentReceivingChainLength = 0
// participant.previousReceivingChainLength = 0
// if sendingEphemeralPrivateKey.Point().CurveName() !=
// receivingEphemeralKey.CurveName() || receivingEphemeralKey.CurveName() !=
// curve.Name {
// return nil, errors.New("curve mismatch")
// }
// participant.curve = curve
// if isSender {
// hash := hkdf.New(
// sha512.New,
// receivingEphemeralKey.Mul(
// sendingEphemeralPrivateKey,
// ).ToAffineCompressed(),
// sessionKey,
// []byte("quilibrium-double-ratchet"),
// )
// rkck := make([]byte, 96)
// if _, err := hash.Read(rkck[:]); err != nil {
// return nil, errors.Wrap(err, "failed establishing root key")
// }
// participant.currentSendingHeaderKey = sendingHeaderKey
// participant.nextReceivingHeaderKey = nextReceivingHeaderKey
// participant.rootKey = rkck[:32]
// participant.sendingChainKey = rkck[32:64]
// participant.nextSendingHeaderKey = rkck[64:96]
// participant.receivingEphemeralKey = receivingEphemeralKey
// } else {
// participant.rootKey = sessionKey
// participant.nextReceivingHeaderKey = sendingHeaderKey
// participant.nextSendingHeaderKey = nextReceivingHeaderKey
// }
// return participant, nil
// }
// func (r *DoubleRatchetParticipant) RatchetEncrypt(
// message []byte,
// ) (*protobufs.P2PChannelEnvelope, error) {
// envelope := &protobufs.P2PChannelEnvelope{
// ProtocolIdentifier: PROTOCOL,
// MessageHeader: &protobufs.MessageCiphertext{},
// MessageBody: &protobufs.MessageCiphertext{},
// }
// newChainKey, messageKey, aeadKey := ratchetKeys(r.sendingChainKey)
// r.sendingChainKey = newChainKey
// var err error
// header := r.encodeHeader()
// envelope.MessageHeader, err = r.encrypt(
// header,
// r.currentSendingHeaderKey,
// nil,
// )
// if err != nil {
// return nil, errors.Wrap(err, "could not encrypt header")
// }
// envelope.MessageBody, err = r.encrypt(
// message,
// messageKey,
// append(append([]byte{}, aeadKey...), envelope.MessageHeader.Ciphertext...),
// )
// if err != nil {
// return nil, errors.Wrap(err, "could not encrypt message")
// }
// r.currentSendingChainLength++
// return envelope, nil
// }
// func (r *DoubleRatchetParticipant) RatchetDecrypt(
// envelope *protobufs.P2PChannelEnvelope,
// ) ([]byte, error) {
// plaintext, err := r.trySkippedMessageKeys(envelope)
// if err != nil {
// return nil, errors.Wrap(err, "could not decrypt from matching skipped key")
// }
// if plaintext != nil {
// return plaintext, nil
// }
// header, shouldRatchet, err := r.decryptHeader(
// envelope.MessageHeader,
// r.currentReceivingHeaderKey,
// )
// if err != nil {
// return nil, errors.Wrap(err, "could not decrypt header")
// }
// receivingEphemeralKey,
// previousReceivingChainLength,
// currentReceivingChainLength,
// err := r.decodeHeader(header)
// if err != nil {
// return nil, errors.Wrap(err, "could not decode header")
// }
// if shouldRatchet {
// if err := r.skipMessageKeys(previousReceivingChainLength); err != nil {
// return nil, errors.Wrap(err, "could not skip previous message keys")
// }
// if err := r.ratchetEphemeralKeys(receivingEphemeralKey); err != nil {
// return nil, errors.Wrap(err, "could not ratchet ephemeral keys")
// }
// }
// if err := r.skipMessageKeys(currentReceivingChainLength); err != nil {
// return nil, errors.Wrap(err, "could not skip message keys")
// }
// newChainKey, messageKey, aeadKey := ratchetKeys(r.receivingChainKey)
// r.receivingChainKey = newChainKey
// r.currentReceivingChainLength++
// plaintext, err = r.decrypt(
// envelope.MessageBody,
// messageKey,
// append(
// append([]byte{}, aeadKey...),
// envelope.MessageHeader.Ciphertext...,
// ),
// )
// return plaintext, errors.Wrap(err, "could not decrypt message")
// }
// func (r *DoubleRatchetParticipant) ratchetEphemeralKeys(
// newReceivingEphemeralKey curves.Point,
// ) error {
// r.previousSendingChainLength = r.currentSendingChainLength
// r.currentSendingChainLength = 0
// r.currentReceivingChainLength = 0
// r.currentSendingHeaderKey = r.nextSendingHeaderKey
// r.currentReceivingHeaderKey = r.nextReceivingHeaderKey
// r.receivingEphemeralKey = newReceivingEphemeralKey
// hash := hkdf.New(
// sha512.New,
// newReceivingEphemeralKey.Mul(
// r.sendingEphemeralPrivateKey,
// ).ToAffineCompressed(),
// r.rootKey,
// []byte("quilibrium-double-ratchet"),
// )
// rkck := make([]byte, 96)
// if _, err := hash.Read(rkck[:]); err != nil {
// return errors.Wrap(err, "failed ratcheting root key")
// }
// r.rootKey = rkck[:32]
// r.receivingChainKey = rkck[32:64]
// r.nextReceivingHeaderKey = rkck[64:]
// r.sendingEphemeralPrivateKey = r.curve.NewScalar().Random(rand.Reader)
// hash = hkdf.New(
// sha512.New,
// newReceivingEphemeralKey.Mul(
// r.sendingEphemeralPrivateKey,
// ).ToAffineCompressed(),
// r.rootKey,
// []byte("quilibrium-double-ratchet"),
// )
// rkck2 := make([]byte, 96)
// if _, err := hash.Read(rkck2[:]); err != nil {
// return errors.Wrap(err, "failed ratcheting root key")
// }
// r.rootKey = rkck2[:32]
// r.sendingChainKey = rkck2[32:64]
// r.nextSendingHeaderKey = rkck2[64:]
// return nil
// }
// func (r *DoubleRatchetParticipant) trySkippedMessageKeys(
// envelope *protobufs.P2PChannelEnvelope,
// ) ([]byte, error) {
// for receivingHeaderKey, skippedKeys := range r.skippedKeysMap {
// header, _, err := r.decryptHeader(
// envelope.MessageHeader,
// []byte(receivingHeaderKey),
// )
// if err == nil {
// _, _, current, err := r.decodeHeader(header)
// if err != nil {
// return nil, errors.Wrap(err, "malformed header")
// }
// messageKey := skippedKeys[current][:32]
// aeadKey := skippedKeys[current][32:]
// plaintext, err := r.decrypt(
// envelope.MessageBody,
// messageKey,
// append(
// append([]byte{}, aeadKey...),
// envelope.MessageHeader.Ciphertext[:]...,
// ),
// )
// if err != nil {
// return nil, errors.Wrap(err, "could not decrypt from skipped key")
// }
// delete(r.skippedKeysMap[receivingHeaderKey], current)
// if len(r.skippedKeysMap[receivingHeaderKey]) == 0 {
// delete(r.skippedKeysMap, receivingHeaderKey)
// }
// return plaintext, nil
// }
// }
// return nil, nil
// }
// func (r *DoubleRatchetParticipant) skipMessageKeys(until uint32) error {
// if r.currentReceivingChainLength+100 < until {
// return errors.New("skip limit exceeded")
// }
// if r.receivingChainKey != nil {
// for r.currentReceivingChainLength < until {
// newChainKey, messageKey, aeadKey := ratchetKeys(r.receivingChainKey)
// skippedKeys := r.skippedKeysMap[string(r.currentReceivingHeaderKey)]
// if skippedKeys == nil {
// r.skippedKeysMap[string(r.currentReceivingHeaderKey)] =
// make(map[uint32][]byte)
// }
// skippedKeys[r.currentReceivingChainLength] = append(
// append([]byte{}, messageKey...),
// aeadKey...,
// )
// r.receivingChainKey = newChainKey
// r.currentReceivingChainLength++
// }
// }
// return nil
// }
// func (r *DoubleRatchetParticipant) encodeHeader() []byte {
// header := []byte{}
// header = append(
// header,
// r.curve.NewGeneratorPoint().Mul(
// r.sendingEphemeralPrivateKey,
// ).ToAffineCompressed()[:]...,
// )
// header = binary.BigEndian.AppendUint32(header, r.previousSendingChainLength)
// header = binary.BigEndian.AppendUint32(header, r.currentSendingChainLength)
// return header
// }
// func (r *DoubleRatchetParticipant) decryptHeader(
// ciphertext *protobufs.MessageCiphertext,
// receivingHeaderKey []byte,
// ) ([]byte, bool, error) {
// header, err := r.decrypt(
// ciphertext,
// receivingHeaderKey,
// nil,
// )
// if err != nil && subtle.ConstantTimeCompare(
// r.currentReceivingHeaderKey,
// receivingHeaderKey,
// ) == 1 {
// if header, err = r.decrypt(
// ciphertext,
// r.nextReceivingHeaderKey,
// nil,
// ); err != nil {
// return nil, false, errors.Wrap(err, "could not decrypt header")
// }
// return header, true, nil
// }
// return header, false, errors.Wrap(err, "could not decrypt header")
// }
// func (r *DoubleRatchetParticipant) decodeHeader(
// header []byte,
// ) (curves.Point, uint32, uint32, error) {
// if len(header) < 9 {
// return nil, 0, 0, errors.New("malformed header")
// }
// currentReceivingChainLength := binary.BigEndian.Uint32(header[len(header)-4:])
// previousReceivingChainLength := binary.BigEndian.Uint32(
// header[len(header)-8 : len(header)-4],
// )
// receivingEphemeralKeyBytes := header[:len(header)-8]
// receivingEphemeralKey, err := r.curve.Point.FromAffineCompressed(
// receivingEphemeralKeyBytes,
// )
// return receivingEphemeralKey,
// previousReceivingChainLength,
// currentReceivingChainLength,
// errors.Wrap(err, "could not decode receiving dh key")
// }
// func (r *DoubleRatchetParticipant) encrypt(
// plaintext []byte,
// key []byte,
// associatedData []byte,
// ) (*protobufs.MessageCiphertext, error) {
// iv := [12]byte{}
// rand.Read(iv[:])
// aesCipher, err := aes.NewCipher(key)
// if err != nil {
// return nil, errors.Wrap(err, "could not construct cipher")
// }
// gcm, err := cipher.NewGCM(aesCipher)
// if err != nil {
// return nil, errors.Wrap(err, "could not construct block")
// }
// ciphertext := &protobufs.MessageCiphertext{}
// if associatedData == nil {
// associatedData = make([]byte, 32)
// if _, err := rand.Read(associatedData); err != nil {
// return nil, errors.Wrap(err, "could not obtain entropy")
// }
// ciphertext.AssociatedData = associatedData
// }
// ciphertext.Ciphertext = gcm.Seal(nil, iv[:], plaintext, associatedData)
// ciphertext.InitializationVector = iv[:]
// return ciphertext, nil
// }
// func (r *DoubleRatchetParticipant) decrypt(
// ciphertext *protobufs.MessageCiphertext,
// key []byte,
// associatedData []byte,
// ) ([]byte, error) {
// if associatedData == nil {
// associatedData = ciphertext.AssociatedData
// }
// aesCipher, err := aes.NewCipher(key)
// if err != nil {
// return nil, errors.Wrap(err, "could not construct cipher")
// }
// gcm, err := cipher.NewGCM(aesCipher)
// if err != nil {
// return nil, errors.Wrap(err, "could not construct block")
// }
// plaintext, err := gcm.Open(
// nil,
// ciphertext.InitializationVector,
// ciphertext.Ciphertext,
// associatedData,
// )
// return plaintext, errors.Wrap(err, "could not decrypt ciphertext")
// }
// func ratchetKeys(inputKey []byte) ([]byte, []byte, []byte) {
// buf := hmac.New(sha512.New, inputKey)
// buf.Write([]byte{AEAD_KEY})
// aeadKey := buf.Sum(nil)
// buf.Reset()
// buf.Write([]byte{MESSAGE_KEY})
// messageKey := buf.Sum(nil)
// buf.Reset()
// buf.Write([]byte{CHAIN_KEY})
// chainKey := buf.Sum(nil)
// return chainKey[:32], messageKey[:32], aeadKey[:32]
// }