diff --git a/pkg/cli/cmd/sync/sync.go b/pkg/cli/cmd/sync/sync.go index 28ec71a7..ec66a2cb 100644 --- a/pkg/cli/cmd/sync/sync.go +++ b/pkg/cli/cmd/sync/sync.go @@ -97,8 +97,7 @@ func (l syncList) getLength() int { return len(l.Notes) + len(l.Books) + len(l.ExpungedNotes) + len(l.ExpungedBooks) } -// processFragments categorizes items in sync fragments into a sync list. It also decrypts any -// encrypted data in sync fragments. +// processFragments categorizes items in sync fragments into a sync list. func processFragments(fragments []client.SyncFragment) (syncList, error) { notes := map[string]client.SyncFragNote{} books := map[string]client.SyncFragBook{} diff --git a/pkg/cli/crypt/crypto.go b/pkg/cli/crypt/crypto.go deleted file mode 100644 index 3637c7ca..00000000 --- a/pkg/cli/crypt/crypto.go +++ /dev/null @@ -1,123 +0,0 @@ -/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Dnote. If not, see . - */ - -// Package crypt provides cryptographic funcitonalities -package crypt - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "io" - - "github.com/pkg/errors" - "golang.org/x/crypto/hkdf" - "golang.org/x/crypto/pbkdf2" -) - -var aesGcmNonceSize = 12 - -func runHkdf(secret, salt, info []byte) ([]byte, error) { - r := hkdf.New(sha256.New, secret, salt, info) - - ret := make([]byte, 32) - _, err := io.ReadFull(r, ret) - if err != nil { - return []byte{}, errors.Wrap(err, "reading key bytes") - } - - return ret, nil -} - -// MakeKeys derives, from the given credential, a key set comprising of an encryption key -// and an authentication key -func MakeKeys(password, email []byte, iteration int) ([]byte, []byte, error) { - masterKey := pbkdf2.Key([]byte(password), []byte(email), iteration, 32, sha256.New) - - authKey, err := runHkdf(masterKey, email, []byte("auth")) - if err != nil { - return nil, nil, errors.Wrap(err, "deriving auth key") - } - - return masterKey, authKey, nil -} - -// AesGcmEncrypt encrypts the plaintext using AES in a GCM mode. It returns -// a ciphertext prepended by a 12 byte pseudo-random nonce, encoded in base64. -func AesGcmEncrypt(key, plaintext []byte) (string, error) { - if key == nil { - return "", errors.New("no key provided") - } - - block, err := aes.NewCipher(key) - if err != nil { - return "", errors.Wrap(err, "initializing aes") - } - - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return "", errors.Wrap(err, "initializing gcm") - } - - nonce := make([]byte, aesGcmNonceSize) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return "", errors.Wrap(err, "generating nonce") - } - - ciphertext := aesgcm.Seal(nonce, nonce, []byte(plaintext), nil) - cipherKeyB64 := base64.StdEncoding.EncodeToString(ciphertext) - - return cipherKeyB64, nil -} - -// AesGcmDecrypt decrypts the encrypted data using AES in a GCM mode. The data should be -// a base64 encoded string in the format of 12 byte nonce followed by a ciphertext. -func AesGcmDecrypt(key []byte, dataB64 string) ([]byte, error) { - if key == nil { - return nil, errors.New("no key provided") - } - - data, err := base64.StdEncoding.DecodeString(dataB64) - if err != nil { - return nil, errors.Wrap(err, "decoding base64 data") - } - - block, err := aes.NewCipher(key) - if err != nil { - return nil, errors.Wrap(err, "initializing aes") - } - - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return nil, errors.Wrap(err, "initializing gcm") - } - - if len(data) < aesGcmNonceSize { - return nil, errors.Wrap(err, "malformed data") - } - - nonce, ciphertext := data[:aesGcmNonceSize], data[aesGcmNonceSize:] - plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - return nil, errors.Wrap(err, "decrypting") - } - - return plaintext, nil -} diff --git a/pkg/cli/crypt/crypto_test.go b/pkg/cli/crypt/crypto_test.go deleted file mode 100644 index 805e2724..00000000 --- a/pkg/cli/crypt/crypto_test.go +++ /dev/null @@ -1,118 +0,0 @@ -/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Dnote. If not, see . - */ - -package crypt - -import ( - "crypto/aes" - "crypto/cipher" - "encoding/base64" - "fmt" - "testing" - - "github.com/dnote/dnote/pkg/assert" - "github.com/pkg/errors" -) - -func TestAesGcmEncrypt(t *testing.T) { - testCases := []struct { - key []byte - plaintext []byte - }{ - { - key: []byte("AES256Key-32Characters1234567890"), - plaintext: []byte("foo bar baz quz"), - }, - { - key: []byte("AES256Key-32Charactersabcdefghij"), - plaintext: []byte("1234 foo 5678 bar 7890 baz"), - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("key %s plaintext %s", tc.key, tc.plaintext), func(t *testing.T) { - // encrypt - dataB64, err := AesGcmEncrypt(tc.key, tc.plaintext) - if err != nil { - t.Fatal(errors.Wrap(err, "performing encryption")) - } - - // test that data can be decrypted - data, err := base64.StdEncoding.DecodeString(dataB64) - if err != nil { - t.Fatal(errors.Wrap(err, "decoding data from base64")) - } - - nonce, ciphertext := data[:12], data[12:] - - fmt.Println(string(data)) - - block, err := aes.NewCipher([]byte(tc.key)) - if err != nil { - t.Fatal(errors.Wrap(err, "initializing aes")) - } - - aesgcm, err := cipher.NewGCM(block) - if err != nil { - t.Fatal(errors.Wrap(err, "initializing gcm")) - } - - plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - t.Fatal(errors.Wrap(err, "decode")) - } - - assert.DeepEqual(t, plaintext, tc.plaintext, "plaintext mismatch") - }) - } -} - -func TestAesGcmDecrypt(t *testing.T) { - testCases := []struct { - key []byte - ciphertextB64 string - expectedPlaintext string - }{ - { - key: []byte("AES256Key-32Characters1234567890"), - ciphertextB64: "M2ov9hWMQ52v1S/zigwX3bJt4cVCV02uiRm/grKqN/rZxNkJrD7vK4Ii0g==", - expectedPlaintext: "foo bar baz quz", - }, - { - key: []byte("AES256Key-32Characters1234567890"), - ciphertextB64: "M4csFKUIUbD1FBEzLgHjscoKgN0lhMGJ0n2nKWiCkE/qSKlRP7kS", - expectedPlaintext: "foo\n1\nbar\n2", - }, - { - key: []byte("AES256Key-32Characters1234567890"), - ciphertextB64: "pe/fnw73MR1clmVIlRSJ5gDwBdnPly/DF7DsR5dJVz4dHZlv0b10WzvJEGOCHZEr+Q==", - expectedPlaintext: "föo\nbār\nbåz & qūz", - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("key %s ciphertext %s", tc.key, tc.ciphertextB64), func(t *testing.T) { - plaintext, err := AesGcmDecrypt(tc.key, tc.ciphertextB64) - if err != nil { - t.Fatal(errors.Wrap(err, "performing decryption")) - } - - assert.DeepEqual(t, plaintext, []byte(tc.expectedPlaintext), "plaintext mismatch") - }) - } -} diff --git a/pkg/server/app/books.go b/pkg/server/app/books.go index c3b89ec0..a476a0c4 100644 --- a/pkg/server/app/books.go +++ b/pkg/server/app/books.go @@ -41,12 +41,11 @@ func (a *App) CreateBook(user database.User, name string) (database.Book, error) } book := database.Book{ - UUID: uuid, - UserID: user.ID, - Label: name, - AddedOn: a.Clock.Now().UnixNano(), - USN: nextUSN, - Encrypted: false, + UUID: uuid, + UserID: user.ID, + Label: name, + AddedOn: a.Clock.Now().UnixNano(), + USN: nextUSN, } if err := tx.Create(&book).Error; err != nil { tx.Rollback() @@ -99,8 +98,6 @@ func (a *App) UpdateBook(tx *gorm.DB, user database.User, book database.Book, la book.USN = nextUSN book.EditedOn = a.Clock.Now().UnixNano() book.Deleted = false - // TODO: remove after all users have been migrated - book.Encrypted = false if err := tx.Save(&book).Error; err != nil { return book, errors.Wrap(err, "updating the book") diff --git a/pkg/server/app/notes.go b/pkg/server/app/notes.go index 7773953d..fabe62be 100644 --- a/pkg/server/app/notes.go +++ b/pkg/server/app/notes.go @@ -59,16 +59,15 @@ func (a *App) CreateNote(user database.User, bookUUID, content string, addedOn * } note := database.Note{ - UUID: uuid, - BookUUID: bookUUID, - UserID: user.ID, - AddedOn: noteAddedOn, - EditedOn: noteEditedOn, - USN: nextUSN, - Body: content, - Public: public, - Encrypted: false, - Client: client, + UUID: uuid, + BookUUID: bookUUID, + UserID: user.ID, + AddedOn: noteAddedOn, + EditedOn: noteEditedOn, + USN: nextUSN, + Body: content, + Public: public, + Client: client, } if err := tx.Create(¬e).Error; err != nil { tx.Rollback() @@ -134,8 +133,6 @@ func (a *App) UpdateNote(tx *gorm.DB, user database.User, note database.Note, p note.USN = nextUSN note.EditedOn = a.Clock.Now().UnixNano() note.Deleted = false - // TODO: remove after all users are migrated - note.Encrypted = false if err := tx.Save(¬e).Error; err != nil { return note, pkgErrors.Wrap(err, "editing note") @@ -180,13 +177,12 @@ func (a *App) GetUserNoteByUUID(userID int, uuid string) (*database.Note, error) // GetNotesParams is params for finding notes type GetNotesParams struct { - Year int - Month int - Page int - Books []string - Search string - Encrypted bool - PerPage int + Year int + Month int + Page int + Books []string + Search string + PerPage int } type ftsParams struct { @@ -215,14 +211,13 @@ notes.added_on, notes.edited_on, notes.usn, notes.deleted, -notes.encrypted, ` + bodyExpr) } func getNotesBaseQuery(db *gorm.DB, userID int, q GetNotesParams) *gorm.DB { conn := db.Where( - "notes.user_id = ? AND notes.deleted = ? AND notes.encrypted = ?", - userID, false, q.Encrypted, + "notes.user_id = ? AND notes.deleted = ?", + userID, false, ) if q.Search != "" { diff --git a/pkg/server/app/notes_test.go b/pkg/server/app/notes_test.go index 7813c54b..710b0dae 100644 --- a/pkg/server/app/notes_test.go +++ b/pkg/server/app/notes_test.go @@ -374,10 +374,9 @@ func TestGetNotes_FTSSearch(t *testing.T) { // Search "baz" result, err := a.GetNotes(user.ID, GetNotesParams{ - Search: "baz", - Encrypted: false, - Page: 1, - PerPage: 30, + Search: "baz", + Page: 1, + PerPage: 30, }) if err != nil { t.Fatal(errors.Wrap(err, "getting notes with FTS search")) @@ -390,10 +389,9 @@ func TestGetNotes_FTSSearch(t *testing.T) { // Search for "running" - should return 1 note result, err = a.GetNotes(user.ID, GetNotesParams{ - Search: "running", - Encrypted: false, - Page: 1, - PerPage: 30, + Search: "running", + Page: 1, + PerPage: 30, }) if err != nil { t.Fatal(errors.Wrap(err, "getting notes with FTS search for review")) @@ -405,10 +403,9 @@ func TestGetNotes_FTSSearch(t *testing.T) { // Search for non-existent term - should return 0 notes result, err = a.GetNotes(user.ID, GetNotesParams{ - Search: "nonexistent", - Encrypted: false, - Page: 1, - PerPage: 30, + Search: "nonexistent", + Page: 1, + PerPage: 30, }) if err != nil { t.Fatal(errors.Wrap(err, "getting notes with FTS search for nonexistent")) @@ -437,10 +434,9 @@ func TestGetNotes_FTSSearch_Snippet(t *testing.T) { // Search for "keyword" in long note - should return snippet with "..." result, err := a.GetNotes(user.ID, GetNotesParams{ - Search: "keyword", - Encrypted: false, - Page: 1, - PerPage: 30, + Search: "keyword", + Page: 1, + PerPage: 30, }) if err != nil { t.Fatal(errors.Wrap(err, "getting notes with FTS search for keyword")) @@ -472,10 +468,9 @@ func TestGetNotes_FTSSearch_ShortWord(t *testing.T) { a.Clock = clock.NewMock() result, err := a.GetNotes(user.ID, GetNotesParams{ - Search: "a", - Encrypted: false, - Page: 1, - PerPage: 30, + Search: "a", + Page: 1, + PerPage: 30, }) if err != nil { t.Fatal(errors.Wrap(err, "getting notes with FTS search for 'a'")) @@ -504,10 +499,9 @@ func TestGetNotes_All(t *testing.T) { a.Clock = clock.NewMock() result, err := a.GetNotes(user.ID, GetNotesParams{ - Search: "", - Encrypted: false, - Page: 1, - PerPage: 30, + Search: "", + Page: 1, + PerPage: 30, }) if err != nil { t.Fatal(errors.Wrap(err, "getting notes with FTS search for 'a'")) diff --git a/pkg/server/controllers/books.go b/pkg/server/controllers/books.go index e2aa6de0..1b4f3810 100644 --- a/pkg/server/controllers/books.go +++ b/pkg/server/controllers/books.go @@ -56,22 +56,11 @@ func (b *Books) getBooks(r *http.Request) ([]database.Book, error) { query := r.URL.Query() name := query.Get("name") - encryptedStr := query.Get("encrypted") if name != "" { part := fmt.Sprintf("%%%s%%", name) conn = conn.Where("LOWER(label) LIKE ?", part) } - if encryptedStr != "" { - var encrypted bool - if encryptedStr == "true" { - encrypted = true - } else { - encrypted = false - } - - conn = conn.Where("encrypted = ?", encrypted) - } var books []database.Book if err := conn.Find(&books).Error; err != nil { diff --git a/pkg/server/controllers/notes.go b/pkg/server/controllers/notes.go index a7434366..74bdd3f1 100644 --- a/pkg/server/controllers/notes.go +++ b/pkg/server/controllers/notes.go @@ -73,7 +73,6 @@ func parseGetNotesQuery(q url.Values) (app.GetNotesParams, error) { yearStr := q.Get("year") monthStr := q.Get("month") books := q["book"] - encryptedStr := q.Get("encrypted") pageStr := q.Get("page") page, err := parsePageQuery(q) @@ -107,21 +106,13 @@ func parseGetNotesQuery(q url.Values) (app.GetNotesParams, error) { month = m } - var encrypted bool - if strings.ToLower(encryptedStr) == "true" { - encrypted = true - } else { - encrypted = false - } - ret := app.GetNotesParams{ - Year: year, - Month: month, - Page: page, - Search: parseSearchQuery(q), - Books: books, - Encrypted: encrypted, - PerPage: notesPerPage, + Year: year, + Month: month, + Page: page, + Search: parseSearchQuery(q), + Books: books, + PerPage: notesPerPage, } return ret, nil diff --git a/pkg/server/database/models.go b/pkg/server/database/models.go index 98571e10..50ef2006 100644 --- a/pkg/server/database/models.go +++ b/pkg/server/database/models.go @@ -40,7 +40,6 @@ type Book struct { EditedOn int64 `json:"edited_on"` USN int `json:"-" gorm:"index"` Deleted bool `json:"-" gorm:"default:false"` - Encrypted bool `json:"-" gorm:"default:false"` } // Note is a model for a note @@ -57,7 +56,6 @@ type Note struct { Public bool `json:"public" gorm:"default:false"` USN int `json:"-" gorm:"index"` Deleted bool `json:"-" gorm:"default:false"` - Encrypted bool `json:"-" gorm:"default:false"` Client string `gorm:"index"` }