123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- 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
- }
|