github.com/boxboat/in-toto-golang@v0.0.3-0.20210303203820-2fa16ecbe6f6/in_toto/canonicaljson.go (about) 1 package in_toto 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "reflect" 9 "regexp" 10 "sort" 11 ) 12 13 /* 14 encodeCanonicalString is a helper function to canonicalize the passed string 15 according to the OLPC canonical JSON specification for strings (see 16 http://wiki.laptop.org/go/Canonical_JSON). String canonicalization consists of 17 escaping backslashes ("\") and double quotes (") and wrapping the resulting 18 string in double quotes ("). 19 */ 20 func encodeCanonicalString(s string) string { 21 re := regexp.MustCompile(`([\"\\])`) 22 return fmt.Sprintf("\"%s\"", re.ReplaceAllString(s, "\\$1")) 23 } 24 25 /* 26 encodeCanonical is a helper function to recursively canonicalize the passed 27 object according to the OLPC canonical JSON specification (see 28 http://wiki.laptop.org/go/Canonical_JSON) and write it to the passed 29 *bytes.Buffer. If canonicalization fails it returns an error. 30 */ 31 func encodeCanonical(obj interface{}, result *bytes.Buffer) (err error) { 32 // Since this function is called recursively, we use panic if an error occurs 33 // and recover in a deferred function, which is always called before 34 // returning. There we set the error that is returned eventually. 35 defer func() { 36 if r := recover(); r != nil { 37 err = errors.New(r.(string)) 38 } 39 }() 40 41 switch objAsserted := obj.(type) { 42 case string: 43 result.WriteString(encodeCanonicalString(objAsserted)) 44 45 case bool: 46 if objAsserted { 47 result.WriteString("true") 48 } else { 49 result.WriteString("false") 50 } 51 52 // The wrapping `EncodeCanonical` function decodes the passed json data with 53 // `decoder.UseNumber` so that any numeric value is stored as `json.Number` 54 // (instead of the default `float64`). This allows us to assert that it is a 55 // non-floating point number, which are the only numbers allowed by the used 56 // canonicalization specification. 57 case json.Number: 58 if _, err := objAsserted.Int64(); err != nil { 59 panic(fmt.Sprintf("Can't canonicalize floating point number '%s'", 60 objAsserted)) 61 } 62 result.WriteString(objAsserted.String()) 63 64 case nil: 65 result.WriteString("null") 66 67 // Canonicalize slice 68 case []interface{}: 69 result.WriteString("[") 70 for i, val := range objAsserted { 71 if err := encodeCanonical(val, result); err != nil { 72 return err 73 } 74 if i < (len(objAsserted) - 1) { 75 result.WriteString(",") 76 } 77 } 78 result.WriteString("]") 79 80 case map[string]interface{}: 81 result.WriteString("{") 82 83 // Make a list of keys 84 var mapKeys []string 85 for key := range objAsserted { 86 mapKeys = append(mapKeys, key) 87 } 88 // Sort keys 89 sort.Strings(mapKeys) 90 91 // Canonicalize map 92 for i, key := range mapKeys { 93 // Note: `key` must be a `string` (see `case map[string]interface{}`) and 94 // canonicalization of strings cannot err out (see `case string`), thus 95 // no error handling is needed here. 96 encodeCanonical(key, result) 97 98 result.WriteString(":") 99 if err := encodeCanonical(objAsserted[key], result); err != nil { 100 return err 101 } 102 if i < (len(mapKeys) - 1) { 103 result.WriteString(",") 104 } 105 i++ 106 } 107 result.WriteString("}") 108 109 default: 110 // We recover in a deferred function defined above 111 panic(fmt.Sprintf("Can't canonicalize '%s' of type '%s'", 112 objAsserted, reflect.TypeOf(objAsserted))) 113 } 114 return nil 115 } 116 117 /* 118 EncodeCanonical JSON canonicalizes the passed object and returns it as a byte 119 slice. It uses the OLPC canonical JSON specification (see 120 http://wiki.laptop.org/go/Canonical_JSON). If canonicalization fails the byte 121 slice is nil and the second return value contains the error. 122 */ 123 func EncodeCanonical(obj interface{}) ([]byte, error) { 124 // FIXME: Terrible hack to turn the passed struct into a map, converting 125 // the struct's variable names to the json key names defined in the struct 126 data, err := json.Marshal(obj) 127 if err != nil { 128 return nil, err 129 } 130 var jsonMap interface{} 131 132 dec := json.NewDecoder(bytes.NewReader(data)) 133 dec.UseNumber() 134 if err := dec.Decode(&jsonMap); err != nil { 135 return nil, err 136 } 137 138 // Create a buffer and write the canonicalized JSON bytes to it 139 var result bytes.Buffer 140 if err := encodeCanonical(jsonMap, &result); err != nil { 141 return nil, err 142 } 143 144 return result.Bytes(), nil 145 }