mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
de50b6d2d4
* feat: Implement EAT (Evidence Attestation Token) generation and verification for attestation responses, replacing raw quotes with EAT tokens in the attestation service and protobuf. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * style: standardize comment formatting and fix a debug log format specifier. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * fix pkg test Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Introduce named constants for OEM IDs and use them in attestation claim extraction. Signed-off-by: SammyOina <sammyoina@gmail.com> * feat: Implement and test minimum length validation for EAT nonce in `NewEATClaims`. Signed-off-by: SammyOina <sammyoina@gmail.com> * feat: Add EATClaims.Sanitize method and integrate it into the validator to enforce claim dependencies. Signed-off-by: SammyOina <sammyoina@gmail.com> * feat: Add Signature field to SNPExtensions and TDXExtensions for enhanced claim validation Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Update dependencies and improve code structure in attestation package Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Introduce comprehensive test suites for EAT, ATLS, TDX, Azure SNP, and vTPM attestation, and improve EAT decoder robustness. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Add encryption and admin keys, an encrypted algorithm file, and update go.mod to use go-jose/v4. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: add new encryption and KBS admin keys while improving TDX attestation test error handling. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Add new KBS admin and encryption keys, an encrypted linear regression algorithm, and refactor TDX test error message checks. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Implement Azure SNP attestation policy, update certificate verification, and add key management. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * refactor: replace hardcoded string literals with variables in Azure SNP attestation tests. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Refactor TDX EAT claims to use individual RTMR fields with `tdx_` prefixes and add an `IntUse` field. Signed-off-by: Sammy Oina <sammyoina@gmail.com> --------- Signed-off-by: Sammy Oina <sammyoina@gmail.com> Signed-off-by: SammyOina <sammyoina@gmail.com>
144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
// Copyright (c) Ultraviolet
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package eat
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/fxamacker/cbor/v2"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/veraison/go-cose"
|
|
)
|
|
|
|
// Decoder decodes EAT tokens (auto-detects JWT vs CBOR).
|
|
type Decoder struct {
|
|
verifyKey *ecdsa.PublicKey
|
|
}
|
|
|
|
// NewDecoder creates a new EAT decoder.
|
|
func NewDecoder(verifyKey *ecdsa.PublicKey) *Decoder {
|
|
return &Decoder{
|
|
verifyKey: verifyKey,
|
|
}
|
|
}
|
|
|
|
// Decode decodes an EAT token (auto-detects format).
|
|
func (d *Decoder) Decode(token []byte) (*EATClaims, error) {
|
|
// Try to detect format
|
|
if isJWT(token) {
|
|
return d.decodeJWT(string(token))
|
|
}
|
|
return d.decodeCBOR(token)
|
|
}
|
|
|
|
// isJWT checks if the token is JWT format.
|
|
func isJWT(token []byte) bool {
|
|
// JWT tokens are base64-encoded strings with dots
|
|
if len(token) < 10 {
|
|
return false
|
|
}
|
|
return bytes.Contains(token, []byte(".")) && !bytes.Contains(token[:10], []byte{0x00})
|
|
}
|
|
|
|
// decodeJWT decodes a JWT token.
|
|
func (d *Decoder) decodeJWT(tokenString string) (*EATClaims, error) {
|
|
claims := &jwtClaims{&EATClaims{}}
|
|
|
|
var token *jwt.Token
|
|
var err error
|
|
|
|
if d.verifyKey == nil {
|
|
token, _, err = new(jwt.Parser).ParseUnverified(tokenString, claims)
|
|
} else {
|
|
// Parse and verify JWT
|
|
token, err = jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
|
// Verify signing method
|
|
if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return d.verifyKey, nil
|
|
})
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse JWT: %w", err)
|
|
}
|
|
|
|
if !token.Valid {
|
|
return nil, fmt.Errorf("invalid JWT token")
|
|
}
|
|
|
|
return claims.EATClaims, nil
|
|
}
|
|
|
|
// decodeCBOR decodes a CBOR token with COSE signature verification.
|
|
func (d *Decoder) decodeCBOR(token []byte) (*EATClaims, error) {
|
|
// Try to unmarshal as COSE_Sign1 message
|
|
var msg cose.Sign1Message
|
|
if err := msg.UnmarshalCBOR(token); err != nil {
|
|
// If it's not a COSE message, try to decode as plain CBOR (backward compatibility)
|
|
claims := &EATClaims{}
|
|
if err := cbor.Unmarshal(token, claims); err != nil {
|
|
return nil, fmt.Errorf("failed to decode CBOR: %w", err)
|
|
}
|
|
return claims, nil
|
|
}
|
|
|
|
// Verify the signature if we have a verification key
|
|
if d.verifyKey != nil {
|
|
verifier, err := cose.NewVerifier(cose.AlgorithmES256, d.verifyKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create COSE verifier: %w", err)
|
|
}
|
|
|
|
if err := msg.Verify(nil, verifier); err != nil {
|
|
return nil, fmt.Errorf("COSE signature verification failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Decode the payload
|
|
claims := &EATClaims{}
|
|
if err := cbor.Unmarshal(msg.Payload, claims); err != nil {
|
|
return nil, fmt.Errorf("failed to decode CBOR payload: %w", err)
|
|
}
|
|
|
|
return claims, nil
|
|
}
|
|
|
|
// DecodeJWT is a convenience function to decode JWT EAT token.
|
|
func DecodeJWT(tokenString string, verifyKey *ecdsa.PublicKey) (*EATClaims, error) {
|
|
decoder := NewDecoder(verifyKey)
|
|
return decoder.decodeJWT(tokenString)
|
|
}
|
|
|
|
// DecodeCBOR is a convenience function to decode CBOR EAT token.
|
|
func DecodeCBOR(token []byte, verifyKey *ecdsa.PublicKey) (*EATClaims, error) {
|
|
decoder := NewDecoder(verifyKey)
|
|
return decoder.decodeCBOR(token)
|
|
}
|
|
|
|
// Decode is a convenience function that auto-detects format.
|
|
func Decode(token []byte, verifyKey *ecdsa.PublicKey) (*EATClaims, error) {
|
|
decoder := NewDecoder(verifyKey)
|
|
return decoder.Decode(token)
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler for pretty printing.
|
|
func (c *EATClaims) MarshalJSON() ([]byte, error) {
|
|
type Alias EATClaims
|
|
return json.Marshal(&struct {
|
|
*Alias
|
|
NonceHex string `json:"eat_nonce_hex,omitempty"`
|
|
UEIDHex string `json:"ueid_hex,omitempty"`
|
|
MeasurementsHex string `json:"measurements_hex,omitempty"`
|
|
}{
|
|
Alias: (*Alias)(c),
|
|
NonceHex: fmt.Sprintf("%x", c.Nonce),
|
|
UEIDHex: fmt.Sprintf("%x", c.UEID),
|
|
MeasurementsHex: fmt.Sprintf("%x", c.Measurements),
|
|
})
|
|
}
|