CC-796: Remove dependency on unsupported version of go-oidc
This commit is contained in:
parent
1b511b2d25
commit
9422ea8ed8
6
go.mod
6
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-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
|
||||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||||
|
@ -51,14 +50,15 @@ require (
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||||
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/coreos/go-oidc.v2 v2.1.0
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require gopkg.in/coreos/go-oidc.v2 v2.2.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||||
|
|
10
go.sum
10
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-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
@ -1052,15 +1050,15 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/coreos/go-oidc.v2 v2.1.0 h1:E8PjVFdj/SLDKB0hvb70KTbMbYVHjqztiQdSkIg8E+I=
|
gopkg.in/coreos/go-oidc.v2 v2.2.1 h1:MY5SZClJ7vhjKfr64a4nHAOV/c3WH2gB9BMrR64J1Mc=
|
||||||
gopkg.in/coreos/go-oidc.v2 v2.1.0/go.mod h1:fYaTe2FS96wZZwR17YTDHwG+Mw6fmyqJNxN2eNCGPCI=
|
gopkg.in/coreos/go-oidc.v2 v2.2.1/go.mod h1:fYaTe2FS96wZZwR17YTDHwG+Mw6fmyqJNxN2eNCGPCI=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A=
|
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||||
gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
@ -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,37 +87,33 @@ func SignCert(token, pubKey string) (string, error) {
|
||||||
return "", errors.New("invalid token")
|
return "", errors.New("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := jose.ParseJWT(token)
|
parsedToken, 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{}
|
||||||
|
err = parsedToken.UnsafeClaimsWithoutVerification(&claims)
|
||||||
if err != nil {
|
if 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")
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "failed to retrieve JWT iss")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := json.Marshal(&signPayload{
|
buf, err := json.Marshal(&signPayload{
|
||||||
PublicKey: pubKey,
|
PublicKey: pubKey,
|
||||||
JWT: token,
|
JWT: token,
|
||||||
Issuer: issuer,
|
Issuer: claims.Issuer,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to marshal signPayload")
|
return "", errors.Wrap(err, "failed to marshal signPayload")
|
||||||
}
|
}
|
||||||
var res *http.Response
|
var res *http.Response
|
||||||
if mockRequest != nil {
|
if mockRequest != nil {
|
||||||
res, err = mockRequest(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
|
res, err = mockRequest(claims.Issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
|
||||||
} else {
|
} else {
|
||||||
client := http.Client{
|
client := http.Client{
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
}
|
}
|
||||||
res, err = client.Post(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
|
res, err = client.Post(claims.Issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
package sshgen
|
package sshgen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -18,8 +16,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/jose"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
"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"
|
||||||
|
@ -97,22 +96,25 @@ func TestCertGenSuccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func tokenGenerator() string {
|
func tokenGenerator() string {
|
||||||
iat := time.Now().Unix()
|
iat := time.Now()
|
||||||
exp := time.Now().Add(time.Minute * 5).Unix()
|
exp := time.Now().Add(time.Minute * 5)
|
||||||
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)
|
claims := jwt.Claims{
|
||||||
|
Audience: jwt.Audience{audTest},
|
||||||
|
IssuedAt: jwt.NewNumericDate(iat),
|
||||||
|
Expiry: jwt.NewNumericDate(exp),
|
||||||
|
}
|
||||||
|
|
||||||
|
key := []byte("secret")
|
||||||
|
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key}, (&jose.SignerOptions{}).WithType("JWT"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
panic(err)
|
||||||
}
|
}
|
||||||
signer := jose.NewSignerRSA("asdf", *k)
|
|
||||||
token, terr := jose.NewSignedJWT(claims, signer)
|
signedToken, err := jwt.Signed(signer).Claims(claims).CompactSerialize()
|
||||||
if terr != nil {
|
if err != nil {
|
||||||
return ""
|
panic(err)
|
||||||
}
|
}
|
||||||
return token.Encode()
|
|
||||||
|
return signedToken
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/retry"
|
"github.com/cloudflare/cloudflared/retry"
|
||||||
|
@ -342,7 +342,7 @@ func GetOrgTokenIfExists(authDomain string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var payload jwtPayload
|
var payload jwtPayload
|
||||||
err = json.Unmarshal(token.Payload, &payload)
|
err = json.Unmarshal(token.UnsafePayloadWithoutVerification(), &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -351,7 +351,7 @@ func GetOrgTokenIfExists(authDomain string) (string, error) {
|
||||||
err := os.Remove(path)
|
err := os.Remove(path)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return token.Encode(), nil
|
return token.CompactSerialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
|
func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
|
||||||
|
@ -364,7 +364,7 @@ func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var payload jwtPayload
|
var payload jwtPayload
|
||||||
err = json.Unmarshal(token.Payload, &payload)
|
err = json.Unmarshal(token.UnsafePayloadWithoutVerification(), &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -373,22 +373,21 @@ func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
|
||||||
err := os.Remove(path)
|
err := os.Remove(path)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return token.Encode(), nil
|
return token.CompactSerialize()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTokenIfExists will return the token from local storage if it exists and not expired
|
// GetTokenIfExists will return the token from local storage if it exists and not expired
|
||||||
func getTokenIfExists(path string) (*jose.JWT, error) {
|
func getTokenIfExists(path string) (*jose.JSONWebSignature, error) {
|
||||||
content, err := ioutil.ReadFile(path)
|
content, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
token, err := jose.ParseJWT(string(content))
|
token, err := jose.ParseSigned(string(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return token, nil
|
||||||
return &token, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveTokenIfExists removes the a token from local storage if it exists
|
// RemoveTokenIfExists removes the a token from local storage if it exists
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -1,13 +1,13 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- "1.9"
|
- "1.12"
|
||||||
- "1.10"
|
- "1.13"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- go get -v -t github.com/coreos/go-oidc/...
|
- go get -v -t github.com/coreos/go-oidc/...
|
||||||
- go get golang.org/x/tools/cmd/cover
|
- go get golang.org/x/tools/cmd/cover
|
||||||
- go get github.com/golang/lint/golint
|
- go get golang.org/x/lint/golint
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./test
|
- ./test
|
||||||
|
|
|
@ -69,6 +69,7 @@ type Provider struct {
|
||||||
authURL string
|
authURL string
|
||||||
tokenURL string
|
tokenURL string
|
||||||
userInfoURL string
|
userInfoURL string
|
||||||
|
algorithms []string
|
||||||
|
|
||||||
// Raw claims returned by the server.
|
// Raw claims returned by the server.
|
||||||
rawClaims []byte
|
rawClaims []byte
|
||||||
|
@ -82,11 +83,27 @@ type cachedKeys struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type providerJSON struct {
|
type providerJSON struct {
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
AuthURL string `json:"authorization_endpoint"`
|
AuthURL string `json:"authorization_endpoint"`
|
||||||
TokenURL string `json:"token_endpoint"`
|
TokenURL string `json:"token_endpoint"`
|
||||||
JWKSURL string `json:"jwks_uri"`
|
JWKSURL string `json:"jwks_uri"`
|
||||||
UserInfoURL string `json:"userinfo_endpoint"`
|
UserInfoURL string `json:"userinfo_endpoint"`
|
||||||
|
Algorithms []string `json:"id_token_signing_alg_values_supported"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// supportedAlgorithms is a list of algorithms explicitly supported by this
|
||||||
|
// package. If a provider supports other algorithms, such as HS256 or none,
|
||||||
|
// those values won't be passed to the IDTokenVerifier.
|
||||||
|
var supportedAlgorithms = map[string]bool{
|
||||||
|
RS256: true,
|
||||||
|
RS384: true,
|
||||||
|
RS512: true,
|
||||||
|
ES256: true,
|
||||||
|
ES384: true,
|
||||||
|
ES512: true,
|
||||||
|
PS256: true,
|
||||||
|
PS384: true,
|
||||||
|
PS512: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
|
// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
|
||||||
|
@ -123,11 +140,18 @@ func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
|
||||||
if p.Issuer != issuer {
|
if p.Issuer != issuer {
|
||||||
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
|
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
|
||||||
}
|
}
|
||||||
|
var algs []string
|
||||||
|
for _, a := range p.Algorithms {
|
||||||
|
if supportedAlgorithms[a] {
|
||||||
|
algs = append(algs, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
return &Provider{
|
return &Provider{
|
||||||
issuer: p.Issuer,
|
issuer: p.Issuer,
|
||||||
authURL: p.AuthURL,
|
authURL: p.AuthURL,
|
||||||
tokenURL: p.TokenURL,
|
tokenURL: p.TokenURL,
|
||||||
userInfoURL: p.UserInfoURL,
|
userInfoURL: p.UserInfoURL,
|
||||||
|
algorithms: algs,
|
||||||
rawClaims: body,
|
rawClaims: body,
|
||||||
remoteKeySet: NewRemoteKeySet(ctx, p.JWKSURL),
|
remoteKeySet: NewRemoteKeySet(ctx, p.JWKSURL),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -79,7 +79,9 @@ type Config struct {
|
||||||
ClientID string
|
ClientID string
|
||||||
// If specified, only this set of algorithms may be used to sign the JWT.
|
// If specified, only this set of algorithms may be used to sign the JWT.
|
||||||
//
|
//
|
||||||
// Since many providers only support RS256, SupportedSigningAlgs defaults to this value.
|
// If the IDTokenVerifier is created from a provider with (*Provider).Verifier, this
|
||||||
|
// defaults to the set of algorithms the provider supports. Otherwise this values
|
||||||
|
// defaults to RS256.
|
||||||
SupportedSigningAlgs []string
|
SupportedSigningAlgs []string
|
||||||
|
|
||||||
// If true, no ClientID check performed. Must be true if ClientID field is empty.
|
// If true, no ClientID check performed. Must be true if ClientID field is empty.
|
||||||
|
@ -105,6 +107,13 @@ type Config struct {
|
||||||
// The returned IDTokenVerifier is tied to the Provider's context and its behavior is
|
// The returned IDTokenVerifier is tied to the Provider's context and its behavior is
|
||||||
// undefined once the Provider's context is canceled.
|
// undefined once the Provider's context is canceled.
|
||||||
func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
|
func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
|
||||||
|
if len(config.SupportedSigningAlgs) == 0 && len(p.algorithms) > 0 {
|
||||||
|
// Make a copy so we don't modify the config values.
|
||||||
|
cp := &Config{}
|
||||||
|
*cp = *config
|
||||||
|
cp.SupportedSigningAlgs = p.algorithms
|
||||||
|
config = cp
|
||||||
|
}
|
||||||
return NewVerifier(p.issuer, p.remoteKeySet, config)
|
return NewVerifier(p.issuer, p.remoteKeySet, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,3 +5,4 @@
|
||||||
*.pem
|
*.pem
|
||||||
*.cov
|
*.cov
|
||||||
jose-util/jose-util
|
jose-util/jose-util
|
||||||
|
jose-util.t.err
|
|
@ -8,8 +8,8 @@ matrix:
|
||||||
- go: tip
|
- go: tip
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- '1.11.x'
|
- '1.14.x'
|
||||||
- '1.12.x'
|
- '1.15.x'
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
go_import_path: gopkg.in/square/go-jose.v2
|
go_import_path: gopkg.in/square/go-jose.v2
|
||||||
|
@ -26,6 +26,8 @@ before_install:
|
||||||
- go get github.com/wadey/gocovmerge
|
- go get github.com/wadey/gocovmerge
|
||||||
- go get github.com/mattn/goveralls
|
- go get github.com/mattn/goveralls
|
||||||
- go get github.com/stretchr/testify/assert
|
- go get github.com/stretchr/testify/assert
|
||||||
|
- go get github.com/stretchr/testify/require
|
||||||
|
- go get github.com/google/go-cmp/cmp
|
||||||
- go get golang.org/x/tools/cmd/cover || true
|
- go get golang.org/x/tools/cmd/cover || true
|
||||||
- go get code.google.com/p/go.tools/cmd/cover || true
|
- go get code.google.com/p/go.tools/cmd/cover || true
|
||||||
- pip install cram --user
|
- pip install cram --user
|
||||||
|
@ -35,10 +37,9 @@ script:
|
||||||
- go test ./cipher -v -covermode=count -coverprofile=cipher/profile.cov
|
- go test ./cipher -v -covermode=count -coverprofile=cipher/profile.cov
|
||||||
- go test ./jwt -v -covermode=count -coverprofile=jwt/profile.cov
|
- go test ./jwt -v -covermode=count -coverprofile=jwt/profile.cov
|
||||||
- go test ./json -v # no coverage for forked encoding/json package
|
- go test ./json -v # no coverage for forked encoding/json package
|
||||||
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t
|
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util
|
||||||
- cd ..
|
- cd ..
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- gocovmerge *.cov */*.cov > merged.coverprofile
|
- gocovmerge *.cov */*.cov > merged.coverprofile
|
||||||
- $HOME/gopath/bin/goveralls -coverprofile merged.coverprofile -service=travis-ci
|
- $HOME/gopath/bin/goveralls -coverprofile merged.coverprofile -service=travis-ci
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
|
||||||
return hmac.Sum(nil)[:ctx.authtagBytes]
|
return hmac.Sum(nil)[:ctx.authtagBytes]
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize ensures the the given slice has a capacity of at least n bytes.
|
// resize ensures that the given slice has a capacity of at least n bytes.
|
||||||
// If the capacity of the slice is less than n, a new slice is allocated
|
// If the capacity of the slice is less than n, a new slice is allocated
|
||||||
// and the existing data will be copied.
|
// and the existing data will be copied.
|
||||||
func resize(in []byte, n uint64) (head, tail []byte) {
|
func resize(in []byte, n uint64) (head, tail []byte) {
|
||||||
|
|
|
@ -216,6 +216,7 @@ func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *Encrypter
|
||||||
|
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
encrypter.compressionAlg = opts.Compression
|
encrypter.compressionAlg = opts.Compression
|
||||||
|
encrypter.extraHeaders = opts.ExtraHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, recipient := range rcpts {
|
for _, recipient := range rcpts {
|
||||||
|
|
|
@ -23,13 +23,12 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"regexp"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"gopkg.in/square/go-jose.v2/json"
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
var stripWhitespaceRegex = regexp.MustCompile("\\s")
|
|
||||||
|
|
||||||
// Helper function to serialize known-good objects.
|
// Helper function to serialize known-good objects.
|
||||||
// Precondition: value is not a nil pointer.
|
// Precondition: value is not a nil pointer.
|
||||||
func mustSerializeJSON(value interface{}) []byte {
|
func mustSerializeJSON(value interface{}) []byte {
|
||||||
|
@ -56,7 +55,14 @@ func mustSerializeJSON(value interface{}) []byte {
|
||||||
|
|
||||||
// Strip all newlines and whitespace
|
// Strip all newlines and whitespace
|
||||||
func stripWhitespace(data string) string {
|
func stripWhitespace(data string) string {
|
||||||
return stripWhitespaceRegex.ReplaceAllString(data, "")
|
buf := strings.Builder{}
|
||||||
|
buf.Grow(len(data))
|
||||||
|
for _, r := range data {
|
||||||
|
if !unicode.IsSpace(r) {
|
||||||
|
buf.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform compression based on algorithm
|
// Perform compression based on algorithm
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -245,6 +246,18 @@ func isValidNumber(s string) bool {
|
||||||
return s == ""
|
return s == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NumberUnmarshalType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// unmarshal a JSON number into an interface{} as a float64
|
||||||
|
UnmarshalFloat NumberUnmarshalType = iota
|
||||||
|
// unmarshal a JSON number into an interface{} as a `json.Number`
|
||||||
|
UnmarshalJSONNumber
|
||||||
|
// unmarshal a JSON number into an interface{} as a int64
|
||||||
|
// if value is an integer otherwise float64
|
||||||
|
UnmarshalIntOrFloat
|
||||||
|
)
|
||||||
|
|
||||||
// decodeState represents the state while decoding a JSON value.
|
// decodeState represents the state while decoding a JSON value.
|
||||||
type decodeState struct {
|
type decodeState struct {
|
||||||
data []byte
|
data []byte
|
||||||
|
@ -252,7 +265,7 @@ type decodeState struct {
|
||||||
scan scanner
|
scan scanner
|
||||||
nextscan scanner // for calls to nextValue
|
nextscan scanner // for calls to nextValue
|
||||||
savedError error
|
savedError error
|
||||||
useNumber bool
|
numberType NumberUnmarshalType
|
||||||
}
|
}
|
||||||
|
|
||||||
// errPhase is used for errors that should not happen unless
|
// errPhase is used for errors that should not happen unless
|
||||||
|
@ -723,17 +736,38 @@ func (d *decodeState) literal(v reflect.Value) {
|
||||||
d.literalStore(d.data[start:d.off], v, false)
|
d.literalStore(d.data[start:d.off], v, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertNumber converts the number literal s to a float64 or a Number
|
// convertNumber converts the number literal s to a float64, int64 or a Number
|
||||||
// depending on the setting of d.useNumber.
|
// depending on d.numberDecodeType.
|
||||||
func (d *decodeState) convertNumber(s string) (interface{}, error) {
|
func (d *decodeState) convertNumber(s string) (interface{}, error) {
|
||||||
if d.useNumber {
|
switch d.numberType {
|
||||||
|
|
||||||
|
case UnmarshalJSONNumber:
|
||||||
return Number(s), nil
|
return Number(s), nil
|
||||||
|
case UnmarshalIntOrFloat:
|
||||||
|
v, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tries to parse integer number in scientific notation
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it has no decimal value use int64
|
||||||
|
if fi, fd := math.Modf(f); fd == 0.0 {
|
||||||
|
return int64(fi), nil
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
default:
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)}
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
f, err := strconv.ParseFloat(s, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)}
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var numberType = reflect.TypeOf(Number(""))
|
var numberType = reflect.TypeOf(Number(""))
|
||||||
|
|
|
@ -31,9 +31,14 @@ func NewDecoder(r io.Reader) *Decoder {
|
||||||
return &Decoder{r: r}
|
return &Decoder{r: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use `SetNumberType` instead
|
||||||
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
||||||
// Number instead of as a float64.
|
// Number instead of as a float64.
|
||||||
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
|
func (dec *Decoder) UseNumber() { dec.d.numberType = UnmarshalJSONNumber }
|
||||||
|
|
||||||
|
// SetNumberType causes the Decoder to unmarshal a number into an interface{} as a
|
||||||
|
// Number, float64 or int64 depending on `t` enum value.
|
||||||
|
func (dec *Decoder) SetNumberType(t NumberUnmarshalType) { dec.d.numberType = t }
|
||||||
|
|
||||||
// Decode reads the next JSON-encoded value from its
|
// Decode reads the next JSON-encoded value from its
|
||||||
// input and stores it in the value pointed to by v.
|
// input and stores it in the value pointed to by v.
|
||||||
|
|
|
@ -17,15 +17,20 @@
|
||||||
package jose
|
package jose
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -57,16 +62,31 @@ type rawJSONWebKey struct {
|
||||||
Dq *byteBuffer `json:"dq,omitempty"`
|
Dq *byteBuffer `json:"dq,omitempty"`
|
||||||
Qi *byteBuffer `json:"qi,omitempty"`
|
Qi *byteBuffer `json:"qi,omitempty"`
|
||||||
// Certificates
|
// Certificates
|
||||||
X5c []string `json:"x5c,omitempty"`
|
X5c []string `json:"x5c,omitempty"`
|
||||||
|
X5u *url.URL `json:"x5u,omitempty"`
|
||||||
|
X5tSHA1 string `json:"x5t,omitempty"`
|
||||||
|
X5tSHA256 string `json:"x5t#S256,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONWebKey represents a public or private key in JWK format.
|
// JSONWebKey represents a public or private key in JWK format.
|
||||||
type JSONWebKey struct {
|
type JSONWebKey struct {
|
||||||
Key interface{}
|
// Cryptographic key, can be a symmetric or asymmetric key.
|
||||||
|
Key interface{}
|
||||||
|
// Key identifier, parsed from `kid` header.
|
||||||
|
KeyID string
|
||||||
|
// Key algorithm, parsed from `alg` header.
|
||||||
|
Algorithm string
|
||||||
|
// Key use, parsed from `use` header.
|
||||||
|
Use string
|
||||||
|
|
||||||
|
// X.509 certificate chain, parsed from `x5c` header.
|
||||||
Certificates []*x509.Certificate
|
Certificates []*x509.Certificate
|
||||||
KeyID string
|
// X.509 certificate URL, parsed from `x5u` header.
|
||||||
Algorithm string
|
CertificatesURL *url.URL
|
||||||
Use string
|
// X.509 certificate thumbprint (SHA-1), parsed from `x5t` header.
|
||||||
|
CertificateThumbprintSHA1 []byte
|
||||||
|
// X.509 certificate thumbprint (SHA-256), parsed from `x5t#S256` header.
|
||||||
|
CertificateThumbprintSHA256 []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON serializes the given key to its JSON representation.
|
// MarshalJSON serializes the given key to its JSON representation.
|
||||||
|
@ -105,6 +125,39 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
||||||
raw.X5c = append(raw.X5c, base64.StdEncoding.EncodeToString(cert.Raw))
|
raw.X5c = append(raw.X5c, base64.StdEncoding.EncodeToString(cert.Raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x5tSHA1Len := len(k.CertificateThumbprintSHA1)
|
||||||
|
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
|
||||||
|
if x5tSHA1Len > 0 {
|
||||||
|
if x5tSHA1Len != sha1.Size {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid SHA-1 thumbprint (must be %d bytes, not %d)", sha1.Size, x5tSHA1Len)
|
||||||
|
}
|
||||||
|
raw.X5tSHA1 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA1)
|
||||||
|
}
|
||||||
|
if x5tSHA256Len > 0 {
|
||||||
|
if x5tSHA256Len != sha256.Size {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid SHA-256 thumbprint (must be %d bytes, not %d)", sha256.Size, x5tSHA256Len)
|
||||||
|
}
|
||||||
|
raw.X5tSHA256 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA256)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If cert chain is attached (as opposed to being behind a URL), check the
|
||||||
|
// keys thumbprints to make sure they match what is expected. This is to
|
||||||
|
// ensure we don't accidentally produce a JWK with semantically inconsistent
|
||||||
|
// data in the headers.
|
||||||
|
if len(k.Certificates) > 0 {
|
||||||
|
expectedSHA1 := sha1.Sum(k.Certificates[0].Raw)
|
||||||
|
expectedSHA256 := sha256.Sum256(k.Certificates[0].Raw)
|
||||||
|
|
||||||
|
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(k.CertificateThumbprintSHA1, expectedSHA1[:]) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid SHA-1 thumbprint, does not match cert chain")
|
||||||
|
}
|
||||||
|
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(k.CertificateThumbprintSHA256, expectedSHA256[:]) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid or SHA-256 thumbprint, does not match cert chain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.X5u = k.CertificatesURL
|
||||||
|
|
||||||
return json.Marshal(raw)
|
return json.Marshal(raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,28 +169,61 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
certs, err := parseCertificateChain(raw.X5c)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("square/go-jose: failed to unmarshal x5c field: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
var key interface{}
|
var key interface{}
|
||||||
|
var certPub interface{}
|
||||||
|
var keyPub interface{}
|
||||||
|
|
||||||
|
if len(certs) > 0 {
|
||||||
|
// We need to check that leaf public key matches the key embedded in this
|
||||||
|
// JWK, as required by the standard (see RFC 7517, Section 4.7). Otherwise
|
||||||
|
// the JWK parsed could be semantically invalid. Technically, should also
|
||||||
|
// check key usage fields and other extensions on the cert here, but the
|
||||||
|
// standard doesn't exactly explain how they're supposed to map from the
|
||||||
|
// JWK representation to the X.509 extensions.
|
||||||
|
certPub = certs[0].PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
switch raw.Kty {
|
switch raw.Kty {
|
||||||
case "EC":
|
case "EC":
|
||||||
if raw.D != nil {
|
if raw.D != nil {
|
||||||
key, err = raw.ecPrivateKey()
|
key, err = raw.ecPrivateKey()
|
||||||
|
if err == nil {
|
||||||
|
keyPub = key.(*ecdsa.PrivateKey).Public()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
key, err = raw.ecPublicKey()
|
key, err = raw.ecPublicKey()
|
||||||
|
keyPub = key
|
||||||
}
|
}
|
||||||
case "RSA":
|
case "RSA":
|
||||||
if raw.D != nil {
|
if raw.D != nil {
|
||||||
key, err = raw.rsaPrivateKey()
|
key, err = raw.rsaPrivateKey()
|
||||||
|
if err == nil {
|
||||||
|
keyPub = key.(*rsa.PrivateKey).Public()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
key, err = raw.rsaPublicKey()
|
key, err = raw.rsaPublicKey()
|
||||||
|
keyPub = key
|
||||||
}
|
}
|
||||||
case "oct":
|
case "oct":
|
||||||
|
if certPub != nil {
|
||||||
|
return errors.New("square/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
|
||||||
|
}
|
||||||
key, err = raw.symmetricKey()
|
key, err = raw.symmetricKey()
|
||||||
case "OKP":
|
case "OKP":
|
||||||
if raw.Crv == "Ed25519" && raw.X != nil {
|
if raw.Crv == "Ed25519" && raw.X != nil {
|
||||||
if raw.D != nil {
|
if raw.D != nil {
|
||||||
key, err = raw.edPrivateKey()
|
key, err = raw.edPrivateKey()
|
||||||
|
if err == nil {
|
||||||
|
keyPub = key.(ed25519.PrivateKey).Public()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
key, err = raw.edPublicKey()
|
key, err = raw.edPublicKey()
|
||||||
|
keyPub = key
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("square/go-jose: unknown curve %s'", raw.Crv)
|
err = fmt.Errorf("square/go-jose: unknown curve %s'", raw.Crv)
|
||||||
|
@ -146,12 +232,78 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
||||||
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
|
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err != nil {
|
||||||
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
k.Certificates, err = parseCertificateChain(raw.X5c)
|
if certPub != nil && keyPub != nil {
|
||||||
|
if !reflect.DeepEqual(certPub, keyPub) {
|
||||||
|
return errors.New("square/go-jose: invalid JWK, public keys in key and x5c fields do not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
|
||||||
|
|
||||||
|
k.CertificatesURL = raw.X5u
|
||||||
|
|
||||||
|
// x5t parameters are base64url-encoded SHA thumbprints
|
||||||
|
// See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
|
||||||
|
x5tSHA1bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA1)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("square/go-jose: invalid JWK, x5t header has invalid encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 7517, Section 4.8 is ambiguous as to whether the digest output should be byte or hex,
|
||||||
|
// for this reason, after base64 decoding, if the size is sha1.Size it's likely that the value is a byte encoded
|
||||||
|
// checksum so we skip this. Otherwise if the checksum was hex encoded we expect a 40 byte sized array so we'll
|
||||||
|
// try to hex decode it. When Marshalling this value we'll always use a base64 encoded version of byte format checksum.
|
||||||
|
if len(x5tSHA1bytes) == 2*sha1.Size {
|
||||||
|
hx, err := hex.DecodeString(string(x5tSHA1bytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal x5c field: %s", err)
|
return fmt.Errorf("square/go-jose: invalid JWK, unable to hex decode x5t: %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
x5tSHA1bytes = hx
|
||||||
|
}
|
||||||
|
|
||||||
|
k.CertificateThumbprintSHA1 = x5tSHA1bytes
|
||||||
|
|
||||||
|
x5tSHA256bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA256)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("square/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(x5tSHA256bytes) == 2*sha256.Size {
|
||||||
|
hx256, err := hex.DecodeString(string(x5tSHA256bytes))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("square/go-jose: invalid JWK, unable to hex decode x5t#S256: %v", err)
|
||||||
|
}
|
||||||
|
x5tSHA256bytes = hx256
|
||||||
|
}
|
||||||
|
|
||||||
|
k.CertificateThumbprintSHA256 = x5tSHA256bytes
|
||||||
|
|
||||||
|
x5tSHA1Len := len(k.CertificateThumbprintSHA1)
|
||||||
|
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
|
||||||
|
if x5tSHA1Len > 0 && x5tSHA1Len != sha1.Size {
|
||||||
|
return errors.New("square/go-jose: invalid JWK, x5t header is of incorrect size")
|
||||||
|
}
|
||||||
|
if x5tSHA256Len > 0 && x5tSHA256Len != sha256.Size {
|
||||||
|
return errors.New("square/go-jose: invalid JWK, x5t#S256 header is of incorrect size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If certificate chain *and* thumbprints are set, verify correctness.
|
||||||
|
if len(k.Certificates) > 0 {
|
||||||
|
leaf := k.Certificates[0]
|
||||||
|
sha1sum := sha1.Sum(leaf.Raw)
|
||||||
|
sha256sum := sha256.Sum256(leaf.Raw)
|
||||||
|
|
||||||
|
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(sha1sum[:], k.CertificateThumbprintSHA1) {
|
||||||
|
return errors.New("square/go-jose: invalid JWK, x5c thumbprint does not match x5t value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(sha256sum[:], k.CertificateThumbprintSHA256) {
|
||||||
|
return errors.New("square/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +332,7 @@ func (s *JSONWebKeySet) Key(kid string) []JSONWebKey {
|
||||||
|
|
||||||
const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
|
const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
|
||||||
const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
|
const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
|
||||||
const edThumbprintTemplate = `{"crv":"%s","kty":"OKP",x":"%s"}`
|
const edThumbprintTemplate = `{"crv":"%s","kty":"OKP","x":"%s"}`
|
||||||
|
|
||||||
func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
||||||
coordLength := curveSize(curve)
|
coordLength := curveSize(curve)
|
||||||
|
@ -254,7 +406,7 @@ func (k *JSONWebKey) IsPublic() bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public creates JSONWebKey with corresponding publik key if JWK represents asymmetric private key.
|
// Public creates JSONWebKey with corresponding public key if JWK represents asymmetric private key.
|
||||||
func (k *JSONWebKey) Public() JSONWebKey {
|
func (k *JSONWebKey) Public() JSONWebKey {
|
||||||
if k.IsPublic() {
|
if k.IsPublic() {
|
||||||
return *k
|
return *k
|
||||||
|
|
|
@ -102,14 +102,14 @@ func (sig Signature) mergedHeaders() rawHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute data to be signed
|
// Compute data to be signed
|
||||||
func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature) []byte {
|
func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature) ([]byte, error) {
|
||||||
var authData bytes.Buffer
|
var authData bytes.Buffer
|
||||||
|
|
||||||
protectedHeader := new(rawHeader)
|
protectedHeader := new(rawHeader)
|
||||||
|
|
||||||
if signature.original != nil && signature.original.Protected != nil {
|
if signature.original != nil && signature.original.Protected != nil {
|
||||||
if err := json.Unmarshal(signature.original.Protected.bytes(), protectedHeader); err != nil {
|
if err := json.Unmarshal(signature.original.Protected.bytes(), protectedHeader); err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
authData.WriteString(signature.original.Protected.base64())
|
authData.WriteString(signature.original.Protected.base64())
|
||||||
} else if signature.protected != nil {
|
} else if signature.protected != nil {
|
||||||
|
@ -134,7 +134,7 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
|
||||||
authData.Write(payload)
|
authData.Write(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
return authData.Bytes()
|
return authData.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSignedFull parses a message in full format.
|
// parseSignedFull parses a message in full format.
|
||||||
|
|
|
@ -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.SetNumberType(json.UnmarshalJSONNumber)
|
||||||
|
|
||||||
|
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,121 @@
|
||||||
|
/*-
|
||||||
|
* 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, ignoring leap seconds. Non-integer values can be represented
|
||||||
|
// in the serialized format, but we round to the nearest second.
|
||||||
|
// See RFC7519 Section 2: https://tools.ietf.org/html/rfc7519#section-2
|
||||||
|
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,169 @@
|
||||||
|
/*-
|
||||||
|
* 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{} {
|
||||||
|
var jwks jose.JSONWebKeySet
|
||||||
|
|
||||||
|
switch jwksType := key.(type) {
|
||||||
|
case *jose.JSONWebKeySet:
|
||||||
|
jwks = *jwksType
|
||||||
|
case jose.JSONWebKeySet:
|
||||||
|
jwks = jwksType
|
||||||
|
default:
|
||||||
|
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
|
||||||
|
}
|
|
@ -17,7 +17,7 @@
|
||||||
package jose
|
package jose
|
||||||
|
|
||||||
// OpaqueSigner is an interface that supports signing payloads with opaque
|
// OpaqueSigner is an interface that supports signing payloads with opaque
|
||||||
// private key(s). Private key operations preformed by implementors may, for
|
// private key(s). Private key operations performed by implementers may, for
|
||||||
// example, occur in a hardware module. An OpaqueSigner may rotate signing keys
|
// example, occur in a hardware module. An OpaqueSigner may rotate signing keys
|
||||||
// transparently to the user of this interface.
|
// transparently to the user of this interface.
|
||||||
type OpaqueSigner interface {
|
type OpaqueSigner interface {
|
||||||
|
|
|
@ -183,7 +183,7 @@ type Header struct {
|
||||||
// Unverified certificate chain parsed from x5c header.
|
// Unverified certificate chain parsed from x5c header.
|
||||||
certificates []*x509.Certificate
|
certificates []*x509.Certificate
|
||||||
|
|
||||||
// Any headers not recognised above get unmarshaled
|
// Any headers not recognised above get unmarshalled
|
||||||
// from JSON in a generic manner and placed in this map.
|
// from JSON in a generic manner and placed in this map.
|
||||||
ExtraHeaders map[HeaderKey]interface{}
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
}
|
}
|
||||||
|
@ -295,12 +295,12 @@ func (parsed rawHeader) getAPV() (*byteBuffer, error) {
|
||||||
return parsed.getByteBuffer(headerAPV)
|
return parsed.getByteBuffer(headerAPV)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getIV extracts parsed "iv" frpom the raw JSON.
|
// getIV extracts parsed "iv" from the raw JSON.
|
||||||
func (parsed rawHeader) getIV() (*byteBuffer, error) {
|
func (parsed rawHeader) getIV() (*byteBuffer, error) {
|
||||||
return parsed.getByteBuffer(headerIV)
|
return parsed.getByteBuffer(headerIV)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTag extracts parsed "tag" frpom the raw JSON.
|
// getTag extracts parsed "tag" from the raw JSON.
|
||||||
func (parsed rawHeader) getTag() (*byteBuffer, error) {
|
func (parsed rawHeader) getTag() (*byteBuffer, error) {
|
||||||
return parsed.getByteBuffer(headerTag)
|
return parsed.getByteBuffer(headerTag)
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,7 +370,11 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input := obj.computeAuthData(payload, &signature)
|
input, err := obj.computeAuthData(payload, &signature)
|
||||||
|
if err != nil {
|
||||||
|
return ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
alg := headers.getSignatureAlgorithm()
|
alg := headers.getSignatureAlgorithm()
|
||||||
err = verifier.verifyPayload(input, signature.Signature, alg)
|
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -421,7 +425,11 @@ outer:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input := obj.computeAuthData(payload, &signature)
|
input, err := obj.computeAuthData(payload, &signature)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
alg := headers.getSignatureAlgorithm()
|
alg := headers.getSignatureAlgorithm()
|
||||||
err = verifier.verifyPayload(input, signature.Signature, alg)
|
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||||
if err == nil {
|
if err == 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-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
# github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
## explicit
|
## explicit
|
||||||
github.com/coreos/go-systemd/daemon
|
github.com/coreos/go-systemd/daemon
|
||||||
|
@ -530,17 +527,18 @@ google.golang.org/protobuf/types/descriptorpb
|
||||||
google.golang.org/protobuf/types/known/anypb
|
google.golang.org/protobuf/types/known/anypb
|
||||||
google.golang.org/protobuf/types/known/durationpb
|
google.golang.org/protobuf/types/known/durationpb
|
||||||
google.golang.org/protobuf/types/known/timestamppb
|
google.golang.org/protobuf/types/known/timestamppb
|
||||||
# gopkg.in/coreos/go-oidc.v2 v2.1.0
|
# gopkg.in/coreos/go-oidc.v2 v2.2.1
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/coreos/go-oidc.v2
|
gopkg.in/coreos/go-oidc.v2
|
||||||
# gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
# gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/natefinch/lumberjack.v2
|
gopkg.in/natefinch/lumberjack.v2
|
||||||
# gopkg.in/square/go-jose.v2 v2.4.0
|
# gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
## explicit
|
## explicit
|
||||||
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