Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions ASN_HEADER_FIX_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# ASN Header Bytes Support in cocli

## Issue Summary
GitHub Issue: [#23 - Enhance cocli corim command to skip over ASN header bytes](https://github.com/veraison/cocli/issues/23)

Several vendors distribute CoRIM manifest files with ASN header bytes (`d9 01 f4 d9 01 f6`) at the beginning. Previously, cocli would fail to process these files with errors like:

```
Error: error decoding signed CoRIM from file.cbor: failed CBOR decoding for COSE-Sign1 signed CoRIM: cbor: invalid COSE_Sign1_Tagged object
```

## Solution Implemented

### Changes Made

1. **Added ASN Header Stripping Function** (`cmd/common.go`):
- Added `stripASNHeaderBytes()` function that detects and removes the ASN header pattern `d9 01 f4 d9 01 f6`
- Function is safe and only strips headers when the exact pattern is found at the beginning of the data
- Returns original data unchanged if no ASN header is detected

2. **Updated CoRIM Commands**:
- **`corim display`** (`cmd/corimDisplay.go`): Added ASN header stripping before CBOR decoding
- **`corim verify`** (`cmd/corimVerify.go`): Added ASN header stripping before COSE signature verification
- **`corim extract`** (`cmd/corimExtract.go`): Added ASN header stripping before tag extraction

3. **Preserved corim submit**: The `corim submit` command was intentionally left unchanged as it should preserve the original file format when submitting to servers.

### Implementation Details

The ASN header bytes `d9 01 f4 d9 01 f6` represent:
- `tagged-corim-type-choice #6.500` (`d9 01 f4`)
- `tagged-signed-corim #6.502` (`d9 01 f6`)

These are remnants from an older draft of the CoRIM specification and are automatically detected and stripped.

### Code Example

```go
// stripASNHeaderBytes removes ASN header bytes from CoRIM files if present.
func stripASNHeaderBytes(data []byte) []byte {
// ASN header pattern: d9 01 f4 d9 01 f6
asnHeaderPattern := []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6}

// Check if the data starts with the ASN header pattern
if len(data) >= len(asnHeaderPattern) && bytes.HasPrefix(data, asnHeaderPattern) {
// Strip the ASN header bytes
return data[len(asnHeaderPattern):]
}

// Return original data if no ASN header is found
return data
}
```

## Testing

### Unit Tests
- Comprehensive unit tests for `stripASNHeaderBytes()` function covering:
- Files with ASN headers
- Files without ASN headers
- Edge cases (empty data, partial headers, etc.)
- Data integrity (original slice remains unmodified)

### Integration Tests
- End-to-end tests for all affected CoRIM commands
- Tests with real CoRIM files that have ASN headers prepended
- Verification that existing functionality remains unchanged

### Test Results
All existing tests pass, confirming backward compatibility:
```bash
$ make test
PASS
ok github.com/veraison/cocli/cmd 1.159s
```

## Usage Examples

### Before Fix
```bash
$ cocli corim display -f PS10xx-G75YG100-E3S-16TB.cbor
Error: error decoding CoRIM (signed or unsigned) from PS10xx-G75YG100-E3S-16TB.cbor: expected map (CBOR Major Type 5), found Major Type 6
```

### After Fix
```bash
$ cocli corim display -f PS10xx-G75YG100-E3S-16TB.cbor
Meta:
{
"signer": {
"name": "...",
"uri": "..."
},
...
}
CoRIM:
{
"corim-id": "...",
...
}
```

## Verification Commands

All CoRIM processing commands now work seamlessly with files that have ASN headers:

```bash
# Display CoRIM content
cocli corim display -f corim-with-asn-headers.cbor

# Verify CoRIM signature
cocli corim verify -f corim-with-asn-headers.cbor -k signing-key.jwk

# Extract embedded tags
cocli corim extract -f corim-with-asn-headers.cbor -o output-dir/
```

## Backwards Compatibility

- ✅ Files without ASN headers continue to work exactly as before
- ✅ All existing functionality is preserved
- ✅ No breaking changes to command-line interface
- ✅ No performance impact for files without ASN headers

## Files Modified

1. `cmd/common.go` - Added `stripASNHeaderBytes()` function
2. `cmd/corimDisplay.go` - Added ASN header stripping to display command
3. `cmd/corimVerify.go` - Added ASN header stripping to verify command
4. `cmd/corimExtract.go` - Added ASN header stripping to extract command
5. `cmd/common_test.go` - Added comprehensive unit tests
6. `cmd/corim_asn_integration_test.go` - Added integration tests

## Resolution

This fix resolves GitHub issue #23 by automatically detecting and stripping ASN header bytes from CoRIM files, allowing cocli to process vendor-distributed CoRIM files without requiring manual preprocessing. Users no longer need to manually strip the first 6 bytes before using cocli commands.
19 changes: 19 additions & 0 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package cmd

import (
"bytes"
"encoding/json"
"fmt"
"path/filepath"
Expand Down Expand Up @@ -90,3 +91,21 @@ func makeFileName(dirName, baseName, ext string) string {
)+ext,
)
}

// stripASNHeaderBytes removes ASN header bytes from CoRIM files if present.
// Several vendors distribute CoRIM manifest files with ASN header bytes:
// d9 01 f4 d9 01 f6 (tagged-corim-type-choice #6.500 of tagged-signed-corim #6.502)
// This function automatically detects and strips these bytes if present.
func stripASNHeaderBytes(data []byte) []byte {
// ASN header pattern: d9 01 f4 d9 01 f6
asnHeaderPattern := []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6}

// Check if the data starts with the ASN header pattern
if len(data) >= len(asnHeaderPattern) && bytes.HasPrefix(data, asnHeaderPattern) {
// Strip the ASN header bytes
return data[len(asnHeaderPattern):]
}

// Return original data if no ASN header is found
return data
}
93 changes: 93 additions & 0 deletions cmd/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2021-2024 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
)

func TestStripASNHeaderBytes(t *testing.T) {
tests := []struct {
name string
input []byte
expected []byte
}{
{
name: "data with ASN header",
input: []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6, 0x01, 0x02, 0x03, 0x04},
expected: []byte{0x01, 0x02, 0x03, 0x04},
},
{
name: "data without ASN header",
input: []byte{0x01, 0x02, 0x03, 0x04},
expected: []byte{0x01, 0x02, 0x03, 0x04},
},
{
name: "empty data",
input: []byte{},
expected: []byte{},
},
{
name: "data shorter than ASN header",
input: []byte{0xd9, 0x01, 0xf4},
expected: []byte{0xd9, 0x01, 0xf4},
},
{
name: "data starting with partial ASN header",
input: []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0x99, 0x01, 0x02},
expected: []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0x99, 0x01, 0x02},
},
{
name: "data with ASN header only",
input: []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6},
expected: []byte{},
},
{
name: "data with ASN header in middle (should not strip)",
input: []byte{0x01, 0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6, 0x02},
expected: []byte{0x01, 0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6, 0x02},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := stripASNHeaderBytes(tt.input)
assert.Equal(t, tt.expected, result, "stripASNHeaderBytes() result mismatch")
})
}
}

func TestStripASNHeaderBytes_Immutability(t *testing.T) {
// Test that the original slice is not modified
original := []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6, 0x01, 0x02, 0x03, 0x04}
originalCopy := make([]byte, len(original))
copy(originalCopy, original)

result := stripASNHeaderBytes(original)

// Original should remain unchanged
assert.Equal(t, originalCopy, original, "Original slice was modified")

// Result should be the stripped version
expected := []byte{0x01, 0x02, 0x03, 0x04}
assert.Equal(t, expected, result, "Result should be stripped")
}

func TestStripASNHeaderBytes_RealWorldScenario(t *testing.T) {
// Test with a scenario similar to the real-world example from the issue
// Simulating the beginning of a CoRIM file with ASN header
asnHeader := []byte{0xd9, 0x01, 0xf4, 0xd9, 0x01, 0xf6}
corimData := []byte{0xd2, 0x84, 0x58, 0x29, 0xa3, 0x01, 0x38, 0x22}

inputWithHeader := append(asnHeader, corimData...)

result := stripASNHeaderBytes(inputWithHeader)

assert.Equal(t, corimData, result, "Should strip ASN header and return CoRIM data")
assert.True(t, bytes.HasPrefix(inputWithHeader, asnHeader), "Input should start with ASN header")
assert.False(t, bytes.HasPrefix(result, asnHeader), "Result should not start with ASN header")
}
3 changes: 3 additions & 0 deletions cmd/corimDisplay.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ func display(corimFile string, showTags bool) error {
return fmt.Errorf("error loading CoRIM from %s: %w", corimFile, err)
}

// strip ASN header bytes if present (d9 01 f4 d9 01 f6)
corimCBOR = stripASNHeaderBytes(corimCBOR)

// try to decode as a signed CoRIM
var s corim.SignedCorim
if err = s.FromCOSE(corimCBOR); err == nil {
Expand Down
3 changes: 3 additions & 0 deletions cmd/corimExtract.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ func extract(signedCorimFile string, outputDir *string) error {
return fmt.Errorf("error loading signed CoRIM from %s: %w", signedCorimFile, err)
}

// strip ASN header bytes if present (d9 01 f4 d9 01 f6)
signedCorimCBOR = stripASNHeaderBytes(signedCorimCBOR)

if err = s.FromCOSE(signedCorimCBOR); err != nil {
return fmt.Errorf("error decoding signed CoRIM from %s: %w", signedCorimFile, err)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/corimVerify.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ func verify(signedCorimFile, keyFile string) error {
return fmt.Errorf("error loading signed CoRIM from %s: %w", signedCorimFile, err)
}

// strip ASN header bytes if present (d9 01 f4 d9 01 f6)
signedCorimCBOR = stripASNHeaderBytes(signedCorimCBOR)

if err = s.FromCOSE(signedCorimCBOR); err != nil {
return fmt.Errorf("error decoding signed CoRIM from %s: %w", signedCorimFile, err)
}
Expand Down
Loading