Adopt square/go-jose for JWT support
Replace uses of go-oidc/jose with square/go-jose. The v3 release of go-oidc does not support any general-purpose JWT APIs and uses square/go-jose internally. This removes the dependency on the master version of go-oidc, which fixes fatal module import problems when importing cloudflared as a module. This fixes #592 Signed-off-by: James Peach <jpeach@cloudflare.com>
This commit is contained in:
parent
0ef161e7d9
commit
7dc86add7a
3
go.mod
3
go.mod
|
@ -7,7 +7,6 @@ require (
|
||||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
|
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
|
||||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
|
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
|
||||||
github.com/coredns/coredns v1.8.7
|
github.com/coredns/coredns v1.8.7
|
||||||
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73
|
|
||||||
github.com/coreos/go-oidc/v3 v3.1.0
|
github.com/coreos/go-oidc/v3 v3.1.0
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
|
@ -52,7 +51,7 @@ require (
|
||||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
|
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
|
||||||
google.golang.org/grpc v1.43.0 // indirect
|
google.golang.org/grpc v1.43.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
gopkg.in/square/go-jose.v2 v2.5.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -131,8 +131,6 @@ github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
|
||||||
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
||||||
github.com/coredns/coredns v1.8.7 h1:wVMjAnyFnY7Mc18AFO+9qbGD6ODPtdVUIlzoWrHr3hk=
|
github.com/coredns/coredns v1.8.7 h1:wVMjAnyFnY7Mc18AFO+9qbGD6ODPtdVUIlzoWrHr3hk=
|
||||||
github.com/coredns/coredns v1.8.7/go.mod h1:bFmbgEfeRz5aizL2VsQ5LRlsvJuXWkgG/MWG9zxqjVM=
|
github.com/coredns/coredns v1.8.7/go.mod h1:bFmbgEfeRz5aizL2VsQ5LRlsvJuXWkgG/MWG9zxqjVM=
|
||||||
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73 h1:7CNPV0LWRCa1FNmqg700pbXhzvmoaXKyfxWRkjRym7Q=
|
|
||||||
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
|
||||||
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
|
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
|
||||||
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
|
|
@ -15,10 +15,10 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
gossh "golang.org/x/crypto/ssh"
|
gossh "golang.org/x/crypto/ssh"
|
||||||
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
cfpath "github.com/cloudflare/cloudflared/token"
|
cfpath "github.com/cloudflare/cloudflared/token"
|
||||||
|
@ -87,18 +87,18 @@ func SignCert(token, pubKey string) (string, error) {
|
||||||
return "", errors.New("invalid token")
|
return "", errors.New("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := jose.ParseJWT(token)
|
object, err := jwt.ParseSigned(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to parse JWT")
|
return "", errors.Wrap(err, "failed to parse JWT")
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := jwt.Claims()
|
claims := jwt.Claims{}
|
||||||
if err != nil {
|
if err := object.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
||||||
return "", errors.Wrap(err, "failed to retrieve JWT claims")
|
return "", errors.Wrap(err, "failed to retrieve JWT claims")
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer, _, err := claims.StringClaim("iss")
|
issuer := claims.Issuer
|
||||||
if err != nil {
|
if issuer == "" {
|
||||||
return "", errors.Wrap(err, "failed to retrieve JWT iss")
|
return "", errors.Wrap(err, "failed to retrieve JWT iss")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -18,11 +19,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
"github.com/stretchr/testify/assert"
|
jwt "gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
cfpath "github.com/cloudflare/cloudflared/token"
|
cfpath "github.com/cloudflare/cloudflared/token"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -38,7 +40,7 @@ type signingArguments struct {
|
||||||
|
|
||||||
func TestCertGenSuccess(t *testing.T) {
|
func TestCertGenSuccess(t *testing.T) {
|
||||||
url, _ := url.Parse("https://cf-test-access.com/testpath")
|
url, _ := url.Parse("https://cf-test-access.com/testpath")
|
||||||
token := tokenGenerator()
|
token := tokenGenerator(t)
|
||||||
|
|
||||||
fullName, err := cfpath.GenerateSSHCertFilePathFromURL(url, keyName)
|
fullName, err := cfpath.GenerateSSHCertFilePathFromURL(url, keyName)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -96,23 +98,26 @@ func TestCertGenSuccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tokenGenerator() string {
|
func tokenGenerator(t *testing.T) string {
|
||||||
iat := time.Now().Unix()
|
|
||||||
exp := time.Now().Add(time.Minute * 5).Unix()
|
|
||||||
claims := jose.Claims{}
|
|
||||||
claims.Add("aud", audTest)
|
|
||||||
claims.Add("iat", iat)
|
|
||||||
claims.Add("nonce", nonceTest)
|
|
||||||
claims.Add("exp", exp)
|
|
||||||
|
|
||||||
k, err := rsa.GenerateKey(rand.Reader, 512)
|
k, err := rsa.GenerateKey(rand.Reader, 512)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return ""
|
|
||||||
}
|
opts := jose.SignerOptions{}
|
||||||
signer := jose.NewSignerRSA("asdf", *k)
|
signer, err := jose.NewSigner(
|
||||||
token, terr := jose.NewSignedJWT(claims, signer)
|
jose.SigningKey{
|
||||||
if terr != nil {
|
Algorithm: jose.RS256,
|
||||||
return ""
|
Key: k,
|
||||||
}
|
},
|
||||||
return token.Encode()
|
opts.WithType("JWT"),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
token, err := jwt.Signed(signer).Claims(&jwt.Claims{
|
||||||
|
Audience: []string{audTest},
|
||||||
|
Expiry: jwt.NewNumericDate(time.Now().Add(time.Minute * 5)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
}).CompactSerialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return token
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/retry"
|
"github.com/cloudflare/cloudflared/retry"
|
||||||
|
@ -46,26 +46,11 @@ type signalHandler struct {
|
||||||
signals []os.Signal
|
signals []os.Signal
|
||||||
}
|
}
|
||||||
|
|
||||||
type jwtPayload struct {
|
|
||||||
Aud []string `json:"aud"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Exp int `json:"exp"`
|
|
||||||
Iat int `json:"iat"`
|
|
||||||
Nbf int `json:"nbf"`
|
|
||||||
Iss string `json:"iss"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Subt string `json:"sub"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type transferServiceResponse struct {
|
type transferServiceResponse struct {
|
||||||
AppToken string `json:"app_token"`
|
AppToken string `json:"app_token"`
|
||||||
OrgToken string `json:"org_token"`
|
OrgToken string `json:"org_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p jwtPayload) isExpired() bool {
|
|
||||||
return int(time.Now().Unix()) > p.Exp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *signalHandler) register(handler func()) {
|
func (s *signalHandler) register(handler func()) {
|
||||||
s.sigChannel = make(chan os.Signal, 1)
|
s.sigChannel = make(chan os.Signal, 1)
|
||||||
signal.Notify(s.sigChannel, s.signals...)
|
signal.Notify(s.sigChannel, s.signals...)
|
||||||
|
@ -337,21 +322,8 @@ func GetOrgTokenIfExists(authDomain string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
token, err := getTokenIfExists(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var payload jwtPayload
|
|
||||||
err = json.Unmarshal(token.Payload, &payload)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if payload.isExpired() {
|
return getTokenIfExists(path)
|
||||||
err := os.Remove(path)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return token.Encode(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
|
func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
|
||||||
|
@ -359,39 +331,37 @@ func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
token, err := getTokenIfExists(path)
|
|
||||||
if err != nil {
|
return getTokenIfExists(path)
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
var payload jwtPayload
|
|
||||||
err = json.Unmarshal(token.Payload, &payload)
|
// GetTokenIfExists will return the token from local storage if it exists
|
||||||
|
// and not expired. Expired tokens will be removed.
|
||||||
|
func getTokenIfExists(path string) (string, error) {
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.isExpired() {
|
token, err := jwt.ParseSigned(string(content))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := jwt.Claims{}
|
||||||
|
if err := token.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims.Expiry == nil || claims.Expiry.Time().Before(time.Now()) {
|
||||||
err := os.Remove(path)
|
err := os.Remove(path)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return token.Encode(), nil
|
|
||||||
|
|
||||||
|
return string(content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTokenIfExists will return the token from local storage if it exists and not expired
|
// RemoveTokenIfExists removes the token from local storage if it exists
|
||||||
func getTokenIfExists(path string) (*jose.JWT, error) {
|
|
||||||
content, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
token, err := jose.ParseJWT(string(content))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveTokenIfExists removes the a token from local storage if it exists
|
|
||||||
func RemoveTokenIfExists(appInfo *AppInfo) error {
|
func RemoveTokenIfExists(appInfo *AppInfo) error {
|
||||||
path, err := GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName)
|
path, err := GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,202 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
CoreOS Project
|
|
||||||
Copyright 2014 CoreOS, Inc
|
|
||||||
|
|
||||||
This product includes software developed at CoreOS, Inc.
|
|
||||||
(http://www.coreos.com/).
|
|
|
@ -1,126 +0,0 @@
|
||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Claims map[string]interface{}
|
|
||||||
|
|
||||||
func (c Claims) Add(name string, value interface{}) {
|
|
||||||
c[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) StringClaim(name string) (string, bool, error) {
|
|
||||||
cl, ok := c[name]
|
|
||||||
if !ok {
|
|
||||||
return "", false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := cl.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", false, fmt.Errorf("unable to parse claim as string: %v", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) StringsClaim(name string) ([]string, bool, error) {
|
|
||||||
cl, ok := c[name]
|
|
||||||
if !ok {
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := cl.([]string); ok {
|
|
||||||
return v, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// When unmarshaled, []string will become []interface{}.
|
|
||||||
if v, ok := cl.([]interface{}); ok {
|
|
||||||
var ret []string
|
|
||||||
for _, vv := range v {
|
|
||||||
str, ok := vv.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
|
|
||||||
}
|
|
||||||
ret = append(ret, str)
|
|
||||||
}
|
|
||||||
return ret, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) Int64Claim(name string) (int64, bool, error) {
|
|
||||||
cl, ok := c[name]
|
|
||||||
if !ok {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := cl.(int64)
|
|
||||||
if !ok {
|
|
||||||
vf, ok := cl.(float64)
|
|
||||||
if !ok {
|
|
||||||
return 0, false, fmt.Errorf("unable to parse claim as int64: %v", name)
|
|
||||||
}
|
|
||||||
v = int64(vf)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) Float64Claim(name string) (float64, bool, error) {
|
|
||||||
cl, ok := c[name]
|
|
||||||
if !ok {
|
|
||||||
return 0, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, ok := cl.(float64)
|
|
||||||
if !ok {
|
|
||||||
vi, ok := cl.(int64)
|
|
||||||
if !ok {
|
|
||||||
return 0, false, fmt.Errorf("unable to parse claim as float64: %v", name)
|
|
||||||
}
|
|
||||||
v = float64(vi)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Claims) TimeClaim(name string) (time.Time, bool, error) {
|
|
||||||
v, ok, err := c.Float64Claim(name)
|
|
||||||
if !ok || err != nil {
|
|
||||||
return time.Time{}, ok, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := math.Trunc(v)
|
|
||||||
ns := (v - s) * math.Pow(10, 9)
|
|
||||||
return time.Unix(int64(s), int64(ns)).UTC(), true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeClaims(payload []byte) (Claims, error) {
|
|
||||||
var c Claims
|
|
||||||
if err := json.Unmarshal(payload, &c); err != nil {
|
|
||||||
return nil, fmt.Errorf("malformed JWT claims, unable to decode: %v", err)
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalClaims(c Claims) ([]byte, error) {
|
|
||||||
b, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeClaims(c Claims) (string, error) {
|
|
||||||
b, err := marshalClaims(c)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeSegment(b), nil
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
// Package jose is DEPRECATED. Use gopkg.in/square/go-jose.v2 instead.
|
|
||||||
package jose
|
|
|
@ -1,112 +0,0 @@
|
||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
HeaderMediaType = "typ"
|
|
||||||
HeaderKeyAlgorithm = "alg"
|
|
||||||
HeaderKeyID = "kid"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Encryption Algorithm Header Parameter Values for JWS
|
|
||||||
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-6
|
|
||||||
AlgHS256 = "HS256"
|
|
||||||
AlgHS384 = "HS384"
|
|
||||||
AlgHS512 = "HS512"
|
|
||||||
AlgRS256 = "RS256"
|
|
||||||
AlgRS384 = "RS384"
|
|
||||||
AlgRS512 = "RS512"
|
|
||||||
AlgES256 = "ES256"
|
|
||||||
AlgES384 = "ES384"
|
|
||||||
AlgES512 = "ES512"
|
|
||||||
AlgPS256 = "PS256"
|
|
||||||
AlgPS384 = "PS384"
|
|
||||||
AlgPS512 = "PS512"
|
|
||||||
AlgNone = "none"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Algorithm Header Parameter Values for JWE
|
|
||||||
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-4.1
|
|
||||||
AlgRSA15 = "RSA1_5"
|
|
||||||
AlgRSAOAEP = "RSA-OAEP"
|
|
||||||
AlgRSAOAEP256 = "RSA-OAEP-256"
|
|
||||||
AlgA128KW = "A128KW"
|
|
||||||
AlgA192KW = "A192KW"
|
|
||||||
AlgA256KW = "A256KW"
|
|
||||||
AlgDir = "dir"
|
|
||||||
AlgECDHES = "ECDH-ES"
|
|
||||||
AlgECDHESA128KW = "ECDH-ES+A128KW"
|
|
||||||
AlgECDHESA192KW = "ECDH-ES+A192KW"
|
|
||||||
AlgECDHESA256KW = "ECDH-ES+A256KW"
|
|
||||||
AlgA128GCMKW = "A128GCMKW"
|
|
||||||
AlgA192GCMKW = "A192GCMKW"
|
|
||||||
AlgA256GCMKW = "A256GCMKW"
|
|
||||||
AlgPBES2HS256A128KW = "PBES2-HS256+A128KW"
|
|
||||||
AlgPBES2HS384A192KW = "PBES2-HS384+A192KW"
|
|
||||||
AlgPBES2HS512A256KW = "PBES2-HS512+A256KW"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Encryption Algorithm Header Parameter Values for JWE
|
|
||||||
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-22
|
|
||||||
EncA128CBCHS256 = "A128CBC-HS256"
|
|
||||||
EncA128CBCHS384 = "A128CBC-HS384"
|
|
||||||
EncA256CBCHS512 = "A256CBC-HS512"
|
|
||||||
EncA128GCM = "A128GCM"
|
|
||||||
EncA192GCM = "A192GCM"
|
|
||||||
EncA256GCM = "A256GCM"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JOSEHeader map[string]string
|
|
||||||
|
|
||||||
func (j JOSEHeader) Validate() error {
|
|
||||||
if _, exists := j[HeaderKeyAlgorithm]; !exists {
|
|
||||||
return fmt.Errorf("header missing %q parameter", HeaderKeyAlgorithm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeHeader(seg string) (JOSEHeader, error) {
|
|
||||||
b, err := decodeSegment(seg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var h JOSEHeader
|
|
||||||
err = json.Unmarshal(b, &h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeHeader(h JOSEHeader) (string, error) {
|
|
||||||
b, err := json.Marshal(h)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeSegment(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode JWT specific base64url encoding with padding stripped
|
|
||||||
func decodeSegment(seg string) ([]byte, error) {
|
|
||||||
if l := len(seg) % 4; l != 0 {
|
|
||||||
seg += strings.Repeat("=", 4-l)
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.DecodeString(seg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode JWT specific base64url encoding with padding stripped
|
|
||||||
func encodeSegment(seg []byte) string {
|
|
||||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
|
|
||||||
}
|
|
|
@ -1,135 +0,0 @@
|
||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JSON Web Key
|
|
||||||
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-36#page-5
|
|
||||||
type JWK struct {
|
|
||||||
ID string
|
|
||||||
Type string
|
|
||||||
Alg string
|
|
||||||
Use string
|
|
||||||
Exponent int
|
|
||||||
Modulus *big.Int
|
|
||||||
Secret []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type jwkJSON struct {
|
|
||||||
ID string `json:"kid"`
|
|
||||||
Type string `json:"kty"`
|
|
||||||
Alg string `json:"alg"`
|
|
||||||
Use string `json:"use"`
|
|
||||||
Exponent string `json:"e"`
|
|
||||||
Modulus string `json:"n"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWK) MarshalJSON() ([]byte, error) {
|
|
||||||
t := jwkJSON{
|
|
||||||
ID: j.ID,
|
|
||||||
Type: j.Type,
|
|
||||||
Alg: j.Alg,
|
|
||||||
Use: j.Use,
|
|
||||||
Exponent: encodeExponent(j.Exponent),
|
|
||||||
Modulus: encodeModulus(j.Modulus),
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(&t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWK) UnmarshalJSON(data []byte) error {
|
|
||||||
var t jwkJSON
|
|
||||||
err := json.Unmarshal(data, &t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e, err := decodeExponent(t.Exponent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := decodeModulus(t.Modulus)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
j.ID = t.ID
|
|
||||||
j.Type = t.Type
|
|
||||||
j.Alg = t.Alg
|
|
||||||
j.Use = t.Use
|
|
||||||
j.Exponent = e
|
|
||||||
j.Modulus = n
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type JWKSet struct {
|
|
||||||
Keys []JWK `json:"keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeExponent(e string) (int, error) {
|
|
||||||
decE, err := decodeBase64URLPaddingOptional(e)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var eBytes []byte
|
|
||||||
if len(decE) < 8 {
|
|
||||||
eBytes = make([]byte, 8-len(decE), 8)
|
|
||||||
eBytes = append(eBytes, decE...)
|
|
||||||
} else {
|
|
||||||
eBytes = decE
|
|
||||||
}
|
|
||||||
eReader := bytes.NewReader(eBytes)
|
|
||||||
var E uint64
|
|
||||||
err = binary.Read(eReader, binary.BigEndian, &E)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return int(E), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeExponent(e int) string {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(b, uint64(e))
|
|
||||||
var idx int
|
|
||||||
for ; idx < 8; idx++ {
|
|
||||||
if b[idx] != 0x0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base64.RawURLEncoding.EncodeToString(b[idx:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turns a URL encoded modulus of a key into a big int.
|
|
||||||
func decodeModulus(n string) (*big.Int, error) {
|
|
||||||
decN, err := decodeBase64URLPaddingOptional(n)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
N := big.NewInt(0)
|
|
||||||
N.SetBytes(decN)
|
|
||||||
return N, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeModulus(n *big.Int) string {
|
|
||||||
return base64.RawURLEncoding.EncodeToString(n.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeBase64URLPaddingOptional decodes Base64 whether there is padding or not.
|
|
||||||
// The stdlib version currently doesn't handle this.
|
|
||||||
// We can get rid of this is if this bug:
|
|
||||||
// https://github.com/golang/go/issues/4237
|
|
||||||
// ever closes.
|
|
||||||
func decodeBase64URLPaddingOptional(e string) ([]byte, error) {
|
|
||||||
if m := len(e) % 4; m != 0 {
|
|
||||||
e += strings.Repeat("=", 4-m)
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.DecodeString(e)
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JWS struct {
|
|
||||||
RawHeader string
|
|
||||||
Header JOSEHeader
|
|
||||||
RawPayload string
|
|
||||||
Payload []byte
|
|
||||||
Signature []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a raw encoded JWS token parses it and verifies the structure.
|
|
||||||
func ParseJWS(raw string) (JWS, error) {
|
|
||||||
parts := strings.Split(raw, ".")
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, only %d segments", len(parts))
|
|
||||||
}
|
|
||||||
|
|
||||||
rawSig := parts[2]
|
|
||||||
jws := JWS{
|
|
||||||
RawHeader: parts[0],
|
|
||||||
RawPayload: parts[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
header, err := decodeHeader(jws.RawHeader)
|
|
||||||
if err != nil {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, unable to decode header, %s", err)
|
|
||||||
}
|
|
||||||
if err = header.Validate(); err != nil {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, %s", err)
|
|
||||||
}
|
|
||||||
jws.Header = header
|
|
||||||
|
|
||||||
payload, err := decodeSegment(jws.RawPayload)
|
|
||||||
if err != nil {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, unable to decode payload: %s", err)
|
|
||||||
}
|
|
||||||
jws.Payload = payload
|
|
||||||
|
|
||||||
sig, err := decodeSegment(rawSig)
|
|
||||||
if err != nil {
|
|
||||||
return JWS{}, fmt.Errorf("malformed JWS, unable to decode signature: %s", err)
|
|
||||||
}
|
|
||||||
jws.Signature = sig
|
|
||||||
|
|
||||||
return jws, nil
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
package jose
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
type JWT JWS
|
|
||||||
|
|
||||||
func ParseJWT(token string) (jwt JWT, err error) {
|
|
||||||
jws, err := ParseJWS(token)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return JWT(jws), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJWT(header JOSEHeader, claims Claims) (jwt JWT, err error) {
|
|
||||||
jwt = JWT{}
|
|
||||||
|
|
||||||
jwt.Header = header
|
|
||||||
jwt.Header[HeaderMediaType] = "JWT"
|
|
||||||
|
|
||||||
claimBytes, err := marshalClaims(claims)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jwt.Payload = claimBytes
|
|
||||||
|
|
||||||
eh, err := encodeHeader(header)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jwt.RawHeader = eh
|
|
||||||
|
|
||||||
ec, err := encodeClaims(claims)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jwt.RawPayload = ec
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWT) KeyID() (string, bool) {
|
|
||||||
kID, ok := j.Header[HeaderKeyID]
|
|
||||||
return kID, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *JWT) Claims() (Claims, error) {
|
|
||||||
return decodeClaims(j.Payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoded data part of the token which may be signed.
|
|
||||||
func (j *JWT) Data() string {
|
|
||||||
return strings.Join([]string{j.RawHeader, j.RawPayload}, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full encoded JWT token string in format: header.claims.signature
|
|
||||||
func (j *JWT) Encode() string {
|
|
||||||
d := j.Data()
|
|
||||||
s := encodeSegment(j.Signature)
|
|
||||||
return strings.Join([]string{d, s}, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSignedJWT(claims Claims, s Signer) (*JWT, error) {
|
|
||||||
header := JOSEHeader{
|
|
||||||
HeaderKeyAlgorithm: s.Alg(),
|
|
||||||
HeaderKeyID: s.ID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt, err := NewJWT(header, claims)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, err := s.Sign([]byte(jwt.Data()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jwt.Signature = sig
|
|
||||||
|
|
||||||
return &jwt, nil
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Verifier interface {
|
|
||||||
ID() string
|
|
||||||
Alg() string
|
|
||||||
Verify(sig []byte, data []byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Signer interface {
|
|
||||||
Verifier
|
|
||||||
Sign(data []byte) (sig []byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVerifier(jwk JWK) (Verifier, error) {
|
|
||||||
if jwk.Type != "RSA" {
|
|
||||||
return nil, fmt.Errorf("unsupported key type %q", jwk.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewVerifierRSA(jwk)
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package jose
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VerifierRSA struct {
|
|
||||||
KeyID string
|
|
||||||
Hash crypto.Hash
|
|
||||||
PublicKey rsa.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
type SignerRSA struct {
|
|
||||||
PrivateKey rsa.PrivateKey
|
|
||||||
VerifierRSA
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVerifierRSA(jwk JWK) (*VerifierRSA, error) {
|
|
||||||
if jwk.Alg != "" && jwk.Alg != "RS256" {
|
|
||||||
return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := VerifierRSA{
|
|
||||||
KeyID: jwk.ID,
|
|
||||||
PublicKey: rsa.PublicKey{
|
|
||||||
N: jwk.Modulus,
|
|
||||||
E: jwk.Exponent,
|
|
||||||
},
|
|
||||||
Hash: crypto.SHA256,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSignerRSA(kid string, key rsa.PrivateKey) *SignerRSA {
|
|
||||||
return &SignerRSA{
|
|
||||||
PrivateKey: key,
|
|
||||||
VerifierRSA: VerifierRSA{
|
|
||||||
KeyID: kid,
|
|
||||||
PublicKey: key.PublicKey,
|
|
||||||
Hash: crypto.SHA256,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VerifierRSA) ID() string {
|
|
||||||
return v.KeyID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VerifierRSA) Alg() string {
|
|
||||||
return "RS256"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VerifierRSA) Verify(sig []byte, data []byte) error {
|
|
||||||
h := v.Hash.New()
|
|
||||||
h.Write(data)
|
|
||||||
return rsa.VerifyPKCS1v15(&v.PublicKey, v.Hash, h.Sum(nil), sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignerRSA) Sign(data []byte) ([]byte, error) {
|
|
||||||
h := s.Hash.New()
|
|
||||||
h.Write(data)
|
|
||||||
return rsa.SignPKCS1v15(rand.Reader, &s.PrivateKey, s.Hash, h.Sum(nil))
|
|
||||||
}
|
|
|
@ -0,0 +1,334 @@
|
||||||
|
/*-
|
||||||
|
* 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))
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*-
|
||||||
|
* 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 (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Claims represents public claim values (as specified in RFC 7519).
|
||||||
|
type Claims struct {
|
||||||
|
Issuer string `json:"iss,omitempty"`
|
||||||
|
Subject string `json:"sub,omitempty"`
|
||||||
|
Audience Audience `json:"aud,omitempty"`
|
||||||
|
Expiry *NumericDate `json:"exp,omitempty"`
|
||||||
|
NotBefore *NumericDate `json:"nbf,omitempty"`
|
||||||
|
IssuedAt *NumericDate `json:"iat,omitempty"`
|
||||||
|
ID string `json:"jti,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumericDate represents date and time as the number of seconds since the
|
||||||
|
// epoch, including leap seconds. Non-integer values can be represented
|
||||||
|
// in the serialized format, but we round to the nearest second.
|
||||||
|
type NumericDate int64
|
||||||
|
|
||||||
|
// NewNumericDate constructs NumericDate from time.Time value.
|
||||||
|
func NewNumericDate(t time.Time) *NumericDate {
|
||||||
|
if t.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// While RFC 7519 technically states that NumericDate values may be
|
||||||
|
// non-integer values, we don't bother serializing timestamps in
|
||||||
|
// claims with sub-second accurancy and just round to the nearest
|
||||||
|
// second instead. Not convined sub-second accuracy is useful here.
|
||||||
|
out := NumericDate(t.Unix())
|
||||||
|
return &out
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON serializes the given NumericDate into its JSON representation.
|
||||||
|
func (n NumericDate) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.FormatInt(int64(n), 10)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON reads a date from its JSON representation.
|
||||||
|
func (n *NumericDate) UnmarshalJSON(b []byte) error {
|
||||||
|
s := string(b)
|
||||||
|
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return ErrUnmarshalNumericDate
|
||||||
|
}
|
||||||
|
|
||||||
|
*n = NumericDate(f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns time.Time representation of NumericDate.
|
||||||
|
func (n *NumericDate) Time() time.Time {
|
||||||
|
if n == nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return time.Unix(int64(*n), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audience represents the recipients that the token is intended for.
|
||||||
|
type Audience []string
|
||||||
|
|
||||||
|
// UnmarshalJSON reads an audience from its JSON representation.
|
||||||
|
func (s *Audience) UnmarshalJSON(b []byte) error {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
*s = []string{v}
|
||||||
|
case []interface{}:
|
||||||
|
a := make([]string, len(v))
|
||||||
|
for i, e := range v {
|
||||||
|
s, ok := e.(string)
|
||||||
|
if !ok {
|
||||||
|
return ErrUnmarshalAudience
|
||||||
|
}
|
||||||
|
a[i] = s
|
||||||
|
}
|
||||||
|
*s = a
|
||||||
|
default:
|
||||||
|
return ErrUnmarshalAudience
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Audience) Contains(v string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2017 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 provides an implementation of the JSON Web Token standard.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package jwt
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*-
|
||||||
|
* 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 "errors"
|
||||||
|
|
||||||
|
// ErrUnmarshalAudience indicates that aud claim could not be unmarshalled.
|
||||||
|
var ErrUnmarshalAudience = errors.New("square/go-jose/jwt: expected string or array value to unmarshal to Audience")
|
||||||
|
|
||||||
|
// ErrUnmarshalNumericDate indicates that JWT NumericDate could not be unmarshalled.
|
||||||
|
var ErrUnmarshalNumericDate = errors.New("square/go-jose/jwt: expected number value to unmarshal NumericDate")
|
||||||
|
|
||||||
|
// ErrInvalidClaims indicates that given claims have invalid type.
|
||||||
|
var ErrInvalidClaims = errors.New("square/go-jose/jwt: expected claims to be value convertible into JSON object")
|
||||||
|
|
||||||
|
// ErrInvalidIssuer indicates invalid iss claim.
|
||||||
|
var ErrInvalidIssuer = errors.New("square/go-jose/jwt: validation failed, invalid issuer claim (iss)")
|
||||||
|
|
||||||
|
// ErrInvalidSubject indicates invalid sub claim.
|
||||||
|
var ErrInvalidSubject = errors.New("square/go-jose/jwt: validation failed, invalid subject claim (sub)")
|
||||||
|
|
||||||
|
// ErrInvalidAudience indicated invalid aud claim.
|
||||||
|
var ErrInvalidAudience = errors.New("square/go-jose/jwt: validation failed, invalid audience claim (aud)")
|
||||||
|
|
||||||
|
// ErrInvalidID indicates invalid jti claim.
|
||||||
|
var ErrInvalidID = errors.New("square/go-jose/jwt: validation failed, invalid ID claim (jti)")
|
||||||
|
|
||||||
|
// ErrNotValidYet indicates that token is used before time indicated in nbf claim.
|
||||||
|
var ErrNotValidYet = errors.New("square/go-jose/jwt: validation failed, token not valid yet (nbf)")
|
||||||
|
|
||||||
|
// ErrExpired indicates that token is used after expiry time indicated in exp claim.
|
||||||
|
var ErrExpired = errors.New("square/go-jose/jwt: validation failed, token is expired (exp)")
|
||||||
|
|
||||||
|
// ErrIssuedInTheFuture indicates that the iat field is in the future.
|
||||||
|
var ErrIssuedInTheFuture = errors.New("square/go-jose/jwt: validation field, token issued in the future (iat)")
|
||||||
|
|
||||||
|
// ErrInvalidContentType indicates that token requires JWT cty header.
|
||||||
|
var ErrInvalidContentType = errors.New("square/go-jose/jwt: expected content type to be JWT (cty header)")
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*-
|
||||||
|
* 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 (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
|
||||||
|
type JSONWebToken struct {
|
||||||
|
payload func(k interface{}) ([]byte, error)
|
||||||
|
unverifiedPayload func() []byte
|
||||||
|
Headers []jose.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
type NestedJSONWebToken struct {
|
||||||
|
enc *jose.JSONWebEncryption
|
||||||
|
Headers []jose.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims deserializes a JSONWebToken into dest using the provided key.
|
||||||
|
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
|
||||||
|
payloadKey := tryJWKS(t.Headers, key)
|
||||||
|
|
||||||
|
b, err := t.payload(payloadKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range dest {
|
||||||
|
if err := json.Unmarshal(b, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsafeClaimsWithoutVerification deserializes the claims of a
|
||||||
|
// JSONWebToken into the dests. For signed JWTs, the claims are not
|
||||||
|
// verified. This function won't work for encrypted JWTs.
|
||||||
|
func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{}) error {
|
||||||
|
if t.unverifiedPayload == nil {
|
||||||
|
return fmt.Errorf("square/go-jose: Cannot get unverified claims")
|
||||||
|
}
|
||||||
|
claims := t.unverifiedPayload()
|
||||||
|
for _, d := range dest {
|
||||||
|
if err := json.Unmarshal(claims, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
|
||||||
|
key := tryJWKS(t.Headers, decryptionKey)
|
||||||
|
|
||||||
|
b, err := t.enc.Decrypt(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := ParseSigned(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSigned parses token from JWS form.
|
||||||
|
func ParseSigned(s string) (*JSONWebToken, error) {
|
||||||
|
sig, err := jose.ParseSigned(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
headers := make([]jose.Header, len(sig.Signatures))
|
||||||
|
for i, signature := range sig.Signatures {
|
||||||
|
headers[i] = signature.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JSONWebToken{
|
||||||
|
payload: sig.Verify,
|
||||||
|
unverifiedPayload: sig.UnsafePayloadWithoutVerification,
|
||||||
|
Headers: headers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEncrypted parses token from JWE form.
|
||||||
|
func ParseEncrypted(s string) (*JSONWebToken, error) {
|
||||||
|
enc, err := jose.ParseEncrypted(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JSONWebToken{
|
||||||
|
payload: enc.Decrypt,
|
||||||
|
Headers: []jose.Header{enc.Header},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
|
||||||
|
func ParseSignedAndEncrypted(s string) (*NestedJSONWebToken, error) {
|
||||||
|
enc, err := jose.ParseEncrypted(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
|
||||||
|
if strings.ToUpper(contentType) != "JWT" {
|
||||||
|
return nil, ErrInvalidContentType
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NestedJSONWebToken{
|
||||||
|
enc: enc,
|
||||||
|
Headers: []jose.Header{enc.Header},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryJWKS(headers []jose.Header, key interface{}) interface{} {
|
||||||
|
jwks, ok := key.(*jose.JSONWebKeySet)
|
||||||
|
if !ok {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid string
|
||||||
|
for _, header := range headers {
|
||||||
|
if header.KeyID != "" {
|
||||||
|
kid = header.KeyID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if kid == "" {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := jwks.Key(kid)
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys[0].Key
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*-
|
||||||
|
* 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 "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultLeeway defines the default leeway for matching NotBefore/Expiry claims.
|
||||||
|
DefaultLeeway = 1.0 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expected defines values used for protected claims validation.
|
||||||
|
// If field has zero value then validation is skipped.
|
||||||
|
type Expected struct {
|
||||||
|
// Issuer matches the "iss" claim exactly.
|
||||||
|
Issuer string
|
||||||
|
// Subject matches the "sub" claim exactly.
|
||||||
|
Subject string
|
||||||
|
// Audience matches the values in "aud" claim, regardless of their order.
|
||||||
|
Audience Audience
|
||||||
|
// ID matches the "jti" claim exactly.
|
||||||
|
ID string
|
||||||
|
// Time matches the "exp", "nbf" and "iat" claims with leeway.
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTime copies expectations with new time.
|
||||||
|
func (e Expected) WithTime(t time.Time) Expected {
|
||||||
|
e.Time = t
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks claims in a token against expected values.
|
||||||
|
// A default leeway value of one minute is used to compare time values.
|
||||||
|
//
|
||||||
|
// The default leeway will cause the token to be deemed valid until one
|
||||||
|
// minute after the expiration time. If you're a server application that
|
||||||
|
// wants to give an extra minute to client tokens, use this
|
||||||
|
// function. If you're a client application wondering if the server
|
||||||
|
// will accept your token, use ValidateWithLeeway with a leeway <=0,
|
||||||
|
// otherwise this function might make you think a token is valid when
|
||||||
|
// it is not.
|
||||||
|
func (c Claims) Validate(e Expected) error {
|
||||||
|
return c.ValidateWithLeeway(e, DefaultLeeway)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateWithLeeway checks claims in a token against expected values. A
|
||||||
|
// custom leeway may be specified for comparing time values. You may pass a
|
||||||
|
// zero value to check time values with no leeway, but you should not that
|
||||||
|
// numeric date values are rounded to the nearest second and sub-second
|
||||||
|
// precision is not supported.
|
||||||
|
//
|
||||||
|
// The leeway gives some extra time to the token from the server's
|
||||||
|
// point of view. That is, if the token is expired, ValidateWithLeeway
|
||||||
|
// will still accept the token for 'leeway' amount of time. This fails
|
||||||
|
// if you're using this function to check if a server will accept your
|
||||||
|
// token, because it will think the token is valid even after it
|
||||||
|
// expires. So if you're a client validating if the token is valid to
|
||||||
|
// be submitted to a server, use leeway <=0, if you're a server
|
||||||
|
// validation a token, use leeway >=0.
|
||||||
|
func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
|
||||||
|
if e.Issuer != "" && e.Issuer != c.Issuer {
|
||||||
|
return ErrInvalidIssuer
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Subject != "" && e.Subject != c.Subject {
|
||||||
|
return ErrInvalidSubject
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.ID != "" && e.ID != c.ID {
|
||||||
|
return ErrInvalidID
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Audience) != 0 {
|
||||||
|
for _, v := range e.Audience {
|
||||||
|
if !c.Audience.Contains(v) {
|
||||||
|
return ErrInvalidAudience
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !e.Time.IsZero() {
|
||||||
|
if c.NotBefore != nil && e.Time.Add(leeway).Before(c.NotBefore.Time()) {
|
||||||
|
return ErrNotValidYet
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Expiry != nil && e.Time.Add(-leeway).After(c.Expiry.Time()) {
|
||||||
|
return ErrExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssuedAt is optional but cannot be in the future. This is not required by the RFC, but
|
||||||
|
// something is misconfigured if this happens and we should not trust it.
|
||||||
|
if c.IssuedAt != nil && e.Time.Add(leeway).Before(c.IssuedAt.Time()) {
|
||||||
|
return ErrIssuedInTheFuture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -62,9 +62,6 @@ github.com/coredns/coredns/plugin/pkg/transport
|
||||||
github.com/coredns/coredns/plugin/pkg/uniq
|
github.com/coredns/coredns/plugin/pkg/uniq
|
||||||
github.com/coredns/coredns/plugin/test
|
github.com/coredns/coredns/plugin/test
|
||||||
github.com/coredns/coredns/request
|
github.com/coredns/coredns/request
|
||||||
# github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73
|
|
||||||
## explicit
|
|
||||||
github.com/coreos/go-oidc/jose
|
|
||||||
# github.com/coreos/go-oidc/v3 v3.1.0
|
# github.com/coreos/go-oidc/v3 v3.1.0
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/coreos/go-oidc/v3/oidc
|
github.com/coreos/go-oidc/v3/oidc
|
||||||
|
@ -537,6 +534,7 @@ gopkg.in/natefinch/lumberjack.v2
|
||||||
gopkg.in/square/go-jose.v2
|
gopkg.in/square/go-jose.v2
|
||||||
gopkg.in/square/go-jose.v2/cipher
|
gopkg.in/square/go-jose.v2/cipher
|
||||||
gopkg.in/square/go-jose.v2/json
|
gopkg.in/square/go-jose.v2/json
|
||||||
|
gopkg.in/square/go-jose.v2/jwt
|
||||||
# gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
|
# gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/tomb.v1
|
gopkg.in/tomb.v1
|
||||||
|
|
Loading…
Reference in New Issue