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 }