| /*- |
| * Copyright 2016 Zbigniew Mandziejewicz |
| * Copyright 2016 Square, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package jwt |
| |
| import ( |
| "bytes" |
| "reflect" |
| |
| "gopkg.in/square/go-jose.v2/json" |
| |
| "gopkg.in/square/go-jose.v2" |
| ) |
| |
| // Builder is a utility for making JSON Web Tokens. Calls can be chained, and |
| // errors are accumulated until the final call to CompactSerialize/FullSerialize. |
| type Builder interface { |
| // Claims encodes claims into JWE/JWS form. Multiple calls will merge claims |
| // into single JSON object. If you are passing private claims, make sure to set |
| // struct field tags to specify the name for the JSON key to be used when |
| // serializing. |
| Claims(i interface{}) Builder |
| // Token builds a JSONWebToken from provided data. |
| Token() (*JSONWebToken, error) |
| // FullSerialize serializes a token using the full serialization format. |
| FullSerialize() (string, error) |
| // CompactSerialize serializes a token using the compact serialization format. |
| CompactSerialize() (string, error) |
| } |
| |
| // NestedBuilder is a utility for making Signed-Then-Encrypted JSON Web Tokens. |
| // Calls can be chained, and errors are accumulated until final call to |
| // CompactSerialize/FullSerialize. |
| type NestedBuilder interface { |
| // Claims encodes claims into JWE/JWS form. Multiple calls will merge claims |
| // into single JSON object. If you are passing private claims, make sure to set |
| // struct field tags to specify the name for the JSON key to be used when |
| // serializing. |
| Claims(i interface{}) NestedBuilder |
| // Token builds a NestedJSONWebToken from provided data. |
| Token() (*NestedJSONWebToken, error) |
| // FullSerialize serializes a token using the full serialization format. |
| FullSerialize() (string, error) |
| // CompactSerialize serializes a token using the compact serialization format. |
| CompactSerialize() (string, error) |
| } |
| |
| type builder struct { |
| payload map[string]interface{} |
| err error |
| } |
| |
| type signedBuilder struct { |
| builder |
| sig jose.Signer |
| } |
| |
| type encryptedBuilder struct { |
| builder |
| enc jose.Encrypter |
| } |
| |
| type nestedBuilder struct { |
| builder |
| sig jose.Signer |
| enc jose.Encrypter |
| } |
| |
| // Signed creates builder for signed tokens. |
| func Signed(sig jose.Signer) Builder { |
| return &signedBuilder{ |
| sig: sig, |
| } |
| } |
| |
| // Encrypted creates builder for encrypted tokens. |
| func Encrypted(enc jose.Encrypter) Builder { |
| return &encryptedBuilder{ |
| enc: enc, |
| } |
| } |
| |
| // SignedAndEncrypted creates builder for signed-then-encrypted tokens. |
| // ErrInvalidContentType will be returned if encrypter doesn't have JWT content type. |
| func SignedAndEncrypted(sig jose.Signer, enc jose.Encrypter) NestedBuilder { |
| if contentType, _ := enc.Options().ExtraHeaders[jose.HeaderContentType].(jose.ContentType); contentType != "JWT" { |
| return &nestedBuilder{ |
| builder: builder{ |
| err: ErrInvalidContentType, |
| }, |
| } |
| } |
| return &nestedBuilder{ |
| sig: sig, |
| enc: enc, |
| } |
| } |
| |
| func (b builder) claims(i interface{}) builder { |
| if b.err != nil { |
| return b |
| } |
| |
| m, ok := i.(map[string]interface{}) |
| switch { |
| case ok: |
| return b.merge(m) |
| case reflect.Indirect(reflect.ValueOf(i)).Kind() == reflect.Struct: |
| m, err := normalize(i) |
| if err != nil { |
| return builder{ |
| err: err, |
| } |
| } |
| return b.merge(m) |
| default: |
| return builder{ |
| err: ErrInvalidClaims, |
| } |
| } |
| } |
| |
| func normalize(i interface{}) (map[string]interface{}, error) { |
| m := make(map[string]interface{}) |
| |
| raw, err := json.Marshal(i) |
| if err != nil { |
| return nil, err |
| } |
| |
| d := json.NewDecoder(bytes.NewReader(raw)) |
| d.UseNumber() |
| |
| if err := d.Decode(&m); err != nil { |
| return nil, err |
| } |
| |
| return m, nil |
| } |
| |
| func (b *builder) merge(m map[string]interface{}) builder { |
| p := make(map[string]interface{}) |
| for k, v := range b.payload { |
| p[k] = v |
| } |
| for k, v := range m { |
| p[k] = v |
| } |
| |
| return builder{ |
| payload: p, |
| } |
| } |
| |
| func (b *builder) token(p func(interface{}) ([]byte, error), h []jose.Header) (*JSONWebToken, error) { |
| return &JSONWebToken{ |
| payload: p, |
| Headers: h, |
| }, nil |
| } |
| |
| func (b *signedBuilder) Claims(i interface{}) Builder { |
| return &signedBuilder{ |
| builder: b.builder.claims(i), |
| sig: b.sig, |
| } |
| } |
| |
| func (b *signedBuilder) Token() (*JSONWebToken, error) { |
| sig, err := b.sign() |
| if err != nil { |
| return nil, err |
| } |
| |
| h := make([]jose.Header, len(sig.Signatures)) |
| for i, v := range sig.Signatures { |
| h[i] = v.Header |
| } |
| |
| return b.builder.token(sig.Verify, h) |
| } |
| |
| func (b *signedBuilder) CompactSerialize() (string, error) { |
| sig, err := b.sign() |
| if err != nil { |
| return "", err |
| } |
| |
| return sig.CompactSerialize() |
| } |
| |
| func (b *signedBuilder) FullSerialize() (string, error) { |
| sig, err := b.sign() |
| if err != nil { |
| return "", err |
| } |
| |
| return sig.FullSerialize(), nil |
| } |
| |
| func (b *signedBuilder) sign() (*jose.JSONWebSignature, error) { |
| if b.err != nil { |
| return nil, b.err |
| } |
| |
| p, err := json.Marshal(b.payload) |
| if err != nil { |
| return nil, err |
| } |
| |
| return b.sig.Sign(p) |
| } |
| |
| func (b *encryptedBuilder) Claims(i interface{}) Builder { |
| return &encryptedBuilder{ |
| builder: b.builder.claims(i), |
| enc: b.enc, |
| } |
| } |
| |
| func (b *encryptedBuilder) CompactSerialize() (string, error) { |
| enc, err := b.encrypt() |
| if err != nil { |
| return "", err |
| } |
| |
| return enc.CompactSerialize() |
| } |
| |
| func (b *encryptedBuilder) FullSerialize() (string, error) { |
| enc, err := b.encrypt() |
| if err != nil { |
| return "", err |
| } |
| |
| return enc.FullSerialize(), nil |
| } |
| |
| func (b *encryptedBuilder) Token() (*JSONWebToken, error) { |
| enc, err := b.encrypt() |
| if err != nil { |
| return nil, err |
| } |
| |
| return b.builder.token(enc.Decrypt, []jose.Header{enc.Header}) |
| } |
| |
| func (b *encryptedBuilder) encrypt() (*jose.JSONWebEncryption, error) { |
| if b.err != nil { |
| return nil, b.err |
| } |
| |
| p, err := json.Marshal(b.payload) |
| if err != nil { |
| return nil, err |
| } |
| |
| return b.enc.Encrypt(p) |
| } |
| |
| func (b *nestedBuilder) Claims(i interface{}) NestedBuilder { |
| return &nestedBuilder{ |
| builder: b.builder.claims(i), |
| sig: b.sig, |
| enc: b.enc, |
| } |
| } |
| |
| func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) { |
| enc, err := b.signAndEncrypt() |
| if err != nil { |
| return nil, err |
| } |
| |
| return &NestedJSONWebToken{ |
| enc: enc, |
| Headers: []jose.Header{enc.Header}, |
| }, nil |
| } |
| |
| func (b *nestedBuilder) CompactSerialize() (string, error) { |
| enc, err := b.signAndEncrypt() |
| if err != nil { |
| return "", err |
| } |
| |
| return enc.CompactSerialize() |
| } |
| |
| func (b *nestedBuilder) FullSerialize() (string, error) { |
| enc, err := b.signAndEncrypt() |
| if err != nil { |
| return "", err |
| } |
| |
| return enc.FullSerialize(), nil |
| } |
| |
| func (b *nestedBuilder) signAndEncrypt() (*jose.JSONWebEncryption, error) { |
| if b.err != nil { |
| return nil, b.err |
| } |
| |
| p, err := json.Marshal(b.payload) |
| if err != nil { |
| return nil, err |
| } |
| |
| sig, err := b.sig.Sign(p) |
| if err != nil { |
| return nil, err |
| } |
| |
| p2, err := sig.CompactSerialize() |
| if err != nil { |
| return nil, err |
| } |
| |
| return b.enc.Encrypt([]byte(p2)) |
| } |