package detail
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io"
mrand "math/rand"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"github.com/xmirrorsecurity/opensca-cli/v3/cmd/config"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/common"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs"
"github.com/pkg/errors"
)
type SaasReponse struct {
Message string `json:"message"`
Code int `json:"code"`
Data interface{} `json:"data"`
}
type DetectReponse struct {
Message string `json:"aesMessage"`
Tag string `json:"aesTag"`
Nonce string `json:"aesNonce"`
}
type DetectRequst struct {
Tag string `json:"aesTag"`
Token string `json:"ossToken"`
Nonce string `json:"aesNonce"`
Message string `json:"aesMessage"`
ClientId string `json:"clientId"`
}
func GetClientId() string {
id := "XXXXXXXXXXXXXXXX"
excFilepath, err := os.Executable()
if err != nil {
logs.Error(err)
}
idFile := filepath.Join(filepath.Dir(excFilepath), ".key")
if _, err := os.Stat(idFile); err != nil {
if f, err := os.Create(idFile); err != nil {
logs.Error(err)
} else {
defer f.Close()
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
idbyte := []byte(id)
for i := range idbyte {
idbyte[i] = chars[mrand.Intn(26)]
}
f.Write(idbyte)
id = string(idbyte)
}
} else {
idbyte, err := os.ReadFile(idFile)
if err != nil {
logs.Error(err)
}
if len(idbyte) == 16 {
if ok, err := regexp.Match(`[A-Z]{16}`, idbyte); ok && err == nil {
id = string(idbyte)
}
}
}
return id
}
func Detect(dtype string, reqbody []byte) (repbody []byte, err error) {
repbody = []byte{}
key, err := getAesKey()
if err != nil {
return repbody, err
}
nonce := make([]byte, 16)
rand.Read(nonce)
ciphertext, tag := encrypt(reqbody, key, nonce)
url := config.Conf().Origin.Url + "/oss-saas/api-v1/open-sca-client/detect"
param := DetectRequst{}
param.ClientId = GetClientId()
param.Token = config.Conf().Origin.Token
param.Tag = base64.StdEncoding.EncodeToString(tag)
param.Nonce = base64.StdEncoding.EncodeToString(nonce)
param.Message = base64.StdEncoding.EncodeToString(ciphertext)
data, err := json.Marshal(param)
if err != nil {
return repbody, err
}
req, err := http.NewRequest("POST", url, bytes.NewReader(data))
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Detect-Type", dtype)
resp, err := common.HttpSaasClient.Do(req)
if err != nil {
return repbody, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
repbody, err = io.ReadAll(resp.Body)
if err != nil {
logs.Error(err)
return
} else {
saasrep := SaasReponse{}
err = json.Unmarshal(repbody, &saasrep)
if err != nil {
logs.Error(err)
}
if saasrep.Code != 0 {
logs.Warn(fmt.Sprintf("url:%s code:%d message: %s", url, saasrep.Code, saasrep.Message))
err = errors.New(saasrep.Message)
return
} else {
data, err = json.Marshal(saasrep.Data)
detect := DetectReponse{}
err = json.Unmarshal([]byte(data), &detect)
if err != nil {
logs.Error(err)
}
var ciphertext []byte
ciphertext, err = base64.StdEncoding.DecodeString(detect.Message)
tag, err = base64.StdEncoding.DecodeString(detect.Tag)
nonce, err = base64.StdEncoding.DecodeString(detect.Nonce)
repbody = decrypt(ciphertext, key, nonce, tag)
return
}
}
} else {
return repbody, fmt.Errorf("%s status code: %d", url, resp.StatusCode)
}
}
func getAesKey() (key []byte, err error) {
u, err := url.Parse(config.Conf().Origin.Url + "/oss-saas/api-v1/open-sca-client/aes-key")
if err != nil {
return key, err
}
param := url.Values{}
param.Set("clientId", GetClientId())
param.Set("ossToken", config.Conf().Origin.Token)
u.RawQuery = param.Encode()
req, err := http.NewRequest("GET", u.String(), nil)
rep, err := common.HttpSaasClient.Do(req)
if err != nil {
logs.Error(err)
return
}
if rep.StatusCode != 200 {
err = fmt.Errorf("url: %s,status code:%d", u.String(), rep.StatusCode)
logs.Error(err)
return
} else {
defer rep.Body.Close()
data, err := io.ReadAll(rep.Body)
if err != nil {
logs.Error(err)
return key, err
}
saasrep := SaasReponse{}
json.Unmarshal(data, &saasrep)
if saasrep.Code != 0 {
logs.Warn(fmt.Sprintf("url:%s code:%d message: %s", u.String(), saasrep.Code, saasrep.Message))
err = errors.New(saasrep.Message)
return key, err
} else {
key = []byte(saasrep.Data.(string))
return key, nil
}
}
}
const tagSize = 16
func encrypt(text, key, nonce []byte) (ciphertext, tag []byte) {
block, err := aes.NewCipher(key)
if err != nil {
logs.Error(err)
return
}
aesgcm, err := cipher.NewGCMWithNonceSize(block, len(nonce))
if err != nil {
logs.Error(err)
return
}
res := aesgcm.Seal(nil, nonce, text, nil)
tagIndex := len(res) - tagSize
return res[:tagIndex], res[tagIndex:]
}
func decrypt(ciphertext, key, nonce, tag []byte) (text []byte) {
block, err := aes.NewCipher(key)
if err != nil {
logs.Error(err)
return
}
aesgcm, err := cipher.NewGCMWithNonceSize(block, len(nonce))
if err != nil {
logs.Error(err)
return
}
text, err = aesgcm.Open(nil, nonce, append(ciphertext, tag...), nil)
if err != nil {
logs.Error(err)
}
return
}