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 17package jose 18 19import ( 20 "encoding/base64" 21 "errors" 22 "fmt" 23 "strings" 24 25 "gopkg.in/square/go-jose.v2/json" 26) 27 28// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing. 29type rawJSONWebSignature struct { 30 Payload *byteBuffer `json:"payload,omitempty"` 31 Signatures []rawSignatureInfo `json:"signatures,omitempty"` 32 Protected *byteBuffer `json:"protected,omitempty"` 33 Header *rawHeader `json:"header,omitempty"` 34 Signature *byteBuffer `json:"signature,omitempty"` 35} 36 37// rawSignatureInfo represents a single JWS signature over the JWS payload and protected header. 38type rawSignatureInfo struct { 39 Protected *byteBuffer `json:"protected,omitempty"` 40 Header *rawHeader `json:"header,omitempty"` 41 Signature *byteBuffer `json:"signature,omitempty"` 42} 43 44// JSONWebSignature represents a signed JWS object after parsing. 45type JSONWebSignature struct { 46 payload []byte 47 // Signatures attached to this object (may be more than one for multi-sig). 48 // Be careful about accessing these directly, prefer to use Verify() or 49 // VerifyMulti() to ensure that the data you're getting is verified. 50 Signatures []Signature 51} 52 53// Signature represents a single signature over the JWS payload and protected header. 54type Signature struct { 55 // Merged header fields. Contains both protected and unprotected header 56 // values. Prefer using Protected and Unprotected fields instead of this. 57 // Values in this header may or may not have been signed and in general 58 // should not be trusted. 59 Header Header 60 61 // Protected header. Values in this header were signed and 62 // will be verified as part of the signature verification process. 63 Protected Header 64 65 // Unprotected header. Values in this header were not signed 66 // and in general should not be trusted. 67 Unprotected Header 68 69 // The actual signature value 70 Signature []byte 71 72 protected *rawHeader 73 header *rawHeader 74 original *rawSignatureInfo 75} 76 77// ParseSigned parses a signed message in compact or full serialization format. 78func ParseSigned(input string) (*JSONWebSignature, error) { 79 input = stripWhitespace(input) 80 if strings.HasPrefix(input, "{") { 81 return parseSignedFull(input) 82 } 83 84 return parseSignedCompact(input) 85} 86 87// Get a header value 88func (sig Signature) mergedHeaders() rawHeader { 89 out := rawHeader{} 90 out.merge(sig.protected) 91 out.merge(sig.header) 92 return out 93} 94 95// Compute data to be signed 96func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature) []byte { 97 var serializedProtected string 98 99 if signature.original != nil && signature.original.Protected != nil { 100 serializedProtected = signature.original.Protected.base64() 101 } else if signature.protected != nil { 102 serializedProtected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON(signature.protected)) 103 } else { 104 serializedProtected = "" 105 } 106 107 return []byte(fmt.Sprintf("%s.%s", 108 serializedProtected, 109 base64.RawURLEncoding.EncodeToString(payload))) 110} 111 112// parseSignedFull parses a message in full format. 113func parseSignedFull(input string) (*JSONWebSignature, error) { 114 var parsed rawJSONWebSignature 115 err := json.Unmarshal([]byte(input), &parsed) 116 if err != nil { 117 return nil, err 118 } 119 120 return parsed.sanitized() 121} 122 123// sanitized produces a cleaned-up JWS object from the raw JSON. 124func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) { 125 if parsed.Payload == nil { 126 return nil, fmt.Errorf("square/go-jose: missing payload in JWS message") 127 } 128 129 obj := &JSONWebSignature{ 130 payload: parsed.Payload.bytes(), 131 Signatures: make([]Signature, len(parsed.Signatures)), 132 } 133 134 if len(parsed.Signatures) == 0 { 135 // No signatures array, must be flattened serialization 136 signature := Signature{} 137 if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 { 138 signature.protected = &rawHeader{} 139 err := json.Unmarshal(parsed.Protected.bytes(), signature.protected) 140 if err != nil { 141 return nil, err 142 } 143 } 144 145 // Check that there is not a nonce in the unprotected header 146 if parsed.Header != nil && parsed.Header.getNonce() != "" { 147 return nil, ErrUnprotectedNonce 148 } 149 150 signature.header = parsed.Header 151 signature.Signature = parsed.Signature.bytes() 152 // Make a fake "original" rawSignatureInfo to store the unprocessed 153 // Protected header. This is necessary because the Protected header can 154 // contain arbitrary fields not registered as part of the spec. See 155 // https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4 156 // If we unmarshal Protected into a rawHeader with its explicit list of fields, 157 // we cannot marshal losslessly. So we have to keep around the original bytes. 158 // This is used in computeAuthData, which will first attempt to use 159 // the original bytes of a protected header, and fall back on marshaling the 160 // header struct only if those bytes are not available. 161 signature.original = &rawSignatureInfo{ 162 Protected: parsed.Protected, 163 Header: parsed.Header, 164 Signature: parsed.Signature, 165 } 166 167 var err error 168 signature.Header, err = signature.mergedHeaders().sanitized() 169 if err != nil { 170 return nil, err 171 } 172 173 if signature.header != nil { 174 signature.Unprotected, err = signature.header.sanitized() 175 if err != nil { 176 return nil, err 177 } 178 } 179 180 if signature.protected != nil { 181 signature.Protected, err = signature.protected.sanitized() 182 if err != nil { 183 return nil, err 184 } 185 } 186 187 // As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded. 188 jwk := signature.Header.JSONWebKey 189 if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) { 190 return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key") 191 } 192 193 obj.Signatures = append(obj.Signatures, signature) 194 } 195 196 for i, sig := range parsed.Signatures { 197 if sig.Protected != nil && len(sig.Protected.bytes()) > 0 { 198 obj.Signatures[i].protected = &rawHeader{} 199 err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected) 200 if err != nil { 201 return nil, err 202 } 203 } 204 205 // Check that there is not a nonce in the unprotected header 206 if sig.Header != nil && sig.Header.getNonce() != "" { 207 return nil, ErrUnprotectedNonce 208 } 209 210 var err error 211 obj.Signatures[i].Header, err = obj.Signatures[i].mergedHeaders().sanitized() 212 if err != nil { 213 return nil, err 214 } 215 216 if obj.Signatures[i].header != nil { 217 obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized() 218 if err != nil { 219 return nil, err 220 } 221 } 222 223 if obj.Signatures[i].protected != nil { 224 obj.Signatures[i].Protected, err = obj.Signatures[i].protected.sanitized() 225 if err != nil { 226 return nil, err 227 } 228 } 229 230 obj.Signatures[i].Signature = sig.Signature.bytes() 231 232 // As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded. 233 jwk := obj.Signatures[i].Header.JSONWebKey 234 if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) { 235 return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key") 236 } 237 238 // Copy value of sig 239 original := sig 240 241 obj.Signatures[i].header = sig.Header 242 obj.Signatures[i].original = &original 243 } 244 245 return obj, nil 246} 247 248// parseSignedCompact parses a message in compact format. 249func parseSignedCompact(input string) (*JSONWebSignature, error) { 250 parts := strings.Split(input, ".") 251 if len(parts) != 3 { 252 return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts") 253 } 254 255 rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0]) 256 if err != nil { 257 return nil, err 258 } 259 260 payload, err := base64.RawURLEncoding.DecodeString(parts[1]) 261 if err != nil { 262 return nil, err 263 } 264 265 signature, err := base64.RawURLEncoding.DecodeString(parts[2]) 266 if err != nil { 267 return nil, err 268 } 269 270 raw := &rawJSONWebSignature{ 271 Payload: newBuffer(payload), 272 Protected: newBuffer(rawProtected), 273 Signature: newBuffer(signature), 274 } 275 return raw.sanitized() 276} 277 278// CompactSerialize serializes an object using the compact serialization format. 279func (obj JSONWebSignature) CompactSerialize() (string, error) { 280 if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil { 281 return "", ErrNotSupported 282 } 283 284 serializedProtected := mustSerializeJSON(obj.Signatures[0].protected) 285 286 return fmt.Sprintf( 287 "%s.%s.%s", 288 base64.RawURLEncoding.EncodeToString(serializedProtected), 289 base64.RawURLEncoding.EncodeToString(obj.payload), 290 base64.RawURLEncoding.EncodeToString(obj.Signatures[0].Signature)), nil 291} 292 293// FullSerialize serializes an object using the full JSON serialization format. 294func (obj JSONWebSignature) FullSerialize() string { 295 raw := rawJSONWebSignature{ 296 Payload: newBuffer(obj.payload), 297 } 298 299 if len(obj.Signatures) == 1 { 300 if obj.Signatures[0].protected != nil { 301 serializedProtected := mustSerializeJSON(obj.Signatures[0].protected) 302 raw.Protected = newBuffer(serializedProtected) 303 } 304 raw.Header = obj.Signatures[0].header 305 raw.Signature = newBuffer(obj.Signatures[0].Signature) 306 } else { 307 raw.Signatures = make([]rawSignatureInfo, len(obj.Signatures)) 308 for i, signature := range obj.Signatures { 309 raw.Signatures[i] = rawSignatureInfo{ 310 Header: signature.header, 311 Signature: newBuffer(signature.Signature), 312 } 313 314 if signature.protected != nil { 315 raw.Signatures[i].Protected = newBuffer(mustSerializeJSON(signature.protected)) 316 } 317 } 318 } 319 320 return string(mustSerializeJSON(raw)) 321} 322