fix: Implement KBS RCAR handshake with cookies

Fixes 'cookie not found' error (401) from KBS by:
1. Adding CookieJar support to KBS client
2. Implementing GetChallenge() to perform /auth handshake and capture session cookie
3. Updating Agent to get challenge, decode nonce, and use it for evidence generation
4. Regenerating mocks
This commit is contained in:
Sammy Oina
2026-01-22 18:05:50 +03:00
parent 288bd95871
commit f6981ac59e
2 changed files with 73 additions and 7 deletions
+24 -7
View File
@@ -533,6 +533,26 @@ func (as *agentService) downloadAndDecryptResource(ctx context.Context, source *
// 2. Get TEE evidence for KBS attestation // 2. Get TEE evidence for KBS attestation
as.logger.Info("getting TEE evidence for KBS attestation") as.logger.Info("getting TEE evidence for KBS attestation")
// Initialize KBS client first to get the challenge (nonce)
as.logger.Info("initiating KBS attestation handshake", "kbs_url", as.computation.KBS.URL)
kbsClient := kbs.NewClient(kbs.Config{
URL: as.computation.KBS.URL,
Timeout: 30 * time.Second,
})
// Start RCAR handshake - get nonce and establish session cookie
nonceStr, err := kbsClient.GetChallenge(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get KBS challenge: %w", err)
}
as.logger.Info("received KBS challenge nonce")
// Decode nonce for Attestation Service
nonceBytes, err := base64.StdEncoding.DecodeString(nonceStr)
if err != nil {
return nil, fmt.Errorf("failed to decode KBS nonce: %w", err)
}
// Auto-detect the platform type // Auto-detect the platform type
platform := attestation.CCPlatform() platform := attestation.CCPlatform()
as.logger.Info("detected platform type", "platform", platform) as.logger.Info("detected platform type", "platform", platform)
@@ -542,6 +562,8 @@ func (as *agentService) downloadAndDecryptResource(ctx context.Context, source *
// Use computation ID as report data // Use computation ID as report data
copy(reportData[:], []byte(as.computation.ID)) copy(reportData[:], []byte(as.computation.ID))
// Use KBS provided nonce
copy(nonce[:], nonceBytes)
var kbsEvidence []byte var kbsEvidence []byte
@@ -574,15 +596,10 @@ func (as *agentService) downloadAndDecryptResource(ctx context.Context, source *
as.logger.Info("evidence prepared for KBS", "kbs_evidence_len", len(kbsEvidence)) as.logger.Info("evidence prepared for KBS", "kbs_evidence_len", len(kbsEvidence))
// 3. Attest with KBS and get token // 3. Attest with KBS and get token
as.logger.Info("attesting with KBS", "kbs_url", as.computation.KBS.URL) as.logger.Info("attesting with KBS")
kbsClient := kbs.NewClient(kbs.Config{
URL: as.computation.KBS.URL,
Timeout: 30 * time.Second,
})
runtimeData := kbs.RuntimeData{ runtimeData := kbs.RuntimeData{
Nonce: base64.StdEncoding.EncodeToString(nonce[:]), Nonce: nonceStr,
} }
token, err := kbsClient.Attest(ctx, kbsEvidence, runtimeData) token, err := kbsClient.Attest(ctx, kbsEvidence, runtimeData)
+49
View File
@@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/http/cookiejar"
"time" "time"
"github.com/absmach/supermq/pkg/errors" "github.com/absmach/supermq/pkg/errors"
@@ -26,6 +27,9 @@ var (
// Client defines the interface for KBS (Key Broker Service) communication. // Client defines the interface for KBS (Key Broker Service) communication.
type Client interface { type Client interface {
// GetChallenge initiates the attestation handshake and returns the nonce.
GetChallenge(ctx context.Context) (string, error)
// Attest performs attestation with KBS and returns a token. // Attest performs attestation with KBS and returns a token.
Attest(ctx context.Context, evidence []byte, runtimeData RuntimeData) (string, error) Attest(ctx context.Context, evidence []byte, runtimeData RuntimeData) (string, error)
@@ -86,14 +90,57 @@ func NewClient(config Config) Client {
config.Timeout = 30 * time.Second config.Timeout = 30 * time.Second
} }
jar, _ := cookiejar.New(nil)
return &kbsClient{ return &kbsClient{
config: config, config: config,
client: &http.Client{ client: &http.Client{
Timeout: config.Timeout, Timeout: config.Timeout,
Jar: jar,
}, },
} }
} }
// GetChallenge initiates the RCAR handshake calling /kbs/v0/auth
func (c *kbsClient) GetChallenge(ctx context.Context) (string, error) {
url := fmt.Sprintf("%s/kbs/v0/auth", c.config.URL)
authReq := AuthRequest{
Version: "0.1.0",
TEE: "sample", // Initial handshake can be generic or specific
}
reqBody, err := json.Marshal(authReq)
if err != nil {
return "", errors.Wrap(ErrAttestationFailed, err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBody))
if err != nil {
return "", errors.Wrap(ErrAttestationFailed, err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return "", errors.Wrap(ErrAttestationFailed, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", errors.Wrap(ErrAttestationFailed,
fmt.Errorf("auth HTTP %d: %s", resp.StatusCode, string(body)))
}
var authResp AuthResponse
if err := json.NewDecoder(resp.Body).Decode(&authResp); err != nil {
return "", errors.Wrap(ErrInvalidResponse, err)
}
return authResp.Nonce, nil
}
// Attest performs attestation with KBS and returns a token. // Attest performs attestation with KBS and returns a token.
func (c *kbsClient) Attest(ctx context.Context, evidence []byte, runtimeData RuntimeData) (string, error) { func (c *kbsClient) Attest(ctx context.Context, evidence []byte, runtimeData RuntimeData) (string, error) {
// Build attest request // Build attest request
@@ -115,6 +162,7 @@ func (c *kbsClient) Attest(ctx context.Context, evidence []byte, runtimeData Run
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
// Cookie jar handles the session cookie automatically
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return "", errors.Wrap(ErrAttestationFailed, err) return "", errors.Wrap(ErrAttestationFailed, err)
@@ -123,6 +171,7 @@ func (c *kbsClient) Attest(ctx context.Context, evidence []byte, runtimeData Run
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body) body, _ := io.ReadAll(resp.Body)
// Try to parse error details if JSON
return "", errors.Wrap(ErrAttestationFailed, return "", errors.Wrap(ErrAttestationFailed,
fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))) fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)))
} }