podman
657 строк · 17.3 Кб
1package libtrust
2
3import (
4"bytes"
5"crypto"
6"crypto/x509"
7"encoding/base64"
8"encoding/json"
9"errors"
10"fmt"
11"sort"
12"time"
13"unicode"
14)
15
16var (
17// ErrInvalidSignContent is used when the content to be signed is invalid.
18ErrInvalidSignContent = errors.New("invalid sign content")
19
20// ErrInvalidJSONContent is used when invalid json is encountered.
21ErrInvalidJSONContent = errors.New("invalid json content")
22
23// ErrMissingSignatureKey is used when the specified signature key
24// does not exist in the JSON content.
25ErrMissingSignatureKey = errors.New("missing signature key")
26)
27
28type jsHeader struct {
29JWK PublicKey `json:"jwk,omitempty"`
30Algorithm string `json:"alg"`
31Chain []string `json:"x5c,omitempty"`
32}
33
34type jsSignature struct {
35Header jsHeader `json:"header"`
36Signature string `json:"signature"`
37Protected string `json:"protected,omitempty"`
38}
39
40type jsSignaturesSorted []jsSignature
41
42func (jsbkid jsSignaturesSorted) Swap(i, j int) { jsbkid[i], jsbkid[j] = jsbkid[j], jsbkid[i] }
43func (jsbkid jsSignaturesSorted) Len() int { return len(jsbkid) }
44
45func (jsbkid jsSignaturesSorted) Less(i, j int) bool {
46ki, kj := jsbkid[i].Header.JWK.KeyID(), jsbkid[j].Header.JWK.KeyID()
47si, sj := jsbkid[i].Signature, jsbkid[j].Signature
48
49if ki == kj {
50return si < sj
51}
52
53return ki < kj
54}
55
56type signKey struct {
57PrivateKey
58Chain []*x509.Certificate
59}
60
61// JSONSignature represents a signature of a json object.
62type JSONSignature struct {
63payload string
64signatures []jsSignature
65indent string
66formatLength int
67formatTail []byte
68}
69
70func newJSONSignature() *JSONSignature {
71return &JSONSignature{
72signatures: make([]jsSignature, 0, 1),
73}
74}
75
76// Payload returns the encoded payload of the signature. This
77// payload should not be signed directly
78func (js *JSONSignature) Payload() ([]byte, error) {
79return joseBase64UrlDecode(js.payload)
80}
81
82func (js *JSONSignature) protectedHeader() (string, error) {
83protected := map[string]interface{}{
84"formatLength": js.formatLength,
85"formatTail": joseBase64UrlEncode(js.formatTail),
86"time": time.Now().UTC().Format(time.RFC3339),
87}
88protectedBytes, err := json.Marshal(protected)
89if err != nil {
90return "", err
91}
92
93return joseBase64UrlEncode(protectedBytes), nil
94}
95
96func (js *JSONSignature) signBytes(protectedHeader string) ([]byte, error) {
97buf := make([]byte, len(js.payload)+len(protectedHeader)+1)
98copy(buf, protectedHeader)
99buf[len(protectedHeader)] = '.'
100copy(buf[len(protectedHeader)+1:], js.payload)
101return buf, nil
102}
103
104// Sign adds a signature using the given private key.
105func (js *JSONSignature) Sign(key PrivateKey) error {
106protected, err := js.protectedHeader()
107if err != nil {
108return err
109}
110signBytes, err := js.signBytes(protected)
111if err != nil {
112return err
113}
114sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
115if err != nil {
116return err
117}
118
119js.signatures = append(js.signatures, jsSignature{
120Header: jsHeader{
121JWK: key.PublicKey(),
122Algorithm: algorithm,
123},
124Signature: joseBase64UrlEncode(sigBytes),
125Protected: protected,
126})
127
128return nil
129}
130
131// SignWithChain adds a signature using the given private key
132// and setting the x509 chain. The public key of the first element
133// in the chain must be the public key corresponding with the sign key.
134func (js *JSONSignature) SignWithChain(key PrivateKey, chain []*x509.Certificate) error {
135// Ensure key.Chain[0] is public key for key
136//key.Chain.PublicKey
137//key.PublicKey().CryptoPublicKey()
138
139// Verify chain
140protected, err := js.protectedHeader()
141if err != nil {
142return err
143}
144signBytes, err := js.signBytes(protected)
145if err != nil {
146return err
147}
148sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
149if err != nil {
150return err
151}
152
153header := jsHeader{
154Chain: make([]string, len(chain)),
155Algorithm: algorithm,
156}
157
158for i, cert := range chain {
159header.Chain[i] = base64.StdEncoding.EncodeToString(cert.Raw)
160}
161
162js.signatures = append(js.signatures, jsSignature{
163Header: header,
164Signature: joseBase64UrlEncode(sigBytes),
165Protected: protected,
166})
167
168return nil
169}
170
171// Verify verifies all the signatures and returns the list of
172// public keys used to sign. Any x509 chains are not checked.
173func (js *JSONSignature) Verify() ([]PublicKey, error) {
174keys := make([]PublicKey, len(js.signatures))
175for i, signature := range js.signatures {
176signBytes, err := js.signBytes(signature.Protected)
177if err != nil {
178return nil, err
179}
180var publicKey PublicKey
181if len(signature.Header.Chain) > 0 {
182certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
183if err != nil {
184return nil, err
185}
186cert, err := x509.ParseCertificate(certBytes)
187if err != nil {
188return nil, err
189}
190publicKey, err = FromCryptoPublicKey(cert.PublicKey)
191if err != nil {
192return nil, err
193}
194} else if signature.Header.JWK != nil {
195publicKey = signature.Header.JWK
196} else {
197return nil, errors.New("missing public key")
198}
199
200sigBytes, err := joseBase64UrlDecode(signature.Signature)
201if err != nil {
202return nil, err
203}
204
205err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
206if err != nil {
207return nil, err
208}
209
210keys[i] = publicKey
211}
212return keys, nil
213}
214
215// VerifyChains verifies all the signatures and the chains associated
216// with each signature and returns the list of verified chains.
217// Signatures without an x509 chain are not checked.
218func (js *JSONSignature) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) {
219chains := make([][]*x509.Certificate, 0, len(js.signatures))
220for _, signature := range js.signatures {
221signBytes, err := js.signBytes(signature.Protected)
222if err != nil {
223return nil, err
224}
225var publicKey PublicKey
226if len(signature.Header.Chain) > 0 {
227certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
228if err != nil {
229return nil, err
230}
231cert, err := x509.ParseCertificate(certBytes)
232if err != nil {
233return nil, err
234}
235publicKey, err = FromCryptoPublicKey(cert.PublicKey)
236if err != nil {
237return nil, err
238}
239intermediates := x509.NewCertPool()
240if len(signature.Header.Chain) > 1 {
241intermediateChain := signature.Header.Chain[1:]
242for i := range intermediateChain {
243certBytes, err := base64.StdEncoding.DecodeString(intermediateChain[i])
244if err != nil {
245return nil, err
246}
247intermediate, err := x509.ParseCertificate(certBytes)
248if err != nil {
249return nil, err
250}
251intermediates.AddCert(intermediate)
252}
253}
254
255verifyOptions := x509.VerifyOptions{
256Intermediates: intermediates,
257Roots: ca,
258}
259
260verifiedChains, err := cert.Verify(verifyOptions)
261if err != nil {
262return nil, err
263}
264chains = append(chains, verifiedChains...)
265
266sigBytes, err := joseBase64UrlDecode(signature.Signature)
267if err != nil {
268return nil, err
269}
270
271err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
272if err != nil {
273return nil, err
274}
275}
276
277}
278return chains, nil
279}
280
281// JWS returns JSON serialized JWS according to
282// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2
283func (js *JSONSignature) JWS() ([]byte, error) {
284if len(js.signatures) == 0 {
285return nil, errors.New("missing signature")
286}
287
288sort.Sort(jsSignaturesSorted(js.signatures))
289
290jsonMap := map[string]interface{}{
291"payload": js.payload,
292"signatures": js.signatures,
293}
294
295return json.MarshalIndent(jsonMap, "", " ")
296}
297
298func notSpace(r rune) bool {
299return !unicode.IsSpace(r)
300}
301
302func detectJSONIndent(jsonContent []byte) (indent string) {
303if len(jsonContent) > 2 && jsonContent[0] == '{' && jsonContent[1] == '\n' {
304quoteIndex := bytes.IndexRune(jsonContent[1:], '"')
305if quoteIndex > 0 {
306indent = string(jsonContent[2 : quoteIndex+1])
307}
308}
309return
310}
311
312type jsParsedHeader struct {
313JWK json.RawMessage `json:"jwk"`
314Algorithm string `json:"alg"`
315Chain []string `json:"x5c"`
316}
317
318type jsParsedSignature struct {
319Header jsParsedHeader `json:"header"`
320Signature string `json:"signature"`
321Protected string `json:"protected"`
322}
323
324// ParseJWS parses a JWS serialized JSON object into a Json Signature.
325func ParseJWS(content []byte) (*JSONSignature, error) {
326type jsParsed struct {
327Payload string `json:"payload"`
328Signatures []jsParsedSignature `json:"signatures"`
329}
330parsed := &jsParsed{}
331err := json.Unmarshal(content, parsed)
332if err != nil {
333return nil, err
334}
335if len(parsed.Signatures) == 0 {
336return nil, errors.New("missing signatures")
337}
338payload, err := joseBase64UrlDecode(parsed.Payload)
339if err != nil {
340return nil, err
341}
342
343js, err := NewJSONSignature(payload)
344if err != nil {
345return nil, err
346}
347js.signatures = make([]jsSignature, len(parsed.Signatures))
348for i, signature := range parsed.Signatures {
349header := jsHeader{
350Algorithm: signature.Header.Algorithm,
351}
352if signature.Header.Chain != nil {
353header.Chain = signature.Header.Chain
354}
355if signature.Header.JWK != nil {
356publicKey, err := UnmarshalPublicKeyJWK([]byte(signature.Header.JWK))
357if err != nil {
358return nil, err
359}
360header.JWK = publicKey
361}
362js.signatures[i] = jsSignature{
363Header: header,
364Signature: signature.Signature,
365Protected: signature.Protected,
366}
367}
368
369return js, nil
370}
371
372// NewJSONSignature returns a new unsigned JWS from a json byte array.
373// JSONSignature will need to be signed before serializing or storing.
374// Optionally, one or more signatures can be provided as byte buffers,
375// containing serialized JWS signatures, to assemble a fully signed JWS
376// package. It is the callers responsibility to ensure uniqueness of the
377// provided signatures.
378func NewJSONSignature(content []byte, signatures ...[]byte) (*JSONSignature, error) {
379var dataMap map[string]interface{}
380err := json.Unmarshal(content, &dataMap)
381if err != nil {
382return nil, err
383}
384
385js := newJSONSignature()
386js.indent = detectJSONIndent(content)
387
388js.payload = joseBase64UrlEncode(content)
389
390// Find trailing } and whitespace, put in protected header
391closeIndex := bytes.LastIndexFunc(content, notSpace)
392if content[closeIndex] != '}' {
393return nil, ErrInvalidJSONContent
394}
395lastRuneIndex := bytes.LastIndexFunc(content[:closeIndex], notSpace)
396if content[lastRuneIndex] == ',' {
397return nil, ErrInvalidJSONContent
398}
399js.formatLength = lastRuneIndex + 1
400js.formatTail = content[js.formatLength:]
401
402if len(signatures) > 0 {
403for _, signature := range signatures {
404var parsedJSig jsParsedSignature
405
406if err := json.Unmarshal(signature, &parsedJSig); err != nil {
407return nil, err
408}
409
410// TODO(stevvooe): A lot of the code below is repeated in
411// ParseJWS. It will require more refactoring to fix that.
412jsig := jsSignature{
413Header: jsHeader{
414Algorithm: parsedJSig.Header.Algorithm,
415},
416Signature: parsedJSig.Signature,
417Protected: parsedJSig.Protected,
418}
419
420if parsedJSig.Header.Chain != nil {
421jsig.Header.Chain = parsedJSig.Header.Chain
422}
423
424if parsedJSig.Header.JWK != nil {
425publicKey, err := UnmarshalPublicKeyJWK([]byte(parsedJSig.Header.JWK))
426if err != nil {
427return nil, err
428}
429jsig.Header.JWK = publicKey
430}
431
432js.signatures = append(js.signatures, jsig)
433}
434}
435
436return js, nil
437}
438
439// NewJSONSignatureFromMap returns a new unsigned JSONSignature from a map or
440// struct. JWS will need to be signed before serializing or storing.
441func NewJSONSignatureFromMap(content interface{}) (*JSONSignature, error) {
442switch content.(type) {
443case map[string]interface{}:
444case struct{}:
445default:
446return nil, errors.New("invalid data type")
447}
448
449js := newJSONSignature()
450js.indent = " "
451
452payload, err := json.MarshalIndent(content, "", js.indent)
453if err != nil {
454return nil, err
455}
456js.payload = joseBase64UrlEncode(payload)
457
458// Remove '\n}' from formatted section, put in protected header
459js.formatLength = len(payload) - 2
460js.formatTail = payload[js.formatLength:]
461
462return js, nil
463}
464
465func readIntFromMap(key string, m map[string]interface{}) (int, bool) {
466value, ok := m[key]
467if !ok {
468return 0, false
469}
470switch v := value.(type) {
471case int:
472return v, true
473case float64:
474return int(v), true
475default:
476return 0, false
477}
478}
479
480func readStringFromMap(key string, m map[string]interface{}) (v string, ok bool) {
481value, ok := m[key]
482if !ok {
483return "", false
484}
485v, ok = value.(string)
486return
487}
488
489// ParsePrettySignature parses a formatted signature into a
490// JSON signature. If the signatures are missing the format information
491// an error is thrown. The formatted signature must be created by
492// the same method as format signature.
493func ParsePrettySignature(content []byte, signatureKey string) (*JSONSignature, error) {
494var contentMap map[string]json.RawMessage
495err := json.Unmarshal(content, &contentMap)
496if err != nil {
497return nil, fmt.Errorf("error unmarshalling content: %s", err)
498}
499sigMessage, ok := contentMap[signatureKey]
500if !ok {
501return nil, ErrMissingSignatureKey
502}
503
504var signatureBlocks []jsParsedSignature
505err = json.Unmarshal([]byte(sigMessage), &signatureBlocks)
506if err != nil {
507return nil, fmt.Errorf("error unmarshalling signatures: %s", err)
508}
509
510js := newJSONSignature()
511js.signatures = make([]jsSignature, len(signatureBlocks))
512
513for i, signatureBlock := range signatureBlocks {
514protectedBytes, err := joseBase64UrlDecode(signatureBlock.Protected)
515if err != nil {
516return nil, fmt.Errorf("base64 decode error: %s", err)
517}
518var protectedHeader map[string]interface{}
519err = json.Unmarshal(protectedBytes, &protectedHeader)
520if err != nil {
521return nil, fmt.Errorf("error unmarshalling protected header: %s", err)
522}
523
524formatLength, ok := readIntFromMap("formatLength", protectedHeader)
525if !ok {
526return nil, errors.New("missing formatted length")
527}
528encodedTail, ok := readStringFromMap("formatTail", protectedHeader)
529if !ok {
530return nil, errors.New("missing formatted tail")
531}
532formatTail, err := joseBase64UrlDecode(encodedTail)
533if err != nil {
534return nil, fmt.Errorf("base64 decode error on tail: %s", err)
535}
536if js.formatLength == 0 {
537js.formatLength = formatLength
538} else if js.formatLength != formatLength {
539return nil, errors.New("conflicting format length")
540}
541if len(js.formatTail) == 0 {
542js.formatTail = formatTail
543} else if bytes.Compare(js.formatTail, formatTail) != 0 {
544return nil, errors.New("conflicting format tail")
545}
546
547header := jsHeader{
548Algorithm: signatureBlock.Header.Algorithm,
549Chain: signatureBlock.Header.Chain,
550}
551if signatureBlock.Header.JWK != nil {
552publicKey, err := UnmarshalPublicKeyJWK([]byte(signatureBlock.Header.JWK))
553if err != nil {
554return nil, fmt.Errorf("error unmarshalling public key: %s", err)
555}
556header.JWK = publicKey
557}
558js.signatures[i] = jsSignature{
559Header: header,
560Signature: signatureBlock.Signature,
561Protected: signatureBlock.Protected,
562}
563}
564if js.formatLength > len(content) {
565return nil, errors.New("invalid format length")
566}
567formatted := make([]byte, js.formatLength+len(js.formatTail))
568copy(formatted, content[:js.formatLength])
569copy(formatted[js.formatLength:], js.formatTail)
570js.indent = detectJSONIndent(formatted)
571js.payload = joseBase64UrlEncode(formatted)
572
573return js, nil
574}
575
576// PrettySignature formats a json signature into an easy to read
577// single json serialized object.
578func (js *JSONSignature) PrettySignature(signatureKey string) ([]byte, error) {
579if len(js.signatures) == 0 {
580return nil, errors.New("no signatures")
581}
582payload, err := joseBase64UrlDecode(js.payload)
583if err != nil {
584return nil, err
585}
586payload = payload[:js.formatLength]
587
588sort.Sort(jsSignaturesSorted(js.signatures))
589
590var marshalled []byte
591var marshallErr error
592if js.indent != "" {
593marshalled, marshallErr = json.MarshalIndent(js.signatures, js.indent, js.indent)
594} else {
595marshalled, marshallErr = json.Marshal(js.signatures)
596}
597if marshallErr != nil {
598return nil, marshallErr
599}
600
601buf := bytes.NewBuffer(make([]byte, 0, len(payload)+len(marshalled)+34))
602buf.Write(payload)
603buf.WriteByte(',')
604if js.indent != "" {
605buf.WriteByte('\n')
606buf.WriteString(js.indent)
607buf.WriteByte('"')
608buf.WriteString(signatureKey)
609buf.WriteString("\": ")
610buf.Write(marshalled)
611buf.WriteByte('\n')
612} else {
613buf.WriteByte('"')
614buf.WriteString(signatureKey)
615buf.WriteString("\":")
616buf.Write(marshalled)
617}
618buf.WriteByte('}')
619
620return buf.Bytes(), nil
621}
622
623// Signatures provides the signatures on this JWS as opaque blobs, sorted by
624// keyID. These blobs can be stored and reassembled with payloads. Internally,
625// they are simply marshaled json web signatures but implementations should
626// not rely on this.
627func (js *JSONSignature) Signatures() ([][]byte, error) {
628sort.Sort(jsSignaturesSorted(js.signatures))
629
630var sb [][]byte
631for _, jsig := range js.signatures {
632p, err := json.Marshal(jsig)
633if err != nil {
634return nil, err
635}
636
637sb = append(sb, p)
638}
639
640return sb, nil
641}
642
643// Merge combines the signatures from one or more other signatures into the
644// method receiver. If the payloads differ for any argument, an error will be
645// returned and the receiver will not be modified.
646func (js *JSONSignature) Merge(others ...*JSONSignature) error {
647merged := js.signatures
648for _, other := range others {
649if js.payload != other.payload {
650return fmt.Errorf("payloads differ from merge target")
651}
652merged = append(merged, other.signatures...)
653}
654
655js.signatures = merged
656return nil
657}
658