Files
one-ai/backend/src/learning-go/web-services-gin/main.go
T
2024-11-01 14:42:16 -06:00

192 lines
5.7 KiB
Go

package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"io"
"net/http"
"strconv"
"strings"
)
// TEST VALUES:
// KEY: passphrasewhichneedstobe32bytes! // TODO: Test with 256 bits
// INPUT: The ultimate question: of life, the universe, of everything!
// SHA-1 HASH: 0c6ca28f5607d86e5426b46795e0fa827f1627d6
// BASE64 HASH: string DGyij1YH2G5UJrRnleD6gn8WJ9Y=
// AES OUTPUT: []uint8{104, 215, 184, 35, 194, 120, 115, 112, 133, 21, 160, 158, 11, 136, 201, 160, 233, 3, 12, 29, 133, 211, 207, 98, 35, 199, 89, 31, 203, 230, 39, 210, 225, 99, 96, 101, 3, 5, 198, 82, 243, 179, 176, 217, 8, 4, 63, 22, 134, 184, 68, 102, 255, 183, 176, 172, 6, 176, 249, 28, 127, 120, 235, 135, 73, 235, 249, 118, 194, 4, 137, 27, 158, 190, 134, 68, 21, 105, 26, 134, 10, 224, 233, 115, 28, 0, 153, 209}
// HEX AES: "68 D7 B8 23 C2 78 73 70 85 15 A0 9E B 88 C9 A0 E9 3 C 1D 85 D3 CF 62 23 C7 59 1F CB E6 27 D2 E1 63 60 65 3 5 C6 52 F3 B3 B0 D9 8 4 3F 16 86 B8 44 66 FF B7 B0 AC 6 B0 F9 1C 7F 78 EB 87 49 EB F9 76 C2 4 89 1B 9E BE 86 44 15 69 1A 86 A E0 E9 73 1C 0 99 D1"
var rawHash = "DGyij1YH2G5UJrRnleD6gn8WJ9Y="
type request struct {
//UserID string `json:"id"`
Prompt string `json:"prompt"`
Sum string `json:"sum"`
}
var requests []request
// TODO: Test if 256 bit key works.
// TODO: Dynamically get key? Need to research best way to store private keys between two devices.
var key = []byte("passphrasewhichneedstobe32bytes!")
func encrypt() {
text := []byte("The ultimate question: of life, the universe, of everything!")
// generate a new aes cipher using our 32 byte long key
c, err := aes.NewCipher(key)
// if there are any errors, handle them
if err != nil {
fmt.Println(err)
}
// gcm or Galois/Counter Mode, is a mode of operation
// for symmetric key cryptographic block ciphers
// - https://en.wikipedia.org/wiki/Galois/Counter_Mode
gcm, err := cipher.NewGCM(c)
// if any error generating new GCM
// handle them
if err != nil {
fmt.Println(err)
}
// creates a new byte array the size of the nonce
// which must be passed to Seal
nonce := make([]byte, gcm.NonceSize())
// populates our nonce with a cryptographically secure
// random sequence
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
fmt.Println(err)
}
// here we encrypt our text using the Seal function
// Seal encrypts and authenticates plaintext, authenticates the
// additional data and appends the result to dst, returning the updated
// slice. The nonce must be NonceSize() bytes long and unique for all
// time, for a given key.
fmt.Println(gcm.Seal(nonce, nonce, text, nil))
//// the WriteFile method returns an error if unsuccessful
//err = ioutil.WriteFile("myfile", gcm.Seal(nonce, nonce, text, nil), 0777)
//// handle this error
//if err != nil {
// // print it out
// fmt.Println(err)
//}
}
func convertHexToBytes(hexString *string) ([]uint8, error) {
// Handle nil pointer case
if hexString == nil {
return nil, errors.New("nil pointer provided for hex string")
}
// Split the hex string by spaces
hexBytes := strings.Fields(*hexString)
// Initialize an empty slice for uint8
data := make([]uint8, len(hexBytes))
// Iterate and convert each hex byte
for i, hexByte := range hexBytes {
// Convert each hex string to a uint8 value (handling errors)
value, err := strconv.ParseUint(hexByte, 16, 8)
if err != nil {
return nil, fmt.Errorf("error parsing hex byte '%s': %w", hexByte, err)
}
// Assign the converted value to the slice
data[i] = uint8(value)
}
// Return the slice of uint8 and any errors encountered
return data, nil
}
func decrypt(input *request) bool {
//ciphertext, err := ioutil.ReadFile("myfile")
ciphertext, err := convertHexToBytes(&input.Prompt)
// if our program was unable to read the file
// print out the reason why it can't
if err != nil {
fmt.Println(err)
}
c, err := aes.NewCipher(key)
if err != nil {
fmt.Println(err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
fmt.Println(err)
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
fmt.Println(err)
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
fmt.Println(err)
}
s := string(plaintext)
if validateHash(s) {
input.Prompt = s
return true
}
input.Prompt = "DATA CORRUPTED OR TAMPERED"
return false
}
// Validate there's no tampering with SHA-1 sum. The decrypted hash and the transmitted hash should be identical.
func validateHash(decrypted string) bool {
// Calculate the SHA256 sum of the decrypted request
hasher := sha1.New()
hasher.Write([]byte(decrypted))
decryptedHashString := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
// TODO: Add error handling if hash doesn't match.
return decryptedHashString == rawHash
}
// User HTTP GET request has the prompt decrypted and verified with a SHA-1 checksum.
// If there's been no data corruption, re-construct user prompt for ollama
// TODO: Eventually, add additional logic that will allow for re-direction and contextual awareness (pre-processing)
func postRequest(c *gin.Context) {
var newInput request
//Call BindJSON to bind the received JSON to
if err := c.BindJSON(&newInput); err != nil {
return
}
if decrypt(&newInput) {
requests = append(requests, newInput)
c.IndentedJSON(http.StatusCreated, newInput) // TODO: Formulate prompt and transfer to AI layer to formulate new JSON response back
} else {
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Failed checksum. Presumably data corrupted."})
}
}
func main() {
router := gin.Default()
router.POST("/prompts", postRequest)
err := router.Run("localhost:8080")
if err != nil {
return
}
}