Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
7.2 KiB

package Seeder
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/pem"
"fmt"
"hash"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Util"
"github.com/gofrs/uuid"
)
// EncryptWithPublicKey encrypts data with public key
func encryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte {
var (
hash hash.Hash
)
hash = sha256.New()
ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
if err != nil {
panic(err)
}
return ciphertext
}
func PKCS5Padding(ciphertext []byte, blockSize int, after int) []byte {
var (
padding int
padtext []byte
)
padding = (blockSize - len(ciphertext)%blockSize)
padtext = bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func generateAesKey() (*pem.Block, cipher.BlockMode) {
var (
pemBlock *pem.Block
block cipher.Block
bKey []byte
bIV []byte
err error
)
bKey = make([]byte, 32)
_, err = rand.Read(bKey)
if err != nil {
panic(err)
}
bIV = make([]byte, 16)
_, err = rand.Read(bIV)
if err != nil {
panic(err)
}
pemBlock = &pem.Block{
Type: "AES KEY",
Bytes: bKey,
}
block, err = aes.NewCipher(bKey)
if err != nil {
panic(err)
}
return pemBlock, cipher.NewCBCEncrypter(block, bIV)
}
func seedMessage(
primaryUser Models.User,
primaryUserThreadKey, secondaryUserThreadKey string,
thread Models.MessageThread,
i int,
) error {
var (
message Models.Message
messageData Models.MessageData
messagePemBlock *pem.Block
messageMode cipher.BlockMode
plaintext string
dataCiphertext []byte
senderIdCiphertext []byte
bPlaintext []byte
bSenderIdPlaintext []byte
err error
)
plaintext = "Test Message"
bPlaintext = PKCS5Padding([]byte(plaintext), aes.BlockSize, len(plaintext))
bSenderIdPlaintext = PKCS5Padding(primaryUser.ID.Bytes(), aes.BlockSize, len(primaryUser.ID.Bytes()))
dataCiphertext = make([]byte, len(bPlaintext))
senderIdCiphertext = make([]byte, len(bSenderIdPlaintext))
messagePemBlock, messageMode = generateAesKey()
messageMode.CryptBlocks(dataCiphertext, bPlaintext)
messageMode.CryptBlocks(senderIdCiphertext, bSenderIdPlaintext)
messageData = Models.MessageData{
Data: dataCiphertext,
SenderID: senderIdCiphertext,
}
message = Models.Message{
MessageData: messageData,
SymmetricKey: encryptWithPublicKey(pem.EncodeToMemory(messagePemBlock), decodedPublicKey),
MessageThreadKey: primaryUserThreadKey,
}
err = Database.CreateMessage(&message)
if err != nil {
return err
}
// The symmetric key would be encrypted with secondary users public key in production
// But due to using the same pub/priv key pair for all users, we will just duplicate it
message = Models.Message{
MessageDataID: message.MessageDataID,
SymmetricKey: encryptWithPublicKey(pem.EncodeToMemory(messagePemBlock), decodedPublicKey),
MessageThreadKey: secondaryUserThreadKey,
}
err = Database.CreateMessage(&message)
if err != nil {
return err
}
return err
}
func seedMessageThread(threadPemBlock *pem.Block, threadMode cipher.BlockMode) (Models.MessageThread, error) {
var (
messageThread Models.MessageThread
name string
bNamePlaintext []byte
nameCiphertext []byte
err error
)
name = "Test Conversation"
bNamePlaintext = PKCS5Padding([]byte(name), aes.BlockSize, len(name))
nameCiphertext = make([]byte, len(bNamePlaintext))
threadMode.CryptBlocks(nameCiphertext, bNamePlaintext)
messageThread = Models.MessageThread{
Name: nameCiphertext,
}
err = Database.CreateMessageThread(&messageThread)
return messageThread, err
}
func seedUpdateMessageThreadUsers(
userJson string,
threadPemBlock *pem.Block,
threadMode cipher.BlockMode,
messageThread Models.MessageThread,
) (Models.MessageThread, error) {
var (
bUsersPlaintext []byte
usersCiphertext []byte
err error
)
bUsersPlaintext = PKCS5Padding([]byte(userJson), aes.BlockSize, len(userJson))
usersCiphertext = make([]byte, len(bUsersPlaintext))
threadMode.CryptBlocks(usersCiphertext, bUsersPlaintext)
messageThread.Users = usersCiphertext
err = Database.UpdateMessageThread(&messageThread)
return messageThread, err
}
func seedMessageThreadUser(
user Models.User,
threadID uuid.UUID,
messageThreadKey string,
threadPemBlock *pem.Block,
threadMode cipher.BlockMode,
) (Models.MessageThreadUser, error) {
var (
messageThreadUser Models.MessageThreadUser
bThreadIdPlaintext []byte
threadIdCiphertext []byte
bKeyPlaintext []byte
keyCiphertext []byte
bAdminPlaintext []byte
adminCiphertext []byte
err error
)
bThreadIdPlaintext = PKCS5Padding(threadID.Bytes(), aes.BlockSize, len(threadID.String()))
threadIdCiphertext = make([]byte, len(bThreadIdPlaintext))
bKeyPlaintext = PKCS5Padding([]byte(messageThreadKey), aes.BlockSize, len(messageThreadKey))
keyCiphertext = make([]byte, len(bKeyPlaintext))
bAdminPlaintext = PKCS5Padding([]byte("true"), aes.BlockSize, len("true"))
adminCiphertext = make([]byte, len(bAdminPlaintext))
threadMode.CryptBlocks(threadIdCiphertext, bThreadIdPlaintext)
threadMode.CryptBlocks(keyCiphertext, bKeyPlaintext)
threadMode.CryptBlocks(adminCiphertext, bAdminPlaintext)
messageThreadUser = Models.MessageThreadUser{
UserID: user.ID,
MessageThreadID: threadIdCiphertext,
MessageThreadKey: keyCiphertext,
Admin: adminCiphertext,
SymmetricKey: encryptWithPublicKey(pem.EncodeToMemory(threadPemBlock), decodedPublicKey),
}
err = Database.CreateMessageThreadUser(&messageThreadUser)
return messageThreadUser, err
}
func SeedMessages() {
var (
messageThread Models.MessageThread
threadPemBlock *pem.Block
threadMode cipher.BlockMode
primaryUser Models.User
primaryUserThreadKey string
secondaryUser Models.User
secondaryUserThreadKey string
userJson string
thread Models.MessageThread
i int
err error
)
threadPemBlock, threadMode = generateAesKey()
messageThread, err = seedMessageThread(threadPemBlock, threadMode)
primaryUserThreadKey = Util.RandomString(32)
secondaryUserThreadKey = Util.RandomString(32)
primaryUser, err = Database.GetUserByUsername("testUser")
if err != nil {
panic(err)
}
_, err = seedMessageThreadUser(
primaryUser,
messageThread.ID,
primaryUserThreadKey,
threadPemBlock,
threadMode,
)
secondaryUser, err = Database.GetUserByUsername("testUser2")
if err != nil {
panic(err)
}
_, err = seedMessageThreadUser(
secondaryUser,
messageThread.ID,
secondaryUserThreadKey,
threadPemBlock,
threadMode,
)
userJson = fmt.Sprintf(
`
[
{
"id": "%s",
"username": "%s",
"admin": "true"
},
{
"id": "%s",
"username": "%s",
"admin": "true"
}
]
`,
primaryUser.ID.String(),
primaryUser.Username,
secondaryUser.ID.String(),
secondaryUser.Username,
)
messageThread, err = seedUpdateMessageThreadUsers(
userJson,
threadPemBlock,
threadMode,
messageThread,
)
for i = 0; i <= 20; i++ {
err = seedMessage(
primaryUser,
primaryUserThreadKey,
secondaryUserThreadKey,
thread,
i,
)
if err != nil {
panic(err)
}
}
}