jwe.go (8686B)
1 /*- 2 * Copyright 2014 Square Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package jose 18 19 import ( 20 "encoding/base64" 21 "fmt" 22 "strings" 23 24 "github.com/go-jose/go-jose/v3/json" 25 ) 26 27 // rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing. 28 type rawJSONWebEncryption struct { 29 Protected *byteBuffer `json:"protected,omitempty"` 30 Unprotected *rawHeader `json:"unprotected,omitempty"` 31 Header *rawHeader `json:"header,omitempty"` 32 Recipients []rawRecipientInfo `json:"recipients,omitempty"` 33 Aad *byteBuffer `json:"aad,omitempty"` 34 EncryptedKey *byteBuffer `json:"encrypted_key,omitempty"` 35 Iv *byteBuffer `json:"iv,omitempty"` 36 Ciphertext *byteBuffer `json:"ciphertext,omitempty"` 37 Tag *byteBuffer `json:"tag,omitempty"` 38 } 39 40 // rawRecipientInfo represents a raw JWE Per-Recipient header JSON object. Used for parsing/serializing. 41 type rawRecipientInfo struct { 42 Header *rawHeader `json:"header,omitempty"` 43 EncryptedKey string `json:"encrypted_key,omitempty"` 44 } 45 46 // JSONWebEncryption represents an encrypted JWE object after parsing. 47 type JSONWebEncryption struct { 48 Header Header 49 protected, unprotected *rawHeader 50 recipients []recipientInfo 51 aad, iv, ciphertext, tag []byte 52 original *rawJSONWebEncryption 53 } 54 55 // recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing. 56 type recipientInfo struct { 57 header *rawHeader 58 encryptedKey []byte 59 } 60 61 // GetAuthData retrieves the (optional) authenticated data attached to the object. 62 func (obj JSONWebEncryption) GetAuthData() []byte { 63 if obj.aad != nil { 64 out := make([]byte, len(obj.aad)) 65 copy(out, obj.aad) 66 return out 67 } 68 69 return nil 70 } 71 72 // Get the merged header values 73 func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader { 74 out := rawHeader{} 75 out.merge(obj.protected) 76 out.merge(obj.unprotected) 77 78 if recipient != nil { 79 out.merge(recipient.header) 80 } 81 82 return out 83 } 84 85 // Get the additional authenticated data from a JWE object. 86 func (obj JSONWebEncryption) computeAuthData() []byte { 87 var protected string 88 89 switch { 90 case obj.original != nil && obj.original.Protected != nil: 91 protected = obj.original.Protected.base64() 92 case obj.protected != nil: 93 protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected))) 94 default: 95 protected = "" 96 } 97 98 output := []byte(protected) 99 if obj.aad != nil { 100 output = append(output, '.') 101 output = append(output, []byte(base64.RawURLEncoding.EncodeToString(obj.aad))...) 102 } 103 104 return output 105 } 106 107 // ParseEncrypted parses an encrypted message in compact or JWE JSON Serialization format. 108 func ParseEncrypted(input string) (*JSONWebEncryption, error) { 109 input = stripWhitespace(input) 110 if strings.HasPrefix(input, "{") { 111 return parseEncryptedFull(input) 112 } 113 114 return parseEncryptedCompact(input) 115 } 116 117 // parseEncryptedFull parses a message in compact format. 118 func parseEncryptedFull(input string) (*JSONWebEncryption, error) { 119 var parsed rawJSONWebEncryption 120 err := json.Unmarshal([]byte(input), &parsed) 121 if err != nil { 122 return nil, err 123 } 124 125 return parsed.sanitized() 126 } 127 128 // sanitized produces a cleaned-up JWE object from the raw JSON. 129 func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) { 130 obj := &JSONWebEncryption{ 131 original: parsed, 132 unprotected: parsed.Unprotected, 133 } 134 135 // Check that there is not a nonce in the unprotected headers 136 if parsed.Unprotected != nil { 137 if nonce := parsed.Unprotected.getNonce(); nonce != "" { 138 return nil, ErrUnprotectedNonce 139 } 140 } 141 if parsed.Header != nil { 142 if nonce := parsed.Header.getNonce(); nonce != "" { 143 return nil, ErrUnprotectedNonce 144 } 145 } 146 147 if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 { 148 err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected) 149 if err != nil { 150 return nil, fmt.Errorf("go-jose/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64()) 151 } 152 } 153 154 // Note: this must be called _after_ we parse the protected header, 155 // otherwise fields from the protected header will not get picked up. 156 var err error 157 mergedHeaders := obj.mergedHeaders(nil) 158 obj.Header, err = mergedHeaders.sanitized() 159 if err != nil { 160 return nil, fmt.Errorf("go-jose/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders) 161 } 162 163 if len(parsed.Recipients) == 0 { 164 obj.recipients = []recipientInfo{ 165 { 166 header: parsed.Header, 167 encryptedKey: parsed.EncryptedKey.bytes(), 168 }, 169 } 170 } else { 171 obj.recipients = make([]recipientInfo, len(parsed.Recipients)) 172 for r := range parsed.Recipients { 173 encryptedKey, err := base64URLDecode(parsed.Recipients[r].EncryptedKey) 174 if err != nil { 175 return nil, err 176 } 177 178 // Check that there is not a nonce in the unprotected header 179 if parsed.Recipients[r].Header != nil && parsed.Recipients[r].Header.getNonce() != "" { 180 return nil, ErrUnprotectedNonce 181 } 182 183 obj.recipients[r].header = parsed.Recipients[r].Header 184 obj.recipients[r].encryptedKey = encryptedKey 185 } 186 } 187 188 for _, recipient := range obj.recipients { 189 headers := obj.mergedHeaders(&recipient) 190 if headers.getAlgorithm() == "" || headers.getEncryption() == "" { 191 return nil, fmt.Errorf("go-jose/go-jose: message is missing alg/enc headers") 192 } 193 } 194 195 obj.iv = parsed.Iv.bytes() 196 obj.ciphertext = parsed.Ciphertext.bytes() 197 obj.tag = parsed.Tag.bytes() 198 obj.aad = parsed.Aad.bytes() 199 200 return obj, nil 201 } 202 203 // parseEncryptedCompact parses a message in compact format. 204 func parseEncryptedCompact(input string) (*JSONWebEncryption, error) { 205 parts := strings.Split(input, ".") 206 if len(parts) != 5 { 207 return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts") 208 } 209 210 rawProtected, err := base64URLDecode(parts[0]) 211 if err != nil { 212 return nil, err 213 } 214 215 encryptedKey, err := base64URLDecode(parts[1]) 216 if err != nil { 217 return nil, err 218 } 219 220 iv, err := base64URLDecode(parts[2]) 221 if err != nil { 222 return nil, err 223 } 224 225 ciphertext, err := base64URLDecode(parts[3]) 226 if err != nil { 227 return nil, err 228 } 229 230 tag, err := base64URLDecode(parts[4]) 231 if err != nil { 232 return nil, err 233 } 234 235 raw := &rawJSONWebEncryption{ 236 Protected: newBuffer(rawProtected), 237 EncryptedKey: newBuffer(encryptedKey), 238 Iv: newBuffer(iv), 239 Ciphertext: newBuffer(ciphertext), 240 Tag: newBuffer(tag), 241 } 242 243 return raw.sanitized() 244 } 245 246 // CompactSerialize serializes an object using the compact serialization format. 247 func (obj JSONWebEncryption) CompactSerialize() (string, error) { 248 if len(obj.recipients) != 1 || obj.unprotected != nil || 249 obj.protected == nil || obj.recipients[0].header != nil { 250 return "", ErrNotSupported 251 } 252 253 serializedProtected := mustSerializeJSON(obj.protected) 254 255 return fmt.Sprintf( 256 "%s.%s.%s.%s.%s", 257 base64.RawURLEncoding.EncodeToString(serializedProtected), 258 base64.RawURLEncoding.EncodeToString(obj.recipients[0].encryptedKey), 259 base64.RawURLEncoding.EncodeToString(obj.iv), 260 base64.RawURLEncoding.EncodeToString(obj.ciphertext), 261 base64.RawURLEncoding.EncodeToString(obj.tag)), nil 262 } 263 264 // FullSerialize serializes an object using the full JSON serialization format. 265 func (obj JSONWebEncryption) FullSerialize() string { 266 raw := rawJSONWebEncryption{ 267 Unprotected: obj.unprotected, 268 Iv: newBuffer(obj.iv), 269 Ciphertext: newBuffer(obj.ciphertext), 270 EncryptedKey: newBuffer(obj.recipients[0].encryptedKey), 271 Tag: newBuffer(obj.tag), 272 Aad: newBuffer(obj.aad), 273 Recipients: []rawRecipientInfo{}, 274 } 275 276 if len(obj.recipients) > 1 { 277 for _, recipient := range obj.recipients { 278 info := rawRecipientInfo{ 279 Header: recipient.header, 280 EncryptedKey: base64.RawURLEncoding.EncodeToString(recipient.encryptedKey), 281 } 282 raw.Recipients = append(raw.Recipients, info) 283 } 284 } else { 285 // Use flattened serialization 286 raw.Header = obj.recipients[0].header 287 raw.EncryptedKey = newBuffer(obj.recipients[0].encryptedKey) 288 } 289 290 if obj.protected != nil { 291 raw.Protected = newBuffer(mustSerializeJSON(obj.protected)) 292 } 293 294 return string(mustSerializeJSON(raw)) 295 }