@@ -0,0 +1,89 @@
|
||||
package hardwareprovision
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"data-terminal-cloud/internal/deviceauth"
|
||||
)
|
||||
|
||||
func TestHTTPRegistrarSignsAndDecryptsDynamicRegistration(t *testing.T) {
|
||||
const productSecret = "1234567890abcdef-product-secret"
|
||||
const deviceSecret = "per-device-secret-with-at-least-32-characters"
|
||||
var received map[string]any
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if err := json.NewDecoder(request.Body).Decode(&received); err != nil {
|
||||
t.Fatalf("decode request: %v", err)
|
||||
}
|
||||
content := "auth_type=1&device_name=" + received["device_name"].(string) +
|
||||
"&random_num=" + strconv.FormatInt(int64(received["random_num"].(float64)), 10) +
|
||||
"&product_key=" + received["product_key"].(string) +
|
||||
"×tamp=" + strconv.FormatInt(int64(received["timestamp"].(float64)), 10)
|
||||
mac := hmac.New(sha256.New, []byte(productSecret))
|
||||
_, _ = mac.Write([]byte(content))
|
||||
if received["signature"] != base64.StdEncoding.EncodeToString(mac.Sum(nil)) {
|
||||
t.Fatal("dynamic registration signature did not match canonical fields")
|
||||
}
|
||||
if _, leaked := received["product_secret"]; leaked {
|
||||
t.Fatal("product secret must not be serialized into the provider request")
|
||||
}
|
||||
_ = json.NewEncoder(writer).Encode(map[string]any{"Result": map[string]any{
|
||||
"payload": encryptDeviceSecretForTest(t, deviceSecret, productSecret), "RTCAppID": "rtc-app",
|
||||
}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
registrar, err := NewHTTPRegistrar(server.URL, productSecret, time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("NewHTTPRegistrar() error = %v", err)
|
||||
}
|
||||
registrar.now = func() time.Time { return time.UnixMilli(1_700_000_000_000) }
|
||||
result, err := registrar.Register(context.Background(), RegisterInput{InstanceID: "instance-1", ProductKey: "product-1", DeviceName: "device-1"})
|
||||
if err != nil {
|
||||
t.Fatalf("Register() error = %v", err)
|
||||
}
|
||||
if result.DeviceSecret != deviceSecret || result.RTCAppID != "rtc-app" {
|
||||
t.Fatalf("Register() result = %+v", result)
|
||||
}
|
||||
if received["InstanceID"] != "instance-1" || received["product_key"] != "product-1" || received["device_name"] != "device-1" {
|
||||
t.Fatalf("unexpected registration request: %+v", received)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisabledProvisioningDoesNotRequireProductSecret(t *testing.T) {
|
||||
service, err := NewService(nil, nil, Options{Enabled: false})
|
||||
if err != nil {
|
||||
t.Fatalf("NewService() error = %v", err)
|
||||
}
|
||||
_, err = service.Provision(context.Background(), deviceauth.Identity{}, "request", ProvisionInput{HardwareID: "device"})
|
||||
if err != ErrUnavailable {
|
||||
t.Fatalf("Provision() error = %v, want ErrUnavailable", err)
|
||||
}
|
||||
}
|
||||
|
||||
func encryptDeviceSecretForTest(t *testing.T, secret, productSecret string) string {
|
||||
t.Helper()
|
||||
key := []byte(productSecret)[:aes.BlockSize]
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plain := []byte(secret)
|
||||
padding := aes.BlockSize - len(plain)%aes.BlockSize
|
||||
for index := 0; index < padding; index++ {
|
||||
plain = append(plain, byte(padding))
|
||||
}
|
||||
encrypted := make([]byte, len(plain))
|
||||
cipher.NewCBCEncrypter(block, key).CryptBlocks(encrypted, plain)
|
||||
return base64.StdEncoding.EncodeToString(encrypted)
|
||||
}
|
||||
Reference in New Issue
Block a user