github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/kms/context.go (about)

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