mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			658 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			658 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package libtrust
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"crypto"
 | |
| 	"crypto/x509"
 | |
| 	"encoding/base64"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"sort"
 | |
| 	"time"
 | |
| 	"unicode"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// ErrInvalidSignContent is used when the content to be signed is invalid.
 | |
| 	ErrInvalidSignContent = errors.New("invalid sign content")
 | |
| 
 | |
| 	// ErrInvalidJSONContent is used when invalid json is encountered.
 | |
| 	ErrInvalidJSONContent = errors.New("invalid json content")
 | |
| 
 | |
| 	// ErrMissingSignatureKey is used when the specified signature key
 | |
| 	// does not exist in the JSON content.
 | |
| 	ErrMissingSignatureKey = errors.New("missing signature key")
 | |
| )
 | |
| 
 | |
| type jsHeader struct {
 | |
| 	JWK       PublicKey `json:"jwk,omitempty"`
 | |
| 	Algorithm string    `json:"alg"`
 | |
| 	Chain     []string  `json:"x5c,omitempty"`
 | |
| }
 | |
| 
 | |
| type jsSignature struct {
 | |
| 	Header    jsHeader `json:"header"`
 | |
| 	Signature string   `json:"signature"`
 | |
| 	Protected string   `json:"protected,omitempty"`
 | |
| }
 | |
| 
 | |
| type jsSignaturesSorted []jsSignature
 | |
| 
 | |
| func (jsbkid jsSignaturesSorted) Swap(i, j int) { jsbkid[i], jsbkid[j] = jsbkid[j], jsbkid[i] }
 | |
| func (jsbkid jsSignaturesSorted) Len() int      { return len(jsbkid) }
 | |
| 
 | |
| func (jsbkid jsSignaturesSorted) Less(i, j int) bool {
 | |
| 	ki, kj := jsbkid[i].Header.JWK.KeyID(), jsbkid[j].Header.JWK.KeyID()
 | |
| 	si, sj := jsbkid[i].Signature, jsbkid[j].Signature
 | |
| 
 | |
| 	if ki == kj {
 | |
| 		return si < sj
 | |
| 	}
 | |
| 
 | |
| 	return ki < kj
 | |
| }
 | |
| 
 | |
| type signKey struct {
 | |
| 	PrivateKey
 | |
| 	Chain []*x509.Certificate
 | |
| }
 | |
| 
 | |
| // JSONSignature represents a signature of a json object.
 | |
| type JSONSignature struct {
 | |
| 	payload      string
 | |
| 	signatures   []jsSignature
 | |
| 	indent       string
 | |
| 	formatLength int
 | |
| 	formatTail   []byte
 | |
| }
 | |
| 
 | |
| func newJSONSignature() *JSONSignature {
 | |
| 	return &JSONSignature{
 | |
| 		signatures: make([]jsSignature, 0, 1),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Payload returns the encoded payload of the signature. This
 | |
| // payload should not be signed directly
 | |
| func (js *JSONSignature) Payload() ([]byte, error) {
 | |
| 	return joseBase64UrlDecode(js.payload)
 | |
| }
 | |
| 
 | |
| func (js *JSONSignature) protectedHeader() (string, error) {
 | |
| 	protected := map[string]interface{}{
 | |
| 		"formatLength": js.formatLength,
 | |
| 		"formatTail":   joseBase64UrlEncode(js.formatTail),
 | |
| 		"time":         time.Now().UTC().Format(time.RFC3339),
 | |
| 	}
 | |
| 	protectedBytes, err := json.Marshal(protected)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	return joseBase64UrlEncode(protectedBytes), nil
 | |
| }
 | |
| 
 | |
| func (js *JSONSignature) signBytes(protectedHeader string) ([]byte, error) {
 | |
| 	buf := make([]byte, len(js.payload)+len(protectedHeader)+1)
 | |
| 	copy(buf, protectedHeader)
 | |
| 	buf[len(protectedHeader)] = '.'
 | |
| 	copy(buf[len(protectedHeader)+1:], js.payload)
 | |
| 	return buf, nil
 | |
| }
 | |
| 
 | |
| // Sign adds a signature using the given private key.
 | |
| func (js *JSONSignature) Sign(key PrivateKey) error {
 | |
| 	protected, err := js.protectedHeader()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	signBytes, err := js.signBytes(protected)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	js.signatures = append(js.signatures, jsSignature{
 | |
| 		Header: jsHeader{
 | |
| 			JWK:       key.PublicKey(),
 | |
| 			Algorithm: algorithm,
 | |
| 		},
 | |
| 		Signature: joseBase64UrlEncode(sigBytes),
 | |
| 		Protected: protected,
 | |
| 	})
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SignWithChain adds a signature using the given private key
 | |
| // and setting the x509 chain. The public key of the first element
 | |
| // in the chain must be the public key corresponding with the sign key.
 | |
| func (js *JSONSignature) SignWithChain(key PrivateKey, chain []*x509.Certificate) error {
 | |
| 	// Ensure key.Chain[0] is public key for key
 | |
| 	//key.Chain.PublicKey
 | |
| 	//key.PublicKey().CryptoPublicKey()
 | |
| 
 | |
| 	// Verify chain
 | |
| 	protected, err := js.protectedHeader()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	signBytes, err := js.signBytes(protected)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	header := jsHeader{
 | |
| 		Chain:     make([]string, len(chain)),
 | |
| 		Algorithm: algorithm,
 | |
| 	}
 | |
| 
 | |
| 	for i, cert := range chain {
 | |
| 		header.Chain[i] = base64.StdEncoding.EncodeToString(cert.Raw)
 | |
| 	}
 | |
| 
 | |
| 	js.signatures = append(js.signatures, jsSignature{
 | |
| 		Header:    header,
 | |
| 		Signature: joseBase64UrlEncode(sigBytes),
 | |
| 		Protected: protected,
 | |
| 	})
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Verify verifies all the signatures and returns the list of
 | |
| // public keys used to sign. Any x509 chains are not checked.
 | |
| func (js *JSONSignature) Verify() ([]PublicKey, error) {
 | |
| 	keys := make([]PublicKey, len(js.signatures))
 | |
| 	for i, signature := range js.signatures {
 | |
| 		signBytes, err := js.signBytes(signature.Protected)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		var publicKey PublicKey
 | |
| 		if len(signature.Header.Chain) > 0 {
 | |
| 			certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			cert, err := x509.ParseCertificate(certBytes)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			publicKey, err = FromCryptoPublicKey(cert.PublicKey)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 		} else if signature.Header.JWK != nil {
 | |
| 			publicKey = signature.Header.JWK
 | |
| 		} else {
 | |
| 			return nil, errors.New("missing public key")
 | |
| 		}
 | |
| 
 | |
| 		sigBytes, err := joseBase64UrlDecode(signature.Signature)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		keys[i] = publicKey
 | |
| 	}
 | |
| 	return keys, nil
 | |
| }
 | |
| 
 | |
| // VerifyChains verifies all the signatures and the chains associated
 | |
| // with each signature and returns the list of verified chains.
 | |
| // Signatures without an x509 chain are not checked.
 | |
| func (js *JSONSignature) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) {
 | |
| 	chains := make([][]*x509.Certificate, 0, len(js.signatures))
 | |
| 	for _, signature := range js.signatures {
 | |
| 		signBytes, err := js.signBytes(signature.Protected)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		var publicKey PublicKey
 | |
| 		if len(signature.Header.Chain) > 0 {
 | |
| 			certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			cert, err := x509.ParseCertificate(certBytes)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			publicKey, err = FromCryptoPublicKey(cert.PublicKey)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			intermediates := x509.NewCertPool()
 | |
| 			if len(signature.Header.Chain) > 1 {
 | |
| 				intermediateChain := signature.Header.Chain[1:]
 | |
| 				for i := range intermediateChain {
 | |
| 					certBytes, err := base64.StdEncoding.DecodeString(intermediateChain[i])
 | |
| 					if err != nil {
 | |
| 						return nil, err
 | |
| 					}
 | |
| 					intermediate, err := x509.ParseCertificate(certBytes)
 | |
| 					if err != nil {
 | |
| 						return nil, err
 | |
| 					}
 | |
| 					intermediates.AddCert(intermediate)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			verifyOptions := x509.VerifyOptions{
 | |
| 				Intermediates: intermediates,
 | |
| 				Roots:         ca,
 | |
| 			}
 | |
| 
 | |
| 			verifiedChains, err := cert.Verify(verifyOptions)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			chains = append(chains, verifiedChains...)
 | |
| 
 | |
| 			sigBytes, err := joseBase64UrlDecode(signature.Signature)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 
 | |
| 			err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 	return chains, nil
 | |
| }
 | |
| 
 | |
| // JWS returns JSON serialized JWS according to
 | |
| // http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2
 | |
| func (js *JSONSignature) JWS() ([]byte, error) {
 | |
| 	if len(js.signatures) == 0 {
 | |
| 		return nil, errors.New("missing signature")
 | |
| 	}
 | |
| 
 | |
| 	sort.Sort(jsSignaturesSorted(js.signatures))
 | |
| 
 | |
| 	jsonMap := map[string]interface{}{
 | |
| 		"payload":    js.payload,
 | |
| 		"signatures": js.signatures,
 | |
| 	}
 | |
| 
 | |
| 	return json.MarshalIndent(jsonMap, "", "   ")
 | |
| }
 | |
| 
 | |
| func notSpace(r rune) bool {
 | |
| 	return !unicode.IsSpace(r)
 | |
| }
 | |
| 
 | |
| func detectJSONIndent(jsonContent []byte) (indent string) {
 | |
| 	if len(jsonContent) > 2 && jsonContent[0] == '{' && jsonContent[1] == '\n' {
 | |
| 		quoteIndex := bytes.IndexRune(jsonContent[1:], '"')
 | |
| 		if quoteIndex > 0 {
 | |
| 			indent = string(jsonContent[2 : quoteIndex+1])
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| type jsParsedHeader struct {
 | |
| 	JWK       json.RawMessage `json:"jwk"`
 | |
| 	Algorithm string          `json:"alg"`
 | |
| 	Chain     []string        `json:"x5c"`
 | |
| }
 | |
| 
 | |
| type jsParsedSignature struct {
 | |
| 	Header    jsParsedHeader `json:"header"`
 | |
| 	Signature string         `json:"signature"`
 | |
| 	Protected string         `json:"protected"`
 | |
| }
 | |
| 
 | |
| // ParseJWS parses a JWS serialized JSON object into a Json Signature.
 | |
| func ParseJWS(content []byte) (*JSONSignature, error) {
 | |
| 	type jsParsed struct {
 | |
| 		Payload    string              `json:"payload"`
 | |
| 		Signatures []jsParsedSignature `json:"signatures"`
 | |
| 	}
 | |
| 	parsed := &jsParsed{}
 | |
| 	err := json.Unmarshal(content, parsed)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if len(parsed.Signatures) == 0 {
 | |
| 		return nil, errors.New("missing signatures")
 | |
| 	}
 | |
| 	payload, err := joseBase64UrlDecode(parsed.Payload)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	js, err := NewJSONSignature(payload)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	js.signatures = make([]jsSignature, len(parsed.Signatures))
 | |
| 	for i, signature := range parsed.Signatures {
 | |
| 		header := jsHeader{
 | |
| 			Algorithm: signature.Header.Algorithm,
 | |
| 		}
 | |
| 		if signature.Header.Chain != nil {
 | |
| 			header.Chain = signature.Header.Chain
 | |
| 		}
 | |
| 		if signature.Header.JWK != nil {
 | |
| 			publicKey, err := UnmarshalPublicKeyJWK([]byte(signature.Header.JWK))
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			header.JWK = publicKey
 | |
| 		}
 | |
| 		js.signatures[i] = jsSignature{
 | |
| 			Header:    header,
 | |
| 			Signature: signature.Signature,
 | |
| 			Protected: signature.Protected,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return js, nil
 | |
| }
 | |
| 
 | |
| // NewJSONSignature returns a new unsigned JWS from a json byte array.
 | |
| // JSONSignature will need to be signed before serializing or storing.
 | |
| // Optionally, one or more signatures can be provided as byte buffers,
 | |
| // containing serialized JWS signatures, to assemble a fully signed JWS
 | |
| // package. It is the callers responsibility to ensure uniqueness of the
 | |
| // provided signatures.
 | |
| func NewJSONSignature(content []byte, signatures ...[]byte) (*JSONSignature, error) {
 | |
| 	var dataMap map[string]interface{}
 | |
| 	err := json.Unmarshal(content, &dataMap)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	js := newJSONSignature()
 | |
| 	js.indent = detectJSONIndent(content)
 | |
| 
 | |
| 	js.payload = joseBase64UrlEncode(content)
 | |
| 
 | |
| 	// Find trailing } and whitespace, put in protected header
 | |
| 	closeIndex := bytes.LastIndexFunc(content, notSpace)
 | |
| 	if content[closeIndex] != '}' {
 | |
| 		return nil, ErrInvalidJSONContent
 | |
| 	}
 | |
| 	lastRuneIndex := bytes.LastIndexFunc(content[:closeIndex], notSpace)
 | |
| 	if content[lastRuneIndex] == ',' {
 | |
| 		return nil, ErrInvalidJSONContent
 | |
| 	}
 | |
| 	js.formatLength = lastRuneIndex + 1
 | |
| 	js.formatTail = content[js.formatLength:]
 | |
| 
 | |
| 	if len(signatures) > 0 {
 | |
| 		for _, signature := range signatures {
 | |
| 			var parsedJSig jsParsedSignature
 | |
| 
 | |
| 			if err := json.Unmarshal(signature, &parsedJSig); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 
 | |
| 			// TODO(stevvooe): A lot of the code below is repeated in
 | |
| 			// ParseJWS. It will require more refactoring to fix that.
 | |
| 			jsig := jsSignature{
 | |
| 				Header: jsHeader{
 | |
| 					Algorithm: parsedJSig.Header.Algorithm,
 | |
| 				},
 | |
| 				Signature: parsedJSig.Signature,
 | |
| 				Protected: parsedJSig.Protected,
 | |
| 			}
 | |
| 
 | |
| 			if parsedJSig.Header.Chain != nil {
 | |
| 				jsig.Header.Chain = parsedJSig.Header.Chain
 | |
| 			}
 | |
| 
 | |
| 			if parsedJSig.Header.JWK != nil {
 | |
| 				publicKey, err := UnmarshalPublicKeyJWK([]byte(parsedJSig.Header.JWK))
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				jsig.Header.JWK = publicKey
 | |
| 			}
 | |
| 
 | |
| 			js.signatures = append(js.signatures, jsig)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return js, nil
 | |
| }
 | |
| 
 | |
| // NewJSONSignatureFromMap returns a new unsigned JSONSignature from a map or
 | |
| // struct. JWS will need to be signed before serializing or storing.
 | |
| func NewJSONSignatureFromMap(content interface{}) (*JSONSignature, error) {
 | |
| 	switch content.(type) {
 | |
| 	case map[string]interface{}:
 | |
| 	case struct{}:
 | |
| 	default:
 | |
| 		return nil, errors.New("invalid data type")
 | |
| 	}
 | |
| 
 | |
| 	js := newJSONSignature()
 | |
| 	js.indent = "   "
 | |
| 
 | |
| 	payload, err := json.MarshalIndent(content, "", js.indent)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	js.payload = joseBase64UrlEncode(payload)
 | |
| 
 | |
| 	// Remove '\n}' from formatted section, put in protected header
 | |
| 	js.formatLength = len(payload) - 2
 | |
| 	js.formatTail = payload[js.formatLength:]
 | |
| 
 | |
| 	return js, nil
 | |
| }
 | |
| 
 | |
| func readIntFromMap(key string, m map[string]interface{}) (int, bool) {
 | |
| 	value, ok := m[key]
 | |
| 	if !ok {
 | |
| 		return 0, false
 | |
| 	}
 | |
| 	switch v := value.(type) {
 | |
| 	case int:
 | |
| 		return v, true
 | |
| 	case float64:
 | |
| 		return int(v), true
 | |
| 	default:
 | |
| 		return 0, false
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func readStringFromMap(key string, m map[string]interface{}) (v string, ok bool) {
 | |
| 	value, ok := m[key]
 | |
| 	if !ok {
 | |
| 		return "", false
 | |
| 	}
 | |
| 	v, ok = value.(string)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // ParsePrettySignature parses a formatted signature into a
 | |
| // JSON signature. If the signatures are missing the format information
 | |
| // an error is thrown. The formatted signature must be created by
 | |
| // the same method as format signature.
 | |
| func ParsePrettySignature(content []byte, signatureKey string) (*JSONSignature, error) {
 | |
| 	var contentMap map[string]json.RawMessage
 | |
| 	err := json.Unmarshal(content, &contentMap)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("error unmarshalling content: %s", err)
 | |
| 	}
 | |
| 	sigMessage, ok := contentMap[signatureKey]
 | |
| 	if !ok {
 | |
| 		return nil, ErrMissingSignatureKey
 | |
| 	}
 | |
| 
 | |
| 	var signatureBlocks []jsParsedSignature
 | |
| 	err = json.Unmarshal([]byte(sigMessage), &signatureBlocks)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("error unmarshalling signatures: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	js := newJSONSignature()
 | |
| 	js.signatures = make([]jsSignature, len(signatureBlocks))
 | |
| 
 | |
| 	for i, signatureBlock := range signatureBlocks {
 | |
| 		protectedBytes, err := joseBase64UrlDecode(signatureBlock.Protected)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("base64 decode error: %s", err)
 | |
| 		}
 | |
| 		var protectedHeader map[string]interface{}
 | |
| 		err = json.Unmarshal(protectedBytes, &protectedHeader)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("error unmarshalling protected header: %s", err)
 | |
| 		}
 | |
| 
 | |
| 		formatLength, ok := readIntFromMap("formatLength", protectedHeader)
 | |
| 		if !ok {
 | |
| 			return nil, errors.New("missing formatted length")
 | |
| 		}
 | |
| 		encodedTail, ok := readStringFromMap("formatTail", protectedHeader)
 | |
| 		if !ok {
 | |
| 			return nil, errors.New("missing formatted tail")
 | |
| 		}
 | |
| 		formatTail, err := joseBase64UrlDecode(encodedTail)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("base64 decode error on tail: %s", err)
 | |
| 		}
 | |
| 		if js.formatLength == 0 {
 | |
| 			js.formatLength = formatLength
 | |
| 		} else if js.formatLength != formatLength {
 | |
| 			return nil, errors.New("conflicting format length")
 | |
| 		}
 | |
| 		if len(js.formatTail) == 0 {
 | |
| 			js.formatTail = formatTail
 | |
| 		} else if bytes.Compare(js.formatTail, formatTail) != 0 {
 | |
| 			return nil, errors.New("conflicting format tail")
 | |
| 		}
 | |
| 
 | |
| 		header := jsHeader{
 | |
| 			Algorithm: signatureBlock.Header.Algorithm,
 | |
| 			Chain:     signatureBlock.Header.Chain,
 | |
| 		}
 | |
| 		if signatureBlock.Header.JWK != nil {
 | |
| 			publicKey, err := UnmarshalPublicKeyJWK([]byte(signatureBlock.Header.JWK))
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("error unmarshalling public key: %s", err)
 | |
| 			}
 | |
| 			header.JWK = publicKey
 | |
| 		}
 | |
| 		js.signatures[i] = jsSignature{
 | |
| 			Header:    header,
 | |
| 			Signature: signatureBlock.Signature,
 | |
| 			Protected: signatureBlock.Protected,
 | |
| 		}
 | |
| 	}
 | |
| 	if js.formatLength > len(content) {
 | |
| 		return nil, errors.New("invalid format length")
 | |
| 	}
 | |
| 	formatted := make([]byte, js.formatLength+len(js.formatTail))
 | |
| 	copy(formatted, content[:js.formatLength])
 | |
| 	copy(formatted[js.formatLength:], js.formatTail)
 | |
| 	js.indent = detectJSONIndent(formatted)
 | |
| 	js.payload = joseBase64UrlEncode(formatted)
 | |
| 
 | |
| 	return js, nil
 | |
| }
 | |
| 
 | |
| // PrettySignature formats a json signature into an easy to read
 | |
| // single json serialized object.
 | |
| func (js *JSONSignature) PrettySignature(signatureKey string) ([]byte, error) {
 | |
| 	if len(js.signatures) == 0 {
 | |
| 		return nil, errors.New("no signatures")
 | |
| 	}
 | |
| 	payload, err := joseBase64UrlDecode(js.payload)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	payload = payload[:js.formatLength]
 | |
| 
 | |
| 	sort.Sort(jsSignaturesSorted(js.signatures))
 | |
| 
 | |
| 	var marshalled []byte
 | |
| 	var marshallErr error
 | |
| 	if js.indent != "" {
 | |
| 		marshalled, marshallErr = json.MarshalIndent(js.signatures, js.indent, js.indent)
 | |
| 	} else {
 | |
| 		marshalled, marshallErr = json.Marshal(js.signatures)
 | |
| 	}
 | |
| 	if marshallErr != nil {
 | |
| 		return nil, marshallErr
 | |
| 	}
 | |
| 
 | |
| 	buf := bytes.NewBuffer(make([]byte, 0, len(payload)+len(marshalled)+34))
 | |
| 	buf.Write(payload)
 | |
| 	buf.WriteByte(',')
 | |
| 	if js.indent != "" {
 | |
| 		buf.WriteByte('\n')
 | |
| 		buf.WriteString(js.indent)
 | |
| 		buf.WriteByte('"')
 | |
| 		buf.WriteString(signatureKey)
 | |
| 		buf.WriteString("\": ")
 | |
| 		buf.Write(marshalled)
 | |
| 		buf.WriteByte('\n')
 | |
| 	} else {
 | |
| 		buf.WriteByte('"')
 | |
| 		buf.WriteString(signatureKey)
 | |
| 		buf.WriteString("\":")
 | |
| 		buf.Write(marshalled)
 | |
| 	}
 | |
| 	buf.WriteByte('}')
 | |
| 
 | |
| 	return buf.Bytes(), nil
 | |
| }
 | |
| 
 | |
| // Signatures provides the signatures on this JWS as opaque blobs, sorted by
 | |
| // keyID. These blobs can be stored and reassembled with payloads. Internally,
 | |
| // they are simply marshaled json web signatures but implementations should
 | |
| // not rely on this.
 | |
| func (js *JSONSignature) Signatures() ([][]byte, error) {
 | |
| 	sort.Sort(jsSignaturesSorted(js.signatures))
 | |
| 
 | |
| 	var sb [][]byte
 | |
| 	for _, jsig := range js.signatures {
 | |
| 		p, err := json.Marshal(jsig)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		sb = append(sb, p)
 | |
| 	}
 | |
| 
 | |
| 	return sb, nil
 | |
| }
 | |
| 
 | |
| // Merge combines the signatures from one or more other signatures into the
 | |
| // method receiver. If the payloads differ for any argument, an error will be
 | |
| // returned and the receiver will not be modified.
 | |
| func (js *JSONSignature) Merge(others ...*JSONSignature) error {
 | |
| 	merged := js.signatures
 | |
| 	for _, other := range others {
 | |
| 		if js.payload != other.payload {
 | |
| 			return fmt.Errorf("payloads differ from merge target")
 | |
| 		}
 | |
| 		merged = append(merged, other.signatures...)
 | |
| 	}
 | |
| 
 | |
| 	js.signatures = merged
 | |
| 	return nil
 | |
| }
 |