diff --git a/cmd/jwtproxy/main.go b/cmd/jwtproxy/main.go index 1b7ed95..7229b06 100644 --- a/cmd/jwtproxy/main.go +++ b/cmd/jwtproxy/main.go @@ -30,6 +30,7 @@ import ( _ "github.com/coreos/jwtproxy/jwt/keyserver/keyregistry/keycache/memory" _ "github.com/coreos/jwtproxy/jwt/keyserver/preshared" _ "github.com/coreos/jwtproxy/jwt/noncestorage/local" + _ "github.com/coreos/jwtproxy/jwt/noncestorage/none" _ "github.com/coreos/jwtproxy/jwt/privatekey/autogenerated" _ "github.com/coreos/jwtproxy/jwt/privatekey/preshared" ) diff --git a/jwt/jwt.go b/jwt/jwt.go index 8191b36..c8f3587 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -72,10 +72,10 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest // Extract token from request. token, err := oidc.ExtractBearerToken(req) if err != nil { - username, password, ok := req.BasicAuth() - if !ok && username == "oauth2" { - return nil, errors.New("No JWT found") - } + username, password, ok := req.BasicAuth() + if !ok && username == "oauth2" { + return nil, errors.New("No JWT found") + } token = password } @@ -120,7 +120,7 @@ func Verify(req *http.Request, keyServer keyserver.Reader, nonceVerifier noncest return nil, errors.New("Invalid 'exp' claim (too long)") } jti, exists, err := claims.StringClaim("jti") - if !exists || err != nil || !nonceVerifier.Verify(jti, exp) { + if err != nil || !nonceVerifier.Verify(jti, exp) { return nil, errors.New("Missing or invalid 'jti' claim") } diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go index 7235c24..096170b 100644 --- a/jwt/jwt_test.go +++ b/jwt/jwt_test.go @@ -28,6 +28,9 @@ import ( "github.com/coreos/go-oidc/key" "github.com/coreos/go-oidc/oidc" "github.com/coreos/jwtproxy/config" + "github.com/coreos/jwtproxy/jwt/noncestorage" + _ "github.com/coreos/jwtproxy/jwt/noncestorage/local" + _ "github.com/coreos/jwtproxy/jwt/noncestorage/none" "github.com/coreos/jwtproxy/stop" "github.com/stretchr/testify/assert" ) @@ -87,10 +90,7 @@ func (ts *testService) Stop() <-chan struct{} { return stop.AlreadyDone } -func TestJWT(t *testing.T) { - // Create a request to sign. - req, _ := http.NewRequest("GET", "http://foo.bar:6666/ez", nil) - +func setup(t *testing.T) *signAndVerifyParams { // Create a public/private key pair used to sign/verify. pkb, _ := pem.Decode([]byte(privateKey)) pkr, _ := x509.ParsePKCS1PrivateKey(pkb.Bytes) @@ -123,6 +123,15 @@ func TestJWT(t *testing.T) { maxTTL: 5 * time.Minute, } + return defaultConfig +} + +func TestJWT(t *testing.T) { + // Create a request to sign. + req, _ := http.NewRequest("GET", "http://foo.bar:6666/ez", nil) + + defaultConfig := setup(t) + // Basic sign / verify. assert.Nil(t, signAndVerify(t, req, *defaultConfig, nil)) @@ -201,6 +210,53 @@ func TestJWT(t *testing.T) { assert.Error(t, signAndVerify(t, req, cfg, nil)) } +func TestValidateJWT(t *testing.T) { + cfg := setup(t) + + req, _ := http.NewRequest("GET", "http://foo.bar:6666/ez", nil) + + assert.Nil(t, signAndVerify(t, req, *cfg, nil)) +} + +func TestLocalValidateJTITwice(t *testing.T) { + cfg := setup(t) + + local, err := noncestorage.New(config.RegistrableComponentConfig{ + Type: "local", + Options: map[string]interface{}{ + "PurgeInterval": 1 * time.Minute, + }, + }) + assert.NoError(t, err) + + req, _ := http.NewRequest("GET", "http://foo.bar:6666/ez", nil) + sign(t, req, *cfg, nil) + + _, err = Verify(req, cfg.services, local, cfg.aud, cfg.maxSkew, cfg.maxTTL) + assert.Nil(t, err) + + //Validating same JTI twice should have error + _, err = Verify(req, cfg.services, local, cfg.aud, cfg.maxSkew, cfg.maxTTL) + assert.Error(t, err) +} + +func TestNoValidateJTITwice(t *testing.T) { + cfg := setup(t) + + local, err := noncestorage.New(config.RegistrableComponentConfig{ + Type: "none", + }) + assert.NoError(t, err) + + req, _ := http.NewRequest("GET", "http://foo.bar:6666/ez", nil) + sign(t, req, *cfg, nil) + + _, err = Verify(req, cfg.services, local, cfg.aud, cfg.maxSkew, cfg.maxTTL) + assert.Nil(t, err) + _, err = Verify(req, cfg.services, local, cfg.aud, cfg.maxSkew, cfg.maxTTL) + assert.Nil(t, err) +} + type signAndVerifyParams struct { services *testService @@ -215,7 +271,7 @@ type signAndVerifyParams struct { type requestModifier func(req *http.Request) -func signAndVerify(t *testing.T, req *http.Request, p signAndVerifyParams, modify requestModifier) error { +func sign(t *testing.T, req *http.Request, p signAndVerifyParams, modify requestModifier) { // Sign. pk, _ := p.services.GetPrivateKey() assert.Nil(t, Sign(req, pk, p.signerParams)) @@ -224,6 +280,10 @@ func signAndVerify(t *testing.T, req *http.Request, p signAndVerifyParams, modif if modify != nil { modify(req) } +} + +func signAndVerify(t *testing.T, req *http.Request, p signAndVerifyParams, modify requestModifier) error { + sign(t, req, p, modify) // Verify. _, err := Verify(req, p.services, p.services, p.aud, p.maxSkew, p.maxTTL) diff --git a/jwt/noncestorage/local/local.go b/jwt/noncestorage/local/local.go index 92f34d5..2b287c3 100644 --- a/jwt/noncestorage/local/local.go +++ b/jwt/noncestorage/local/local.go @@ -54,6 +54,10 @@ func constructor(registrableComponentConfig config.RegistrableComponentConfig) ( } func (ln *Local) Verify(nonce string, expiration time.Time) bool { + if nonce == "" { + return false + } + if _, found := ln.Get(nonce); found { return false } diff --git a/jwt/noncestorage/none/none.go b/jwt/noncestorage/none/none.go new file mode 100644 index 0000000..ed0605a --- /dev/null +++ b/jwt/noncestorage/none/none.go @@ -0,0 +1,45 @@ +// Copyright 2016 CoreOS, 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 none + +import ( + "time" + + "github.com/coreos/jwtproxy/config" + "github.com/coreos/jwtproxy/jwt/noncestorage" + "github.com/coreos/jwtproxy/stop" +) + +func init() { + noncestorage.Register("none", constructor) +} + +// None no jti validation +type None struct { +} + +func constructor(registrableComponentConfig config.RegistrableComponentConfig) (noncestorage.NonceStorage, error) { + return &None{}, nil +} + +// Verify Always returns true +func (ln *None) Verify(nonce string, expiration time.Time) bool { + return true +} + +// Stop already done +func (ln *None) Stop() <-chan struct{} { + return stop.AlreadyDone +}