mirror of
https://github.com/cubixle/vault.git
synced 2026-04-24 18:24:47 +01:00
181 lines
3.8 KiB
Go
181 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/labstack/echo/v4/middleware"
|
|
)
|
|
|
|
// Item holds the data.
|
|
type Item struct {
|
|
Data string `json:"data"`
|
|
Expiry time.Time `json:"expiryDate"`
|
|
TTL int `json:"ttl"`
|
|
}
|
|
|
|
// Vault holds the vault data and key.
|
|
type Vault struct {
|
|
Vault string `json:"vault" binding:"required"`
|
|
Key string `json:"key" binding:"required"`
|
|
}
|
|
|
|
func main() {
|
|
appURL := os.Getenv("VAULT_APP_URL")
|
|
if appURL == "" {
|
|
appURL = "*"
|
|
}
|
|
|
|
port := os.Getenv("VAULT_PORT")
|
|
if port == "" {
|
|
port = "8080"
|
|
}
|
|
|
|
router := echo.New()
|
|
|
|
router.POST("/", createAction)
|
|
router.POST("/decrypt", decryptAction)
|
|
|
|
router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
|
AllowOrigins: []string{appURL},
|
|
}))
|
|
|
|
if err := router.Start(":" + port); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func createAction(c echo.Context) error {
|
|
item := &Item{}
|
|
if err := c.Bind(item); err != nil {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "invalid form data"}`)
|
|
}
|
|
|
|
if item.Data == "" {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "missing data"}`)
|
|
}
|
|
|
|
if item.TTL == 0 {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "missing ttl"}`)
|
|
}
|
|
|
|
currentTime := time.Now()
|
|
item.Expiry = currentTime.Add(time.Duration(item.TTL) * time.Second)
|
|
|
|
key := generateUniqueID(16)
|
|
json, err := json.Marshal(&item)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "invalid form data"}`)
|
|
}
|
|
|
|
data := encrypt([]byte(key), string(json))
|
|
|
|
var vault Vault
|
|
vault.Key = key
|
|
vault.Vault = data
|
|
|
|
return c.JSON(http.StatusOK, vault)
|
|
}
|
|
|
|
func decryptAction(c echo.Context) error {
|
|
var vault Vault
|
|
err := c.Bind(&vault)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "invalid form data"}`)
|
|
}
|
|
|
|
if vault.Key == "" {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "missing key"}`)
|
|
}
|
|
|
|
if vault.Vault == "" {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "missing vault data"}`)
|
|
}
|
|
data := decrypt([]byte(vault.Key), vault.Vault)
|
|
|
|
var item Item
|
|
err = json.Unmarshal([]byte(data), &item)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "invalid form data"}`)
|
|
}
|
|
|
|
currentTime := time.Now()
|
|
if currentTime.Unix() > item.Expiry.Unix() {
|
|
return c.JSON(http.StatusBadRequest, `{"error": "invalid time"}`)
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
func generateUniqueID(length int) string {
|
|
n := length
|
|
b := make([]byte, n)
|
|
if _, err := rand.Read(b); err != nil {
|
|
panic(err)
|
|
}
|
|
s := fmt.Sprintf("%X", b)
|
|
|
|
return strings.ToLower(s)
|
|
}
|
|
|
|
func encrypt(key []byte, message string) string {
|
|
plainText := []byte(message)
|
|
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
//IV needs to be unique, but doesn't have to be secure.
|
|
//It's common to put it at the beginning of the ciphertext.
|
|
cipherText := make([]byte, aes.BlockSize+len(plainText))
|
|
iv := cipherText[:aes.BlockSize]
|
|
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
|
|
return ""
|
|
}
|
|
|
|
stream := cipher.NewCFBEncrypter(block, iv)
|
|
stream.XORKeyStream(cipherText[aes.BlockSize:], plainText)
|
|
|
|
//returns to base64 encoded string
|
|
encmess := base64.URLEncoding.EncodeToString(cipherText)
|
|
return encmess
|
|
}
|
|
|
|
func decrypt(key []byte, securemess string) string {
|
|
cipherText, err := base64.URLEncoding.DecodeString(securemess)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
if len(cipherText) < aes.BlockSize {
|
|
return ""
|
|
}
|
|
|
|
iv := cipherText[:aes.BlockSize]
|
|
cipherText = cipherText[aes.BlockSize:]
|
|
|
|
stream := cipher.NewCFBDecrypter(block, iv)
|
|
// XORKeyStream can work in-place if the two arguments are the same.
|
|
stream.XORKeyStream(cipherText, cipherText)
|
|
|
|
decodedmess := string(cipherText)
|
|
return decodedmess
|
|
}
|