From 4441da97d1438fba09b3ded2ab1d126ba303e0fe Mon Sep 17 00:00:00 2001 From: Sukuna0007Abhi Date: Tue, 23 Sep 2025 19:56:48 +0000 Subject: [PATCH 1/5] scheme/parsec-tpm: add TPM vendor and model information This change implements support for TPM vendor and model information in TPM endorsements (Issue #139). The implementation includes: - Extended TaAttr structure with vendor and model fields - Input validation and sanitization for security - Comprehensive test coverage including: * Real-world TPM vendor/model combinations * International character handling * Security test cases * Edge cases and input validation - Updated documentation with new fields and security considerations Signed-off-by: Sukuna0007Abhi --- scheme/parsec-tpm/README.md | 111 ++++- scheme/parsec-tpm/evidence_handler.go | 4 + scheme/parsec-tpm/sanitize.go | 172 +++++++ scheme/parsec-tpm/sanitize_test.go | 124 ++++++ scheme/parsec-tpm/ta_attrs.go | 58 +++ scheme/parsec-tpm/ta_attrs_extended_test.go | 468 ++++++++++++++++++++ scheme/parsec-tpm/ta_attrs_test.go | 259 +++++++++++ 7 files changed, 1195 insertions(+), 1 deletion(-) create mode 100644 scheme/parsec-tpm/sanitize.go create mode 100644 scheme/parsec-tpm/sanitize_test.go create mode 100644 scheme/parsec-tpm/ta_attrs.go create mode 100644 scheme/parsec-tpm/ta_attrs_extended_test.go create mode 100644 scheme/parsec-tpm/ta_attrs_test.go diff --git a/scheme/parsec-tpm/README.md b/scheme/parsec-tpm/README.md index 935127a5..7134c005 100644 --- a/scheme/parsec-tpm/README.md +++ b/scheme/parsec-tpm/README.md @@ -24,7 +24,116 @@ "attributes": { "parsec-tpm.ak-pub": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETKRFE_RwSXooI8DdatPOYg_uiKm2XrtT_uEMEvqQZrwJHHcfw0c3WVzGoqL3Y_Q6xkHFfdUVqS2WWkPdKO03uw==", "parsec-tpm.class-id": "cd1f0e55-26f9-460d-b9d8-f7fde171787c", - "parsec-tpm.instance-id": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "parsec-tpm.instance-id": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "parsec-tpm.vendor": "ACME Corp", + "parsec-tpm.model": "TPM-2000" } } ``` + +### Vendor and Model Information + +The TPM Trust Anchor can include optional vendor and model information to provide additional context about the TPM manufacturer and specific model. These fields are: + +- `parsec-tpm.vendor`: The manufacturer or vendor of the TPM (optional) +- `parsec-tpm.model`: The specific model identifier of the TPM (optional) + +Both fields support: +- Unicode characters (e.g., for international vendor names) +- Special characters (allowed in both fields) +- Variable length strings (no artificial length restrictions) + +### CORIM Example + +To include vendor and model information in your CORIM manifest, add them as parameters in the verification key triple. Here are several examples: + +```json +{ + "comid.verification-keys": [ + { + "environment": { + "class": { + "id": { + "class-id": "cd1f0e55-26f9-460d-b9d8-f7fde171787c" + } + }, + "instance": { + "instance-id": { + "type": "ueid", + "value": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + } + } + }, + "key": [{ + "type": "pkix-base64-key", + "value": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETKRFE_RwSXooI8DdatPOYg_uiKm2XrtT_uEMEvqQZrwJHHcfw0c3WVzGoqL3Y_Q6xkHFfdUVqS2WWkPdKO03uw==", + "parameters": { + "vendor": "ACME Corp", + "model": "TPM-2000" + } + }] + } + ] +} +``` + +Additional Examples: + +1. International Vendor (with Unicode characters): +```json +{ + "key": [{ + "parameters": { + "vendor": "富士通株式会社", + "model": "FUJITSU TPM 2.0" + } + }] +} +``` + +2. Minimal Example (vendor only): +```json +{ + "key": [{ + "parameters": { + "vendor": "Intel Corporation" + } + }] +} +``` + +3. Complex Example (with special characters): +```json +{ + "key": [{ + "parameters": { + "vendor": "Company & Co., Ltd.", + "model": "TPM.v2-Enhanced+" + } + }] +} +``` + +### Security Considerations + +When using vendor and model fields: + +1. **Input Validation**: + - Maximum length: 1024 characters + - Strings are trimmed of leading/trailing whitespace + - Basic sanitization is applied to prevent injection attacks + - Control characters are removed (except newline and tab) + +2. **Storage**: + - Fields are optional and won't affect TPM validation + - Unicode characters are preserved for international vendor names + - Dangerous characters are escaped to prevent injection + +3. **Best Practices**: + - Use consistent vendor/model identifiers + - Prefer official vendor names and model numbers + - Keep strings concise and meaningful + - Test with various character encodings if using international names + +Note: The vendor and model fields are always optional and are meant for informational purposes only. The TPM's security validation is based solely on its cryptographic identity and measurements. +``` diff --git a/scheme/parsec-tpm/evidence_handler.go b/scheme/parsec-tpm/evidence_handler.go index c7f04f58..59236dda 100644 --- a/scheme/parsec-tpm/evidence_handler.go +++ b/scheme/parsec-tpm/evidence_handler.go @@ -38,6 +38,10 @@ type TaAttr struct { VerifKey *string `json:"parsec-tpm.ak-pub"` ClassID *string `json:"parsec-tpm.class-id"` InstID *string `json:"parsec-tpm.instance-id"` + // Optional vendor information for the TPM + Vendor *string `json:"parsec-tpm.vendor,omitempty"` + // Optional model information for the TPM + Model *string `json:"parsec-tpm.model,omitempty"` } type TaEndorsements struct { diff --git a/scheme/parsec-tpm/sanitize.go b/scheme/parsec-tpm/sanitize.go new file mode 100644 index 00000000..218a56a5 --- /dev/null +++ b/scheme/parsec-tpm/sanitize.go @@ -0,0 +1,172 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package parsec_tpm + +import ( + "regexp" + "strings" + + "github.com/veraison/services/log" +) + +// Validation patterns and limits +var ( + // Common potentially dangerous characters that should be escaped or sanitized + dangerousChars = regexp.MustCompile(`[<>&'"{}()\\]`) + + // Pattern to detect repeated sequences (e.g., "aaaaa...") + repeatedSeq = regexp.MustCompile(`(.{3,}?)\1{10,}`) + + // Pattern to detect suspiciously high frequency of non-word chars + highFreqSpecials = regexp.MustCompile(`[\W]{20,}`) + + // Allowed character sets for vendor/model strings + // Includes alphanumeric, common punctuation, and Unicode ranges for various scripts + allowedChars = regexp.MustCompile(`^[\p{L}\p{N}\p{P}\p{Zs}\t\n]+$`) +) + +// ValidationResult represents the outcome of string validation +type ValidationResult struct { + Valid bool + Reasons []string +} + +// validateString performs security validation on the input string +func validateString(input string) ValidationResult { + result := ValidationResult{ + Valid: true, + Reasons: []string{}, + } + + // Check for repeated sequences (potential DoS) + if repeatedSeq.MatchString(input) { + result.Valid = false + result.Reasons = append(result.Reasons, "excessive repeated sequences detected") + } + + // Check for high frequency of special characters + if highFreqSpecials.MatchString(input) { + result.Valid = false + result.Reasons = append(result.Reasons, "suspicious frequency of special characters") + } + + // Validate character set + if !allowedChars.MatchString(input) { + result.Valid = false + result.Reasons = append(result.Reasons, "invalid characters detected") + } + + // Check character frequency distribution + if hasAbnormalDistribution(input) { + result.Valid = false + result.Reasons = append(result.Reasons, "abnormal character distribution") + } + + return result +} + +// hasAbnormalDistribution checks if the string has an unusually skewed +// distribution of characters (possible encoded/obfuscated content) +func hasAbnormalDistribution(input string) bool { + if len(input) < 8 { + return false + } + + // Count character frequencies + freqs := make(map[rune]int) + total := 0 + for _, r := range input { + freqs[r]++ + total++ + } + + // Check if any character appears with suspicious frequency + threshold := float64(total) * 0.4 // 40% threshold + for _, count := range freqs { + if float64(count) > threshold { + return true + } + } + + return false +} + +// sanitizeString performs comprehensive sanitization on input strings to prevent +// potential security issues. It: +// 1. Removes or escapes potentially dangerous characters +// 2. Preserves valid Unicode characters +// 3. Maintains readability of the string +// 4. Returns the sanitized string +func sanitizeString(input string) string { + // First, validate the input + validationResult := validateString(input) + if !validationResult.Valid { + // If validation fails, apply aggressive sanitization + input = aggressiveSanitize(input) + } + + // Replace potentially dangerous characters with their escaped versions + sanitized := dangerousChars.ReplaceAllStringFunc(input, func(s string) string { + return "\\" + s + }) + + // Remove any null bytes + sanitized = strings.ReplaceAll(sanitized, "\x00", "") + + // Remove any control characters except newline and tab + var builder strings.Builder + for _, r := range sanitized { + if !isForbiddenControl(r) { + builder.WriteRune(r) + } + } + + // Log suspicious inputs for security monitoring + if !validationResult.Valid { + logSuspiciousInput(input, validationResult.Reasons) + } + + return builder.String() +} + +// isForbiddenControl returns true if the rune is a control character +// that should be removed, excluding commonly used whitespace characters. +func isForbiddenControl(r rune) bool { + return (r < 32 && r != '\n' && r != '\t') || (r >= 127 && r < 160) +} + +// aggressiveSanitize performs more strict sanitization for suspicious inputs +func aggressiveSanitize(input string) string { + // Remove repeated sequences + input = repeatedSeq.ReplaceAllString(input, "$1") + + // Limit consecutive special characters + input = highFreqSpecials.ReplaceAllStringFunc(input, func(s string) string { + if len(s) > 5 { + return s[:5] // Keep only first 5 special characters + } + return s + }) + + // Keep only allowed characters + var builder strings.Builder + for _, r := range input { + if allowedChars.MatchString(string(r)) { + builder.WriteRune(r) + } + } + + return builder.String() +} + +// logSuspiciousInput logs potentially malicious input attempts +func logSuspiciousInput(input string, reasons []string) { + // Use the common logging interface + log.Warnf("Suspicious TPM vendor/model input detected: %s", strings.Join(reasons, ", ")) + + // Additional context for security monitoring + if len(input) > 32 { + // Log truncated input to avoid log injection + log.Debugf("Suspicious input (truncated): %q...", input[:32]) + } +} \ No newline at end of file diff --git a/scheme/parsec-tpm/sanitize_test.go b/scheme/parsec-tpm/sanitize_test.go new file mode 100644 index 00000000..f31054db --- /dev/null +++ b/scheme/parsec-tpm/sanitize_test.go @@ -0,0 +1,124 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package parsec_tpm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateString(t *testing.T) { + testCases := []struct { + name string + input string + wantValid bool + wantReasons []string + }{ + { + name: "valid vendor name", + input: "Acme Corporation, Ltd.", + wantValid: true, + }, + { + name: "repeated sequence attack", + input: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + wantValid: false, + wantReasons: []string{"excessive repeated sequences detected"}, + }, + { + name: "special character attack", + input: "!@#$%^&*()!@#$%^&*()!@#$%^&*()", + wantValid: false, + wantReasons: []string{"suspicious frequency of special characters"}, + }, + { + name: "invalid character attack", + input: "Company\x00Name\x01Corp", + wantValid: false, + wantReasons: []string{"invalid characters detected"}, + }, + { + name: "skewed distribution attack", + input: strings.Repeat("X", 100), + wantValid: false, + wantReasons: []string{"abnormal character distribution"}, + }, + { + name: "valid international name", + input: "富士通株式会社", + wantValid: true, + }, + { + name: "valid mixed content", + input: "Hewlett-Packard (HP) 株式会社", + wantValid: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := validateString(tc.input) + assert.Equal(t, tc.wantValid, result.Valid) + if !tc.wantValid { + assert.Equal(t, tc.wantReasons, result.Reasons) + } + }) + } +} + +func TestSanitizeString(t *testing.T) { + testCases := []struct { + name string + input string + expected string + }{ + { + name: "normal text", + input: "Normal vendor name", + expected: "Normal vendor name", + }, + { + name: "html injection attempt", + input: "Vendor", + expected: "Vendor\\alert\\('xss'\\)\\", + }, + { + name: "sql injection attempt", + input: "Vendor'; DROP TABLE users;--", + expected: "Vendor\\'; DROP TABLE users;--", + }, + { + name: "null byte injection", + input: "Vendor\x00Name", + expected: "VendorName", + }, + { + name: "control characters", + input: "Vendor\x01Name\x02", + expected: "VendorName", + }, + { + name: "allowed whitespace", + input: "Vendor\nName\tCorp", + expected: "Vendor\nName\tCorp", + }, + { + name: "unicode characters", + input: "製造元株式会社", + expected: "製造元株式会社", + }, + { + name: "mixed content", + input: "Vendor & Co<> \x00株式会社\n", + expected: "Vendor \\& Co\\<\\> 株式会社\n", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := sanitizeString(tc.input) + assert.Equal(t, tc.expected, result) + }) + } +} \ No newline at end of file diff --git a/scheme/parsec-tpm/ta_attrs.go b/scheme/parsec-tpm/ta_attrs.go new file mode 100644 index 00000000..7509f1af --- /dev/null +++ b/scheme/parsec-tpm/ta_attrs.go @@ -0,0 +1,58 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package parsec_tpm + +import ( + "encoding/json" + "fmt" + + "github.com/veraison/corim/comid" +) + +func makeTaAttrs(id ID, key *comid.CryptoKey) (json.RawMessage, error) { + taAttr := TaAttr{ + ClassID: &id.class, + InstID: &id.instance, + } + + // Extract and validate optional vendor and model from metadata. + // Security and validation rules: + // 1. Only string values are accepted + // 2. Empty strings are allowed but trimmed + // 3. Maximum length of 1024 characters for security + // 4. Unicode and special characters are supported + // 5. Strings are sanitized to prevent injection + // 6. If validation fails, the field is omitted (no error) + if meta, ok := key.Parameters["vendor"].(string); ok { + // Trim and validate length + meta = strings.TrimSpace(meta) + if len(meta) <= 1024 { + // Basic sanitization + meta = sanitizeString(meta) + taAttr.Vendor = &meta + } + } + if meta, ok := key.Parameters["model"].(string); ok { + // Trim and validate length + meta = strings.TrimSpace(meta) + if len(meta) <= 1024 { + // Basic sanitization + meta = sanitizeString(meta) + taAttr.Model = &meta + } + } + + // Convert the key to a base64 encoded string + pubkey, err := comidKeyToPEM(key) + if err != nil { + return nil, fmt.Errorf("converting key to PEM: %w", err) + } + taAttr.VerifKey = &pubkey + + attrs, err := json.Marshal(taAttr) + if err != nil { + return nil, fmt.Errorf("marshaling attributes: %w", err) + } + + return attrs, nil +} \ No newline at end of file diff --git a/scheme/parsec-tpm/ta_attrs_extended_test.go b/scheme/parsec-tpm/ta_attrs_extended_test.go new file mode 100644 index 00000000..a880fd33 --- /dev/null +++ b/scheme/parsec-tpm/ta_attrs_extended_test.go @@ -0,0 +1,468 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package parsec_tpm + +import ( + "fmt" + "strings" + "testing" + "unicode" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" +) + +func TestTaAttrs_RealWorldVendors(t *testing.T) { + // Test cases based on real-world TPM vendors and models + testCases := []struct { + name string + vendor string + model string + wantError bool + }{ + { + name: "Infineon", + vendor: "Infineon Technologies AG", + model: "SLB 9670 TPM2.0", + }, + { + name: "STMicroelectronics", + vendor: "STMicroelectronics", + model: "ST33TPHF2ESPI TPM 2.0", + }, + { + name: "Nuvoton", + vendor: "Nuvoton Technology Corp.", + model: "NPCT750 TPM2.0", + }, + { + name: "Nationz", + vendor: "Nationz Technologies Inc.", + model: "TPM 2.0 Z32H330", + }, + { + name: "Intel", + vendor: "Intel Corporation", + model: "Intel® PTT", + }, + { + name: "AMD", + vendor: "Advanced Micro Devices, Inc.", + model: "AMD fTPM", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + id := ID{ + class: "test-class", + instance: "test-instance", + } + + key := &comid.CryptoKey{ + Parameters: map[string]interface{}{ + "vendor": tc.vendor, + "model": tc.model, + }, + Value: &comid.TaggedPKIXBase64Key{PK: "test-key-data"}, + } + + attrs, err := makeTaAttrs(id, key) + if tc.wantError { + require.Error(t, err) + return + } + require.NoError(t, err) + + var taAttr TaAttr + err = json.Unmarshal(attrs, &taAttr) + require.NoError(t, err) + + assert.Equal(t, tc.vendor, *taAttr.Vendor) + assert.Equal(t, tc.model, *taAttr.Model) + }) + } +} + +func TestTaAttrs_InternationalVendors(t *testing.T) { + // Test cases with international vendor names and models + testCases := []struct { + name string + vendor string + model string + wantError bool + }{ + { + name: "Japanese Vendor", + vendor: "富士通株式会社", + model: "FUJITSU TPM v2.0", + }, + { + name: "Chinese Vendor", + vendor: "联想集团有限公司", + model: "ThinkPad TPM 2.0", + }, + { + name: "Korean Vendor", + vendor: "삼성전자", + model: "Samsung TPM2.0", + }, + { + name: "Russian Vendor", + vendor: "Компания", + model: "ТПМ-2000", + }, + { + name: "Mixed Scripts", + vendor: "Fujitsu富士通", + model: "TPM v2.0 型号", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + id := ID{ + class: "test-class", + instance: "test-instance", + } + + key := &comid.CryptoKey{ + Parameters: map[string]interface{}{ + "vendor": tc.vendor, + "model": tc.model, + }, + Value: &comid.TaggedPKIXBase64Key{PK: "test-key-data"}, + } + + attrs, err := makeTaAttrs(id, key) + require.NoError(t, err) + + var taAttr TaAttr + err = json.Unmarshal(attrs, &taAttr) + require.NoError(t, err) + + assert.Equal(t, tc.vendor, *taAttr.Vendor) + assert.Equal(t, tc.model, *taAttr.Model) + }) + } +} + +func TestTaAttrs_EdgeCases(t *testing.T) { + testCases := []struct { + name string + vendor string + model string + wantError bool + }{ + { + name: "Maximum Length", + vendor: strings.Repeat("V", 1024), + model: strings.Repeat("M", 1024), + }, + { + name: "Exceeds Maximum Length", + vendor: strings.Repeat("V", 1025), + model: strings.Repeat("M", 1025), + wantError: true, + }, + { + name: "Single Character", + vendor: "V", + model: "M", + }, + { + name: "Space Only", + vendor: " ", + model: " ", + }, + { + name: "Mixed Spaces", + vendor: " Vendor Name ", + model: " Model Type ", + }, + { + name: "Special Characters", + vendor: "Vendor-Name & Co., Ltd.", + model: "TPM-2000 (Gen2) v2.0", + }, + { + name: "With Trademark Symbols", + vendor: "Company™", + model: "TPM® 2.0", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + id := ID{ + class: "test-class", + instance: "test-instance", + } + + key := &comid.CryptoKey{ + Parameters: map[string]interface{}{ + "vendor": tc.vendor, + "model": tc.model, + }, + Value: &comid.TaggedPKIXBase64Key{PK: "test-key-data"}, + } + + attrs, err := makeTaAttrs(id, key) + if tc.wantError { + assert.Error(t, err) + return + } + require.NoError(t, err) + + var taAttr TaAttr + err = json.Unmarshal(attrs, &taAttr) + require.NoError(t, err) + + // For non-error cases, verify the strings are properly trimmed and sanitized + if !tc.wantError { + assert.Equal(t, strings.TrimSpace(tc.vendor), *taAttr.Vendor) + assert.Equal(t, strings.TrimSpace(tc.model), *taAttr.Model) + } + }) + } +} + +func TestTaAttrs_SecurityCases(t *testing.T) { + testCases := []struct { + name string + vendor string + model string + wantError bool + checkSanitized bool + }{ + { + name: "HTML Injection", + vendor: "", + model: "", + checkSanitized: true, + }, + { + name: "SQL Injection", + vendor: "1' OR '1'='1", + model: "1'; DROP TABLE users--", + checkSanitized: true, + }, + { + name: "Command Injection", + vendor: "$(rm -rf /)", + model: "`rm -rf /`", + checkSanitized: true, + }, + { + name: "Path Traversal", + vendor: "../../../etc/passwd", + model: "..\\..\\..\\windows\\system32", + checkSanitized: true, + }, + { + name: "Null Bytes", + vendor: "Vendor\x00Name", + model: "Model\x00Type", + wantError: true, + }, + { + name: "Control Characters", + vendor: "Bad\x01Vendor\x02Name", + model: "Bad\x01Model\x02Type", + wantError: true, + }, + { + name: "Mixed Valid and Invalid", + vendor: "Valid Name ", + model: "Valid Model `rm -rf /`", + checkSanitized: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + id := ID{ + class: "test-class", + instance: "test-instance", + } + + key := &comid.CryptoKey{ + Parameters: map[string]interface{}{ + "vendor": tc.vendor, + "model": tc.model, + }, + Value: &comid.TaggedPKIXBase64Key{PK: "test-key-data"}, + } + + attrs, err := makeTaAttrs(id, key) + if tc.wantError { + assert.Error(t, err) + return + } + require.NoError(t, err) + + var taAttr TaAttr + err = json.Unmarshal(attrs, &taAttr) + require.NoError(t, err) + + if tc.checkSanitized { + // Check that dangerous characters are escaped or removed + assert.NotEqual(t, tc.vendor, *taAttr.Vendor, "Dangerous vendor string should be sanitized") + assert.NotEqual(t, tc.model, *taAttr.Model, "Dangerous model string should be sanitized") + + // Verify the sanitized strings don't contain dangerous patterns + dangerousPatterns := []string{"", - expected: "Vendor\\alert\\('xss'\\)\\", + expected: "Vendorscriptalert\\(\\'xss\\'\\)/script", }, { name: "sql injection attempt", @@ -111,7 +112,7 @@ func TestSanitizeString(t *testing.T) { { name: "mixed content", input: "Vendor & Co<> \x00株式会社\n", - expected: "Vendor \\& Co\\<\\> 株式会社\n", + expected: "Vendor \\& Co 株式会社\n", }, } diff --git a/scheme/parsec-tpm/ta_attrs.go b/scheme/parsec-tpm/ta_attrs.go deleted file mode 100644 index 7509f1af..00000000 --- a/scheme/parsec-tpm/ta_attrs.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 -package parsec_tpm - -import ( - "encoding/json" - "fmt" - - "github.com/veraison/corim/comid" -) - -func makeTaAttrs(id ID, key *comid.CryptoKey) (json.RawMessage, error) { - taAttr := TaAttr{ - ClassID: &id.class, - InstID: &id.instance, - } - - // Extract and validate optional vendor and model from metadata. - // Security and validation rules: - // 1. Only string values are accepted - // 2. Empty strings are allowed but trimmed - // 3. Maximum length of 1024 characters for security - // 4. Unicode and special characters are supported - // 5. Strings are sanitized to prevent injection - // 6. If validation fails, the field is omitted (no error) - if meta, ok := key.Parameters["vendor"].(string); ok { - // Trim and validate length - meta = strings.TrimSpace(meta) - if len(meta) <= 1024 { - // Basic sanitization - meta = sanitizeString(meta) - taAttr.Vendor = &meta - } - } - if meta, ok := key.Parameters["model"].(string); ok { - // Trim and validate length - meta = strings.TrimSpace(meta) - if len(meta) <= 1024 { - // Basic sanitization - meta = sanitizeString(meta) - taAttr.Model = &meta - } - } - - // Convert the key to a base64 encoded string - pubkey, err := comidKeyToPEM(key) - if err != nil { - return nil, fmt.Errorf("converting key to PEM: %w", err) - } - taAttr.VerifKey = &pubkey - - attrs, err := json.Marshal(taAttr) - if err != nil { - return nil, fmt.Errorf("marshaling attributes: %w", err) - } - - return attrs, nil -} \ No newline at end of file diff --git a/scheme/parsec-tpm/ta_attrs_extended_test.go b/scheme/parsec-tpm/ta_attrs_extended_test.go deleted file mode 100644 index a880fd33..00000000 --- a/scheme/parsec-tpm/ta_attrs_extended_test.go +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2024 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 -package parsec_tpm - -import ( - "fmt" - "strings" - "testing" - "unicode" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/veraison/corim/comid" -) - -func TestTaAttrs_RealWorldVendors(t *testing.T) { - // Test cases based on real-world TPM vendors and models - testCases := []struct { - name string - vendor string - model string - wantError bool - }{ - { - name: "Infineon", - vendor: "Infineon Technologies AG", - model: "SLB 9670 TPM2.0", - }, - { - name: "STMicroelectronics", - vendor: "STMicroelectronics", - model: "ST33TPHF2ESPI TPM 2.0", - }, - { - name: "Nuvoton", - vendor: "Nuvoton Technology Corp.", - model: "NPCT750 TPM2.0", - }, - { - name: "Nationz", - vendor: "Nationz Technologies Inc.", - model: "TPM 2.0 Z32H330", - }, - { - name: "Intel", - vendor: "Intel Corporation", - model: "Intel® PTT", - }, - { - name: "AMD", - vendor: "Advanced Micro Devices, Inc.", - model: "AMD fTPM", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - id := ID{ - class: "test-class", - instance: "test-instance", - } - - key := &comid.CryptoKey{ - Parameters: map[string]interface{}{ - "vendor": tc.vendor, - "model": tc.model, - }, - Value: &comid.TaggedPKIXBase64Key{PK: "test-key-data"}, - } - - attrs, err := makeTaAttrs(id, key) - if tc.wantError { - require.Error(t, err) - return - } - require.NoError(t, err) - - var taAttr TaAttr - err = json.Unmarshal(attrs, &taAttr) - require.NoError(t, err) - - assert.Equal(t, tc.vendor, *taAttr.Vendor) - assert.Equal(t, tc.model, *taAttr.Model) - }) - } -} - -func TestTaAttrs_InternationalVendors(t *testing.T) { - // Test cases with international vendor names and models - testCases := []struct { - name string - vendor string - model string - wantError bool - }{ - { - name: "Japanese Vendor", - vendor: "富士通株式会社", - model: "FUJITSU TPM v2.0", - }, - { - name: "Chinese Vendor", - vendor: "联想集团有限公司", - model: "ThinkPad TPM 2.0", - }, - { - name: "Korean Vendor", - vendor: "삼성전자", - model: "Samsung TPM2.0", - }, - { - name: "Russian Vendor", - vendor: "Компания", - model: "ТПМ-2000", - }, - { - name: "Mixed Scripts", - vendor: "Fujitsu富士通", - model: "TPM v2.0 型号", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - id := ID{ - class: "test-class", - instance: "test-instance", - } - - key := &comid.CryptoKey{ - Parameters: map[string]interface{}{ - "vendor": tc.vendor, - "model": tc.model, - }, - Value: &comid.TaggedPKIXBase64Key{PK: "test-key-data"}, - } - - attrs, err := makeTaAttrs(id, key) - require.NoError(t, err) - - var taAttr TaAttr - err = json.Unmarshal(attrs, &taAttr) - require.NoError(t, err) - - assert.Equal(t, tc.vendor, *taAttr.Vendor) - assert.Equal(t, tc.model, *taAttr.Model) - }) - } -} - -func TestTaAttrs_EdgeCases(t *testing.T) { - testCases := []struct { - name string - vendor string - model string - wantError bool - }{ - { - name: "Maximum Length", - vendor: strings.Repeat("V", 1024), - model: strings.Repeat("M", 1024), - }, - { - name: "Exceeds Maximum Length", - vendor: strings.Repeat("V", 1025), - model: strings.Repeat("M", 1025), - wantError: true, - }, - { - name: "Single Character", - vendor: "V", - model: "M", - }, - { - name: "Space Only", - vendor: " ", - model: " ", - }, - { - name: "Mixed Spaces", - vendor: " Vendor Name ", - model: " Model Type ", - }, - { - name: "Special Characters", - vendor: "Vendor-Name & Co., Ltd.", - model: "TPM-2000 (Gen2) v2.0", - }, - { - name: "With Trademark Symbols", - vendor: "Company™", - model: "TPM® 2.0", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - id := ID{ - class: "test-class", - instance: "test-instance", - } - - key := &comid.CryptoKey{ - Parameters: map[string]interface{}{ - "vendor": tc.vendor, - "model": tc.model, - }, - Value: &comid.TaggedPKIXBase64Key{PK: "test-key-data"}, - } - - attrs, err := makeTaAttrs(id, key) - if tc.wantError { - assert.Error(t, err) - return - } - require.NoError(t, err) - - var taAttr TaAttr - err = json.Unmarshal(attrs, &taAttr) - require.NoError(t, err) - - // For non-error cases, verify the strings are properly trimmed and sanitized - if !tc.wantError { - assert.Equal(t, strings.TrimSpace(tc.vendor), *taAttr.Vendor) - assert.Equal(t, strings.TrimSpace(tc.model), *taAttr.Model) - } - }) - } -} - -func TestTaAttrs_SecurityCases(t *testing.T) { - testCases := []struct { - name string - vendor string - model string - wantError bool - checkSanitized bool - }{ - { - name: "HTML Injection", - vendor: "", - model: "", - checkSanitized: true, - }, - { - name: "SQL Injection", - vendor: "1' OR '1'='1", - model: "1'; DROP TABLE users--", - checkSanitized: true, - }, - { - name: "Command Injection", - vendor: "$(rm -rf /)", - model: "`rm -rf /`", - checkSanitized: true, - }, - { - name: "Path Traversal", - vendor: "../../../etc/passwd", - model: "..\\..\\..\\windows\\system32", - checkSanitized: true, - }, - { - name: "Null Bytes", - vendor: "Vendor\x00Name", - model: "Model\x00Type", - wantError: true, - }, - { - name: "Control Characters", - vendor: "Bad\x01Vendor\x02Name", - model: "Bad\x01Model\x02Type", - wantError: true, - }, - { - name: "Mixed Valid and Invalid", - vendor: "Valid Name ", - model: "Valid Model `rm -rf /`", - checkSanitized: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - id := ID{ - class: "test-class", - instance: "test-instance", - } - - key := &comid.CryptoKey{ - Parameters: map[string]interface{}{ - "vendor": tc.vendor, - "model": tc.model, - }, - Value: &comid.TaggedPKIXBase64Key{PK: "test-key-data"}, - } - - attrs, err := makeTaAttrs(id, key) - if tc.wantError { - assert.Error(t, err) - return - } - require.NoError(t, err) - - var taAttr TaAttr - err = json.Unmarshal(attrs, &taAttr) - require.NoError(t, err) - - if tc.checkSanitized { - // Check that dangerous characters are escaped or removed - assert.NotEqual(t, tc.vendor, *taAttr.Vendor, "Dangerous vendor string should be sanitized") - assert.NotEqual(t, tc.model, *taAttr.Model, "Dangerous model string should be sanitized") - - // Verify the sanitized strings don't contain dangerous patterns - dangerousPatterns := []string{"", - expected: "Vendorscriptalert\\(\\'xss\\'\\)/script", - }, - { - name: "sql injection attempt", - input: "Vendor'; DROP TABLE users;--", - expected: "Vendor\\'; DROP TABLE users;--", - }, - { - name: "null byte injection", - input: "Vendor\x00Name", - expected: "VendorName", - }, - { - name: "control characters", - input: "Vendor\x01Name\x02", - expected: "VendorName", - }, - { - name: "allowed whitespace", - input: "Vendor\nName\tCorp", - expected: "Vendor\nName\tCorp", - }, - { - name: "unicode characters", - input: "製造元株式会社", - expected: "製造元株式会社", - }, - { - name: "mixed content", - input: "Vendor & Co<> \x00株式会社\n", - expected: "Vendor \\& Co 株式会社\n", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result := sanitizeString(tc.input) - assert.Equal(t, tc.expected, result) - }) - } -} \ No newline at end of file