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