// myChatKeys provides functions to manage a user's chat keys // New keys are created after a set duration has passed // Old keys are deleted after the user has downloaded all of the messages during the period when they were used package myChatKeys import "seekia/internal/cryptography/kyber" import "seekia/internal/cryptography/nacl" import "seekia/internal/encoding" import "seekia/internal/helpers" import "seekia/internal/identity" import "seekia/internal/logger" import "seekia/internal/messaging/readMessages" import "seekia/internal/myDatastores/myMap" import "seekia/internal/myDatastores/myMapList" import "seekia/internal/myIdentity" import "sync" import "time" import "strings" import "errors" //TODO: Add pruning function to get rid of old keys // This mutex will be locked whenever we update the chat keys maplist var updatingMyChatKeysMutex sync.Mutex // This mutex will be locked whenever we update the chatKeysLatestUpdateTime map object var updatingMyChatKeysLatestUpdateTimeMutex sync.Mutex var myChatKeysMapListDatastore *myMapList.MyMapList // Map Structure: Identity Hash + "@" + Network Type -> Latest update unix time var myChatKeysLatestUpdateTimeMapDatastore *myMap.MyMap // Map Structure: Identity Hash + "@" + Network Type -> Nacl Key + "+" + Kyber Key var myNewestBroadcastPublicChatKeysMapDatastore *myMap.MyMap // This function must be called whenever an app user signs in func InitializeMyChatKeysDatastores()error{ updatingMyChatKeysMutex.Lock() defer updatingMyChatKeysMutex.Unlock() updatingMyChatKeysLatestUpdateTimeMutex.Lock() defer updatingMyChatKeysLatestUpdateTimeMutex.Unlock() newMyChatKeysLatestUpdateTimeMapDatastore, err := myMap.CreateNewMap("MyChatKeysLatestUpdateTime") if (err != nil) { return err } newMyChatKeysMapListDatastore, err := myMapList.CreateNewMapList("MyChatKeys") if (err != nil) { return err } newMyNewestBroadcastPublicChatKeysMapDatastore, err := myMap.CreateNewMap("MyNewestBroadcastPublicChatKeys") if (err != nil) { return err } myChatKeysLatestUpdateTimeMapDatastore = newMyChatKeysLatestUpdateTimeMapDatastore myChatKeysMapListDatastore = newMyChatKeysMapListDatastore myNewestBroadcastPublicChatKeysMapDatastore = newMyNewestBroadcastPublicChatKeysMapDatastore return nil } // This function provides the decryption keys for a particular identity hash //Outputs: // -bool: My identity found // -bool: Any chat keys found (will be false if user has never broadcasted their profile) // -[]ChatKeySet: Chat keys list // -error func GetMyChatDecryptionKeySetsList(myIdentityHash [16]byte, networkType byte)(bool, bool, []readMessages.ChatKeySet, error){ isMine, myIdentityType, err := myIdentity.CheckIfIdentityHashIsMine(myIdentityHash) if (err != nil) { return false, false, nil, err } if (isMine == false) { return false, false, nil, nil } if (myIdentityType != "Mate" && myIdentityType != "Moderator"){ return false, false, nil, errors.New("GetMyChatDecryptionKeysMapList called with Host identity.") } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, false, nil, errors.New("GetMyChatDecryptionKeySetsList called with invalid networkType: " + networkTypeString) } myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil) { myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:]) return false, false, nil, errors.New("CheckIfIdentityHashIsMine not verifying identity hash: " + myIdentityHashHex) } networkTypeString := helpers.ConvertByteToString(networkType) lookupMap := map[string]string{ "MyIdentityHash": myIdentityHashString, "NetworkType": networkTypeString, } anyExist, myChatKeysMapList, err := myChatKeysMapListDatastore.GetMapListItems(lookupMap) if (err != nil){ return false, false, nil, err } if (anyExist == false){ return true, false, nil, nil } chatDecryptionKeySetsList := make([]readMessages.ChatKeySet, 0, len(myChatKeysMapList)) for _, keySetMap := range myChatKeysMapList{ myNaclPublicKey, exists := keySetMap["NaclPublicKey"] if (exists == false){ return false, false, nil, errors.New("Malformed myChatKeys map list: Item missing NaclPublicKey") } myNaclPrivateKey, exists := keySetMap["NaclPrivateKey"] if (exists == false){ return false, false, nil, errors.New("Malformed myChatKeys map list: Item missing NaclPrivateKey") } myKyberPrivateKey, exists := keySetMap["KyberPrivateKey"] if (exists == false){ return false, false, nil, errors.New("Malformed myChatKeys map list: Item missing KyberPrivateKey") } myNaclPublicKeyBytes, err := encoding.DecodeBase64StringToBytes(myNaclPublicKey) if (err != nil){ return false, false, nil, errors.New("Malformed myChatKeys map list: NaclPublicKey is not Base64.") } if (len(myNaclPublicKeyBytes) != 32){ return false, false, nil, errors.New("Malformed myChatKeys map list: NaclPublicKey is invalid length.") } myNaclPublicKeyArray := [32]byte(myNaclPublicKeyBytes) myNaclPrivateKeyBytes, err := encoding.DecodeHexStringToBytes(myNaclPrivateKey) if (err != nil){ return false, false, nil, errors.New("Malformed myChatKeys map list: NaclPrivateKey is not Hex.") } if (len(myNaclPrivateKeyBytes) != 32){ return false, false, nil, errors.New("Malformed myChatKeys map list: NaclPrivateKey is invalid length.") } myNaclPrivateKeyArray := [32]byte(myNaclPrivateKeyBytes) myKyberPrivateKeyBytes, err := encoding.DecodeHexStringToBytes(myKyberPrivateKey) if (err != nil){ return false, false, nil, errors.New("Malformed myChatKeys map list: KyberPrivateKey is not Hex.") } if (len(myKyberPrivateKeyBytes) != 1536){ return false, false, nil, errors.New("Malformed myChatKeys map list: KyberPrivateKey is invalid length.") } myKyberPrivateKeyArray := [1536]byte(myKyberPrivateKeyBytes) newChatKeySetObject := readMessages.ChatKeySet{ NaclPublicKey: myNaclPublicKeyArray, NaclPrivateKey: myNaclPrivateKeyArray, KyberPrivateKey: myKyberPrivateKeyArray, } chatDecryptionKeySetsList = append(chatDecryptionKeySetsList, newChatKeySetObject) } return true, true, chatDecryptionKeySetsList, nil } // This function generates new chat keys for an identity. func GenerateNewChatKeys(myIdentityHash [16]byte, networkType byte)error{ myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil) { myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:]) return errors.New("GenerateNewChatKeys called with invalid identity hash: " + myIdentityHashHex) } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return errors.New("GenerateNewChatKeys called with invalid networkType: " + networkTypeString) } updatingMyChatKeysMutex.Lock() defer updatingMyChatKeysMutex.Unlock() currentTime := time.Now().Unix() currentTimeString := helpers.ConvertInt64ToString(currentTime) networkTypeString := helpers.ConvertByteToString(networkType) newNaclPublicKey, newNaclPrivateKey, err := nacl.GetNewRandomPublicPrivateNaclKeys() if (err != nil) { return err } newKyberPublicKey, newKyberPrivateKey, err := kyber.GetNewRandomPublicPrivateKyberKeys() if (err != nil) { return err } newNaclPublicKeyString := encoding.EncodeBytesToBase64String(newNaclPublicKey[:]) newNaclPrivateKeyString := encoding.EncodeBytesToHexString(newNaclPrivateKey[:]) newKyberPublicKeyString := encoding.EncodeBytesToBase64String(newKyberPublicKey[:]) newKyberPrivateKeyString := encoding.EncodeBytesToHexString(newKyberPrivateKey[:]) newMapListItem := map[string]string{ "MyIdentityHash": myIdentityHashString, "CreatedTime": currentTimeString, "NetworkType": networkTypeString, "NaclPublicKey": newNaclPublicKeyString, "NaclPrivateKey": newNaclPrivateKeyString, "KyberPublicKey": newKyberPublicKeyString, "KyberPrivateKey": newKyberPrivateKeyString, } err = myChatKeysMapListDatastore.AddMapListItem(newMapListItem) if (err != nil) { return err } return nil } // This function will get a user's newest public chat keys // It will create new chat keys for the provided identity if none exist //Outputs: // -bool: My identity exists // -[32]byte: My Nacl Public key // -[1568]byte: My Kyber Public key // -error func GetMyNewestPublicChatKeys(myIdentityHash [16]byte, networkType byte)(bool, [32]byte, [1568]byte, error){ isMine, myIdentityType, err := myIdentity.CheckIfIdentityHashIsMine(myIdentityHash) if (err != nil) { return false, [32]byte{}, [1568]byte{}, err } if (isMine == false){ return false, [32]byte{}, [1568]byte{}, nil } if (myIdentityType != "Mate" && myIdentityType != "Moderator"){ return false, [32]byte{}, [1568]byte{}, errors.New("GetMyNewestPublicChatKeys called with host identity.") } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, [32]byte{}, [1568]byte{}, errors.New("GetMyNewestPublicChatKeys called with invalid networkType: " + networkTypeString) } myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil){ myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:]) return false, [32]byte{}, [1568]byte{}, errors.New("CheckIfIdentityHashIsMine not verifying identity hash: " + myIdentityHashHex) } updatingMyChatKeysMutex.Lock() defer updatingMyChatKeysMutex.Unlock() networkTypeString := helpers.ConvertByteToString(networkType) lookupMap := map[string]string{ "MyIdentityHash": myIdentityHashString, "NetworkType": networkTypeString, } anyItemsFound, myChatKeysMapList, err := myChatKeysMapListDatastore.GetMapListItems(lookupMap) if (err != nil){ return false, [32]byte{}, [1568]byte{}, err } if (anyItemsFound == true){ if (len(myChatKeysMapList) == 0){ return false, [32]byte{}, [1568]byte{}, errors.New("GetMapListItems returning empty items list after claiming items were found.") } newestChatKeysCreatedTime := int64(0) var newestNaclPublicKey [32]byte var newestKyberPublicKey [1568]byte for index, chatKeysSetMap := range myChatKeysMapList{ itemMyIdentityHash, exists := chatKeysSetMap["MyIdentityHash"] if (exists == false) { return false, [32]byte{}, [1568]byte{}, errors.New("myMapList lookup not working properly.") } if (itemMyIdentityHash != myIdentityHashString){ return false, [32]byte{}, [1568]byte{}, errors.New("myMapList lookup not working properly.") } timeKeysCreated, exists := chatKeysSetMap["CreatedTime"] if (exists == false) { return false, [32]byte{}, [1568]byte{}, errors.New("Malformed myChatKeys map list: Item missing CreatedTime") } naclPublicKey, exists := chatKeysSetMap["NaclPublicKey"] if (exists == false) { return false, [32]byte{}, [1568]byte{}, errors.New("Malformed myChatKeys map list: Item missing NaclPublicKey") } kyberPublicKey, exists := chatKeysSetMap["KyberPublicKey"] if (exists == false) { return false, [32]byte{}, [1568]byte{}, errors.New("Malformed myChatKeys map list: Item missing KyberPublicKey") } naclPublicKeyBytes, err := encoding.DecodeBase64StringToBytes(naclPublicKey) if (err != nil){ return false, [32]byte{}, [1568]byte{}, errors.New("Malformed myChatKeys map list: Item contains invalid NaclPublicKey: Not hex.") } if (len(naclPublicKeyBytes) != 32){ return false, [32]byte{}, [1568]byte{}, errors.New("Malformed myChatKeys map list: Item contains invalid NaclPublicKey: Invalid length.") } naclPublicKeyArray := [32]byte(naclPublicKeyBytes) kyberPublicKeyBytes, err := encoding.DecodeBase64StringToBytes(kyberPublicKey) if (err != nil){ return false, [32]byte{}, [1568]byte{}, errors.New("Malformed myChatKeys map list: Item contains invalid KyberPublicKey: Not hex.") } if (len(kyberPublicKeyBytes) != 1568){ return false, [32]byte{}, [1568]byte{}, errors.New("Malformed myChatKeys map list: Item contains invalid KyberPublicKey: Invalid length.") } kyberPublicKeyArray := [1568]byte(kyberPublicKeyBytes) timeKeysCreatedInt64, err := helpers.ConvertStringToInt64(timeKeysCreated) if (err != nil) { return false, [32]byte{}, [1568]byte{}, errors.New("Malformed myChatKeys map list: Item contains invalid CreatedTime: " + timeKeysCreated) } if (index == 0 || newestChatKeysCreatedTime < timeKeysCreatedInt64){ newestChatKeysCreatedTime = timeKeysCreatedInt64 newestNaclPublicKey = naclPublicKeyArray newestKyberPublicKey = kyberPublicKeyArray } } return true, newestNaclPublicKey, newestKyberPublicKey, nil } // We must create new chat keys currentTime := time.Now().Unix() currentTimeString := helpers.ConvertInt64ToString(currentTime) newNaclPublicKey, newNaclPrivateKey, err := nacl.GetNewRandomPublicPrivateNaclKeys() if (err != nil) { return false, [32]byte{}, [1568]byte{}, err } newKyberPublicKey, newKyberPrivateKey, err := kyber.GetNewRandomPublicPrivateKyberKeys() if (err != nil) { return false, [32]byte{}, [1568]byte{}, err } newNaclPublicKeyString := encoding.EncodeBytesToBase64String(newNaclPublicKey[:]) newNaclPrivateKeyString := encoding.EncodeBytesToHexString(newNaclPrivateKey[:]) newKyberPublicKeyString := encoding.EncodeBytesToBase64String(newKyberPublicKey[:]) newKyberPrivateKeyString := encoding.EncodeBytesToHexString(newKyberPrivateKey[:]) newChatKeysSetMap := map[string]string{ "MyIdentityHash": myIdentityHashString, "NetworkType": networkTypeString, "CreatedTime": currentTimeString, "NaclPublicKey": newNaclPublicKeyString, "NaclPrivateKey": newNaclPrivateKeyString, "KyberPublicKey": newKyberPublicKeyString, "KyberPrivateKey": newKyberPrivateKeyString, } err = myChatKeysMapListDatastore.AddMapListItem(newChatKeysSetMap) if (err != nil) { return false, [32]byte{}, [1568]byte{}, err } return true, newNaclPublicKey, newKyberPublicKey, nil } // This function is only called when a new profile is added to myBroadcasts func SetMyChatKeysLatestUpdateTime(myIdentityHash [16]byte, networkType byte, newLatestUpdateTime int64)error{ isMine, myIdentityType, err := myIdentity.CheckIfIdentityHashIsMine(myIdentityHash) if (err != nil) { return err } if (isMine == false){ return errors.New("SetMyChatKeysLatestUpdateTime called with identity that is not mine.") } if (myIdentityType != "Mate" && myIdentityType != "Moderator"){ return errors.New("SetMyChatKeysLatestUpdateTime called with Host identity.") } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return errors.New("SetMyChatKeysLatestUpdateTime called with invalid networkType: " + networkTypeString) } updatingMyChatKeysLatestUpdateTimeMutex.Lock() defer updatingMyChatKeysLatestUpdateTimeMutex.Unlock() exists, existingLatestUpdateTime, err := GetMyChatKeysLatestUpdateTime(myIdentityHash, networkType) if (err != nil) { return err } if (exists == true){ if (existingLatestUpdateTime > newLatestUpdateTime) { // This would only happen if our system clock was invalid, and became valid // We will log this, but allow our latestUpdateTime to be updated err := logger.AddLogEntry("General", "Existing latest chat keys update time is later than new update time.") if (err != nil) { return err } } else if (existingLatestUpdateTime == newLatestUpdateTime){ // No update is needed. return nil } } myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil){ myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:]) return errors.New("CheckIfIdentityHashIsMine not verifying identity hash: " + myIdentityHashHex) } networkTypeString := helpers.ConvertByteToString(networkType) mapEntryKey := myIdentityHashString + "@" + networkTypeString newLatestUpdateTimeString := helpers.ConvertInt64ToString(newLatestUpdateTime) err = myChatKeysLatestUpdateTimeMapDatastore.SetMapEntry(mapEntryKey, newLatestUpdateTimeString) if (err != nil) { return err } return nil } //Outputs: // -bool: Update time exists (wont exist if profile has never been broadcast, or existing profile is not imported yet) // -int64: Current latest update time // -error func GetMyChatKeysLatestUpdateTime(myIdentityHash [16]byte, networkType byte)(bool, int64, error){ isMine, myIdentityType, err := myIdentity.CheckIfIdentityHashIsMine(myIdentityHash) if (err != nil) { return false, 0, err } if (isMine == false){ return false, 0, errors.New("GetMyChatKeysLatestUpdateTime called with identity that is not mine.") } if (myIdentityType != "Mate" && myIdentityType != "Moderator"){ return false, 0, errors.New("GetMyChatKeysLatestUpdateTime called with Host identity.") } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, 0, errors.New("GetMyChatKeysLatestUpdateTime called with invalid networkType: " + networkTypeString) } myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil){ myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:]) return false, 0, errors.New("CheckIfIdentityHashIsMine not verifying my identity hash: " + myIdentityHashHex) } networkTypeString := helpers.ConvertByteToString(networkType) mapEntryKey := myIdentityHashString + "@" + networkTypeString exists, latestUpdateTimeString, err := myChatKeysLatestUpdateTimeMapDatastore.GetMapEntry(mapEntryKey) if (err != nil) { return false, 0, err } if (exists == false) { return false, 0, nil } latestUpdateTimeInt64, err := helpers.ConvertStringToInt64(latestUpdateTimeString) if (err != nil) { return false, 0, errors.New("myChatKeysLatestUpdateTimeMapDatastore is corrupt: Contains invalid latestUpdateTime: " + latestUpdateTimeString) } return true, latestUpdateTimeInt64, nil } // This function is used to keep track of the user's newest broadcasted chat keys // We need to do this because we need to be able to tell if the profile we are broadcasting contains new keys // This is stored seperately from myBroadcasts // This datastore can prevent users from having to update their latestChatKeysUpdateTime when moving to a new device, if they import their keys func SetMyNewestBroadcastPublicChatKeys(myIdentityHash [16]byte, networkType byte, naclKey [32]byte, kyberKey [1568]byte)error{ isMine, myIdentityType, err := myIdentity.CheckIfIdentityHashIsMine(myIdentityHash) if (err != nil) { return err } if (isMine == false){ return errors.New("SetMyNewestBroadcastPublicChatKeys called with identity that is not mine.") } if (myIdentityType != "Mate" && myIdentityType != "Moderator"){ return errors.New("SetMyNewestBroadcastPublicChatKeys called with Host identity.") } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return errors.New("SetMyNewestBroadcastPublicChatKeys called with invalid networkType: " + networkTypeString) } myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil){ myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:]) return errors.New("CheckIfIdentityHashIsMine not verifying my identity hash: " + myIdentityHashHex) } networkTypeString := helpers.ConvertByteToString(networkType) mapEntryKey := myIdentityHashString + "@" + networkTypeString naclKeyEncoded := encoding.EncodeBytesToBase64String(naclKey[:]) kyberKeyEncoded := encoding.EncodeBytesToBase64String(kyberKey[:]) newEntryValue := naclKeyEncoded + "+" + kyberKeyEncoded err = myNewestBroadcastPublicChatKeysMapDatastore.SetMapEntry(mapEntryKey, newEntryValue) if (err != nil) { return err } return nil } //Outputs: // -bool: My identity found // -bool: Any keys found // -[32]byte: Newest broadcast Nacl key (Encoded base64) // -[1568]byte: Newest broadcast Kyber key (Encoded base64) // -error func GetMyNewestBroadcastPublicChatKeys(myIdentityHash [16]byte, networkType byte)(bool, bool, [32]byte, [1568]byte, error){ isMine, myIdentityType, err := myIdentity.CheckIfIdentityHashIsMine(myIdentityHash) if (err != nil) { return false, false, [32]byte{}, [1568]byte{}, err } if (isMine == false){ return false, false, [32]byte{}, [1568]byte{}, nil } if (myIdentityType != "Mate" && myIdentityType != "Moderator"){ return false, false, [32]byte{}, [1568]byte{}, errors.New("GetMyNewestBroadcastPublicChatKeys called with Host identity.") } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, false, [32]byte{}, [1568]byte{}, errors.New("GetMyNewestBroadcastPublicChatKeys called with invalid networkType: " + networkTypeString) } myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil){ myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:]) return false, false, [32]byte{}, [1568]byte{}, errors.New("CheckIfIdentityHashIsMine not verifying my identity hash: " + myIdentityHashHex) } networkTypeString := helpers.ConvertByteToString(networkType) mapEntryKey := myIdentityHashString + "@" + networkTypeString exists, mapItemValue, err := myNewestBroadcastPublicChatKeysMapDatastore.GetMapEntry(mapEntryKey) if (err != nil) { return false, false, [32]byte{}, [1568]byte{}, err } if (exists == false){ return true, false, [32]byte{}, [1568]byte{}, nil } naclKeyString, kyberKeyString, delimiterFound := strings.Cut(mapItemValue, "+") if (delimiterFound == false){ return false, false, [32]byte{}, [1568]byte{}, errors.New("Malformed myNewestBroadcastPublicChatKeysMapDatastore item value: " + mapItemValue) } naclKeyBytes, err := encoding.DecodeBase64StringToBytes(naclKeyString) if (err != nil){ return false, false, [32]byte{}, [1568]byte{}, errors.New("Malformed myNewestBroadcastPublicChatKeysMapDatastore item value: Invalid NaclKey: Not Base64: " + naclKeyString) } if (len(naclKeyBytes) != 32){ return false, false, [32]byte{}, [1568]byte{}, errors.New("Malformed myNewestBroadcastPublicChatKeysMapDatastore item value: Invalid NaclKey: Invalid length: " + naclKeyString) } naclKey := [32]byte(naclKeyBytes) kyberKeyBytes, err := encoding.DecodeBase64StringToBytes(kyberKeyString) if (err != nil){ return false, false, [32]byte{}, [1568]byte{}, errors.New("Malformed myNewestBroadcastPublicChatKeysMapDatastore item value: Invalid KyberKey: Not Base64: " + kyberKeyString) } if (len(kyberKeyBytes) != 1568){ return false, false, [32]byte{}, [1568]byte{}, errors.New("Malformed myNewestBroadcastPublicChatKeysMapDatastore item value: Invalid KyberKey: Invalid length: " + kyberKeyString) } kyberKey := [1568]byte(kyberKeyBytes) return true, true, naclKey, kyberKey, nil }