package noteslib import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/pbkdf2" "crypto/rand" "crypto/sha256" "database/sql" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/url" "strconv" "strings" "sync" "time" _ "modernc.org/sqlite" ) type Note struct { Body string `json:"body"` Color int64 `json:"color"` Date string `json:"date"` ID string `json:"id"` Title string `json:"title"` } func decodeColorInt(color int64) (r int64, g int64, b int64, a int64) { // https://developer.android.com/reference/android/graphics/Color#color-ints if color == 0 { return 255, 255, 255, 255 } a = (color >> 24) & 0xff // or color >>> 24 r = (color >> 16) & 0xff g = (color >> 8) & 0xff b = (color) & 0xff return } func encodeToColorInt(r int64, g int64, b int64, a int64) int64 { // https://developer.android.com/reference/android/graphics/Color#color-ints return (a&0xff)<<24 | (r&0xff)<<16 | (g&0xff)<<8 | (b & 0xff) } func fetch(serverAddress string, username string) (jsonBody [][2]string, err error) { response, err := http.Get( fmt.Sprintf("https://%s/get?user=%s", serverAddress, url.QueryEscape(username)), ) if err != nil { return } defer response.Body.Close() responseBody, err := io.ReadAll(response.Body) if err != nil { return } err = json.Unmarshal(responseBody, &jsonBody) if err != nil { return } return } func load(db *sql.DB) (notes [][2]string, err error) { var rowCount int err = db.QueryRow("SELECT COUNT(*) FROM notes").Scan(&rowCount) if err != nil { return } const limit = 100 if rowCount > limit { // change this if there's ever an option to increase limit rowCount = limit // change this if there's ever an option to increase limit } rows, err := db.Query("SELECT * FROM notes LIMIT ?", rowCount) if err != nil { return } defer rows.Close() notes = make([][2]string, rowCount) for i := 0; rows.Next(); i++ { var currentRow [2]string if err != nil { return } notes[i] = currentRow } return } func list(password string, jsonBody [2]string, notes *[]Note, wg *sync.WaitGroup) (err error) { defer wg.Done() decryptedData, err := decrypt(jsonBody[1], password) if err != nil { return } var decryptedBody Note err = json.Unmarshal(decryptedData, &decryptedBody) if err != nil { return } decryptedBody.ID = jsonBody[0] if len(decryptedBody.Title) > 0 && len(decryptedBody.Body) > 0 { *notes = append(*notes, decryptedBody) // ew return } return } func del(serverAddress string, username string, uuid string, db *sql.DB) (err error) { response, err := http.Get(fmt.Sprintf("https://%s/remove?user=%s&id=%s", serverAddress, url.QueryEscape(username), uuid)) if err != nil { return } defer response.Body.Close() _, err = io.ReadAll(response.Body) if err != nil { return } stmt, err := db.Prepare("DELETE FROM notes WHERE id=?") if err != nil { return } defer stmt.Close() _, err = stmt.Exec(uuid) return } func create(title string, body string, newColor string, serverAddress string, username string, password string, templateNote Note) (err error) { aInt, err := strconv.ParseInt(newColor[0:2], 16, 64) if err != nil { return } rInt, err := strconv.ParseInt(newColor[2:4], 16, 64) if err != nil { return } gInt, err := strconv.ParseInt(newColor[4:6], 16, 64) if err != nil { return } bInt, err := strconv.ParseInt(newColor[6:8], 16, 64) if err != nil { return } note := Note{ Title: title, Body: strings.ReplaceAll(body, "/", "\n"), Color: encodeToColorInt(rInt, gInt, bInt, aInt), Date: strconv.FormatInt(time.Now().UnixMilli(), 10), } jsonNote, err := json.Marshal(note) if err != nil { return } data, err := encrypt(jsonNote, password) if err != nil { return } response, err := http.Get( fmt.Sprintf( "https://%s/new?user=%s&data=%s", serverAddress, url.QueryEscape(username), url.QueryEscape(data), ), ) if err != nil { return } defer response.Body.Close() _, err = io.ReadAll(response.Body) return } func deriveKey(password string, salt []byte) (block cipher.Block, err error) { key, err := pbkdf2.Key(sha256.New, password, salt, 65536, 32) if err != nil { return } block, err = aes.NewCipher(key) return } func decrypt(data string, password string) (strippedPlaintext []byte, err error) { dataBytes, err := base64.StdEncoding.DecodeString(data) if err != nil { return } dataBytesLength := len(dataBytes) if dataBytesLength < 32 { err = fmt.Errorf("Data is of length %d, 32 required.", dataBytesLength) return } iv := dataBytes[0:16] salt := dataBytes[16:32] ciphertext := dataBytes[32:] ciphertextLength := len(ciphertext) if ciphertextLength%aes.BlockSize != 0 { err = fmt.Errorf("Ciphertext length %d is not a multiple of block size %d\n", ciphertextLength, aes.BlockSize) return } plaintext := make([]byte, ciphertextLength) block, err := deriveKey(password, salt) if err != nil { return } mode := cipher.NewCBCDecrypter(block, iv) mode.CryptBlocks(plaintext, ciphertext) strippedPlaintext, err = unpad(plaintext, 16) return } func encrypt(data []byte, password string) (encrypted string, err error) { salt := make([]byte, 16) iv := make([]byte, 16) rand.Read(salt) rand.Read(iv) plaintext, err := pad(data, 16) if err != nil { return } block, err := deriveKey(password, salt) if err != nil { return } mode := cipher.NewCBCEncrypter(block, iv) ciphertext := make([]byte, len(plaintext)) mode.CryptBlocks(ciphertext, plaintext) encrypted = base64.StdEncoding.EncodeToString( bytes.Join( [][]byte{ iv, salt, ciphertext, }, []byte(""), ), ) return } func pad(buf []byte, size int) ([]byte, error) { // https://github.com/mergermarket/go-pkcs7/blob/master/pkcs7.go bufLen := len(buf) padLen := size - bufLen%size padded := make([]byte, bufLen+padLen) copy(padded, buf) for i := range padLen { padded[bufLen+i] = byte(padLen) } return padded, nil } func unpad(padded []byte, size int) (buf []byte, err error) { if len(padded)%size != 0 { err = fmt.Errorf("Padded value wasn't in correct size for pkcs7") return } bufLen := len(padded) - int(padded[len(padded)-1]) buf = make([]byte, bufLen) copy(buf, padded[:bufLen]) return }