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