- Kompletna Go implementacija licencnog servera (19 Go fajlova) - Klijentski API: activate, deactivate, validate - Admin API: CRUD licence, stats, audit log - Admin dashboard: htmx + Go templates - RSA-2048 potpisivanje licencnih podataka - Rate limiting i API key autentifikacija - MySQL migracije i seed podaci (ESIR, ARV, LIGHT_TICKET) - Unit testovi: keygen, crypto, model, middleware (24 testa) - Dokumentacija: SPEC.md, ARCHITECTURE.md, SETUP.md, API.md, TESTING.md, README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
148 lines
3.3 KiB
Go
148 lines
3.3 KiB
Go
package service
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func setupTestKey(t *testing.T) (string, *rsa.PrivateKey) {
|
|
t.Helper()
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "test_private.pem")
|
|
|
|
keyBytes := x509.MarshalPKCS1PrivateKey(key)
|
|
block := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}
|
|
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pem.Encode(f, block)
|
|
f.Close()
|
|
|
|
return path, key
|
|
}
|
|
|
|
func TestNewCryptoService(t *testing.T) {
|
|
path, _ := setupTestKey(t)
|
|
|
|
svc, err := NewCryptoService(path)
|
|
if err != nil {
|
|
t.Fatalf("NewCryptoService error: %v", err)
|
|
}
|
|
if svc == nil {
|
|
t.Fatal("CryptoService je nil")
|
|
}
|
|
}
|
|
|
|
func TestNewCryptoService_InvalidPath(t *testing.T) {
|
|
_, err := NewCryptoService("/nonexistent/path.pem")
|
|
if err == nil {
|
|
t.Error("ocekivana greska za nepostojeci fajl")
|
|
}
|
|
}
|
|
|
|
func TestNewCryptoService_InvalidPEM(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "bad.pem")
|
|
os.WriteFile(path, []byte("not a pem file"), 0600)
|
|
|
|
_, err := NewCryptoService(path)
|
|
if err == nil {
|
|
t.Error("ocekivana greska za nevalidan PEM")
|
|
}
|
|
}
|
|
|
|
func TestSign_Format(t *testing.T) {
|
|
path, _ := setupTestKey(t)
|
|
svc, _ := NewCryptoService(path)
|
|
|
|
sig, err := svc.Sign([]byte("test data"))
|
|
if err != nil {
|
|
t.Fatalf("Sign error: %v", err)
|
|
}
|
|
|
|
if !strings.HasPrefix(sig, "RSA-SHA256:") {
|
|
t.Errorf("potpis nema RSA-SHA256: prefix: %s", sig)
|
|
}
|
|
|
|
// Verify base64 part is valid
|
|
b64 := strings.TrimPrefix(sig, "RSA-SHA256:")
|
|
_, err = base64.StdEncoding.DecodeString(b64)
|
|
if err != nil {
|
|
t.Errorf("base64 deo potpisa nije validan: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSign_VerifyWithPublicKey(t *testing.T) {
|
|
path, privKey := setupTestKey(t)
|
|
svc, _ := NewCryptoService(path)
|
|
|
|
data := []byte(`{"license_key":"LT-TEST-1234","product":"LIGHT_TICKET"}`)
|
|
sig, err := svc.Sign(data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Extract base64 signature
|
|
b64 := strings.TrimPrefix(sig, "RSA-SHA256:")
|
|
sigBytes, _ := base64.StdEncoding.DecodeString(b64)
|
|
|
|
// Verify with public key
|
|
hash := sha256.Sum256(data)
|
|
err = rsa.VerifyPKCS1v15(&privKey.PublicKey, crypto.SHA256, hash[:], sigBytes)
|
|
if err != nil {
|
|
t.Errorf("potpis nije validan: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSign_TamperedData(t *testing.T) {
|
|
path, privKey := setupTestKey(t)
|
|
svc, _ := NewCryptoService(path)
|
|
|
|
data := []byte(`{"license_key":"LT-TEST-1234"}`)
|
|
sig, _ := svc.Sign(data)
|
|
|
|
b64 := strings.TrimPrefix(sig, "RSA-SHA256:")
|
|
sigBytes, _ := base64.StdEncoding.DecodeString(b64)
|
|
|
|
// Tamper with data
|
|
tampered := []byte(`{"license_key":"LT-FAKE-9999"}`)
|
|
hash := sha256.Sum256(tampered)
|
|
err := rsa.VerifyPKCS1v15(&privKey.PublicKey, crypto.SHA256, hash[:], sigBytes)
|
|
if err == nil {
|
|
t.Error("tampered data ne sme proci verifikaciju")
|
|
}
|
|
}
|
|
|
|
func TestPublicKeyPEM(t *testing.T) {
|
|
path, _ := setupTestKey(t)
|
|
svc, _ := NewCryptoService(path)
|
|
|
|
pubPEM, err := svc.PublicKeyPEM()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !strings.Contains(pubPEM, "BEGIN PUBLIC KEY") {
|
|
t.Error("public key PEM nema ocekivani header")
|
|
}
|
|
if !strings.Contains(pubPEM, "END PUBLIC KEY") {
|
|
t.Error("public key PEM nema ocekivani footer")
|
|
}
|
|
}
|