From 2554c7f4cadd2e5f323e1b3480fa613be811c0e2 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Sun, 2 Apr 2023 10:58:50 +1000 Subject: [PATCH] retrieve secret working --- controllers/retrieve_secrets.go | 35 +++++++++++++++ controllers/store_secrets.go | 13 +++--- models/secret.go | 79 ++++++++++++++++++++++++++++++--- models/user.go | 2 + 4 files changed, 117 insertions(+), 12 deletions(-) diff --git a/controllers/retrieve_secrets.go b/controllers/retrieve_secrets.go index b0b639f..f76f7fc 100644 --- a/controllers/retrieve_secrets.go +++ b/controllers/retrieve_secrets.go @@ -1,6 +1,9 @@ package controllers import ( + "ccsecrets/models" + "ccsecrets/utils/token" + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -14,8 +17,40 @@ type RetrieveInput struct { func RetrieveSecret(c *gin.Context) { var input RetrieveInput + // Validate the input matches our struct if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } + fmt.Printf("StoreSecret received JSON input '%v'\n", input) + + // Get the user and role id of the requestor + user_id, err := token.ExtractTokenID(c) + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + u, err := models.GetUserRoleByID(user_id) + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Populate fields + s := models.Secret{} + s.RoleId = u.RoleId + s.DeviceName = input.DeviceName + s.DeviceCategory = input.DeviceCategory + + results, err := models.GetSecrets(&s) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // output results as json + c.JSON(http.StatusOK, gin.H{"message": "success", "data": results}) } diff --git a/controllers/store_secrets.go b/controllers/store_secrets.go index 726d926..808e2f0 100644 --- a/controllers/store_secrets.go +++ b/controllers/store_secrets.go @@ -50,12 +50,13 @@ func StoreSecret(c *gin.Context) { } // This is just here for testing to make sure that decryption works - _, err = s.DecryptSecret() - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"Error decrypting secret": err.Error()}) - return - } - + /* + _, err = s.DecryptSecret() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"Error decrypting secret": err.Error()}) + return + } + */ _, err = s.SaveSecret() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"Error saving secret": err.Error()}) diff --git a/models/secret.go b/models/secret.go index a9e58fc..bc9b7f3 100644 --- a/models/secret.go +++ b/models/secret.go @@ -5,20 +5,26 @@ import ( "crypto/cipher" "crypto/rand" "encoding/hex" + "errors" "fmt" "io" "os" + + "github.com/jmoiron/sqlx" ) +// We use the json:"-" field tag to prevent showing these details to the user type Secret struct { - SecretId int `db:"SecretId"` - RoleId int `db:"RoleId"` + SecretId int `db:"SecretId" json:"-"` + RoleId int `db:"RoleId" json:"-"` DeviceName string `db:"DeviceName"` DeviceCategory string `db:"DeviceCategory"` UserName string `db:"UserName"` Secret string `db:"Secret"` } +const nonceSize = 12 + func (s *Secret) SaveSecret() (*Secret, error) { var err error @@ -38,6 +44,54 @@ func (s *Secret) SaveSecret() (*Secret, error) { return s, nil } +func GetSecrets(s *Secret) ([]Secret, error) { + var err error + var rows *sqlx.Rows + var secretResults []Secret + + fmt.Printf("GetSecret querying values '%v'\n", s) + + // Determine whether to query for a specific device or a category of devices + if s.DeviceName != "" { + rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceName LIKE ? AND RoleId = ?", s.DeviceName, s.RoleId) + } else if s.DeviceCategory != "" { + rows, err = db.Queryx("SELECT * FROM secrets WHERE DeviceCategory LIKE ? AND RoleId = ?", s.DeviceCategory, s.RoleId) + } else { + fmt.Printf("GetSecret no valid search options specified\n") + err = errors.New("no valid search options specified") + return secretResults, err + } + + // TODO - do we want to generate an error if the query returns more than one result? + + if err != nil { + fmt.Printf("GetSecret error executing sql record : '%s'\n", err) + return secretResults, err + } else { + // parse all the results into a slice + for rows.Next() { + var r Secret + err = rows.StructScan(&r) + if err != nil { + fmt.Printf("GetSecret error parsing sql record : '%s'\n", err) + return secretResults, err + } + + // Decrypt the secret + _, err = r.DecryptSecret() + if err != nil { + fmt.Printf("GetSecret unable to decrypt stored secret '%v', skipping result.\n", r.Secret) + } else { + secretResults = append(secretResults, r) + } + + } + fmt.Printf("GetSecret retrieved '%d' results\n", len(secretResults)) + } + + return secretResults, nil +} + func (s *Secret) EncryptSecret() (*Secret, error) { keyString := os.Getenv("SECRETS_KEY") @@ -56,7 +110,7 @@ func (s *Secret) EncryptSecret() (*Secret, error) { } // Never use more than 2^32 random nonces with a given key because of the risk of a repeat. - nonce := make([]byte, 12) + nonce := make([]byte, nonceSize) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { fmt.Printf("EncryptSecret nonce generation error '%s'\n", err) return s, err @@ -93,12 +147,23 @@ func (s *Secret) DecryptSecret() (*Secret, error) { // to select AES-128 or AES-256. //key := []byte("ECB518652A170880555136EA1F9752D6") - crypted, _ := hex.DecodeString(s.Secret) + if len(s.Secret) < nonceSize { + fmt.Printf("DecryptSecret ciphertext is too short to decrypt\n") + return s, errors.New("ciphertext is too short") + } + + crypted, err := hex.DecodeString(s.Secret) + if err != nil { + fmt.Printf("DecryptSecret unable to convert hex encoded string due to error '%s'\n", err) + return s, err + } + + fmt.Printf("DecryptSecret processing secret '%x'\n", crypted) //nonce, _ := hex.DecodeString("64a9433eae7ccceee2fc0eda") // The nonce is the first 12 bytes from the ciphertext - nonce := crypted[:12] - ciphertext := crypted[12:] + nonce := crypted[:nonceSize] + ciphertext := crypted[nonceSize:] fmt.Printf("DecryptSecret applying key '%v' and nonce '%x' to ciphertext '%x'\n", key, nonce, ciphertext) @@ -121,5 +186,7 @@ func (s *Secret) DecryptSecret() (*Secret, error) { } fmt.Printf("DecryptSecret plaintext is '%s'\n", plaintext) + + s.Secret = string(plaintext) return s, nil } diff --git a/models/user.go b/models/user.go index 927bfb0..4eb8bac 100644 --- a/models/user.go +++ b/models/user.go @@ -26,6 +26,8 @@ func (u *User) SaveUser() (*User, error) { var err error + // TODO - validate username not already in use + result, err := db.NamedExec((`INSERT INTO users (RoleId, UserName, Password) VALUES (:RoleId, :UserName, :Password)`), u) if err != nil {