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 }