237 lines
10 KiB
Go
237 lines
10 KiB
Go
|
|
// createMessages provides functions to create Seekia chat messages
|
|
// The chat message structure will eventually be documented in /documentation/Specification.md
|
|
|
|
package createMessages
|
|
|
|
//TODO: Add an upper limit on bytes, and an upper limit for size of communication.
|
|
//TODO: Verify all message values and function inputs.
|
|
|
|
import "seekia/internal/allowedText"
|
|
import "seekia/internal/appValues"
|
|
import "seekia/internal/cryptography/blake3"
|
|
import "seekia/internal/cryptography/chaPolyShrink"
|
|
import "seekia/internal/cryptography/edwardsKeys"
|
|
import "seekia/internal/cryptography/kyber"
|
|
import "seekia/internal/cryptography/nacl"
|
|
import "seekia/internal/encoding"
|
|
import "seekia/internal/helpers"
|
|
import "seekia/internal/identity"
|
|
|
|
import messagepack "github.com/vmihailenco/msgpack/v5"
|
|
|
|
import "slices"
|
|
import "errors"
|
|
|
|
|
|
//Outputs:
|
|
// -[]byte: Message bytes
|
|
// -[26]byte: Message Hash
|
|
// -error
|
|
func CreateChatMessage(
|
|
networkType byte,
|
|
senderIdentityHash [16]byte,
|
|
senderIdentityPublicKey [32]byte,
|
|
senderIdentityPrivateKey [64]byte,
|
|
messageCreationTime int64,
|
|
senderCurrentSecretInboxSeed [22]byte,
|
|
senderNextSecretInboxSeed [22]byte,
|
|
senderDeviceIdentifier [11]byte,
|
|
senderLatestChatKeysUpdateTime int64,
|
|
communication string,
|
|
recipientIdentityHash [16]byte,
|
|
recipientInbox [10]byte,
|
|
recipientNaclKey [32]byte,
|
|
recipientKyberKey [1568]byte,
|
|
doubleSealedKeysSealerKey [32]byte)([]byte, [26]byte, error){
|
|
|
|
// We create the basaldataEncryptionKey, messageCipherKey, and messageCipherKeyHash
|
|
|
|
basaldataEncryptionKey, err := helpers.GetNewRandom32ByteArray()
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
messageCipherKey, err := blake3.Get32ByteBlake3Hash(basaldataEncryptionKey[:])
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
cipherKeyHash, err := blake3.GetBlake3HashAsBytes(25, messageCipherKey[:])
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
// We create the noncipheredSection, which contains the message inbox, message version, network type, cipherKeyHash, and the doubleSealedKeys
|
|
|
|
keyPieceA, err := helpers.GetNewRandom32ByteArray()
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
keyPieceB := helpers.XORTwo32ByteArrays(keyPieceA, basaldataEncryptionKey)
|
|
|
|
naclEncryptedKeyPieceA, err := nacl.EncryptKeyWithNacl(recipientNaclKey, keyPieceA)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
kyberEncryptedKeyPieceB, err := kyber.EncryptKeyWithKyber(recipientKyberKey, keyPieceB)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
innerSealedKeyPiecesList := slices.Concat(naclEncryptedKeyPieceA[:], kyberEncryptedKeyPieceB[:])
|
|
|
|
doubleSealedKeysNonce, err := helpers.GetNewRandom24ByteArray()
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
doubleSealedKeys, err := chaPolyShrink.EncryptChaPolyShrink(innerSealedKeyPiecesList, doubleSealedKeysSealerKey, doubleSealedKeysNonce, false, 0, false, [32]byte{})
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
// We create the DoubleSealedKeys to store the encryption keys for the cipheredMessage and the basaldata
|
|
|
|
messageVersion := appValues.GetMessageVersion()
|
|
|
|
cipheredMessageNonce, err := helpers.GetNewRandom24ByteArray()
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
messageVersionEncoded, err := encoding.EncodeMessagePackBytes(messageVersion)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
networkTypeEncoded, err := encoding.EncodeMessagePackBytes(networkType)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
recipientInboxEncoded, err := encoding.EncodeMessagePackBytes(recipientInbox)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
doubleSealedKeysNonceEncoded, err := encoding.EncodeMessagePackBytes(doubleSealedKeysNonce)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
doubleSealedKeysEncoded, err := encoding.EncodeMessagePackBytes(doubleSealedKeys)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
cipherKeyHashEncoded, err := encoding.EncodeMessagePackBytes(cipherKeyHash)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
cipheredMessageNonceEncoded, err := encoding.EncodeMessagePackBytes(cipheredMessageNonce)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
noncipheredSectionSlice := []messagepack.RawMessage{messageVersionEncoded, networkTypeEncoded, recipientInboxEncoded, doubleSealedKeysNonceEncoded, doubleSealedKeysEncoded, cipherKeyHashEncoded, cipheredMessageNonceEncoded}
|
|
|
|
noncipheredSectionEncoded, err := encoding.EncodeMessagePackBytes(noncipheredSectionSlice)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
// The purpose of the noncipheredSectionHash is to prevent someone from tampering with the message and replaying the ciphered portion
|
|
// By using the noncipheredSectionHash as the additionalData, the noncipheredSection cannot be tampered with
|
|
|
|
noncipheredSectionHash, err := blake3.Get32ByteBlake3Hash(noncipheredSectionEncoded)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
// We create the inner message
|
|
|
|
timeIsValid := helpers.VerifyCreationTime(messageCreationTime)
|
|
if (timeIsValid == false){
|
|
return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid messageCreationTime.")
|
|
}
|
|
timeIsValid = helpers.VerifyCreationTime(senderLatestChatKeysUpdateTime)
|
|
if (timeIsValid == false){
|
|
return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid senderLatestChatKeysUpdateTime")
|
|
}
|
|
|
|
recipientIdentityType, err := identity.GetIdentityTypeFromIdentityHash(recipientIdentityHash)
|
|
if (err != nil){
|
|
recipientIdentityHashHex := encoding.EncodeBytesToHexString(recipientIdentityHash[:])
|
|
return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid recipientIdentityHash: " + recipientIdentityHashHex)
|
|
}
|
|
|
|
senderIdentityHashExpected, err := identity.ConvertIdentityKeyToIdentityHash(senderIdentityPublicKey, recipientIdentityType)
|
|
if (err != nil) {
|
|
senderIdentityPublicKeyHex := encoding.EncodeBytesToHexString(senderIdentityPublicKey[:])
|
|
return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid senderIdentityPublicKey: " + senderIdentityPublicKeyHex)
|
|
}
|
|
|
|
if (senderIdentityHash != senderIdentityHashExpected){
|
|
senderIdentityHashHex := encoding.EncodeBytesToHexString(senderIdentityHash[:])
|
|
return nil, [26]byte{}, errors.New("CreateChatMessage called with invalid senderIdentityHash: " + senderIdentityHashHex)
|
|
}
|
|
|
|
recipientIdentityHashEncoded, err := encoding.EncodeMessagePackBytes(recipientIdentityHash)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
creationTimeEncoded, err := encoding.EncodeMessagePackBytes(messageCreationTime)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
senderCurrentSecretInboxSeedEncoded, err := encoding.EncodeMessagePackBytes(senderCurrentSecretInboxSeed)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
senderNextSecretInboxSeedEncoded, err := encoding.EncodeMessagePackBytes(senderNextSecretInboxSeed)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
senderDeviceIdentifierEncoded, err := encoding.EncodeMessagePackBytes(senderDeviceIdentifier)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
senderLatestChatKeysUpdateTimeEncoded, err := encoding.EncodeMessagePackBytes(senderLatestChatKeysUpdateTime)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
messageBasaldataSlice := []messagepack.RawMessage{recipientIdentityHashEncoded, creationTimeEncoded, senderCurrentSecretInboxSeedEncoded, senderNextSecretInboxSeedEncoded, senderDeviceIdentifierEncoded, senderLatestChatKeysUpdateTimeEncoded}
|
|
|
|
messageBasaldataEncoded, err := encoding.EncodeMessagePackBytes(messageBasaldataSlice)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
basaldataEncryptionNonce, err := helpers.GetNewRandom24ByteArray()
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
encryptedBasaldata, err := chaPolyShrink.EncryptChaPolyShrink(messageBasaldataEncoded, basaldataEncryptionKey, basaldataEncryptionNonce, false, 0, false, [32]byte{})
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
communicationIsAllowed := allowedText.VerifyStringIsAllowed(communication)
|
|
if (communicationIsAllowed == false){
|
|
return nil, [26]byte{}, errors.New("CreateChatMessage called with communication containing unallowed characters.")
|
|
}
|
|
|
|
//TODO: Add length restriction to communication
|
|
|
|
senderIdentityKeyEncoded, err := encoding.EncodeMessagePackBytes(senderIdentityPublicKey)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
senderIdentityTypeEncoded, err := encoding.EncodeMessagePackBytes(recipientIdentityType)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
basaldataEncryptionNonceEncoded, err := encoding.EncodeMessagePackBytes(basaldataEncryptionNonce)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
encryptedBasaldataEncoded, err := encoding.EncodeMessagePackBytes(encryptedBasaldata)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
communicationEncoded, err := encoding.EncodeMessagePackBytes(communication)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
innerMessageContentSlice := []messagepack.RawMessage{senderIdentityKeyEncoded, senderIdentityTypeEncoded, basaldataEncryptionNonceEncoded, encryptedBasaldataEncoded, communicationEncoded}
|
|
|
|
innerMessageContentEncoded, err := encoding.EncodeMessagePackBytes(innerMessageContentSlice)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
innerMessageContentHash, err := blake3.Get32ByteBlake3Hash(innerMessageContentEncoded)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
innerMessageSignature := edwardsKeys.CreateSignature(senderIdentityPrivateKey, innerMessageContentHash)
|
|
|
|
signatureEncoded, err := encoding.EncodeMessagePackBytes(innerMessageSignature)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
innerMessageSlice := []messagepack.RawMessage{signatureEncoded, innerMessageContentEncoded}
|
|
|
|
innerMessageBytes, err := encoding.EncodeMessagePackBytes(innerMessageSlice)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
cipheredMessage, err := chaPolyShrink.EncryptChaPolyShrink(innerMessageBytes, messageCipherKey, cipheredMessageNonce, true, 1000, true, noncipheredSectionHash)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
cipheredMessageEncoded, err := encoding.EncodeMessagePackBytes(cipheredMessage)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
// We encode the outer message
|
|
|
|
finalMessageSlice := []messagepack.RawMessage{noncipheredSectionEncoded, cipheredMessageEncoded}
|
|
|
|
finalMessageBytes, err := encoding.EncodeMessagePackBytes(finalMessageSlice)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
messageHash, err := blake3.GetBlake3HashAsBytes(26, finalMessageBytes)
|
|
if (err != nil) { return nil, [26]byte{}, err }
|
|
|
|
messageHashArray := [26]byte(messageHash)
|
|
|
|
return finalMessageBytes, messageHashArray, nil
|
|
}
|
|
|
|
|