diff --git a/agent/service.go b/agent/service.go index 9bb2d0c5..e48d6825 100644 --- a/agent/service.go +++ b/agent/service.go @@ -533,6 +533,26 @@ func (as *agentService) downloadAndDecryptResource(ctx context.Context, source * // 2. Get 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 platform := attestation.CCPlatform() 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 copy(reportData[:], []byte(as.computation.ID)) + // Use KBS provided nonce + copy(nonce[:], nonceBytes) 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)) // 3. Attest with KBS and get token - as.logger.Info("attesting with KBS", "kbs_url", as.computation.KBS.URL) - - kbsClient := kbs.NewClient(kbs.Config{ - URL: as.computation.KBS.URL, - Timeout: 30 * time.Second, - }) + as.logger.Info("attesting with KBS") runtimeData := kbs.RuntimeData{ - Nonce: base64.StdEncoding.EncodeToString(nonce[:]), + Nonce: nonceStr, } token, err := kbsClient.Attest(ctx, kbsEvidence, runtimeData) diff --git a/pkg/kbs/client.go b/pkg/kbs/client.go index 367821a3..ea335a41 100644 --- a/pkg/kbs/client.go +++ b/pkg/kbs/client.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "net/http" + "net/http/cookiejar" "time" "github.com/absmach/supermq/pkg/errors" @@ -26,6 +27,9 @@ var ( // Client defines the interface for KBS (Key Broker Service) communication. 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(ctx context.Context, evidence []byte, runtimeData RuntimeData) (string, error) @@ -86,14 +90,57 @@ func NewClient(config Config) Client { config.Timeout = 30 * time.Second } + jar, _ := cookiejar.New(nil) + return &kbsClient{ config: config, client: &http.Client{ 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. func (c *kbsClient) Attest(ctx context.Context, evidence []byte, runtimeData RuntimeData) (string, error) { // 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") + // Cookie jar handles the session cookie automatically resp, err := c.client.Do(req) if err != nil { 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 { body, _ := io.ReadAll(resp.Body) + // Try to parse error details if JSON return "", errors.Wrap(ErrAttestationFailed, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))) }