feat(protocol): define native session credentials
Verify Protocol / module (push) Successful in 1m13s
Verify Protocol / verify (push) Successful in 35s

This commit is contained in:
sechmachine
2026-08-11 18:38:19 +07:00
parent 79d9e49497
commit afcd5d99db
32 changed files with 1565 additions and 149 deletions
+74 -6
View File
@@ -10,7 +10,7 @@ import (
)
func TestManifestRejectsForbiddenAndUnknownFields(t *testing.T) {
valid := `{"version":"1","purpose":"launch","session_id":"session-1","reconnect_sequence":0,"gateway":{"id":"gateway-1","addresses":["gateway.control.test:443"],"public_identity":"gateway-1"},"tunnel":{"versions":["verse-gateway-v1/1"],"features":["control.v1"]},"profile":{"id":"standard","bounds":{"minimum_kbps":1,"target_kbps":2,"maximum_kbps":3}},"grant":{"opaque_value":"opaque-one-time-grant-value-with-at-least-43-bytes","expires_at":"2099-01-01T00:00:00Z","audience":"versevdi-gateway"},"correlation_id":"correlation-1"}`
valid := `{"version":"1","purpose":"launch","session_id":"session-1","reconnect_sequence":0,"gateway":{"id":"gateway-1","addresses":["gateway.control.test:443"],"public_identity":"gateway.control.test"},"tunnel":{"versions":["verse-gateway-v1/1"],"features":["control.v1"]},"profile":{"id":"standard","bounds":{"minimum_kbps":1,"target_kbps":2,"maximum_kbps":3}},"grant":{"opaque_value":"opaque-one-time-grant-value-with-at-least-43-bytes","expires_at":"2099-01-01T00:00:00Z","audience":"versevdi-gateway"},"correlation_id":"correlation-1"}`
manifest, err := protocol.DecodeConnectionManifest([]byte(valid))
if err != nil || manifest.Gateway.ID != "gateway-1" {
t.Fatalf("valid manifest = %+v, err = %v", manifest, err)
@@ -42,11 +42,6 @@ func TestDisplayModeIsOptionalStrictAndBounded(t *testing.T) {
request := protocol.SessionRequest{
ClientDeviceID: "device-1", DeviceKeyID: "key-1", PoolID: "pool-1", IdempotencyKey: "request-1",
PolicySnapshot: protocol.AllocationPolicy{
MinimumKbps: 1000, TargetKbps: 2000, MaximumKbps: 3000, Tier: "standard",
Audience: "versevdi-gateway", Protocol: "verse", ProtocolVersion: 1,
GrantTTLSeconds: 60, ReservationLeaseSeconds: 300,
},
}
legacy, err := protocol.EncodeSessionRequest(request)
if err != nil {
@@ -100,6 +95,79 @@ func TestDisplayModeIsOptionalStrictAndBounded(t *testing.T) {
}
}
func TestSessionRequestRejectsClientPolicySnapshot(t *testing.T) {
if protocol.CurrentWireVersion != "2" || protocol.NMinus1WireVersion != "1" || protocol.NMinus2WireVersion != "0" {
t.Fatalf("wire compatibility = current %q, n-1 %q, n-2 %q", protocol.CurrentWireVersion, protocol.NMinus1WireVersion, protocol.NMinus2WireVersion)
}
request := `{"client_device_id":"device-1","device_key_id":"key-1","pool_id":"pool-1","idempotency_key":"request-1"}`
if _, err := protocol.DecodeSessionRequest([]byte(request)); err != nil {
t.Fatalf("policy-free SessionRequest rejected: %v", err)
}
withPolicy := strings.Replace(request, `"idempotency_key":"request-1"`, `"idempotency_key":"request-1","policy_snapshot":{}`, 1)
if _, err := protocol.DecodeSessionRequest([]byte(withPolicy)); err == nil {
t.Fatal("SessionRequest accepted client-supplied policy_snapshot")
}
if _, ok := reflect.TypeOf(protocol.SessionRequest{}).FieldByName("PolicySnapshot"); ok {
t.Fatal("SessionRequest generated a client-owned PolicySnapshot field")
}
if _, ok := reflect.TypeOf(protocol.BrokerSession{}).FieldByName("PolicySnapshot"); !ok {
t.Fatal("BrokerSession lost its server-owned PolicySnapshot disclosure")
}
}
func TestAuthenticatedSessionShapesAreRouteSpecific(t *testing.T) {
browser := `{"username":"alice","provider":"local","roles":["user"],"role":"user"}`
decodedBrowser, err := protocol.DecodeBrowserAuthenticatedSession([]byte(browser))
if err != nil || decodedBrowser.Username != "alice" {
t.Fatalf("browser session = %+v, err = %v", decodedBrowser, err)
}
native := `{"username":"alice","provider":"local","roles":["user"],"role":"user","native_identity":{"client_device_id":"device-1","device_key_id":"key-1"}}`
decodedNative, err := protocol.DecodeNativeAuthenticatedSession([]byte(native))
if err != nil || decodedNative.NativeIdentity.DeviceKeyID != "key-1" {
t.Fatalf("native session = %+v, err = %v", decodedNative, err)
}
if _, err := protocol.DecodeBrowserAuthenticatedSession([]byte(native)); err == nil {
t.Fatal("browser response accepted native identity")
}
if _, err := protocol.DecodeNativeAuthenticatedSession([]byte(browser)); err == nil {
t.Fatal("native response accepted missing native identity")
}
for _, invalid := range []string{
strings.Replace(native, `,"device_key_id":"key-1"`, "", 1),
strings.Replace(native, `"native_identity":{"client_device_id":"device-1","device_key_id":"key-1"}`, `"native_identity":null`, 1),
strings.Replace(native, `"device_key_id":"key-1"`, `"device_key_id":"key-1","unknown":true`, 1),
} {
if _, err := protocol.DecodeNativeAuthenticatedSession([]byte(invalid)); err == nil {
t.Fatalf("NativeAuthenticatedSession accepted non-atomic or unknown native identity: %s", invalid)
}
}
for _, invalidRoles := range []string{`[""]`, `["` + strings.Repeat("r", 65) + `"]`} {
invalid := strings.Replace(browser, `["user"]`, invalidRoles, 1)
if _, err := protocol.DecodeBrowserAuthenticatedSession([]byte(invalid)); err == nil {
t.Fatalf("BrowserAuthenticatedSession accepted invalid roles: %s", invalid)
}
}
}
func TestNativeTunnelCredentialIsStrictAndBounded(t *testing.T) {
valid := `{"client_device_id":"device-1","device_key_id":"key-1","certificate_chain_pem":"certificate","trust_bundle_pem":"trust","expires_at":"2099-01-01T00:00:00Z"}`
credential, err := protocol.DecodeNativeTunnelCredential([]byte(valid))
if err != nil || credential.ClientDeviceID != "device-1" {
t.Fatalf("native tunnel credential = %+v, err = %v", credential, err)
}
for _, invalid := range []string{
strings.Replace(valid, `"device_key_id":"key-1",`, "", 1),
strings.Replace(valid, `"expires_at":"2099-01-01T00:00:00Z"`, `"expires_at":"not-a-time"`, 1),
strings.Replace(valid, `"expires_at":"2099-01-01T00:00:00Z"`, `"expires_at":"2099-01-01T00:00:00+00:00"`, 1),
strings.Replace(valid, `"expires_at":"2099-01-01T00:00:00Z"`, `"expires_at":"2099-01-01T00:00:00.100Z"`, 1),
strings.Replace(valid, `"expires_at":"2099-01-01T00:00:00Z"`, `"expires_at":"2099-01-01T00:00:00Z","unknown":true`, 1),
} {
if _, err := protocol.DecodeNativeTunnelCredential([]byte(invalid)); err == nil {
t.Fatalf("NativeTunnelCredential accepted invalid payload: %s", invalid)
}
}
}
func TestPageInfoRejectsOutOfBoundsLimit(t *testing.T) {
if _, err := protocol.DecodePageInfo([]byte(`{"limit":101,"next_cursor":""}`)); err == nil {
t.Fatal("DecodePageInfo accepted limit above the contract maximum")