mirror of https://github.com/hashicorp/consul
335 lines
7.2 KiB
Go
335 lines
7.2 KiB
Go
|
/*-
|
||
|
* 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))
|
||
|
}
|