187 lines
4.9 KiB
Go
187 lines
4.9 KiB
Go
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/zlib"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/fxamacker/cbor/v2"
|
|
"github.com/veraison/go-cose"
|
|
)
|
|
|
|
const base45Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
|
|
|
|
var base45reverse map[byte]int
|
|
|
|
func init() {
|
|
base45reverse = make(map[byte]int)
|
|
for i, c := range []byte(base45Alphabet) {
|
|
base45reverse[c] = i
|
|
}
|
|
}
|
|
|
|
func convertInterfaceMap(m map[interface{}]interface{}) map[string]interface{} {
|
|
result := make(map[string]interface{})
|
|
for k, v := range m {
|
|
var keyStr string
|
|
switch kType := k.(type) {
|
|
case string:
|
|
keyStr = kType
|
|
case float64:
|
|
keyStr = strconv.FormatFloat(kType, 'f', -1, 64)
|
|
case int:
|
|
keyStr = strconv.Itoa(kType)
|
|
case uint64:
|
|
keyStr = strconv.FormatUint(kType, 10)
|
|
case int64:
|
|
keyStr = strconv.FormatInt(kType, 10)
|
|
default:
|
|
fmt.Printf("Encountered unexpected key type: %T with value: %v\n", kType, k)
|
|
continue
|
|
}
|
|
|
|
// Check if the value is a nested map
|
|
if nestedMap, isMap := v.(map[interface{}]interface{}); isMap {
|
|
result[keyStr] = convertInterfaceMap(nestedMap)
|
|
} else if nestedSlice, isSlice := v.([]interface{}); isSlice {
|
|
result[keyStr] = convertInterfaceSlice(nestedSlice)
|
|
} else {
|
|
result[keyStr] = v
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func convertInterfaceSlice(s []interface{}) []interface{} {
|
|
result := make([]interface{}, len(s))
|
|
for i, v := range s {
|
|
if nestedMap, isMap := v.(map[interface{}]interface{}); isMap {
|
|
result[i] = convertInterfaceMap(nestedMap)
|
|
} else if nestedSlice, isSlice := v.([]interface{}); isSlice {
|
|
result[i] = convertInterfaceSlice(nestedSlice)
|
|
} else {
|
|
result[i] = v
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func decodeBase45(s string) ([]byte, error) {
|
|
var out []byte
|
|
for i := 0; i < len(s); {
|
|
if len(s)-i < 2 {
|
|
return nil, fmt.Errorf("invalid base45 string length")
|
|
}
|
|
if len(s)-i < 3 {
|
|
// 2-character case
|
|
c1, ok1 := base45reverse[s[i]]
|
|
c2, ok2 := base45reverse[s[i+1]]
|
|
if !ok1 || !ok2 {
|
|
return nil, fmt.Errorf("invalid character in base45 string")
|
|
}
|
|
val := c1 + c2*45
|
|
if val > 255 {
|
|
return nil, fmt.Errorf("invalid 2-character encoding")
|
|
}
|
|
out = append(out, byte(val))
|
|
i += 2
|
|
} else {
|
|
// 3-character case
|
|
c1, ok1 := base45reverse[s[i]]
|
|
c2, ok2 := base45reverse[s[i+1]]
|
|
c3, ok3 := base45reverse[s[i+2]]
|
|
if !ok1 || !ok2 || !ok3 {
|
|
return nil, fmt.Errorf("invalid character in base45 string")
|
|
}
|
|
val := c1 + c2*45 + c3*45*45
|
|
if val > 65535 {
|
|
return nil, fmt.Errorf("invalid 3-character encoding")
|
|
}
|
|
out = append(out, byte(val>>8), byte(val&0xFF))
|
|
i += 3
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// DecodeHCert decodes a base45 string encoded using Zlib/COSE/CBOR pipeline.
|
|
// The string is an HCERT so it starts with "HC1:".
|
|
func DecodeHCert(hcert string) (map[string]interface{}, error) {
|
|
if !strings.HasPrefix(hcert, "HC1:") {
|
|
return nil, fmt.Errorf("invalid HCERT prefix")
|
|
}
|
|
|
|
// 1. Remove "HC1:" prefix
|
|
base45Encoded := strings.TrimPrefix(hcert, "HC1:")
|
|
|
|
// 2. Base45 decode
|
|
compressedCose, err := decodeBase45(base45Encoded)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("base45 decoding failed: %w", err)
|
|
}
|
|
|
|
// 3. Zlib decompress
|
|
var cosePayload []byte
|
|
r, err := zlib.NewReader(bytes.NewReader(compressedCose))
|
|
if err != nil {
|
|
// Not zlib compressed, use as is
|
|
cosePayload = compressedCose
|
|
} else {
|
|
decompressed, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("zlib decompression failed: %w", err)
|
|
}
|
|
cosePayload = decompressed
|
|
defer r.Close()
|
|
}
|
|
|
|
// 4. COSE decode
|
|
var payload []byte
|
|
var msg cose.Sign1Message
|
|
if err := msg.UnmarshalCBOR(cosePayload); err == nil {
|
|
payload = msg.Payload
|
|
} else {
|
|
var signMessage cose.SignMessage
|
|
if err2 := signMessage.UnmarshalCBOR(cosePayload); err2 == nil {
|
|
payload = signMessage.Payload
|
|
} else {
|
|
// Both failed, try manual parsing
|
|
var rawCoseMessage interface{}
|
|
if err3 := cbor.Unmarshal(cosePayload, &rawCoseMessage); err3 != nil {
|
|
return nil, fmt.Errorf("cose unmarshalling failed for both Sign1Message and SignMessage and raw: %v, %v, %v", err, err2, err3)
|
|
}
|
|
|
|
coseArray, ok := rawCoseMessage.([]interface{})
|
|
if !ok {
|
|
if tagged, ok := rawCoseMessage.(cbor.Tag); ok {
|
|
if content, ok := tagged.Content.([]interface{}); ok {
|
|
coseArray = content
|
|
}
|
|
}
|
|
}
|
|
|
|
if coseArray == nil || len(coseArray) < 3 {
|
|
return nil, fmt.Errorf("cose unmarshalling failed for both Sign1Message and SignMessage: %v, %v", err, err2)
|
|
}
|
|
|
|
p, ok := coseArray[2].([]byte)
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to extract payload from cose message: payload is not a byte string")
|
|
}
|
|
payload = p
|
|
}
|
|
}
|
|
|
|
// 5. CBOR decode the payload from COSE message
|
|
var cborData map[interface{}]interface{}
|
|
if err := cbor.Unmarshal(payload, &cborData); err != nil {
|
|
return nil, fmt.Errorf("cbor unmarshalling of payload failed: %w", err)
|
|
}
|
|
|
|
cborDataConverted := convertInterfaceMap(cborData)
|
|
return cborDataConverted, nil
|
|
}
|