Skip to content

Commit

Permalink
Crypto is now server-managed
Browse files Browse the repository at this point in the history
  • Loading branch information
proofrock committed Mar 19, 2024
1 parent 2250c55 commit 253e7e1
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 147 deletions.
104 changes: 104 additions & 0 deletions backend/crypton/crypton.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2024- Germano Rizzo
*
* This file is part of Seif.
*
* Seif 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.
*
* Seif 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 Seif. If not, see <http://www.gnu.org/licenses/>.
*/
package crypton

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
)

const IV_LEN = 48 >> 3
const KEY_LEN = 96 >> 3

const IV_LEN_COMPLETE = 12 // FIXME must be a real constant
const KEY_LEN_COMPLETE = aes.BlockSize

func genRandomBytes(length int) ([]byte, error) {
ret := make([]byte, length)

if _, err := rand.Read(ret); err != nil {
return nil, err
}
return ret, nil
}

func lengthen(bs []byte, length int) []byte {
ret := make([]byte, length)
copy(ret, bs)
return ret
}

func Bs2str(bs []byte) string {
return base64.URLEncoding.EncodeToString(bs)
}

func Str2bs(str string) ([]byte, error) {
return base64.URLEncoding.DecodeString(str)
}

func Encode(message string) (id []byte, key []byte, crypto []byte, err error) {
key, err = genRandomBytes(KEY_LEN)
if err != nil {
return nil, nil, nil, err
}
key2 := lengthen(key, KEY_LEN_COMPLETE)

aesBlock, err := aes.NewCipher(key2)
if err != nil {
return nil, nil, nil, err
}

aesgcm, err := cipher.NewGCM(aesBlock)
if err != nil {
return nil, nil, nil, err
}

id, err = genRandomBytes(IV_LEN)
if err != nil {
return nil, nil, nil, err
}
id2 := lengthen(id, IV_LEN_COMPLETE)

crypto = aesgcm.Seal(nil, id2, []byte(message), nil)

return
}

func Decode(id []byte, key []byte, crypto []byte) (message string, err error) {
aesBlock, err := aes.NewCipher(lengthen(key, KEY_LEN_COMPLETE))
if err != nil {
return "", err
}

aesgcm, err := cipher.NewGCM(aesBlock)
if err != nil {
return "", err
}

plain, err := aesgcm.Open(nil, lengthen(id, IV_LEN_COMPLETE), crypto, nil)
if err != nil {
return "", err
}

message = string(plain)

return
}
6 changes: 2 additions & 4 deletions backend/db_ops/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ const DB_VERSION = 1

const SQL_CREATE = `
CREATE TABLE SECRETS (
ID TEXT PRIMARY KEY,
IV TEXT,
SECRET TEXT,
SHA TEXT,
ID TEXT PRIMARY KEY NOT NULL,
SECRET BLOB NOT NULL,
EXPIRY INTEGER,
TS TEXT
)`
Expand Down
2 changes: 1 addition & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.18.0 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
modernc.org/libc v1.45.0 // indirect
modernc.org/libc v1.45.2 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/strutil v1.2.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.45.0 h1:qmAJZf9tYFqK/SFSFqpBc9uHWGsvoYWtRcMQdG+JEfM=
modernc.org/libc v1.45.0/go.mod h1:YkRHLoN4L70OdO1cVmM83KZhRbRvsc3XogfVzbTXBwE=
modernc.org/libc v1.45.2 h1:oRlBu8xlBen2awVAWuLOkvYNBPaIKFxFOj9wA/jaXHM=
modernc.org/libc v1.45.2/go.mod h1:YkRHLoN4L70OdO1cVmM83KZhRbRvsc3XogfVzbTXBwE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
Expand Down
56 changes: 43 additions & 13 deletions backend/handlers/get_secret/get_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,51 +19,81 @@
package get_secret

import (
"context"
"seif/crypton"
"seif/db_ops"
"seif/params"
"seif/utils"

"github.com/gofiber/fiber/v2"
)

type Secret struct {
IV string `json:"iv"`
Sec string `json:"secret"`
SHA string `json:"sha"`
}

type response struct {
Secret *Secret `json:"secret"`
Secret *string `json:"secret"`
}

const SQL = "DELETE FROM SECRETS WHERE ID = $1 RETURNING IV, SECRET, SHA"
const SQL1 = "SELECT SECRET FROM SECRETS WHERE ID = $1"
const SQL2 = "DELETE FROM SECRETS WHERE ID = $1"

func GetSecret(c *fiber.Ctx) error {
id := c.Query("id", "")
idBs, err := crypton.Str2bs(id)
if err != nil {
return utils.SendError(c, fiber.StatusBadRequest, utils.FHE004, "id", &err)
}

key := c.Query("key", "")
keyBs, err := crypton.Str2bs(key)
if err != nil {
return utils.SendError(c, fiber.StatusBadRequest, utils.FHE004, "key", &err)
}

defer func() { go db_ops.Backup() }()
params.Lock.Lock()
defer params.Lock.Unlock()

ret := response{}

rows, err := params.Db.Query(SQL, id)
tx, err := params.Db.BeginTx(context.Background(), nil)
if err != nil {
return utils.SendError(c, fiber.StatusInternalServerError, "FHE007", "", &err)
}
defer tx.Rollback()

rows, err := tx.Query(SQL1, id)
if err != nil {
return utils.SendError(c, fiber.StatusInternalServerError, utils.FHE002, "secret", &err)
return utils.SendError(c, fiber.StatusInternalServerError, utils.FHE001, "secret", &err)
}
defer rows.Close()
if rows.Next() {
var secret Secret
err = rows.Scan(&secret.IV, &secret.Sec, &secret.SHA)
var secret []byte
err = rows.Scan(&secret)
if err != nil {
return utils.SendError(c, fiber.StatusInternalServerError, utils.FHE001, "secret", &err)
}
ret.Secret = &secret

plaintxt, err := crypton.Decode(idBs, keyBs, secret)
if err != nil {
return utils.SendError(c, fiber.StatusBadRequest, utils.FHE008, "decryption", &err)
}

ret.Secret = &plaintxt
}
if err = rows.Err(); err != nil {
return utils.SendError(c, fiber.StatusInternalServerError, utils.FHE003, "secret", &err)
}

if ret.Secret != nil {
_, err := tx.Exec(SQL2, id)
if err != nil {
return utils.SendError(c, fiber.StatusInternalServerError, utils.FHE009, "secret", &err)
}
}

if err := tx.Commit(); err != nil {
return utils.SendError(c, fiber.StatusInternalServerError, utils.FHE009, "transaction", &err)
}

c.JSON(ret)
return c.SendStatus(fiber.StatusOK)
}
45 changes: 14 additions & 31 deletions backend/handlers/put_secret/putSecret.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,76 +19,59 @@
package put_secret

import (
"crypto/rand"
"encoding/base64"
"fmt"
"seif/crypton"
"seif/db_ops"
"seif/handlers/get_secret"
"seif/params"
"seif/utils"

"github.com/gofiber/fiber/v2"
)

type request struct {
get_secret.Secret
Expiry int `json:"expiry"`
Secret string `json:"secret"`
Expiry int `json:"expiry"`
}

type response struct {
Id string `json:"id"`
Id string `json:"id"`
Key string `json:"key"`
}

const SQL = `
INSERT INTO SECRETS (ID, IV, SECRET, SHA, EXPIRY, TS)
VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
RETURNING ID`

// generateRandomBase64 generates a 42-bit random value encoded in base64.
func generate42bitRandomBase64() (string, error) {
// 42 bits = 5.25 bytes, but we need a whole number of bytes
b := make([]byte, 6) // Using 6 bytes (48 bits) to have a whole number greater than 42 bits
_, err := rand.Read(b)
if err != nil {
return "", err
}
// Mask the last 6 bits of the last byte to zero to ensure only 42 bits are random
b[5] &= 0xC0 // 0xC0 is 11000000 in binary, which sets the last 6 bits to zero

// Encode to base64
encoded := base64.URLEncoding.EncodeToString(b)
// Trim the result to the correct length: 7 characters for 42 bits
return encoded[:7], nil
}
INSERT INTO SECRETS (ID, SECRET, EXPIRY, TS)
VALUES ($1, $2, $3, CURRENT_TIMESTAMP)`

func PutSecret(c *fiber.Ctx) error {
req := new(request)
if err := c.BodyParser(req); err != nil {
return utils.SendError(c, fiber.StatusBadRequest, utils.FHE004, "body", &err)
}

if len(req.Sec) > params.MaxBytes {
if len(req.Secret) > params.MaxBytes {
return utils.SendError(c, fiber.StatusBadRequest, utils.FHE005, "", nil)
}

if req.Expiry < 1 || req.Expiry > params.MaxDays {
return utils.SendError(c, fiber.StatusBadRequest, utils.FHE006, fmt.Sprint(params.MaxDays), nil)
}

id, err := generate42bitRandomBase64()
id, key, crypto, err := crypton.Encode(req.Secret)
if err != nil {
return utils.SendError(c, fiber.StatusBadRequest, utils.FHE007, "", &err)
return utils.SendError(c, fiber.StatusInternalServerError, utils.FHE007, "", &err)
}

ret := response{Id: crypton.Bs2str(id), Key: crypton.Bs2str(key)}

defer func() { go db_ops.Backup() }()
params.Lock.Lock()
defer params.Lock.Unlock()

_, err = params.Db.Exec(SQL, id, req.IV, req.Sec, req.SHA, req.Expiry)
_, err = params.Db.Exec(SQL, ret.Id, crypto, req.Expiry)
if err != nil {
return utils.SendError(c, fiber.StatusInternalServerError, utils.FHE002, "secrets", &err)
}

c.JSON(response{Id: id})
c.JSON(ret)
return c.SendStatus(fiber.StatusOK)
}
2 changes: 2 additions & 0 deletions backend/utils/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ var FHE004 = "%s is malformed"
var FHE005 = "secret is too long"
var FHE006 = "invalid expiry, must be between 1 and %s days"
var FHE007 = "cannot generate random key"
var FHE008 = "%s failed"
var FHE009 = "cannot delete %s"
20 changes: 7 additions & 13 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"vite": "^4.0.0"
},
"dependencies": {
"crypto-js": "^4.0.0",
"sweetalert2": "^11.0.0"
}
}
Loading

0 comments on commit 253e7e1

Please sign in to comment.