security: Đóng gói và bảo vệ khóa giải mã nội bộ trong binary, gỡ bỏ hoàn toàn khỏi cấu hình và tài liệu
This commit is contained in:
@@ -42,7 +42,6 @@
|
|||||||
"current_version": "v1.0.2"
|
"current_version": "v1.0.2"
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"crypto_key": "ENC(R8h55AZzKMVH6QO+X/mBHvoMBLSBOZ9HCXSH2CbLrEwCUgGzFf7aJ5p8VCv7DZAMY6TkJQemYZIcXRS0)",
|
|
||||||
"key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
"key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,6 @@ type UpdaterConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LicenseConfig struct {
|
type LicenseConfig struct {
|
||||||
CryptoKey string `json:"crypto_key"` // stored as ENC(...)
|
|
||||||
Key string `json:"key"` // stored ciphertext U2FsdGVk...
|
Key string `json:"key"` // stored ciphertext U2FsdGVk...
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +60,6 @@ type AppConfig struct {
|
|||||||
DecryptedDBPassword string `json:"-"`
|
DecryptedDBPassword string `json:"-"`
|
||||||
DecryptedUpdaterToken string `json:"-"`
|
DecryptedUpdaterToken string `json:"-"`
|
||||||
DecryptedAdminPassword string `json:"-"`
|
DecryptedAdminPassword string `json:"-"`
|
||||||
DecryptedCryptoKey string `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -162,7 +160,6 @@ func DefaultConfig() *AppConfig {
|
|||||||
CurrentVersion: "v1.0.2",
|
CurrentVersion: "v1.0.2",
|
||||||
},
|
},
|
||||||
License: LicenseConfig{
|
License: LicenseConfig{
|
||||||
CryptoKey: "BAD5A05F330D5B8CDDFCA9E7ECDC840B",
|
|
||||||
Key: "",
|
Key: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -182,18 +179,15 @@ func LoadConfig(path string) (*AppConfig, error) {
|
|||||||
cfg.DecryptedAdminPassword = cfg.Server.AdminPassword
|
cfg.DecryptedAdminPassword = cfg.Server.AdminPassword
|
||||||
cfg.DecryptedDBPassword = cfg.Database.Password
|
cfg.DecryptedDBPassword = cfg.Database.Password
|
||||||
cfg.DecryptedUpdaterToken = cfg.Updater.Token
|
cfg.DecryptedUpdaterToken = cfg.Updater.Token
|
||||||
cfg.DecryptedCryptoKey = cfg.License.CryptoKey
|
|
||||||
|
|
||||||
// Encrypt for disk save
|
// Encrypt for disk save
|
||||||
encAdmin, _ := Encrypt(cfg.Server.AdminPassword)
|
encAdmin, _ := Encrypt(cfg.Server.AdminPassword)
|
||||||
encDB, _ := Encrypt(cfg.Database.Password)
|
encDB, _ := Encrypt(cfg.Database.Password)
|
||||||
encTok, _ := Encrypt(cfg.Updater.Token)
|
encTok, _ := Encrypt(cfg.Updater.Token)
|
||||||
encCrypto, _ := Encrypt(cfg.License.CryptoKey)
|
|
||||||
|
|
||||||
cfg.Server.AdminPassword = encAdmin
|
cfg.Server.AdminPassword = encAdmin
|
||||||
cfg.Database.Password = encDB
|
cfg.Database.Password = encDB
|
||||||
cfg.Updater.Token = encTok
|
cfg.Updater.Token = encTok
|
||||||
cfg.License.CryptoKey = encCrypto
|
|
||||||
|
|
||||||
data, _ := json.MarshalIndent(cfg, "", " ")
|
data, _ := json.MarshalIndent(cfg, "", " ")
|
||||||
_ = os.MkdirAll(filepath.Dir(configPath), 0755)
|
_ = os.MkdirAll(filepath.Dir(configPath), 0755)
|
||||||
@@ -217,24 +211,10 @@ func LoadConfig(path string) (*AppConfig, error) {
|
|||||||
decAdmin, _ := Decrypt(cfg.Server.AdminPassword)
|
decAdmin, _ := Decrypt(cfg.Server.AdminPassword)
|
||||||
decDB, _ := Decrypt(cfg.Database.Password)
|
decDB, _ := Decrypt(cfg.Database.Password)
|
||||||
decTok, _ := Decrypt(cfg.Updater.Token)
|
decTok, _ := Decrypt(cfg.Updater.Token)
|
||||||
if cfg.License.CryptoKey == "" {
|
|
||||||
cfg.License.CryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
|
||||||
}
|
|
||||||
decCrypto, _ := Decrypt(cfg.License.CryptoKey)
|
|
||||||
if decCrypto == "" {
|
|
||||||
decCrypto = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.DecryptedAdminPassword = decAdmin
|
cfg.DecryptedAdminPassword = decAdmin
|
||||||
cfg.DecryptedDBPassword = decDB
|
cfg.DecryptedDBPassword = decDB
|
||||||
cfg.DecryptedUpdaterToken = decTok
|
cfg.DecryptedUpdaterToken = decTok
|
||||||
cfg.DecryptedCryptoKey = decCrypto
|
|
||||||
|
|
||||||
// Ensure crypto key is stored encrypted on disk
|
|
||||||
if !strings.HasPrefix(cfg.License.CryptoKey, "ENC(") {
|
|
||||||
encCrypto, _ := Encrypt(cfg.License.CryptoKey)
|
|
||||||
cfg.License.CryptoKey = encCrypto
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-migrate legacy owner & repo
|
// Auto-migrate legacy owner & repo
|
||||||
if cfg.Updater.Owner == "" || cfg.Updater.Owner == "hdhiswork" {
|
if cfg.Updater.Owner == "" || cfg.Updater.Owner == "hdhiswork" {
|
||||||
@@ -270,14 +250,6 @@ func SaveConfig(cfg *AppConfig) error {
|
|||||||
enc, _ := Encrypt(clone.Updater.Token)
|
enc, _ := Encrypt(clone.Updater.Token)
|
||||||
clone.Updater.Token = enc
|
clone.Updater.Token = enc
|
||||||
}
|
}
|
||||||
if clone.License.CryptoKey == "" {
|
|
||||||
clone.License.CryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(clone.License.CryptoKey, "ENC(") {
|
|
||||||
clone.DecryptedCryptoKey = clone.License.CryptoKey
|
|
||||||
enc, _ := Encrypt(clone.License.CryptoKey)
|
|
||||||
clone.License.CryptoKey = enc
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.MarshalIndent(&clone, "", " ")
|
data, err := json.MarshalIndent(&clone, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -106,14 +106,13 @@ Mở tệp `config.json` bằng Notepad, VSCode hoặc `nano`:
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": {
|
"license": {
|
||||||
"crypto_key": "ENC(U2FsdGVkX18...)",
|
"key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
||||||
"active_key": "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> - Mật khẩu CSDL (`password`) và Passphrase giải mã (`crypto_key`) được hỗ trợ lưu dạng mã hóa `ENC(...)`. Nếu nhập dạng chuỗi thông thường (Plaintext), hệ thống vẫn tự động nhận diện và kết nối an toàn.
|
> - Mật khẩu CSDL (`password`) được hỗ trợ lưu dạng mã hóa `ENC(...)`. Nếu nhập dạng chuỗi thông thường (Plaintext), hệ thống vẫn tự động nhận diện và kết nối an toàn.
|
||||||
> - Có thể thêm bao nhiêu CSDL tùy ý vào mảng `databases`. Người dùng có thể chuyển đổi linh hoạt ngay trên dropdown của thanh công cụ Web.
|
> - Có thể thêm bao nhiêu CSDL tùy ý vào mảng `databases`. Người dùng có thể chuyển đổi linh hoạt ngay trên dropdown của thanh công cụ Web.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -123,7 +122,7 @@ Mở tệp `config.json` bằng Notepad, VSCode hoặc `nano`:
|
|||||||
### 5.1 Quy cách và Cấu trúc Bản quyền
|
### 5.1 Quy cách và Cấu trúc Bản quyền
|
||||||
Hệ thống sử dụng cơ chế bảo mật theo tiêu chuẩn thư viện `OTH.Adapter / HisPrivateCode`:
|
Hệ thống sử dụng cơ chế bảo mật theo tiêu chuẩn thư viện `OTH.Adapter / HisPrivateCode`:
|
||||||
- **Thuật toán**: OpenSSL EVP_BytesToKey (MD5 key derivation + AES-256-CBC).
|
- **Thuật toán**: OpenSSL EVP_BytesToKey (MD5 key derivation + AES-256-CBC).
|
||||||
- **Khóa dẫn xuất nội bộ**: `BAD5A05F330D5B8CDDFCA9E7ECDC840B`.
|
- **Cơ chế xác thực**: Khóa bí mật nội bộ được đóng gói và tích hợp tự động bên trong file thực thi binary/exe; khi triển khai người dùng hoặc kỹ thuật viên chỉ cần nhập duy nhất chuỗi token DECODE.
|
||||||
- **Cấu trúc dữ liệu JSON sau giải mã**:
|
- **Cấu trúc dữ liệu JSON sau giải mã**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-12
@@ -30,9 +30,6 @@ func HandleGetAdminConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
if respCfg.Updater.Token != "" {
|
if respCfg.Updater.Token != "" {
|
||||||
respCfg.Updater.Token = "••••••••"
|
respCfg.Updater.Token = "••••••••"
|
||||||
}
|
}
|
||||||
if respCfg.License.CryptoKey != "" {
|
|
||||||
respCfg.License.CryptoKey = "••••••••"
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
@@ -83,15 +80,7 @@ func HandleSaveAdminConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
input.Updater.Token = enc
|
input.Updater.Token = enc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle License Crypto Key
|
// Handle License Key
|
||||||
if input.License.CryptoKey == "" || input.License.CryptoKey == "••••••••" {
|
|
||||||
input.License.CryptoKey = current.License.CryptoKey
|
|
||||||
input.DecryptedCryptoKey = current.DecryptedCryptoKey
|
|
||||||
} else {
|
|
||||||
input.DecryptedCryptoKey = input.License.CryptoKey
|
|
||||||
enc, _ := config.Encrypt(input.License.CryptoKey)
|
|
||||||
input.License.CryptoKey = enc
|
|
||||||
}
|
|
||||||
if input.License.Key == "" {
|
if input.License.Key == "" {
|
||||||
input.License.Key = current.License.Key
|
input.License.Key = current.License.Key
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-11
@@ -114,13 +114,22 @@ func OpenSSLDecrypt(encryptedBase64 string, passphrase string) (string, error) {
|
|||||||
return string(plaintext[:len(plaintext)-padLen]), nil
|
return string(plaintext[:len(plaintext)-padLen]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internalSecretKey returns the internal private passphrase (DHCryptoJSKEY)
|
||||||
|
func internalSecretKey() string {
|
||||||
|
part1 := []byte{0x42, 0x41, 0x44, 0x35, 0x41, 0x30, 0x35, 0x46}
|
||||||
|
part2 := []byte{0x33, 0x33, 0x30, 0x44, 0x35, 0x42, 0x38, 0x43}
|
||||||
|
part3 := []byte{0x44, 0x44, 0x46, 0x43, 0x41, 0x39, 0x45, 0x37}
|
||||||
|
part4 := []byte{0x45, 0x43, 0x44, 0x43, 0x38, 0x34, 0x30, 0x42}
|
||||||
|
return string(append(append(append(part1, part2...), part3...), part4...))
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateAndParseLicense parses and evaluates the license ciphertext
|
// ValidateAndParseLicense parses and evaluates the license ciphertext
|
||||||
func ValidateAndParseLicense(encryptedKey string, cryptoKey string) (*LicenseInfo, error) {
|
func ValidateAndParseLicense(encryptedKey string, cryptoKey string) (*LicenseInfo, error) {
|
||||||
if strings.TrimSpace(encryptedKey) == "" {
|
if strings.TrimSpace(encryptedKey) == "" {
|
||||||
return nil, errors.New("chuỗi key bản quyền trống")
|
return nil, errors.New("chuỗi key bản quyền trống")
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(cryptoKey) == "" {
|
if strings.TrimSpace(cryptoKey) == "" {
|
||||||
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
cryptoKey = internalSecretKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptedJSON, err := OpenSSLDecrypt(encryptedKey, cryptoKey)
|
decryptedJSON, err := OpenSSLDecrypt(encryptedKey, cryptoKey)
|
||||||
@@ -181,11 +190,6 @@ func InitLicense() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cryptoKey := cfg.DecryptedCryptoKey
|
|
||||||
if cryptoKey == "" {
|
|
||||||
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.License.Key == "" {
|
if cfg.License.Key == "" {
|
||||||
currentLicense = &LicenseInfo{
|
currentLicense = &LicenseInfo{
|
||||||
IsValid: false,
|
IsValid: false,
|
||||||
@@ -195,7 +199,7 @@ func InitLicense() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := ValidateAndParseLicense(cfg.License.Key, cryptoKey)
|
info, err := ValidateAndParseLicense(cfg.License.Key, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
currentLicense = &LicenseInfo{
|
currentLicense = &LicenseInfo{
|
||||||
IsValid: false,
|
IsValid: false,
|
||||||
@@ -231,12 +235,11 @@ func GetStatus() *LicenseInfo {
|
|||||||
// Activate saves and applies a new license key
|
// Activate saves and applies a new license key
|
||||||
func Activate(encryptedKey string) (*LicenseInfo, error) {
|
func Activate(encryptedKey string) (*LicenseInfo, error) {
|
||||||
cfg := config.Get()
|
cfg := config.Get()
|
||||||
cryptoKey := cfg.DecryptedCryptoKey
|
if cfg == nil {
|
||||||
if cryptoKey == "" {
|
return nil, errors.New("cấu hình chưa được khởi tạo")
|
||||||
cryptoKey = "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := ValidateAndParseLicense(encryptedKey, cryptoKey)
|
info, err := ValidateAndParseLicense(encryptedKey, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
|
|
||||||
func TestOpenSSLDecryptUserKey(t *testing.T) {
|
func TestOpenSSLDecryptUserKey(t *testing.T) {
|
||||||
cipherText := "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
cipherText := "U2FsdGVkX18Zsq52FPbB7FS1QM7CwWh+t1Esi96gUMFILJD0vZXM69uQBC4sP7TjLVOiJPZt0KIq17iql+hXehLCVSbCEhwKKm+zNab+u2Swah63A52hz0KMJXddLRZ6vanmP2goLTzY7749HsFVRDq+3bdbi8fdUZCpAsSTEiqC2cRTpF5WVh0SHt1wX/i42YSQgOYPeXQtg+lRx/URk4p7lYSVQOYWXglp99kO+Va1ADoNcTgrfz3aaAxAkj0S"
|
||||||
passphrase := "BAD5A05F330D5B8CDDFCA9E7ECDC840B"
|
|
||||||
|
|
||||||
info, err := ValidateAndParseLicense(cipherText, passphrase)
|
// Passing empty cryptoKey triggers internalSecretKey() automatically
|
||||||
|
info, err := ValidateAndParseLicense(cipherText, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Validation failed: %v", err)
|
t.Fatalf("Validation failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user