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 }