package main import ( "bytes" "crypto" "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/hmac" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/binary" "encoding/json" "errors" "fmt" "log" "math/big" "net/http" "strings" "time" "github.com/patrickmn/go-cache" ) // Key type definitions type KeyType string const ( KeyTypeEd25519 KeyType = "ed25519" KeyTypeECDSA KeyType = "ecdsa-p256" KeyTypeRSAPSS KeyType = "rsa-2048-pss" KeyTypeECDH KeyType = "ecdh-p256" ) // Unified key structures for better organization type PublicKeyInfo struct { Key []byte Type KeyType } type UnifiedKeyInfo struct { PublicKey []byte // Public key for asymmetric crypto KeyType KeyType // Type of key (ecdsa, rsa-2048, ed25519) SymmetricKey []byte // AES-256 key for symmetric encryption (if available) ServerPrivKey interface{} // Server's private key for ECDH (if applicable) HwKey interface{} } // In-memory caches with consistent expiration times const ( defaultCacheExpiry = 3 * time.Hour cleanupInterval = 10 * time.Minute ) var ( usersCache = cache.New(defaultCacheExpiry, cleanupInterval) // Cache for user data accelKeys = cache.New(defaultCacheExpiry, cleanupInterval) // Cache for unified acceleration keys tokensCache = cache.New(defaultCacheExpiry, cleanupInterval) // Cache for tokens and associated hardware keys challengeCache = cache.New(5*time.Minute, time.Minute) // Cache for attestation challenges (shorter expiry) ) // TPM policy constants (from TPM20.cpp) var ( defaultUserPolicy = []byte{ 0x8f, 0xcd, 0x21, 0x69, 0xab, 0x92, 0x69, 0x4e, 0x0c, 0x63, 0x3f, 0x1a, 0xb7, 0x72, 0x84, 0x2b, 0x82, 0x41, 0xbb, 0xc2, 0x02, 0x88, 0x98, 0x1f, 0xc7, 0xac, 0x1e, 0xdd, 0xc1, 0xfd, 0xdb, 0x0e, } adminObjectChangeAuthPolicy = []byte{ 0xe5, 0x29, 0xf5, 0xd6, 0x11, 0x28, 0x72, 0x95, 0x4e, 0x8e, 0xd6, 0x60, 0x51, 0x17, 0xb7, 0x57, 0xe2, 0x37, 0xc6, 0xe1, 0x95, 0x13, 0xa9, 0x49, 0xfe, 0xe1, 0xf2, 0x04, 0xc4, 0x58, 0x02, 0x3a, } adminCertifyPolicy = []byte{ 0xaf, 0x2c, 0xa5, 0x69, 0x69, 0x9c, 0x43, 0x6a, 0x21, 0x00, 0x6f, 0x1c, 0xb8, 0xa2, 0x75, 0x6c, 0x98, 0xbc, 0x1c, 0x76, 0x5a, 0x35, 0x59, 0xc5, 0xfe, 0x1c, 0x3f, 0x5e, 0x72, 0x28, 0xa7, 0xe7, } adminCertifyPolicyNoPin = []byte{ 0x04, 0x8e, 0x9a, 0x3a, 0xce, 0x08, 0x58, 0x3f, 0x79, 0xf3, 0x44, 0xff, 0x78, 0x5b, 0xbe, 0xa9, 0xf0, 0x7a, 0xc7, 0xfa, 0x33, 0x25, 0xb3, 0xd4, 0x9a, 0x21, 0xdd, 0x51, 0x94, 0xc6, 0x58, 0x50, } adminActivateCredentialPolicy = []byte{ 0xc4, 0x13, 0xa8, 0x47, 0xb1, 0x11, 0x12, 0xb1, 0xcb, 0xdd, 0xd4, 0xec, 0xa4, 0xda, 0xaa, 0x15, 0xa1, 0x85, 0x2c, 0x1c, 0x3b, 0xba, 0x57, 0x46, 0x1d, 0x25, 0x76, 0x05, 0xf3, 0xd5, 0xaf, 0x53, } ) // TPM constants const ( TPM_CC_PolicyPCR = 0x0000017F TPM_CC_PolicyOR = 0x00000171 TPM_ALG_SHA1 = 0x0004 TPM_ALG_SHA256 = 0x000B AVAILABLE_PLATFORM_PCRS = 24 ) // Custom logger for debugging func debugLog(step string, format string, v ...interface{}) { message := fmt.Sprintf(format, v...) log.Printf("[DEBUG] %s: %s", step, message) } // Helper function to read big-endian 16-bit value and following data func readBigEndian2B(data []byte, cursor *int) (uint16, []byte, error) { if len(data) < *cursor+2 { return 0, nil, errors.New("insufficient data for 2B size") } size := binary.BigEndian.Uint16(data[*cursor : *cursor+2]) *cursor += 2 if len(data) < *cursor+int(size) { return 0, nil, errors.New("insufficient data for 2B content") } content := data[*cursor : *cursor+int(size)] *cursor += int(size) return size, content, nil } // Calculate user policy with PCR data func calculateUserPolicyWithPCRs(pcrTable []byte, pcrMask uint32, pcrAlgId uint16, keyBlobPcrDigest []byte) ([]byte, error) { var digestSize int if pcrAlgId == TPM_ALG_SHA256 { digestSize = 32 } else if pcrAlgId == TPM_ALG_SHA1 { digestSize = 20 } else { return nil, fmt.Errorf("unsupported PCR algorithm: 0x%04X", pcrAlgId) } // Build PCR composite from selected PCRs var pcrComposite []byte for n := 0; n < AVAILABLE_PLATFORM_PCRS; n++ { if (pcrMask & (1 << n)) != 0 { start := n * digestSize end := start + digestSize if len(pcrTable) < end { return nil, fmt.Errorf("insufficient PCR data for PCR %d", n) } pcrComposite = append(pcrComposite, pcrTable[start:end]...) } } // Calculate PCR composite digest using SHA256 (determined by policy, not PCR bank algorithm) h := sha256.New() h.Write(pcrComposite) pcrCompositeDigest := h.Sum(nil) // Verify PCR composite digest matches key blob if !bytes.Equal(keyBlobPcrDigest, pcrCompositeDigest) { return nil, errors.New("PCR composite digest mismatch") } // Build policy digest buffer for PCR policy calculation policyDigestBuffer := make([]byte, 0, 32+4+4+2+1+3+32) // Add default user policy policyDigestBuffer = append(policyDigestBuffer, defaultUserPolicy...) // Add TPM_CC_PolicyPCR tpmCcBytes := make([]byte, 4) binary.BigEndian.PutUint32(tpmCcBytes, TPM_CC_PolicyPCR) policyDigestBuffer = append(policyDigestBuffer, tpmCcBytes...) // Add TPML_PCR_SELECTION.count (1) countBytes := make([]byte, 4) binary.BigEndian.PutUint32(countBytes, 1) policyDigestBuffer = append(policyDigestBuffer, countBytes...) // Add TPML_PCR_SELECTION.TPMS_PCR_SELECTION.hash algBytes := make([]byte, 2) binary.BigEndian.PutUint16(algBytes, pcrAlgId) policyDigestBuffer = append(policyDigestBuffer, algBytes...) // Add TPML_PCR_SELECTION.TPMS_PCR_SELECTION.sizeofSelect (3) policyDigestBuffer = append(policyDigestBuffer, 0x03) // Add TPML_PCR_SELECTION.TPMS_PCR_SELECTION.Select (3 bytes) pcrSelectBytes := make([]byte, 3) pcrSelectBytes[0] = byte(pcrMask & 0xFF) pcrSelectBytes[1] = byte((pcrMask >> 8) & 0xFF) pcrSelectBytes[2] = byte((pcrMask >> 16) & 0xFF) policyDigestBuffer = append(policyDigestBuffer, pcrSelectBytes...) // Add PCR digest policyDigestBuffer = append(policyDigestBuffer, pcrCompositeDigest...) // Calculate final policy digest using SHA256 h = sha256.New() h.Write(policyDigestBuffer) return h.Sum(nil), nil } // Attestation structures type AttestationRequest struct { Attestation string `json:"attestation"` AIKPublicKey string `json:"aik_public_key"` PCRValues string `json:"pcr_values"` KeyType string `json:"key_type"` Nonce string `json:"nonce,omitempty"` } type ChallengeResponse struct { Nonce string `json:"nonce"` } // ============ Crypto Utility Functions ============ // Generate cryptographically secure random string func generateRandomString(n int) (string, error) { b := make([]byte, n) if _, err := rand.Read(b); err != nil { return "", err } return base64.StdEncoding.EncodeToString(b), nil } // Parse public key from base64 encoded string based on key type func parsePublicKey(keyData string, keyType string) (interface{}, error) { debugLog("parsePublicKey", "Parsing %s key, length: %d", keyType, len(keyData)) decoded, err := base64.StdEncoding.DecodeString(keyData) if err != nil { return nil, fmt.Errorf("failed to decode key: %w", err) } // Normalize key type to lowercase keyType = strings.ToLower(keyType) switch keyType { case string(KeyTypeEd25519): return parseEd25519PublicKey(decoded) case string(KeyTypeECDSA): return parseECDSAPublicKey(decoded) case string(KeyTypeRSAPSS): return parseRSAPublicKey(decoded) case string(KeyTypeECDH): return parseECDHPublicKey(decoded) } return nil, errors.New("unsupported key type") } func parseEd25519PublicKey(decoded []byte) (interface{}, error) { if len(decoded) != ed25519.PublicKeySize { return nil, errors.New("invalid Ed25519 key size") } return ed25519.PublicKey(decoded), nil } func parseECDSAPublicKey(decoded []byte) (interface{}, error) { // Try uncompressed point format first if len(decoded) == 65 && decoded[0] == 0x04 { return parseRawECDSAPublicKeyX962(decoded) } // Otherwise try PKIX format key, err := x509.ParsePKIXPublicKey(decoded) if err != nil { return nil, fmt.Errorf("failed to parse ECDSA public key: %w", err) } if ecKey, ok := key.(*ecdsa.PublicKey); ok { return ecKey, nil } return nil, errors.New("key is not ECDSA") } func parseRSAPublicKey(decoded []byte) (interface{}, error) { key, err := x509.ParsePKIXPublicKey(decoded) if err != nil { return nil, fmt.Errorf("failed to parse RSA public key: %w", err) } if rsaKey, ok := key.(*rsa.PublicKey); ok { return rsaKey, nil } return nil, errors.New("key is not RSA") } func parseECDHPublicKey(decoded []byte) (interface{}, error) { curve := ecdh.P256() // Try to handle the key directly first pubKey, err := curve.NewPublicKey(decoded) if err == nil { return pubKey, nil } // Try uncompressed point format if len(decoded) == 65 && decoded[0] == 0x04 { x := new(big.Int).SetBytes(decoded[1:33]) y := new(big.Int).SetBytes(decoded[33:65]) if !elliptic.P256().IsOnCurve(x, y) { return nil, fmt.Errorf("point is not on P-256 curve") } rawKey := elliptic.Marshal(elliptic.P256(), x, y) return curve.NewPublicKey(rawKey) } // Try PKIX format key, err := x509.ParsePKIXPublicKey(decoded) if err == nil { if ecKey, ok := key.(*ecdsa.PublicKey); ok && ecKey.Curve == elliptic.P256() { rawKey := elliptic.Marshal(ecKey.Curve, ecKey.X, ecKey.Y) return curve.NewPublicKey(rawKey) } } return nil, fmt.Errorf("invalid or unsupported ECDH key format") } // Parse raw ECDSA public key in X9.62 uncompressed point format func parseRawECDSAPublicKeyX962(data []byte) (*ecdsa.PublicKey, error) { if len(data) != 65 || data[0] != 0x04 { return nil, fmt.Errorf("invalid EC public key format") } x := new(big.Int).SetBytes(data[1:33]) y := new(big.Int).SetBytes(data[33:65]) pubKey := &ecdsa.PublicKey{ Curve: elliptic.P256(), // Assuming P-256 X: x, Y: y, } return pubKey, nil } // Verify a signature using the appropriate algorithm based on key type func verifySignature(publicKey interface{}, data []byte, signature []byte) bool { hash := sha256.Sum256(data) switch key := publicKey.(type) { case ed25519.PublicKey: return ed25519.Verify(key, data, signature) case *ecdsa.PublicKey: // Try ASN.1 signature first if ecdsa.VerifyASN1(key, hash[:], signature) { return true } // Fallback to raw r||s format if len(signature) == 64 { r := new(big.Int).SetBytes(signature[:32]) s := new(big.Int).SetBytes(signature[32:]) return ecdsa.Verify(key, hash[:], r, s) } return false case *rsa.PublicKey: opts := &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.SHA256, } err := rsa.VerifyPSS(key, crypto.SHA256, hash[:], signature, opts) return err == nil default: return false } } // Generate a new ECDH key pair func generateECDHKeyPair() (*ecdh.PrivateKey, error) { curve := ecdh.P256() return curve.GenerateKey(rand.Reader) } // ============ AES Encryption Functions ============ // Validate request with HMAC func validateHMACRequest(symmetricKey []byte, data, sig string) error { debugLog("HMAC", "Validating HMAC for data (%d chars)", len(data)) if sig == "" { return errors.New("missing HMAC signature") } // Log the input for debugging log.Printf("HMAC debug - data: %s, sig: %s", data, sig) // Compute HMAC using SHA-256 mac := hmac.New(sha256.New, symmetricKey) mac.Write([]byte(data)) expectedHMAC := mac.Sum(nil) expectedBase64 := base64.StdEncoding.EncodeToString(expectedHMAC) // Debug info log.Printf("HMAC debug - computed base64: %s", expectedBase64) // Decode the received signature sigBytes, err := base64.StdEncoding.DecodeString(sig) if err != nil { return fmt.Errorf("invalid HMAC format: %w", err) } // Compare both ways for debugging log.Printf("HMAC debug - bytes equal: %v", bytes.Equal(sigBytes, expectedHMAC)) log.Printf("HMAC debug - base64 equal: %v", sig == expectedBase64) // Compare the HMACs if !bytes.Equal(sigBytes, expectedHMAC) { // Return more information for debugging return fmt.Errorf("HMAC mismatch: expected %s, got %s", expectedBase64, sig) } return nil } // Compute shared secret from ECDH key exchange func computeSharedSecret(privateKey *ecdh.PrivateKey, publicKey *ecdh.PublicKey) ([]byte, error) { debugLog("ECDH", "Computing shared secret between keys") sharedSecret, err := privateKey.ECDH(publicKey) if err != nil { return nil, err } // Log for debugging log.Printf("HMAC debug - derived key hash: %s", base64.StdEncoding.EncodeToString(sharedSecret[:])) // The derived secret should be 32 bytes for P-256 curve return sharedSecret[:], nil } // ============ HTTP Helper Functions ============ // Add CORS headers to response func setCORSHeaders(w http.ResponseWriter) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, x-rpc-sec-bound-token-hw-pub, x-rpc-sec-bound-token-hw-pub-type, x-rpc-sec-bound-token-accel-pub, x-rpc-sec-bound-token-accel-pub-type, x-rpc-sec-bound-token-accel-pub-sig, x-rpc-sec-bound-token-data, x-rpc-sec-bound-token-data-sig, x-rpc-sec-bound-token-accel-pub-id") w.Header().Set("Access-Control-Expose-Headers", "x-rpc-sec-bound-token-accel-pub-id, x-rpc-sec-bound-token-accel-pub") } // Send error response with CORS headers func errorResponse(w http.ResponseWriter, message string, status int) { setCORSHeaders(w) http.Error(w, message, status) } // Send authentication success response func sendAuthenticationSuccess(w http.ResponseWriter) { setCORSHeaders(w) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]bool{"authenticated": true}) } // Verify signed data with proper error handling func verifySignedData(key interface{}, data string, signature string) error { sigDecoded, err := base64.StdEncoding.DecodeString(signature) if err != nil { return fmt.Errorf("invalid signature format: %w", err) } if !verifySignature(key, []byte(data), sigDecoded) { return errors.New("invalid signature") } return nil } // Parse and validate key with proper error handling func parseAndValidateKey(keyData string, keyType string, keyDesc string) (interface{}, error) { if keyData == "" || keyType == "" { return nil, fmt.Errorf("missing %s or type", keyDesc) } key, err := parsePublicKey(keyData, keyType) if err != nil { return nil, fmt.Errorf("invalid %s: %w", keyDesc, err) } return key, nil } // ============ HTTP Handlers ============ // Register new user func registerHandler(w http.ResponseWriter, r *http.Request) { debugLog("registerHandler", "Received registration request from %s", r.RemoteAddr) setCORSHeaders(w) if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } var userData struct { Username string `json:"username"` Password string `json:"password"` } if err := json.NewDecoder(r.Body).Decode(&userData); err != nil { errorResponse(w, "Invalid request body", http.StatusBadRequest) return } // Store user data usersCache.Set(userData.Username, userData, cache.DefaultExpiration) log.Printf("Registered user: %s", userData.Username) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"message": "User registered successfully"}) } // Login and get auth token func loginHandler(w http.ResponseWriter, r *http.Request) { debugLog("loginHandler", "Received login request from %s", r.RemoteAddr) setCORSHeaders(w) if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } var credentials struct { Username string `json:"username"` Password string `json:"password"` } if err := json.NewDecoder(r.Body).Decode(&credentials); err != nil { errorResponse(w, "Invalid request body", http.StatusBadRequest) return } // Get and validate hardware public key hwPubKey := r.Header.Get("x-rpc-sec-bound-token-hw-pub") hwPubType := r.Header.Get("x-rpc-sec-bound-token-hw-pub-type") if hwPubKey == "" || hwPubType == "" { errorResponse(w, "Missing hardware public key or type", http.StatusBadRequest) return } // Parse and validate hardware public key format _, err := parsePublicKey(hwPubKey, hwPubType) if err != nil { errorResponse(w, fmt.Sprintf("Invalid hardware public key: %v", err), http.StatusBadRequest) return } // Verify user credentials userData, found := usersCache.Get(credentials.Username) if !found || userData.(struct { Username string `json:"username"` Password string `json:"password"` }).Password != credentials.Password { errorResponse(w, "Invalid username or password", http.StatusUnauthorized) return } // Generate and store token token, err := generateRandomString(32) if err != nil { errorResponse(w, "Failed to generate token", http.StatusInternalServerError) return } tokensCache.Set(token, PublicKeyInfo{ Key: []byte(hwPubKey), Type: KeyType(strings.ToLower(hwPubType)), }, cache.DefaultExpiration) log.Printf("User logged in: %s with key type: %s", credentials.Username, hwPubType) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"token": token}) } // ============ Enhanced Crypto Utility Functions ============ // Verify ECDSA signature and then use the same key for ECDH key exchange func verifyDataWithCliPubECDSA(clientPubKeyData string, data string, signature string) (*ecdsa.PublicKey, error) { debugLog("verifyDataWithCliPubECDSA", "Processing dual-purpose P-256 key, data: %s", data) // First parse as ECDSA public key for signature verification ecdsaKey, err := parsePublicKeyAsECDSAAndCheckCurveForECDH(clientPubKeyData) if err != nil { return nil, fmt.Errorf("failed to parse ECDSA key: %w", err) } // Verify the data signature using ECDSA if err := verifyECDSASignature(ecdsaKey, data, signature); err != nil { return nil, fmt.Errorf("ECDSA signature verification failed: %w", err) } debugLog("verifyDataWithCliPubECDSA", "ECDSA signature verified successfully") return ecdsaKey, nil } // Parse ECDSA public key specifically for dual ECDSA/ECDH usage func parsePublicKeyAsECDSAAndCheckCurveForECDH(keyData string) (*ecdsa.PublicKey, error) { decoded, err := base64.StdEncoding.DecodeString(keyData) if err != nil { return nil, fmt.Errorf("failed to decode key: %w", err) } // Try uncompressed point format first (0x04 prefix) if len(decoded) == 65 && decoded[0] == 0x04 { return parseRawECDSAPublicKeyX962(decoded) } // Try PKIX format key, err := x509.ParsePKIXPublicKey(decoded) if err != nil { return nil, fmt.Errorf("failed to parse PKIX public key: %w", err) } ecdsaKey, ok := key.(*ecdsa.PublicKey) if !ok { return nil, errors.New("key is not ECDSA") } // Ensure it's P-256 curve for dual usage if ecdsaKey.Curve != elliptic.P256() { return nil, errors.New("key must use P-256 curve for dual ECDSA/ECDH usage") } return ecdsaKey, nil } // Verify ECDSA signature specifically func verifyECDSASignature(publicKey *ecdsa.PublicKey, data string, signature string) error { sigBytes, err := base64.StdEncoding.DecodeString(signature) if err != nil { return fmt.Errorf("invalid signature format: %w", err) } hash := sha256.Sum256([]byte(data)) // Try ASN.1 signature format first if ecdsa.VerifyASN1(publicKey, hash[:], sigBytes) { return nil } // Try raw r||s format (64 bytes for P-256) if len(sigBytes) == 64 { r := new(big.Int).SetBytes(sigBytes[:32]) s := new(big.Int).SetBytes(sigBytes[32:]) if ecdsa.Verify(publicKey, hash[:], r, s) { return nil } } return errors.New("signature verification failed") } // Convert ECDSA public key to ECDH public key for key exchange func convertECDSAToECDH(ecdsaKey *ecdsa.PublicKey) (*ecdh.PublicKey, error) { debugLog("convertECDSAToECDH", "Converting ECDSA P-256 key to ECDH format") if ecdsaKey.Curve != elliptic.P256() { return nil, errors.New("only P-256 curve supported for ECDSA to ECDH conversion") } // Marshal the ECDSA public key to uncompressed point format rawKey := elliptic.Marshal(ecdsaKey.Curve, ecdsaKey.X, ecdsaKey.Y) // Create ECDH public key from the marshaled data curve := ecdh.P256() ecdhKey, err := curve.NewPublicKey(rawKey) if err != nil { return nil, fmt.Errorf("failed to create ECDH key: %w", err) } return ecdhKey, nil } // Perform ECDH key exchange and return shared secret func performECDHKeyExchange(serverPrivKey *ecdh.PrivateKey, clientECDHKey *ecdh.PublicKey) ([]byte, error) { debugLog("performECDHKeyExchange", "Performing ECDH key exchange with client's P-256 key") // Compute shared secret sharedSecret, err := computeSharedSecret(serverPrivKey, clientECDHKey) if err != nil { return nil, fmt.Errorf("ECDH computation failed: %w", err) } debugLog("performECDHKeyExchange", "ECDH key exchange completed, shared secret length: %d", len(sharedSecret)) return sharedSecret, nil } // Handle acceleration key registration with enhanced dual-purpose ECDSA/ECDH support func verifyAccelKeyRegistrationRequest(w http.ResponseWriter, r *http.Request, hwKeyInfo PublicKeyInfo) error { debugLog("verifyAccelKeyRegistration", "Processing acceleration key registration, hw key type: %s", hwKeyInfo.Type) // Get request headers accelPub := r.Header.Get("x-rpc-sec-bound-token-accel-pub") accelPubType := r.Header.Get("x-rpc-sec-bound-token-accel-pub-type") accelPubSig := r.Header.Get("x-rpc-sec-bound-token-accel-pub-sig") data := r.Header.Get("x-rpc-sec-bound-token-data") dataSig := r.Header.Get("x-rpc-sec-bound-token-data-sig") if accelPub == "" || accelPubType == "" || accelPubSig == "" { errorResponse(w, "Missing acceleration key or signature", http.StatusBadRequest) return errors.New("missing acceleration key or signature") } // Parse hardware key for verification hwKey, err := parseAndValidateKey(string(hwKeyInfo.Key), string(hwKeyInfo.Type), "hardware key") if err != nil { errorResponse(w, err.Error(), http.StatusUnauthorized) return err } // Verify acceleration key is signed by hardware key if err := verifySignedData(hwKey, accelPub, accelPubSig); err != nil { errorResponse(w, fmt.Sprintf("hardware key verification failed: %v", err), http.StatusUnauthorized) return err } // Generate key ID accelKeyId, err := generateRandomString(16) if err != nil { errorResponse(w, "Failed to generate key ID", http.StatusInternalServerError) return err } // Create unified key info unifiedKey := UnifiedKeyInfo{ PublicKey: []byte(accelPub), KeyType: KeyType(strings.ToLower(accelPubType)), } // Handle ECDH case with dual-purpose ECDSA/ECDH key if strings.ToLower(accelPubType) == string(KeyTypeECDH) { if err := performDualPurposeECDHExchange(w, accelPub, data, dataSig, &unifiedKey); err != nil { errorResponse(w, err.Error(), http.StatusBadRequest) return err } } else { // Handle regular asymmetric key case if err := validateAsymmetricRequest(unifiedKey, data, dataSig); err != nil { errorResponse(w, err.Error(), http.StatusUnauthorized) return err } } // Store the unified key accelKeys.Set(accelKeyId, unifiedKey, cache.DefaultExpiration) w.Header().Set("x-rpc-sec-bound-token-accel-pub-id", accelKeyId) debugLog("verifyAccelKeyRegistration", "Acceleration key registered with ID: %s", accelKeyId) return nil } // Setup ECDH exchange with dual-purpose ECDSA/ECDH key func performDualPurposeECDHExchange(w http.ResponseWriter, accelPub string, data string, dataSig string, unifiedKey *UnifiedKeyInfo) error { debugLog("performDualPurposeECDHExchange", "Setting up dual-purpose ECDSA/ECDH exchange") if data == "" || dataSig == "" { return errors.New("missing data or signature for ECDH initial verification") } // Step 1: Verify ECDSA signature of data, and convert the key to ECDH public key, must be SECG secp256r1 / X9.62 prime256v1 curve. clientECDSAPub, err := verifyDataWithCliPubECDSA(accelPub, data, dataSig) if err != nil { return fmt.Errorf("dual-purpose key verification failed: %v", err) } // Convert client's ECDSA key to ECDH format clientECDHPub, err := convertECDSAToECDH(clientECDSAPub) if err != nil { return fmt.Errorf("failed to convert client key to ECDH: %w", err) } // Step 2: Generate server's Random ECDH key pair serverECDHPriv, err := generateECDHKeyPair() if err != nil { return fmt.Errorf("failed to generate server ECDH key: %v", err) } // Step 3: Perform ECDH key exchange sharedSecret, err := performECDHKeyExchange(serverECDHPriv, clientECDHPub) if err != nil { return fmt.Errorf("ECDH key exchange failed: %v", err) } // Step 4: Return server's public key to client serverPubKeyBytes, _ := x509.MarshalPKIXPublicKey(serverECDHPriv.PublicKey()) serverPubKeyBase64 := base64.StdEncoding.EncodeToString(serverPubKeyBytes) w.Header().Set("x-rpc-sec-bound-token-accel-pub", serverPubKeyBase64) // Step 5: Store the results in unified key info unifiedKey.SymmetricKey = sharedSecret unifiedKey.ServerPrivKey = serverECDHPriv debugLog("performDualPurposeECDHExchange", "Dual-purpose ECDH exchange completed successfully") return nil } // Verify authenticated requests func verifyRequest(w http.ResponseWriter, r *http.Request, keyInfo UnifiedKeyInfo) error { debugLog("verifyRequest", "Verifying request, key type: %s, has symmetric key: %v", keyInfo.KeyType, keyInfo.SymmetricKey != nil) // Get data and signature from request data := r.Header.Get("x-rpc-sec-bound-token-data") dataSig := r.Header.Get("x-rpc-sec-bound-token-data-sig") debugLog("verifyRequest", "Request data: %s, signature length: %d", data, len(dataSig)) // Special case for ECDH initial requests if keyInfo.KeyType == KeyTypeECDH && keyInfo.HwKey != nil { // This is an initial ECDH request that should be verified with hardware key hwKey := keyInfo.HwKey // Verify that we have the data and signature if data == "" || dataSig == "" { errorResponse(w, "missing data or signature for verification", http.StatusUnauthorized) return errors.New("missing data or signature for verification") } // Verify the data signature using the hardware key if err := verifySignedData(hwKey, data, dataSig); err != nil { errorResponse(w, fmt.Sprintf("failed to verify data signature: %v", err), http.StatusUnauthorized) return err } log.Printf("ECDH initial auth successful with hardware key: %s", data) return nil } if dataSig != "" && keyInfo.SymmetricKey != nil { debugLog("verifyRequest", "Using HMAC validation with symmetric key, length: %d", len(keyInfo.SymmetricKey)) // Handle HMAC case if err := validateHMACRequest(keyInfo.SymmetricKey, data, dataSig); err != nil { log.Printf("HMAC validation failed: %v", err) errorResponse(w, err.Error(), http.StatusUnauthorized) return err } log.Printf("HMAC auth successful: %s", data) } else { debugLog("verifyRequest", "Using asymmetric signature validation") // Handle asymmetric signature case if err := validateAsymmetricRequest(keyInfo, data, dataSig); err != nil { errorResponse(w, err.Error(), http.StatusUnauthorized) return err } log.Printf("Asymmetric auth successful: %s", data) } // Authentication succeeded return nil } // Validate request with asymmetric signature func validateAsymmetricRequest(keyInfo UnifiedKeyInfo, data, dataSig string) error { debugLog("asymmetricValidation", "Validating asymmetric signature, key type: %s", keyInfo.KeyType) if data == "" || dataSig == "" { return errors.New("missing required headers for asymmetric verification") } // Parse and validate acceleration public key accelKey, err := parseAndValidateKey(string(keyInfo.PublicKey), string(keyInfo.KeyType), "acceleration key") if err != nil { return err } // Verify request is signed by acceleration key if err := verifySignedData(accelKey, data, dataSig); err != nil { return err } return nil } // Handle request using an existing key func verifyExistingKeyRequest(w http.ResponseWriter, r *http.Request) error { accelKeyId := r.Header.Get("x-rpc-sec-bound-token-accel-pub-id") debugLog("verifyExistingKey", "Verifying request with existing key ID: %s", accelKeyId) if accelKeyId == "" { errorResponse(w, "Missing acceleration key ID", http.StatusBadRequest) return errors.New("missing acceleration key ID") } // Get the key info keyInfoValue, found := accelKeys.Get(accelKeyId) if !found { errorResponse(w, "Invalid acceleration key ID", http.StatusUnauthorized) return errors.New("invalid acceleration key ID") } unifiedKey := keyInfoValue.(UnifiedKeyInfo) // Validate the request return verifyRequest(w, r, unifiedKey) } // Main endpoint for all authenticated requests func authenticatedHandler(w http.ResponseWriter, r *http.Request) { debugLog("authenticatedHandler", "Received authenticated request from %s, method: %s", r.RemoteAddr, r.Method) setCORSHeaders(w) if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } // Validate token token, err := extractAndValidateToken(r) if err != nil { errorResponse(w, err.Error(), http.StatusUnauthorized) return } // Get hardware key info associated with the token tokenInfoValue, found := tokensCache.Get(token) if !found { errorResponse(w, "Invalid token", http.StatusUnauthorized) return } hwKeyInfo := tokenInfoValue.(PublicKeyInfo) // Handle either key registration or regular request if r.Header.Get("x-rpc-sec-bound-token-accel-pub") != "" { debugLog("authenticatedHandler", "Request includes new acceleration key") // This is a new key registration (either asymmetric or ECDH) err = verifyAccelKeyRegistrationRequest(w, r, hwKeyInfo) if err != nil { errorResponse(w, err.Error(), http.StatusBadRequest) return } } else { debugLog("authenticatedHandler", "Request uses existing key") // Handle request with existing key err = verifyExistingKeyRequest(w, r) if err != nil { errorResponse(w, err.Error(), http.StatusUnauthorized) return } } // If we reach here, the request is authenticated. Run business logic sendAuthenticationSuccess(w) } // Extract and validate the authorization token func extractAndValidateToken(r *http.Request) (string, error) { debugLog("tokenValidation", "Extracting token from Authorization header") authHeader := r.Header.Get("Authorization") if !strings.HasPrefix(authHeader, "Bearer ") { return "", errors.New("invalid authorization header") } return strings.TrimPrefix(authHeader, "Bearer "), nil } // Challenge handler - generates nonce for attestation func challengeHandler(w http.ResponseWriter, r *http.Request) { debugLog("challengeHandler", "Received challenge request from %s", r.RemoteAddr) setCORSHeaders(w) if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } // Extract and validate token token, err := extractAndValidateToken(r) if err != nil { errorResponse(w, err.Error(), http.StatusUnauthorized) return } // Verify token exists in cache _, found := tokensCache.Get(token) if !found { errorResponse(w, "Invalid token", http.StatusUnauthorized) return } // Generate a fresh nonce nonce, err := generateRandomString(32) if err != nil { errorResponse(w, "Failed to generate challenge", http.StatusInternalServerError) return } // Store the nonce with the token for later verification challengeCache.Set(token+":nonce", nonce, cache.DefaultExpiration) log.Printf("Generated attestation challenge for token: %s", token[:8]+"...") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(ChallengeResponse{Nonce: nonce}) } // Validate TPM key attestation structure func validateTpmKeyAttestation(attestationBytes []byte, aikPubBytes []byte, nonce string) error { debugLog("validateTpmKeyAttestation", "Validating TPM key attestation, size: %d bytes", len(attestationBytes)) // Check minimum size for PCP_KEY_ATTESTATION_BLOB header if len(attestationBytes) < 24 { // Magic(4) + Platform(4) + HeaderSize(4) + cbKeyAttest(4) + cbSignature(4) + cbKeyBlob(4) return errors.New("attestation blob too small") } // Parse the PCP_KEY_ATTESTATION_BLOB header magic := binary.LittleEndian.Uint32(attestationBytes[0:4]) platform := binary.LittleEndian.Uint32(attestationBytes[4:8]) headerSize := binary.LittleEndian.Uint32(attestationBytes[8:12]) cbKeyAttest := binary.LittleEndian.Uint32(attestationBytes[12:16]) cbSignature := binary.LittleEndian.Uint32(attestationBytes[16:20]) cbKeyBlob := binary.LittleEndian.Uint32(attestationBytes[20:24]) debugLog("validateTpmKeyAttestation", "Magic: 0x%08X, Platform: %d, HeaderSize: %d", magic, platform, headerSize) debugLog("validateTpmKeyAttestation", "KeyAttest: %d, Signature: %d, KeyBlob: %d", cbKeyAttest, cbSignature, cbKeyBlob) // Validate magic number for key attestation const PCP_KEY_ATTESTATION_MAGIC = 0x4B414453 // 'SDAK' in little endian if magic != PCP_KEY_ATTESTATION_MAGIC { return fmt.Errorf("invalid magic number: expected 0x%08X, got 0x%08X", PCP_KEY_ATTESTATION_MAGIC, magic) } // Validate platform type (TPM 1.2 or 2.0) const PCPTYPE_TPM12 = 0x00000001 const PCPTYPE_TPM20 = 0x00000002 if platform != PCPTYPE_TPM12 && platform != PCPTYPE_TPM20 { return fmt.Errorf("invalid platform type: %d", platform) } // Validate header size if headerSize < 24 { return fmt.Errorf("invalid header size: %d", headerSize) } // Check that the blob is large enough to contain all claimed data expectedSize := headerSize + cbKeyAttest + cbSignature + cbKeyBlob if uint32(len(attestationBytes)) < expectedSize { return fmt.Errorf("attestation blob size mismatch: expected at least %d, got %d", expectedSize, len(attestationBytes)) } // Validate AIK public key size (should be reasonable for ECDSA P-256 or RSA) if len(aikPubBytes) < 64 || len(aikPubBytes) > 2048 { return fmt.Errorf("invalid AIK public key size: %d", len(aikPubBytes)) } // Additional validation could include: // - Parsing and validating the TPM2B_ATTEST structure within cbKeyAttest // - Verifying the signature using the AIK public key // - Checking nonce inclusion if provided // For now, we accept the attestation if it has the correct structure debugLog("validateTpmKeyAttestation", "TPM key attestation validation passed") return nil } // Attestation handler - verifies TPM key attestation func attestationHandler(w http.ResponseWriter, r *http.Request) { debugLog("attestationHandler", "Received attestation request from %s", r.RemoteAddr) setCORSHeaders(w) if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } // Extract and validate token token, err := extractAndValidateToken(r) if err != nil { errorResponse(w, err.Error(), http.StatusUnauthorized) return } // Verify token exists in cache _, found := tokensCache.Get(token) if !found { errorResponse(w, "Invalid token", http.StatusUnauthorized) return } // Parse the attestation request var attestReq AttestationRequest if err := json.NewDecoder(r.Body).Decode(&attestReq); err != nil { errorResponse(w, "Invalid request body", http.StatusBadRequest) return } // Validate required fields if attestReq.Attestation == "" || attestReq.AIKPublicKey == "" { errorResponse(w, "Missing attestation or AIK public key", http.StatusBadRequest) return } // Verify nonce if provided if attestReq.Nonce != "" { expectedNonce, found := challengeCache.Get(token + ":nonce") if !found { errorResponse(w, "Invalid or expired nonce", http.StatusBadRequest) return } if expectedNonce.(string) != attestReq.Nonce { errorResponse(w, "Nonce mismatch", http.StatusBadRequest) return } // Remove used nonce challengeCache.Delete(token + ":nonce") } // Decode and validate the TPM attestation structure attestationBytes, err := base64.StdEncoding.DecodeString(attestReq.Attestation) if err != nil { errorResponse(w, "Invalid attestation format", http.StatusBadRequest) return } aikPubBytes, err := base64.StdEncoding.DecodeString(attestReq.AIKPublicKey) if err != nil { errorResponse(w, "Invalid AIK public key format", http.StatusBadRequest) return } // Validate TPM key attestation structure if err := validateTmpKeyAttestation(attestationBytes, aikPubBytes, attestReq.Nonce, attestReq.PCRValues); err != nil { errorResponse(w, fmt.Sprintf("TPM attestation validation failed: %v", err), http.StatusBadRequest) return } // Log successful attestation with PCR info pcrInfo := "no PCRs" if attestReq.PCRValues != "" { pcrBytes, err := base64.StdEncoding.DecodeString(attestReq.PCRValues) if err == nil && len(pcrBytes) > 0 { // Determine PCR format if len(pcrBytes)%24 == 0 { // 24 PCRs digestSize := len(pcrBytes) / 24 if digestSize == 20 { pcrInfo = fmt.Sprintf("%d SHA1 PCRs", 24) } else if digestSize == 32 { pcrInfo = fmt.Sprintf("%d SHA256 PCRs", 24) } else { pcrInfo = fmt.Sprintf("%d PCRs (%d bytes each)", 24, digestSize) } } else { pcrInfo = fmt.Sprintf("%d PCR bytes", len(pcrBytes)) } } } log.Printf("TPM key attestation successful for token: %s, key type: %s, attestation size: %d bytes, PCRs: %s", token[:8]+"...", attestReq.KeyType, len(attestationBytes), pcrInfo) // Store attestation info with token for future reference tokensCache.Set(token+":attestation", map[string]interface{}{ "attestation": attestReq.Attestation, "aik_public_key": attestReq.AIKPublicKey, "pcr_values": attestReq.PCRValues, "key_type": attestReq.KeyType, "timestamp": time.Now(), "validated": true, }, cache.DefaultExpiration) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ "status": "success", "message": "TPM key attestation verified successfully", "timestamp": time.Now(), }) } // Enhanced TPM key attestation validation with full cryptographic verification func validateTmpKeyAttestation(attestationBytes []byte, aikPubBytes []byte, nonce string, pcrValues string) error { debugLog("validateTmpKeyAttestation", "Validating TPM key attestation with crypto verification, size: %d bytes", len(attestationBytes)) // Check minimum size for PCP_KEY_ATTESTATION_BLOB header if len(attestationBytes) < 24 { // Magic(4) + Platform(4) + HeaderSize(4) + cbKeyAttest(4) + cbSignature(4) + cbKeyBlob(4) return errors.New("attestation blob too small") } // Parse the PCP_KEY_ATTESTATION_BLOB header magic := binary.LittleEndian.Uint32(attestationBytes[0:4]) platform := binary.LittleEndian.Uint32(attestationBytes[4:8]) headerSize := binary.LittleEndian.Uint32(attestationBytes[8:12]) cbKeyAttest := binary.LittleEndian.Uint32(attestationBytes[12:16]) cbSignature := binary.LittleEndian.Uint32(attestationBytes[16:20]) cbKeyBlob := binary.LittleEndian.Uint32(attestationBytes[20:24]) debugLog("validateTmpKeyAttestation", "Magic: 0x%08X, Platform: %d, HeaderSize: %d", magic, platform, headerSize) debugLog("validateTmpKeyAttestation", "KeyAttest: %d, Signature: %d, KeyBlob: %d", cbKeyAttest, cbSignature, cbKeyBlob) // Validate magic number for key attestation const PCP_KEY_ATTESTATION_MAGIC = 0x5344414B if magic != PCP_KEY_ATTESTATION_MAGIC { return fmt.Errorf("invalid magic number: expected 0x%08X, got 0x%08X", PCP_KEY_ATTESTATION_MAGIC, magic) } // Validate platform type (TPM 1.2 or 2.0) const PCPTYPE_TPM12 = 0x00000001 const PCPTYPE_TPM20 = 0x00000002 if platform != PCPTYPE_TPM12 && platform != PCPTYPE_TPM20 { return fmt.Errorf("invalid platform type: %d", platform) } // Validate header size if headerSize < 24 { return fmt.Errorf("invalid header size: %d", headerSize) } // Check that the blob is large enough to contain all claimed data expectedSize := headerSize + cbKeyAttest + cbSignature + cbKeyBlob if uint32(len(attestationBytes)) < expectedSize { return fmt.Errorf("attestation blob size mismatch: expected at least %d, got %d", expectedSize, len(attestationBytes)) } // Extract components from attestation blob cursor := int(headerSize) keyAttestData := attestationBytes[cursor : cursor+int(cbKeyAttest)] cursor += int(cbKeyAttest) signatureData := attestationBytes[cursor : cursor+int(cbSignature)] cursor += int(cbSignature) keyBlobData := attestationBytes[cursor : cursor+int(cbKeyBlob)] debugLog("validateTmpKeyAttestation", "Extracted components: KeyAttest=%d, Signature=%d, KeyBlob=%d bytes", len(keyAttestData), len(signatureData), len(keyBlobData)) // Step 1: Parse and validate AIK public key aikPubKey, err := parseAndValidateAIKPublicKey(aikPubBytes) if err != nil { return fmt.Errorf("failed to parse AIK public key: %w", err) } // Step 2: Verify the attestation signature if err := verifyAttestationSignature(aikPubKey, keyAttestData, signatureData); err != nil { return fmt.Errorf("attestation signature verification failed: %w", err) } // Step 3: Validate TPM attestation structure and nonce if err := validateTPMAttestStructure(keyAttestData, nonce, platform); err != nil { return fmt.Errorf("TPM attest structure validation failed: %w", err) } // Step 4: Validate key blob and policies (based on ValidateKeyAttest20) if err := validateKeyBlobAndPolicies(keyBlobData, keyAttestData, pcrValues, platform); err != nil { return fmt.Errorf("key blob validation failed: %w", err) } debugLog("validateTmpKeyAttestation", "TPM key attestation validation passed") return nil } // Validate key blob and TPM policies (based on ValidateKeyAttest20) func validateKeyBlobAndPolicies(keyBlobData []byte, keyAttestData []byte, pcrValues string, platform uint32) error { if platform != 0x00000002 { // Only TPM 2.0 supported for now debugLog("validateKeyBlobAndPolicies", "Skipping key blob validation for platform %d", platform) return nil } debugLog("validateKeyBlobAndPolicies", "Validating key blob and policies, blob size: %d", len(keyBlobData)) // Parse PCP_KEY_BLOB_WIN8 header if len(keyBlobData) < 72 { // Minimum size for PCP_KEY_BLOB_WIN8 return errors.New("key blob too small for header") } cursor := 0 // Read header fields magic := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 if magic != 0x4D504350 { return fmt.Errorf("invalid key blob magic: 0x%08X", magic) } cbHeader := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 pcpType := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 if pcpType != 0x00000002 { // PCPTYPE_TPM20 return fmt.Errorf("invalid PCP type: %d, expected TPM 2.0", pcpType) } if cbHeader < 56 { return fmt.Errorf("invalid header size: %d", cbHeader) } // Skip to the size fields cursor = 16 // Skip to cbPublic cbPublic := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 cbPrivate := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 cbMigrationPublic := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 cbMigrationPrivate := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 cbPolicyDigestList := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 cbPCRBinding := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 cbPCRDigest := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 cbEncryptedSecret := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 cbTpm12HostageBlob := binary.LittleEndian.Uint32(keyBlobData[cursor : cursor+4]) debugLog("validateKeyBlobAndPolicies", "Key blob sizes: Public=%d, Private=%d, PolicyDigestList=%d", cbPublic, cbPrivate, cbPolicyDigestList) // Validate total size expectedSize := cbHeader + cbPublic + cbPrivate + cbMigrationPublic + cbMigrationPrivate + cbPolicyDigestList + cbPCRBinding + cbPCRDigest + cbEncryptedSecret + cbTpm12HostageBlob if uint32(len(keyBlobData)) < expectedSize { return fmt.Errorf("key blob size mismatch: expected %d, got %d", expectedSize, len(keyBlobData)) } // Step 1: Extract and validate key name from attestation keyNameFromAttest, err := extractKeyNameFromAttestation(keyAttestData) if err != nil { return fmt.Errorf("failed to extract key name from attestation: %w", err) } // Step 2: Calculate key name from public key in blob publicKeyOffset := cbHeader + 2 // Skip size field in TPMT_PUBLIC if uint32(len(keyBlobData)) < publicKeyOffset+cbPublic-2 { return errors.New("key blob too small for public key") } keyNameFromBlob, err := calculateKeyNameFromPublic(keyBlobData[publicKeyOffset : publicKeyOffset+cbPublic-2]) if err != nil { return fmt.Errorf("failed to calculate key name from public key: %w", err) } // Step 3: Validate key names match if !bytes.Equal(keyNameFromAttest, keyNameFromBlob) { return fmt.Errorf("key name mismatch: attestation vs blob") } debugLog("validateKeyBlobAndPolicies", "Key name validation passed") // Step 4: Validate policy digests if err := validatePolicyDigests(keyBlobData, cbHeader, cbPublic, cbPrivate, cbMigrationPublic, cbMigrationPrivate, cbPolicyDigestList, pcrValues); err != nil { return fmt.Errorf("policy digest validation failed: %w", err) } debugLog("validateKeyBlobAndPolicies", "Key blob and policy validation passed") return nil } // Extract key name from TPM attestation data func extractKeyNameFromAttestation(keyAttestData []byte) ([]byte, error) { cursor := 0 // Skip magic (4 bytes) and type (2 bytes) cursor += 6 // Skip qualifiedSigner qualifiedSignerSize, newCursor, err := readTPM2BSize(keyAttestData, cursor) if err != nil { return nil, err } cursor = newCursor + int(qualifiedSignerSize) // Skip extraData extraDataSize, newCursor, err := readTPM2BSize(keyAttestData, cursor) if err != nil { return nil, err } cursor = newCursor + int(extraDataSize) // Skip TPMS_CLOCK_INFO (17 bytes) and firmwareVersion (8 bytes) cursor += 25 // Read key name nameSize, newCursor, err := readTPM2BSize(keyAttestData, cursor) if err != nil { return nil, err } cursor = newCursor if nameSize == 0 { return nil, errors.New("empty key name in attestation") } if len(keyAttestData) < cursor+int(nameSize) { return nil, errors.New("attestation too small for key name") } return keyAttestData[cursor : cursor+int(nameSize)], nil } // Calculate key name from public key (simplified implementation) func calculateKeyNameFromPublic(publicKeyData []byte) ([]byte, error) { // This is a simplified implementation // In a full implementation, you would: // 1. Parse the TPMT_PUBLIC structure // 2. Calculate name = Hash(nameAlg || publicArea) // For now, we'll create a placeholder that matches expected format if len(publicKeyData) < 10 { return nil, errors.New("public key data too small") } // Create a SHA-256 hash of the public key data as a simplified name hash := sha256.Sum256(publicKeyData) // TPM name format: nameAlg (2 bytes) + hash nameAlg := []byte{0x00, 0x0B} // TPM_ALG_SHA256 keyName := make([]byte, len(nameAlg)+len(hash)) copy(keyName, nameAlg) copy(keyName[len(nameAlg):], hash[:]) return keyName, nil } // Validate TPM policy digests (comprehensive implementation based on ValidateKeyAttest20) func validatePolicyDigests(keyBlobData []byte, cbHeader, cbPublic, cbPrivate, cbMigrationPublic, cbMigrationPrivate, cbPolicyDigestList uint32, pcrValues string) error { // Parse key attributes to determine if key is PCR-bound cursor := int(cbHeader + 2 + 2 + 2) // Skip size, keytype, nameAlg if len(keyBlobData) < cursor+4 { return errors.New("key blob too small for key attributes") } keyAttributes := binary.BigEndian.Uint32(keyBlobData[cursor : cursor+4]) // Navigate to policy digest list cursor = int(cbHeader + cbPublic + cbPrivate + cbMigrationPublic + cbMigrationPrivate) if len(keyBlobData) < cursor+4 { return errors.New("key blob too small for policy digest count") } policyDigestCount := binary.BigEndian.Uint32(keyBlobData[cursor : cursor+4]) cursor += 4 debugLog("validatePolicyDigests", "Policy digest count: %d", policyDigestCount) // Only non-exportable keys may be attested - must have exactly 4 or 6 policy digests if policyDigestCount != 4 && policyDigestCount != 6 { return fmt.Errorf("invalid policy digest count: %d, expected 4 or 6", policyDigestCount) } // Calculate user policy digest reference var userPolicyDigestReference []byte if pcrValues != "" && (keyAttributes&0x00000040) == 0 { // Key is PCR-bound and we have PCR data // Parse PCR data pcrBytes, err := base64.StdEncoding.DecodeString(pcrValues) if err != nil { return fmt.Errorf("invalid PCR values format: %w", err) } // Get PCR information from key blob pcrCursor := int(cbHeader + cbPublic + cbPrivate + cbMigrationPublic + cbMigrationPrivate + cbPolicyDigestList) if len(keyBlobData) < pcrCursor+3 { return errors.New("key blob too small for PCR mask") } // Read PCR mask (3 bytes, little-endian) keyBlobPcrMask := uint32(keyBlobData[pcrCursor]) | (uint32(keyBlobData[pcrCursor+1]) << 8) | (uint32(keyBlobData[pcrCursor+2]) << 16) pcrCursor += 3 // Get PCR digest from key blob pcrDigestSize := 32 // SHA256_DIGEST_SIZE for policy calculation if len(keyBlobData) < pcrCursor+pcrDigestSize { return errors.New("key blob too small for PCR digest") } keyBlobPcrDigest := keyBlobData[pcrCursor : pcrCursor+pcrDigestSize] // Determine PCR algorithm from PCR data size var pcrAlgId uint16 = TPM_ALG_SHA1 digestSize := len(pcrBytes) / AVAILABLE_PLATFORM_PCRS if digestSize == 32 { pcrAlgId = TPM_ALG_SHA256 } // Calculate user policy with PCRs userPolicyDigestReference, err = calculateUserPolicyWithPCRs(pcrBytes, keyBlobPcrMask, pcrAlgId, keyBlobPcrDigest) if err != nil { return fmt.Errorf("failed to calculate user policy with PCRs: %w", err) } } else if (keyAttributes & 0x00000040) != 0 { // userWithAuth - not PCR bound // Use default user policy userPolicyDigestReference = make([]byte, len(defaultUserPolicy)) copy(userPolicyDigestReference, defaultUserPolicy) } else { // Key is PCR-bound but caller didn't provide PCR data for validation // Accept the user policy digest stored in the key blob policyCursor := int(cbHeader + cbPublic + cbPrivate + cbMigrationPublic + cbMigrationPrivate + 4) _, userPolicyDigest, err := readBigEndian2B(keyBlobData, &policyCursor) if err != nil { return fmt.Errorf("failed to read user policy digest: %w", err) } userPolicyDigestReference = userPolicyDigest } // Read and validate each policy digest policyCursor := int(cbHeader + cbPublic + cbPrivate + cbMigrationPublic + cbMigrationPrivate + 4) // Expected policy digests in order expectedPolicies := [][]byte{ userPolicyDigestReference, adminObjectChangeAuthPolicy, adminCertifyPolicy, adminActivateCredentialPolicy, } if policyDigestCount == 6 { expectedPolicies = append(expectedPolicies, []byte{}, // Empty policy for Duplicate (zero-length) adminCertifyPolicyNoPin) } // Validate each policy digest for i := 0; i < int(policyDigestCount); i++ { digestSize, digest, err := readBigEndian2B(keyBlobData, &policyCursor) if err != nil { return fmt.Errorf("failed to read policy digest %d: %w", i, err) } // Special case for empty digest (Windows 10 duplicate policy) if i == 4 && policyDigestCount == 6 && digestSize == 0 { debugLog("validatePolicyDigests", "Policy %d: empty digest (duplicate policy)", i) continue } // Validate digest size and content if digestSize != 32 && digestSize != 0 { return fmt.Errorf("invalid policy digest size for policy %d: %d", i, digestSize) } if !bytes.Equal(digest, expectedPolicies[i]) { return fmt.Errorf("policy digest %d mismatch", i) } debugLog("validatePolicyDigests", "Policy %d: validated successfully", i) } // Calculate and verify the overall policy digest (PolicyOR of all individual policies) policyOrDigestBuffer := make([]byte, 0, 32+4+5*32) // Start with zeros for the old policy hash policyOrDigestBuffer = append(policyOrDigestBuffer, make([]byte, 32)...) // Add TPM_CC_PolicyOR tpmCcBytes := make([]byte, 4) binary.BigEndian.PutUint32(tpmCcBytes, TPM_CC_PolicyOR) policyOrDigestBuffer = append(policyOrDigestBuffer, tpmCcBytes...) // Add all policy digests policyOrDigestBuffer = append(policyOrDigestBuffer, userPolicyDigestReference...) policyOrDigestBuffer = append(policyOrDigestBuffer, adminObjectChangeAuthPolicy...) policyOrDigestBuffer = append(policyOrDigestBuffer, adminCertifyPolicy...) policyOrDigestBuffer = append(policyOrDigestBuffer, adminActivateCredentialPolicy...) if policyDigestCount > 4 { policyOrDigestBuffer = append(policyOrDigestBuffer, adminCertifyPolicyNoPin...) } // Calculate final policy digest h := sha256.New() h.Write(policyOrDigestBuffer) finalPolicyDigest := h.Sum(nil) // Verify against the auth policy in the public key authPolicyCursor := int(cbHeader + 2 + 2 + 2 + 4) // Skip to authPolicy _, authPolicy, err := readBigEndian2B(keyBlobData, &authPolicyCursor) if err != nil { return fmt.Errorf("failed to read auth policy: %w", err) } if !bytes.Equal(authPolicy, finalPolicyDigest) { return errors.New("final policy digest verification failed") } debugLog("validatePolicyDigests", "All policy digest validation passed") return nil } // Parse and validate AIK public key (RSA or ECDSA) func parseAndValidateAIKPublicKey(aikPubBytes []byte) (interface{}, error) { // Try to parse as PKIX format first pubKey, err := x509.ParsePKIXPublicKey(aikPubBytes) if err != nil { return nil, fmt.Errorf("failed to parse PKIX public key: %w", err) } // Validate key type and size switch key := pubKey.(type) { case *rsa.PublicKey: if key.N.BitLen() < 1024 { return nil, fmt.Errorf("RSA key too small: %d bits", key.N.BitLen()) } return key, nil case *ecdsa.PublicKey: if key.Curve != elliptic.P256() { return nil, fmt.Errorf("unsupported ECDSA curve") } return key, nil default: return nil, fmt.Errorf("unsupported AIK key type: %T", key) } } // Verify attestation signature using AIK public key func verifyAttestationSignature(aikPubKey interface{}, keyAttestData []byte, signatureData []byte) error { // Calculate SHA-1 hash of the key attestation data (as per TPM spec) hash := sha1.Sum(keyAttestData) switch key := aikPubKey.(type) { case *rsa.PublicKey: // Use PKCS#1 v1.5 padding for RSA signature verification return rsa.VerifyPKCS1v15(key, crypto.SHA1, hash[:], signatureData) case *ecdsa.PublicKey: // For ECDSA, try ASN.1 format first if ecdsa.VerifyASN1(key, hash[:], signatureData) { return nil } // Try raw r||s format if ASN.1 fails if len(signatureData) == 64 { r := new(big.Int).SetBytes(signatureData[:32]) s := new(big.Int).SetBytes(signatureData[32:]) if ecdsa.Verify(key, hash[:], r, s) { return nil } } return errors.New("ECDSA signature verification failed") default: return fmt.Errorf("unsupported key type for signature verification: %T", key) } } // Validate TPM attestation structure and nonce (enhanced based on ValidateKeyAttest20) func validateTPMAttestStructure(keyAttestData []byte, nonce string, platform uint32) error { if len(keyAttestData) < 8 { return errors.New("key attest data too small") } // For TPM 2.0, perform comprehensive validation if platform == 0x00000002 { // TPM 2.0 return validateTPM20AttestStructure(keyAttestData, nonce) } else if platform == 0x00000001 { // TPM 1.2 return validateTPM12AttestStructure(keyAttestData, nonce) } return fmt.Errorf("unsupported platform type: %d", platform) } // Validate TPM 2.0 attestation structure (based on ValidateKeyAttest20) func validateTPM20AttestStructure(keyAttestData []byte, nonce string) error { debugLog("validateTPM20AttestStructure", "Validating TPM 2.0 attestation structure, size: %d", len(keyAttestData)) cursor := 0 // Step 1: Read magic number (TPM_GENERATED_VALUE) if len(keyAttestData) < cursor+4 { return errors.New("attestation too small for magic number") } magic := binary.BigEndian.Uint32(keyAttestData[cursor : cursor+4]) cursor += 4 if magic != 0xff544347 { // TPM_GENERATED_VALUE return fmt.Errorf("invalid TPM_GENERATED magic: 0x%08X, expected 0xff544347", magic) } // Step 2: Read attestation type if len(keyAttestData) < cursor+2 { return errors.New("attestation too small for type field") } attestType := binary.BigEndian.Uint16(keyAttestData[cursor : cursor+2]) cursor += 2 if attestType != 0x8017 { // TPM_ST_ATTEST_CERTIFY return fmt.Errorf("invalid attestation type: 0x%04X, expected 0x8017 (TPM_ST_ATTEST_CERTIFY)", attestType) } // Step 3: Skip qualifiedSigner (TPM2B_NAME) qualifiedSignerSize, newCursor, err := readTPM2BSize(keyAttestData, cursor) if err != nil { return fmt.Errorf("failed to read qualifiedSigner size: %w", err) } cursor = newCursor + int(qualifiedSignerSize) // Step 4: Read extraData (nonce) - TPM2B_DATA extraDataSize, newCursor, err := readTPM2BSize(keyAttestData, cursor) if err != nil { return fmt.Errorf("failed to read extraData size: %w", err) } cursor = newCursor var extraData []byte if extraDataSize > 0 { if len(keyAttestData) < cursor+int(extraDataSize) { return errors.New("attestation too small for extraData") } extraData = keyAttestData[cursor : cursor+int(extraDataSize)] } cursor += int(extraDataSize) // Step 5: Skip TPMS_CLOCK_INFO (8 + 1 + 4 + 4 = 17 bytes) // UINT64 clock, BYTE resetCount, UINT32 restartCount, BYTE safe if len(keyAttestData) < cursor+17 { return errors.New("attestation too small for TPMS_CLOCK_INFO") } cursor += 17 // Step 6: Skip firmwareVersion (8 bytes) if len(keyAttestData) < cursor+8 { return errors.New("attestation too small for firmwareVersion") } cursor += 8 // Step 7: Read name (TPM2B_NAME) - this is the key name nameSize, newCursor, err := readTPM2BSize(keyAttestData, cursor) if err != nil { return fmt.Errorf("failed to read name size: %w", err) } cursor = newCursor var keyName []byte if nameSize > 0 { if len(keyAttestData) < cursor+int(nameSize) { return errors.New("attestation too small for key name") } keyName = keyAttestData[cursor : cursor+int(nameSize)] } cursor += int(nameSize) // Step 8: Skip qualifiedName (TPM2B_NAME) qualifiedNameSize, newCursor, err := readTPM2BSize(keyAttestData, cursor) if err != nil { return fmt.Errorf("failed to read qualifiedName size: %w", err) } cursor = newCursor + int(qualifiedNameSize) // Step 9: Ensure that there is no trailing data that has been signed if cursor != len(keyAttestData) { return fmt.Errorf("unexpected trailing data in attestation: expected %d bytes, consumed %d", len(keyAttestData), cursor) } // Step 10: Validate nonce if provided (matches C++ logic) if nonce != "" { nonceBytes, err := base64.StdEncoding.DecodeString(nonce) if err != nil { return fmt.Errorf("invalid nonce format: %w", err) } // Check the nonce if requested - matches C++ condition exactly if len(nonceBytes) != len(extraData) || !bytes.Equal(extraData, nonceBytes) { return fmt.Errorf("nonce mismatch: expected %d bytes, got %d bytes in extraData", len(nonceBytes), len(extraData)) } debugLog("validateTPM20AttestStructure", "Nonce validation successful") } // Log key information for debugging if len(keyName) > 0 { debugLog("validateTPM20AttestStructure", "Key name: %d bytes, first 16 bytes: %x", len(keyName), keyName[:min(16, len(keyName))]) } debugLog("validateTPM20AttestStructure", "TPM 2.0 attestation structure validation passed") return nil } // Validate TPM 1.2 attestation structure (simplified) func validateTPM12AttestStructure(keyAttestData []byte, nonce string) error { debugLog("validateTPM12AttestStructure", "Validating TPM 1.2 attestation structure, size: %d", len(keyAttestData)) // TPM 1.2 has a different structure - this is a simplified validation // In a full implementation, you would parse the TPM_CERTIFY_INFO structure if len(keyAttestData) < 20 { return errors.New("TPM 1.2 attestation too small") } // Basic validation - just ensure we have reasonable data debugLog("validateTPM12AttestStructure", "TPM 1.2 attestation structure validation passed (simplified)") return nil } // Helper function to read TPM2B size field (2 bytes big-endian) func readTPM2BSize(data []byte, cursor int) (uint16, int, error) { if len(data) < cursor+2 { return 0, cursor, errors.New("insufficient data for TPM2B size field") } size := binary.BigEndian.Uint16(data[cursor : cursor+2]) return size, cursor + 2, nil } // Helper function for min operation func min(a, b int) int { if a < b { return a } return b } func main() { http.HandleFunc("/register", registerHandler) http.HandleFunc("/login", loginHandler) http.HandleFunc("/authenticated", authenticatedHandler) // Experimental http.HandleFunc("/challenge", challengeHandler) http.HandleFunc("/attest", attestationHandler) debugLog("main", "Server starting on port 28280") log.Fatal(http.ListenAndServe(":28280", nil)) }