storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/kms/context.go (about)

     1  // MinIO Cloud Storage, (C) 2021 MinIO, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kms
    16  
    17  import (
    18  	"bytes"
    19  	"sort"
    20  	"unicode/utf8"
    21  )
    22  
    23  // Context is a set of key-value pairs that
    24  // are associated with a generate data encryption
    25  // key (DEK).
    26  //
    27  // A KMS implementation may bind the context to the
    28  // generated DEK such that the same context must be
    29  // provided when decrypting an encrypted DEK.
    30  type Context map[string]string
    31  
    32  // MarshalText returns a canonical text representation of
    33  // the Context.
    34  
    35  // MarshalText sorts the context keys and writes the sorted
    36  // key-value pairs as canonical JSON object. The sort order
    37  // is based on the un-escaped keys. It never returns an error.
    38  func (c Context) MarshalText() ([]byte, error) {
    39  	if len(c) == 0 {
    40  		return []byte{'{', '}'}, nil
    41  	}
    42  
    43  	// Pre-allocate a buffer - 128 bytes is an arbitrary
    44  	// heuristic value that seems like a good starting size.
    45  	var b = bytes.NewBuffer(make([]byte, 0, 128))
    46  	if len(c) == 1 {
    47  		for k, v := range c {
    48  			b.WriteString(`{"`)
    49  			escapeStringJSON(b, k)
    50  			b.WriteString(`":"`)
    51  			escapeStringJSON(b, v)
    52  			b.WriteString(`"}`)
    53  		}
    54  		return b.Bytes(), nil
    55  	}
    56  
    57  	sortedKeys := make([]string, 0, len(c))
    58  	for k := range c {
    59  		sortedKeys = append(sortedKeys, k)
    60  	}
    61  	sort.Strings(sortedKeys)
    62  
    63  	b.WriteByte('{')
    64  	for i, k := range sortedKeys {
    65  		b.WriteByte('"')
    66  		escapeStringJSON(b, k)
    67  		b.WriteString(`":"`)
    68  		escapeStringJSON(b, c[k])
    69  		b.WriteByte('"')
    70  		if i < len(sortedKeys)-1 {
    71  			b.WriteByte(',')
    72  		}
    73  	}
    74  	b.WriteByte('}')
    75  	return b.Bytes(), nil
    76  }
    77  
    78  // Adapted from Go stdlib.
    79  
    80  var hexTable = "0123456789abcdef"
    81  
    82  // escapeStringJSON will escape a string for JSON and write it to dst.
    83  func escapeStringJSON(dst *bytes.Buffer, s string) {
    84  	start := 0
    85  	for i := 0; i < len(s); {
    86  		if b := s[i]; b < utf8.RuneSelf {
    87  			if htmlSafeSet[b] {
    88  				i++
    89  				continue
    90  			}
    91  			if start < i {
    92  				dst.WriteString(s[start:i])
    93  			}
    94  			dst.WriteByte('\\')
    95  			switch b {
    96  			case '\\', '"':
    97  				dst.WriteByte(b)
    98  			case '\n':
    99  				dst.WriteByte('n')
   100  			case '\r':
   101  				dst.WriteByte('r')
   102  			case '\t':
   103  				dst.WriteByte('t')
   104  			default:
   105  				// This encodes bytes < 0x20 except for \t, \n and \r.
   106  				// If escapeHTML is set, it also escapes <, >, and &
   107  				// because they can lead to security holes when
   108  				// user-controlled strings are rendered into JSON
   109  				// and served to some browsers.
   110  				dst.WriteString(`u00`)
   111  				dst.WriteByte(hexTable[b>>4])
   112  				dst.WriteByte(hexTable[b&0xF])
   113  			}
   114  			i++
   115  			start = i
   116  			continue
   117  		}
   118  		c, size := utf8.DecodeRuneInString(s[i:])
   119  		if c == utf8.RuneError && size == 1 {
   120  			if start < i {
   121  				dst.WriteString(s[start:i])
   122  			}
   123  			dst.WriteString(`\ufffd`)
   124  			i += size
   125  			start = i
   126  			continue
   127  		}
   128  		// U+2028 is LINE SEPARATOR.
   129  		// U+2029 is PARAGRAPH SEPARATOR.
   130  		// They are both technically valid characters in JSON strings,
   131  		// but don't work in JSONP, which has to be evaluated as JavaScript,
   132  		// and can lead to security holes there. It is valid JSON to
   133  		// escape them, so we do so unconditionally.
   134  		// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
   135  		if c == '\u2028' || c == '\u2029' {
   136  			if start < i {
   137  				dst.WriteString(s[start:i])
   138  			}
   139  			dst.WriteString(`\u202`)
   140  			dst.WriteByte(hexTable[c&0xF])
   141  			i += size
   142  			start = i
   143  			continue
   144  		}
   145  		i += size
   146  	}
   147  	if start < len(s) {
   148  		dst.WriteString(s[start:])
   149  	}
   150  }
   151  
   152  // htmlSafeSet holds the value true if the ASCII character with the given
   153  // array position can be safely represented inside a JSON string, embedded
   154  // inside of HTML <script> tags, without any additional escaping.
   155  //
   156  // All values are true except for the ASCII control characters (0-31), the
   157  // double quote ("), the backslash character ("\"), HTML opening and closing
   158  // tags ("<" and ">"), and the ampersand ("&").
   159  var htmlSafeSet = [utf8.RuneSelf]bool{
   160  	' ':      true,
   161  	'!':      true,
   162  	'"':      false,
   163  	'#':      true,
   164  	'$':      true,
   165  	'%':      true,
   166  	'&':      false,
   167  	'\'':     true,
   168  	'(':      true,
   169  	')':      true,
   170  	'*':      true,
   171  	'+':      true,
   172  	',':      true,
   173  	'-':      true,
   174  	'.':      true,
   175  	'/':      true,
   176  	'0':      true,
   177  	'1':      true,
   178  	'2':      true,
   179  	'3':      true,
   180  	'4':      true,
   181  	'5':      true,
   182  	'6':      true,
   183  	'7':      true,
   184  	'8':      true,
   185  	'9':      true,
   186  	':':      true,
   187  	';':      true,
   188  	'<':      false,
   189  	'=':      true,
   190  	'>':      false,
   191  	'?':      true,
   192  	'@':      true,
   193  	'A':      true,
   194  	'B':      true,
   195  	'C':      true,
   196  	'D':      true,
   197  	'E':      true,
   198  	'F':      true,
   199  	'G':      true,
   200  	'H':      true,
   201  	'I':      true,
   202  	'J':      true,
   203  	'K':      true,
   204  	'L':      true,
   205  	'M':      true,
   206  	'N':      true,
   207  	'O':      true,
   208  	'P':      true,
   209  	'Q':      true,
   210  	'R':      true,
   211  	'S':      true,
   212  	'T':      true,
   213  	'U':      true,
   214  	'V':      true,
   215  	'W':      true,
   216  	'X':      true,
   217  	'Y':      true,
   218  	'Z':      true,
   219  	'[':      true,
   220  	'\\':     false,
   221  	']':      true,
   222  	'^':      true,
   223  	'_':      true,
   224  	'`':      true,
   225  	'a':      true,
   226  	'b':      true,
   227  	'c':      true,
   228  	'd':      true,
   229  	'e':      true,
   230  	'f':      true,
   231  	'g':      true,
   232  	'h':      true,
   233  	'i':      true,
   234  	'j':      true,
   235  	'k':      true,
   236  	'l':      true,
   237  	'm':      true,
   238  	'n':      true,
   239  	'o':      true,
   240  	'p':      true,
   241  	'q':      true,
   242  	'r':      true,
   243  	's':      true,
   244  	't':      true,
   245  	'u':      true,
   246  	'v':      true,
   247  	'w':      true,
   248  	'x':      true,
   249  	'y':      true,
   250  	'z':      true,
   251  	'{':      true,
   252  	'|':      true,
   253  	'}':      true,
   254  	'~':      true,
   255  	'\u007f': true,
   256  }