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  }