Merge pull request #142012 from everettraven/upstream/deps/go-oidc-jose

update go-jose and go-oidc packages to latest versions
This commit is contained in:
kubernetes-prow[bot] authored and GitHub committed 2026-09-14 21:45:19 +00:00
commit e5f62fbfa6
115 files changed
+2845 -3439

No files matched your search

@@ -1,4 +1,4 @@
= vendor/github.com/coreos/go-oidc licensed under: =
= vendor/github.com/coreos/go-oidc/v3 licensed under: =
Apache License
Version 2.0, January 2004
@@ -203,4 +203,4 @@ Apache License
limitations under the License.
= vendor/github.com/coreos/go-oidc/LICENSE d2794c0df5b907fdace235a619d80314
= vendor/github.com/coreos/go-oidc/v3/LICENSE d2794c0df5b907fdace235a619d80314
@@ -1,4 +1,4 @@
= vendor/gopkg.in/go-jose/go-jose.v2 licensed under: =
= vendor/github.com/go-jose/go-jose/v4 licensed under: =
Apache License
@@ -203,4 +203,4 @@
See the License for the specific language governing permissions and
limitations under the License.
= vendor/gopkg.in/go-jose/go-jose.v2/LICENSE 3b83ef96387f14655fc854ddc3c6bd57
= vendor/github.com/go-jose/go-jose/v4/LICENSE 3b83ef96387f14655fc854ddc3c6bd57
-206
View File
@@ -1,206 +0,0 @@
= vendor/github.com/pquerna/cachecontrol licensed under: =
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.
= vendor/github.com/pquerna/cachecontrol/LICENSE 3b83ef96387f14655fc854ddc3c6bd57
+2 -3
View File
@@ -19,7 +19,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/container-storage-interface/spec v1.13.0
github.com/coredns/corefile-migration v1.0.35
github.com/coreos/go-oidc v2.5.0+incompatible
github.com/coreos/go-oidc/v3 v3.21.0
github.com/coreos/go-systemd/v22 v22.7.0
github.com/cpuguy83/go-md2man/v2 v2.0.6
github.com/cyphar/filepath-securejoin v0.7.0
@@ -27,6 +27,7 @@ require (
github.com/docker/go-units v0.5.0
github.com/emicklei/go-restful/v3 v3.13.0
github.com/fsnotify/fsnotify v1.9.0
github.com/go-jose/go-jose/v4 v4.1.5
github.com/go-logr/logr v1.4.3
github.com/go-openapi/jsonreference v1.0.0
github.com/godbus/dbus/v5 v5.2.2
@@ -81,7 +82,6 @@ require (
google.golang.org/grpc v1.82.2
google.golang.org/protobuf v1.36.12
gopkg.in/evanphx/json-patch.v4 v4.13.0
gopkg.in/go-jose/go-jose.v2 v2.6.3
gopkg.in/inf.v0 v0.9.1
k8s.io/api v0.0.0
k8s.io/apiextensions-apiserver v0.0.0
@@ -191,7 +191,6 @@ require (
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/prometheus/client_golang v1.24.0 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
+4 -7
View File
@@ -61,8 +61,8 @@ 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/corefile-migration v1.0.35 h1:kZ4NRtenBmavOkFHCBCekxhrvoEzwAKRlNnD+9daVkI=
github.com/coredns/corefile-migration v1.0.35/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY=
github.com/coreos/go-oidc v2.5.0+incompatible h1:6W0vGJR3Tu0r0PwfmjOrRZSlfxeEln8dsejt3ZWIvwo=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0 h1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
@@ -108,7 +108,8 @@ github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -267,8 +268,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -467,8 +466,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
-1
View File
@@ -14,7 +14,6 @@ github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNf
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -22,7 +22,7 @@ import (
"testing"
"time"
"gopkg.in/go-jose/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4/jwt"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -21,7 +21,7 @@ import (
"testing"
"time"
"gopkg.in/go-jose/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4/jwt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
@@ -24,7 +24,7 @@ import (
"testing"
"time"
"gopkg.in/go-jose/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4/jwt"
admissionregistration "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+1 -1
View File
@@ -22,8 +22,8 @@ import (
"fmt"
"time"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/google/uuid"
"gopkg.in/go-jose/go-jose.v2/jwt"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+1 -1
View File
@@ -23,7 +23,7 @@ import (
"testing"
"time"
"gopkg.in/go-jose/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4/jwt"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
v1 "k8s.io/api/core/v1"
@@ -25,10 +25,10 @@ import (
"net"
"time"
jose "github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
jose "gopkg.in/go-jose/go-jose.v2"
"gopkg.in/go-jose/go-jose.v2/jwt"
externaljwtv1 "k8s.io/externaljwt/apis/v1"
"k8s.io/kubernetes/pkg/serviceaccount"
@@ -178,6 +178,7 @@ func (p *Plugin) validateJWTHeader(ctx context.Context, response *externaljwtv1.
// - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader
// - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey
// - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey
// - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms
// - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs
case "RS256", "ES256", "ES384", "ES512":
// OK
@@ -30,11 +30,11 @@ import (
"testing"
"time"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/types/known/timestamppb"
"gopkg.in/go-jose/go-jose.v2/jwt"
"k8s.io/kubernetes/pkg/serviceaccount"
+41 -4
View File
@@ -28,8 +28,8 @@ import (
"fmt"
"strings"
jose "gopkg.in/go-jose/go-jose.v2"
"gopkg.in/go-jose/go-jose.v2/jwt"
jose "github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
v1 "k8s.io/api/core/v1"
@@ -337,12 +337,25 @@ type Validator[PrivateClaims any] interface {
Validate(ctx context.Context, tokenData string, public *jwt.Claims, private *PrivateClaims) (*apiserverserviceaccount.ServiceAccountInfo, error)
}
// IMPORTANT: The algorithms listed below must be kept in sync with:
// - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader
// - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey
// - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey
// - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms
// - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs
var AcceptableServiceAccountSignatureAlgorithms = []jose.SignatureAlgorithm{
jose.ES256,
jose.ES384,
jose.ES512,
jose.RS256,
}
func (j *jwtTokenAuthenticator[PrivateClaims]) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
if !j.hasCorrectIssuer(tokenData) {
return nil, false, nil
}
tok, err := jwt.ParseSigned(tokenData)
tok, err := jwt.ParseSigned(tokenData, AcceptableServiceAccountSignatureAlgorithms)
if err != nil {
return nil, false, nil
}
@@ -445,8 +458,31 @@ func (j *jwtTokenAuthenticator[PrivateClaims]) hasCorrectIssuer(tokenData string
return j.issuers[claims.Issuer]
}
// audienceOverrider is a utility struct used to
// ensure that the audience claim always serializes
// as a list of string values.
// This prevents the go-jose change in https://github.com/go-jose/go-jose/blob/25b55feb059b8b08e16a73c601c8b80571fa5208/jwt/claims.go#L129-L135
// from causing the 'aud' claim in serviceaccount tokens
// to suddenly be plain strings.
// Use of this utility struct is intended to allow
// updating to the latest version of go-jose while maintaining
// backwards compatibility for serviceaccount token payloads.
type audienceOverrider struct {
Aud []string `json:"aud,omitempty"`
}
// GenerateToken is shared between internal and external signer code to ensure that claim merging logic remains consistent between them.
func GenerateToken(signer jose.Signer, iss string, claims *jwt.Claims, privateClaims interface{}) (string, error) {
audOverride := &audienceOverrider{
Aud: []string{},
}
if claims != nil {
for _, aud := range claims.Audience {
audOverride.Aud = append(audOverride.Aud, aud)
}
}
// claims are applied in reverse precedence
return jwt.Signed(signer).
Claims(privateClaims).
@@ -454,5 +490,6 @@ func GenerateToken(signer jose.Signer, iss string, claims *jwt.Claims, privateCl
Claims(&jwt.Claims{
Issuer: iss,
}).
CompactSerialize()
Claims(audOverride).
Serialize()
}
+3 -3
View File
@@ -25,7 +25,7 @@ import (
"strings"
"testing"
jose "gopkg.in/go-jose/go-jose.v2"
jose "github.com/go-jose/go-jose/v4"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -472,7 +472,7 @@ func (k *keyIDPrefixer) GetPublicKeys(ctx context.Context, keyIDHint string) []s
}
func checkJSONWebSignatureHasKeyID(t *testing.T, jwsString string, expectedKeyID string) {
jws, err := jose.ParseSigned(jwsString)
jws, err := jose.ParseSigned(jwsString, serviceaccount.AcceptableServiceAccountSignatureAlgorithms)
if err != nil {
t.Fatalf("Error checking for key ID: couldn't parse token: %v", err)
}
@@ -531,7 +531,7 @@ func generateECDSATokenWithMalformedIss(t *testing.T, serviceAccount *v1.Service
ecdsaToken := generateECDSAToken(t, "panda", serviceAccount, ecdsaSecret)
ecdsaTokenJWS, err := jose.ParseSigned(ecdsaToken)
ecdsaTokenJWS, err := jose.ParseSigned(ecdsaToken, serviceaccount.AcceptableServiceAccountSignatureAlgorithms)
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -24,7 +24,7 @@ import (
"fmt"
"time"
"gopkg.in/go-jose/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4/jwt"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+1 -1
View File
@@ -23,7 +23,7 @@ import (
"testing"
"time"
"gopkg.in/go-jose/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4/jwt"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+1 -1
View File
@@ -27,7 +27,7 @@ import (
"net/url"
"sync/atomic"
jose "gopkg.in/go-jose/go-jose.v2"
jose "github.com/go-jose/go-jose/v4"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
+1 -1
View File
@@ -29,8 +29,8 @@ import (
"testing"
restful "github.com/emicklei/go-restful/v3"
jose "github.com/go-jose/go-jose/v4"
"github.com/google/go-cmp/cmp"
jose "gopkg.in/go-jose/go-jose.v2"
"k8s.io/kubernetes/pkg/routes"
"k8s.io/kubernetes/pkg/serviceaccount"
+2 -4
View File
@@ -26,7 +26,7 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
@@ -51,7 +51,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -147,7 +147,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -295,7 +294,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+2 -3
View File
@@ -8,10 +8,11 @@ godebug default=go1.27
require (
github.com/blang/semver/v4 v4.0.0
github.com/coreos/go-oidc v2.5.0+incompatible
github.com/coreos/go-oidc/v3 v3.21.0
github.com/coreos/go-systemd/v22 v22.7.0
github.com/emicklei/go-restful/v3 v3.13.0
github.com/fsnotify/fsnotify v1.9.0
github.com/go-jose/go-jose/v4 v4.1.5
github.com/go-logr/logr v1.4.3
github.com/go-openapi/jsonreference v1.0.0
github.com/google/cel-go v0.29.2
@@ -46,7 +47,6 @@ require (
google.golang.org/grpc v1.82.2
google.golang.org/protobuf v1.36.12
gopkg.in/evanphx/json-patch.v4 v4.13.0
gopkg.in/go-jose/go-jose.v2 v2.6.3
gopkg.in/natefinch/lumberjack.v2 v2.2.1
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0
@@ -99,7 +99,6 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/moby/spdystream v0.5.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/prometheus/client_golang v1.24.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.70.0 // indirect
+4 -8
View File
@@ -26,8 +26,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/coreos/go-oidc v2.5.0+incompatible h1:6W0vGJR3Tu0r0PwfmjOrRZSlfxeEln8dsejt3ZWIvwo=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0 h1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
@@ -52,7 +52,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -147,8 +148,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -177,7 +176,6 @@ github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
@@ -291,8 +289,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
@@ -33,6 +33,7 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"mime"
@@ -43,12 +44,13 @@ import (
"sync/atomic"
"time"
"github.com/coreos/go-oidc"
"github.com/coreos/go-oidc/v3/oidc"
jose "github.com/go-jose/go-jose/v4"
celgo "github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
jose "gopkg.in/go-jose/go-jose.v2"
"golang.org/x/time/rate"
"k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/sets"
@@ -68,11 +70,9 @@ import (
"k8s.io/klog/v2"
)
var (
// synchronizeTokenIDVerifierForTest should be set to true to force a
// wait until the token ID verifiers are ready.
synchronizeTokenIDVerifierForTest = false
)
// synchronizeTokenIDVerifierForTest should be set to true to force a
// wait until the token ID verifiers are ready.
var synchronizeTokenIDVerifierForTest = false
const (
wellKnownEndpointPath = "/.well-known/openid-configuration"
@@ -214,6 +214,17 @@ type jwtAuthenticator struct {
requiredClaims map[string]string
healthCheck atomic.Pointer[errorHolder]
// lifecycleCtx is used to determine whether or not the authenticator
// should continue to be used.
// This is mainly used to replace the fact that cancelled contexts
// used to cause verification errors through coreos/go-oidc but that
// was dropped in coreos/go-oidc v3 so that cancelled contexts no longer
// cause outgoing requests to fail (i.e for fetching remote keys).
// When lifecycleCtx is cancelled, this authenticator will return
// an error on token authentication requests and will report as being
// unhealthy.
lifecycleCtx context.Context
}
// idTokenVerifier is a wrapper around oidc.IDTokenVerifier. It uses the oidc.IDTokenVerifier
@@ -407,6 +418,7 @@ func New(lifecycleCtx context.Context, opts Options) (AuthenticatorTokenWithHeal
resolver: resolver,
celMapper: celMapper,
requiredClaims: requiredClaims,
lifecycleCtx: lifecycleCtx,
}
authn.healthCheck.Store(&errorHolder{
err: fmt.Errorf("oidc: authenticator for issuer %q is not initialized", authn.jwtAuthenticator.Issuer.URL),
@@ -455,7 +467,13 @@ func New(lifecycleCtx context.Context, opts Options) (AuthenticatorTokenWithHeal
clientWithJWKSMetrics.Transport = withMetricsRoundTripper(client.Transport, providerJSON.JWKSURL, issuerURL, opts.APIServerID, lifecycleCtx)
client = &clientWithJWKSMetrics
remoteKeySet := oidc.NewRemoteKeySet(oidc.ClientContext(lifecycleCtx, client), providerJSON.JWKSURL)
clientWithJWKSRateLimiting := *client
clientWithJWKSRateLimiting.Transport = &rateLimitedRoundTripper{
base: client.Transport,
limiter: rate.NewLimiter(rate.Limit(0.1), 3), // 6 requests per minute with a burst of 3.
}
remoteKeySet := oidc.NewRemoteKeySet(oidc.ClientContext(lifecycleCtx, &clientWithJWKSRateLimiting), providerJSON.JWKSURL)
authn.setVerifier(&idTokenVerifier{oidc.NewVerifier(issuerURL, remoteKeySet, verifierConfig), audiences})
return true, nil
}
@@ -604,6 +622,26 @@ func (t *discoveryURLRoundTripper) RoundTrip(req *http.Request) (*http.Response,
return t.base.RoundTrip(req)
}
// rateLimitedRoundTripper is a http.RoundTripper that
// enforces a rate limit on successful requests.
type rateLimitedRoundTripper struct {
base http.RoundTripper
limiter *rate.Limiter
}
func (t *rateLimitedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
tokenReservation := t.limiter.Reserve()
if !tokenReservation.OK() {
return nil, errors.New("client has issued too many requests")
}
resp, err := t.base.RoundTrip(req)
if err != nil || resp == nil || resp.StatusCode != http.StatusOK {
tokenReservation.Cancel()
}
return resp, err
}
// untrustedIssuer extracts an untrusted "iss" claim from the given JWT token,
// or returns an error if the token can not be parsed. Since the JWT is not
// verified, the returned issuer should not be trusted.
@@ -858,6 +896,10 @@ func (v *idTokenVerifier) verifyAudience(t *oidc.IDToken) error {
}
func (a *jwtAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
if err := a.lifecycleCtx.Err(); err != nil {
return nil, false, err
}
if !hasCorrectIssuer(a.jwtAuthenticator.Issuer.URL, token) {
return nil, false, nil
}
@@ -972,6 +1014,10 @@ func (a *jwtAuthenticator) AuthenticateToken(ctx context.Context, token string)
}
func (a *jwtAuthenticator) HealthCheck() error {
if err := a.lifecycleCtx.Err(); err != nil {
return fmt.Errorf("oidc: authenticator for issuer %q is not healthy due to lifecycle context cancellation: %w", a.jwtAuthenticator.Issuer.URL, err)
}
if holder := *a.healthCheck.Load(); holder.err != nil {
return fmt.Errorf("oidc: authenticator for issuer %q is not healthy: %w", a.jwtAuthenticator.Issuer.URL, holder.err)
}
@@ -37,7 +37,7 @@ import (
"text/template"
"time"
"gopkg.in/go-jose/go-jose.v2"
"github.com/go-jose/go-jose/v4"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/wait"
@@ -116,7 +116,12 @@ type staticKeySet struct {
}
func (s *staticKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
jws, err := jose.ParseSigned(jwt)
allowedSignatureAlgorithms := []jose.SignatureAlgorithm{}
for _, key := range s.keys {
allowedSignatureAlgorithms = append(allowedSignatureAlgorithms, jose.SignatureAlgorithm(key.Algorithm))
}
jws, err := jose.ParseSigned(jwt, allowedSignatureAlgorithms)
if err != nil {
return nil, err
}
@@ -1946,7 +1951,7 @@ func TestToken(t *testing.T) {
"username": "jane",
"exp": %d
}`, valid.Unix()),
wantErr: `oidc: verify token: oidc: id token signed with unsupported algorithm, expected ["RS256"] got "PS256"`,
wantErr: `oidc: verify token: oidc: malformed jwt: unexpected signature algorithm "PS256"; expected ["RS256"]`,
},
{
name: "ps256",
+2 -4
View File
@@ -23,7 +23,7 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
@@ -48,7 +48,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -141,7 +141,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -262,7 +261,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+1 -2
View File
@@ -7,8 +7,8 @@ go 1.27.0
godebug default=go1.27
require (
github.com/go-jose/go-jose/v4 v4.1.5
github.com/stretchr/testify v1.12.1
gopkg.in/go-jose/go-jose.v2 v2.6.3
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/klog/v2 v2.140.0
@@ -20,7 +20,6 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/crypto v0.56.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/text v0.41.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
+3 -4
View File
@@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
@@ -40,8 +42,7 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
@@ -56,8 +57,6 @@ golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -20,15 +20,26 @@ import (
"fmt"
"strings"
jose "gopkg.in/go-jose/go-jose.v2"
jose "github.com/go-jose/go-jose/v4"
)
// ComputeDetachedSignature takes content and token details and computes a detached
// JWS signature. This is described in Appendix F of RFC 7515. Basically, this
// is a regular JWS with the content part of the signature elided.
func ComputeDetachedSignature(content, tokenID, tokenSecret string) (string, error) {
key := []byte(tokenSecret)
// if the token secret is not at least 32 bytes in length,
// pad it with zeros until it is 32 bytes. This satisfies
// the go-jose requirement that keys for HS256 signatures need
// to be at least this long.
if len([]byte(tokenSecret)) < 32 {
key = make([]byte, 32)
copy(key, []byte(tokenSecret))
}
jwk := &jose.JSONWebKey{
Key: []byte(tokenSecret),
Key: key,
KeyID: tokenID,
}
@@ -17,6 +17,7 @@ limitations under the License.
package jws
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -67,3 +68,55 @@ func TestDetachedTokenIsValid(t *testing.T) {
assert.False(t, DetachedTokenIsValid(sig2, content, id, secret),
"Content %q and token \"%s:%s\" should not equal signature: %q", content, id, secret, sig)
}
func TestSignatureCompatibility(t *testing.T) {
testcases := []struct {
name string
secret string
expected string
}{
{
name: "empty secret",
secret: "",
expected: "eyJhbGciOiJIUzI1NiIsImtpZCI6ImlkIn0..GBG9nJpyHc7Tk_iryUsRXOvX6U-19DDCA2AmlC326DU",
},
{
name: "1 byte secret",
secret: "x",
expected: "eyJhbGciOiJIUzI1NiIsImtpZCI6ImlkIn0..x-LVIHM7D6uo6buZFEW2ldnwJL2Ddduh2ZhxFN33v5w",
},
{
name: "8 byte secret",
secret: strings.Repeat("x", 8),
expected: "eyJhbGciOiJIUzI1NiIsImtpZCI6ImlkIn0..pCqXr-QOqtnLik5dOn0DZu842z-u_rchSqd6B-MoOFQ",
},
{
name: "31 byte secret",
secret: strings.Repeat("x", 31),
expected: "eyJhbGciOiJIUzI1NiIsImtpZCI6ImlkIn0..Wy4AKm0ArcyixzYnGnwAjWR7POeVBKBwnkLZw8GUkG8",
},
{
name: "32 byte secret",
secret: strings.Repeat("x", 32),
expected: "eyJhbGciOiJIUzI1NiIsImtpZCI6ImlkIn0..1z14ArXPw5a3L8hIAZmzMWdDXXsxED78LO7wn3m4a0k",
},
{
name: "33 byte secret",
secret: strings.Repeat("x", 33),
expected: "eyJhbGciOiJIUzI1NiIsImtpZCI6ImlkIn0..exPZdsbPApmX14UF7bf2KoBdZwrkzSotja4egZ60UXs",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
sig, err := ComputeDetachedSignature("content", "id", tc.secret)
if err != nil {
t.Fatal(err)
}
if sig != tc.expected {
t.Fatalf("wanted signature %q but got %q", tc.expected, sig)
}
})
}
}
+2 -4
View File
@@ -22,7 +22,7 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
@@ -45,7 +45,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -137,7 +137,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -257,7 +256,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+2 -4
View File
@@ -16,7 +16,7 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -33,7 +33,7 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -116,7 +116,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -217,7 +216,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+2 -4
View File
@@ -23,7 +23,7 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
@@ -46,7 +46,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -141,7 +141,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -267,7 +266,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+2 -3
View File
@@ -14,7 +14,7 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -27,6 +27,7 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
@@ -104,7 +105,6 @@ github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRb
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -189,7 +189,6 @@ google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+2 -4
View File
@@ -22,7 +22,7 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
@@ -45,7 +45,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -137,7 +137,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -257,7 +256,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+2 -4
View File
@@ -22,7 +22,7 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
@@ -45,7 +45,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -138,7 +138,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@@ -264,7 +263,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
@@ -28,7 +28,7 @@ import (
"strings"
"time"
"gopkg.in/go-jose/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4/jwt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
@@ -28,7 +28,7 @@ import (
"strings"
"time"
"gopkg.in/go-jose/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4/jwt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
@@ -29,12 +29,13 @@ import (
"runtime"
"time"
"github.com/coreos/go-oidc"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/spf13/cobra"
"golang.org/x/oauth2"
"gopkg.in/go-jose/go-jose.v2/jwt"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/serviceaccount"
)
// CmdTestServiceAccountIssuerDiscovery is used by agnhost Cobra.
@@ -100,7 +101,7 @@ func main(cmd *cobra.Command, args []string) {
}
func validate(ctx context.Context, raw string) error {
tok, err := jwt.ParseSigned(raw)
tok, err := jwt.ParseSigned(raw, serviceaccount.AcceptableServiceAccountSignatureAlgorithms)
if err != nil {
log.Fatal(err)
}
@@ -127,6 +128,7 @@ func validate(ctx context.Context, raw string) error {
// - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader
// - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey
// - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey
// - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms
// - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs
validTok, err := iss.Verifier(&oidc.Config{
+1 -1
View File
@@ -39,10 +39,10 @@ import (
"testing"
"time"
"github.com/go-jose/go-jose/v4"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/go-jose/go-jose.v2"
authenticationv1 "k8s.io/api/authentication/v1"
rbacv1 "k8s.io/api/rbac/v1"
+3 -3
View File
@@ -34,8 +34,8 @@ import (
"testing"
"time"
jose "gopkg.in/go-jose/go-jose.v2"
"gopkg.in/go-jose/go-jose.v2/jwt"
jose "github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
authenticationv1 "k8s.io/api/authentication/v1"
@@ -1228,7 +1228,7 @@ func TestServiceAccountTokenCreate(t *testing.T) {
t.Fatalf("len(jwks.Keys) = %d, want 1", len(jwks.Keys))
}
key := jwks.Keys[0]
tok, err := jwt.ParseSigned(token)
tok, err := jwt.ParseSigned(token, serviceaccount.AcceptableServiceAccountSignatureAlgorithms)
if err != nil {
t.Fatalf("could not parse token %q: %v", token, err)
}
+1 -1
View File
@@ -19,7 +19,7 @@ limitations under the License.
package handlers
import (
"gopkg.in/go-jose/go-jose.v2"
"github.com/go-jose/go-jose/v4"
)
type Token struct {
+1 -1
View File
@@ -21,8 +21,8 @@ limitations under the License.
package handlers
import (
"github.com/go-jose/go-jose/v4"
mock "github.com/stretchr/testify/mock"
"gopkg.in/go-jose/go-jose.v2"
)
// NewMockTokenHandler creates a new instance of MockTokenHandler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+1 -1
View File
@@ -31,8 +31,8 @@ import (
"os"
"testing"
"github.com/go-jose/go-jose/v4"
"github.com/stretchr/testify/require"
"gopkg.in/go-jose/go-jose.v2"
"k8s.io/kubernetes/test/utils/oidc/handlers"
)
-2
View File
@@ -1,2 +0,0 @@
/bin
/gopath
-18
View File
@@ -1,18 +0,0 @@
language: go
go:
- "1.14"
- "1.15"
arch:
- AMD64
- ppc64le
install:
- go get -v -t github.com/coreos/go-oidc/...
- go get golang.org/x/tools/cmd/cover
- go get golang.org/x/lint/golint
script:
- ./test
notifications:
email: false
-71
View File
@@ -1,71 +0,0 @@
# How to Contribute
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
GitHub pull requests. This document outlines some of the conventions on
development workflow, commit message formatting, contact points and other
resources to make it easier to get your contribution accepted.
# Certificate of Origin
By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project currently uses the general CoreOS email list and IRC channel:
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
are very busy and read the mailing lists.
## Getting Started
- Fork the repository on GitHub
- Read the [README](README.md) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!
### Format of the Commit Message
We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why.
```
scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
Fixes #38
```
The format can be described more formally as follows:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.
-36
View File
@@ -1,36 +0,0 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
-3
View File
@@ -1,3 +0,0 @@
Eric Chiang <ericchiang@google.com> (@ericchiang)
Mike Danese <mikedanese@google.com> (@mikedanese)
Rithu Leena John <rjohn@redhat.com> (@rithujohn191)
-72
View File
@@ -1,72 +0,0 @@
# go-oidc
[![GoDoc](https://godoc.org/github.com/coreos/go-oidc?status.svg)](https://godoc.org/github.com/coreos/go-oidc)
[![Build Status](https://travis-ci.org/coreos/go-oidc.png?branch=master)](https://travis-ci.org/coreos/go-oidc)
## OpenID Connect support for Go
This package enables OpenID Connect support for the [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2) package.
```go
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
// handle error
}
// Configure an OpenID Connect aware OAuth2 client.
oauth2Config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
// Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(),
// "openid" is a required scope for OpenID Connect flows.
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
```
OAuth2 redirects are unchanged.
```go
func handleRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
}
```
The on responses, the provider can be used to verify ID Tokens.
```go
var verifier = provider.Verifier(&oidc.Config{ClientID: clientID})
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Verify state and errors.
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
// handle error
}
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
// handle missing token
}
// Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
// handle error
}
// Extract custom claims
var claims struct {
Email string `json:"email"`
Verified bool `json:"email_verified"`
}
if err := idToken.Claims(&claims); err != nil {
// handle error
}
}
```
-61
View File
@@ -1,61 +0,0 @@
## CoreOS Community Code of Conduct
### Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of
fostering an open and welcoming community, we pledge to respect all people who
contribute through reporting issues, posting feature requests, updating
documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct. By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently applying these
principles to every aspect of managing this project. Project maintainers who do
not follow or enforce the Code of Conduct may be permanently removed from the
project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting a project maintainer, Brandon Philips
<brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
This Code of Conduct is adapted from the Contributor Covenant
(http://contributor-covenant.org), version 1.2.0, available at
http://contributor-covenant.org/version/1/2/0/
### CoreOS Events Code of Conduct
CoreOS events are working conferences intended for professional networking and
collaboration in the CoreOS community. Attendees are expected to behave
according to professional standards and in accordance with their employer’s
policies on appropriate workplace behavior.
While at CoreOS events or related social networking opportunities, attendees
should not engage in discriminatory or offensive speech or actions including
but not limited to gender, sexuality, race, age, disability, or religion.
Speakers should be especially aware of these concerns.
CoreOS does not condone any statements by speakers contrary to these standards.
CoreOS reserves the right to deny entrance and/or eject from an event (without
refund) any individual found to be engaging in discriminatory or offensive
speech or actions.
Please bring any concerns to the immediate attention of designated on-site
staff, Brandon Philips <brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
-21
View File
@@ -1,21 +0,0 @@
//go:build !golint
// +build !golint
// Don't lint this file. We don't want to have to add a comment to each constant.
package oidc
const (
// JOSE asymmetric signing algorithm values as defined by RFC 7518
//
// see: https://tools.ietf.org/html/rfc7518#section-3.1
RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
ES256 = "ES256" // ECDSA using P-256 and SHA-256
ES384 = "ES384" // ECDSA using P-384 and SHA-384
ES512 = "ES512" // ECDSA using P-521 and SHA-512
PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
)
-228
View File
@@ -1,228 +0,0 @@
package oidc
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
"github.com/pquerna/cachecontrol"
jose "gopkg.in/go-jose/go-jose.v2"
)
// keysExpiryDelta is the allowed clock skew between a client and the OpenID Connect
// server.
//
// When keys expire, they are valid for this amount of time after.
//
// If the keys have not expired, and an ID Token claims it was signed by a key not in
// the cache, if and only if the keys expire in this amount of time, the keys will be
// updated.
const keysExpiryDelta = 30 * time.Second
// NewRemoteKeySet returns a KeySet that can validate JSON web tokens by using HTTP
// GETs to fetch JSON web token sets hosted at a remote URL. This is automatically
// used by NewProvider using the URLs returned by OpenID Connect discovery, but is
// exposed for providers that don't support discovery or to prevent round trips to the
// discovery URL.
//
// The returned KeySet is a long lived verifier that caches keys based on cache-control
// headers. Reuse a common remote key set instead of creating new ones as needed.
//
// The behavior of the returned KeySet is undefined once the context is canceled.
func NewRemoteKeySet(ctx context.Context, jwksURL string) KeySet {
return newRemoteKeySet(ctx, jwksURL, time.Now)
}
func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time) *remoteKeySet {
if now == nil {
now = time.Now
}
return &remoteKeySet{jwksURL: jwksURL, ctx: ctx, now: now}
}
type remoteKeySet struct {
jwksURL string
ctx context.Context
now func() time.Time
// guard all other fields
mu sync.Mutex
// inflight suppresses parallel execution of updateKeys and allows
// multiple goroutines to wait for its result.
inflight *inflight
// A set of cached keys and their expiry.
cachedKeys []jose.JSONWebKey
expiry time.Time
}
// inflight is used to wait on some in-flight request from multiple goroutines.
type inflight struct {
doneCh chan struct{}
keys []jose.JSONWebKey
err error
}
func newInflight() *inflight {
return &inflight{doneCh: make(chan struct{})}
}
// wait returns a channel that multiple goroutines can receive on. Once it returns
// a value, the inflight request is done and result() can be inspected.
func (i *inflight) wait() <-chan struct{} {
return i.doneCh
}
// done can only be called by a single goroutine. It records the result of the
// inflight request and signals other goroutines that the result is safe to
// inspect.
func (i *inflight) done(keys []jose.JSONWebKey, err error) {
i.keys = keys
i.err = err
close(i.doneCh)
}
// result cannot be called until the wait() channel has returned a value.
func (i *inflight) result() ([]jose.JSONWebKey, error) {
return i.keys, i.err
}
func (r *remoteKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
jws, err := jose.ParseSigned(jwt)
if err != nil {
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
}
return r.verify(ctx, jws)
}
func (r *remoteKeySet) verify(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
// We don't support JWTs signed with multiple signatures.
keyID := ""
for _, sig := range jws.Signatures {
keyID = sig.Header.KeyID
break
}
keys, expiry := r.keysFromCache()
// Don't check expiry yet. This optimizes for when the provider is unavailable.
for _, key := range keys {
if keyID == "" || key.KeyID == keyID {
if payload, err := jws.Verify(&key); err == nil {
return payload, nil
}
}
}
if !r.now().Add(keysExpiryDelta).After(expiry) {
// Keys haven't expired, don't refresh.
return nil, errors.New("failed to verify id token signature")
}
keys, err := r.keysFromRemote(ctx)
if err != nil {
return nil, fmt.Errorf("fetching keys %v", err)
}
for _, key := range keys {
if keyID == "" || key.KeyID == keyID {
if payload, err := jws.Verify(&key); err == nil {
return payload, nil
}
}
}
return nil, errors.New("failed to verify id token signature")
}
func (r *remoteKeySet) keysFromCache() (keys []jose.JSONWebKey, expiry time.Time) {
r.mu.Lock()
defer r.mu.Unlock()
return r.cachedKeys, r.expiry
}
// keysFromRemote syncs the key set from the remote set, records the values in the
// cache, and returns the key set.
func (r *remoteKeySet) keysFromRemote(ctx context.Context) ([]jose.JSONWebKey, error) {
// Need to lock to inspect the inflight request field.
r.mu.Lock()
// If there's not a current inflight request, create one.
if r.inflight == nil {
r.inflight = newInflight()
// This goroutine has exclusive ownership over the current inflight
// request. It releases the resource by nil'ing the inflight field
// once the goroutine is done.
go func() {
// Sync keys and finish inflight when that's done.
keys, expiry, err := r.updateKeys()
r.inflight.done(keys, err)
// Lock to update the keys and indicate that there is no longer an
// inflight request.
r.mu.Lock()
defer r.mu.Unlock()
if err == nil {
r.cachedKeys = keys
r.expiry = expiry
}
// Free inflight so a different request can run.
r.inflight = nil
}()
}
inflight := r.inflight
r.mu.Unlock()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-inflight.wait():
return inflight.result()
}
}
func (r *remoteKeySet) updateKeys() ([]jose.JSONWebKey, time.Time, error) {
req, err := http.NewRequest("GET", r.jwksURL, nil)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: can't create request: %v", err)
}
resp, err := doRequest(r.ctx, req)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, time.Time{}, fmt.Errorf("unable to read response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed: %s %s", resp.Status, body)
}
var keySet jose.JSONWebKeySet
err = unmarshalResp(resp, body, &keySet)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: failed to decode keys: %v %s", err, body)
}
// If the server doesn't provide cache control headers, assume the
// keys expire immediately.
expiry := r.now()
_, e, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{})
if err == nil && e.After(expiry) {
expiry = e
}
return keySet.Keys, expiry, nil
}
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash
set -e
# Filter out any files with a !golint build tag.
LINTABLE=$( go list -tags=golint -f '
{{- range $i, $file := .GoFiles -}}
{{ $file }} {{ end }}
{{ range $i, $file := .TestGoFiles -}}
{{ $file }} {{ end }}' github.com/coreos/go-oidc )
go test -v -i -race github.com/coreos/go-oidc/...
go test -v -race github.com/coreos/go-oidc/...
golint -set_exit_status $LINTABLE
go vet github.com/coreos/go-oidc/...
go build -v ./example/...
File renamed without changes.
File renamed without changes.
+65
View File
@@ -0,0 +1,65 @@
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
//
// Construct a [Provider] and [oauth2.Config] through the identity provider's
// discovery document with [NewProvider], and construct an ID Token verifier
// with [Provider.Verifier]:
//
// provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
// if err != nil {
// // handle error
// }
//
// // Configure an OpenID Connect aware OAuth2 client.
// oauth2Config := oauth2.Config{
// ClientID: clientID,
// ClientSecret: clientSecret,
// RedirectURL: redirectURL,
// // Discovery returns the OAuth2 endpoints.
// Endpoint: provider.Endpoint(),
// // "openid" is a required scope for OpenID Connect flows.
// Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail},
// }
//
// idTokenVerifier := provider.Verifier(&oidc.Config{ClientID: clientID})
//
// OAuth 2.0 redirects then opt into the OpenID Connect flow with [ScopeOpenID]:
//
// func handleRedirect(w http.ResponseWriter, r *http.Request) {
// http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
// }
//
// When handling an OAuth 2.0 response, an [IDTokenVerifier] can be used to
// validate the "id_token" payload, containing well-known fields such as the
// user's email, name, and picture URL:
//
// func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// // Verify state and other OAuth 2.0 responses.
//
// // Perform standard token exchange.
// oauth2Token, err := oauth2Config.Exchange(r.Context(), r.URL.Query().Get("code"))
// if err != nil {
// // ...
// }
// // Extract the ID Token from OAuth2 token.
// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
// if !ok {
// // ...
// }
// // Parse and verify ID Token payload.
// idToken, err := idTokenVerifier.Verify(r.Context(), rawIDToken)
// if err != nil {
// // ...
// }
// // Parse well-known claims from the token.
// // https://openid.net/specs/openid-connect-core-1_0.html#Claims
// var claims struct {
// Email string `json:"email"`
// EmailVerified bool `json:"email_verified"`
// Name string `json:"name"`
// Picture string `json:"picture"`
// }
// if err := idToken.Claims(&claims); err != nil {
// // ...
// }
// }
package oidc
+40
View File
@@ -0,0 +1,40 @@
package oidc
import jose "github.com/go-jose/go-jose/v4"
// JOSE asymmetric signing algorithm values as defined by RFC 7518
//
// see: https://tools.ietf.org/html/rfc7518#section-3.1
const (
RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
ES256 = "ES256" // ECDSA using P-256 and SHA-256
ES384 = "ES384" // ECDSA using P-384 and SHA-384
ES512 = "ES512" // ECDSA using P-521 and SHA-512
PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
EdDSA = "EdDSA" // Ed25519 using SHA-512
)
var allAlgs = []jose.SignatureAlgorithm{
jose.RS256,
jose.RS384,
jose.RS512,
jose.ES256,
jose.ES384,
jose.ES512,
jose.PS256,
jose.PS384,
jose.PS512,
jose.EdDSA,
}
var supportedJOSEAlgs = map[string]bool{}
func init() {
for _, alg := range allAlgs {
supportedJOSEAlgs[string(alg)] = true
}
}
+330
View File
@@ -0,0 +1,330 @@
package oidc
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"sync"
jose "github.com/go-jose/go-jose/v4"
)
// StaticKeySet is a verifier that validates JWT against a static set of public keys.
type StaticKeySet struct {
// PublicKeys used to verify the JWT. Supported types are *rsa.PublicKey,
// *ecdsa.PublicKey, and ed25519.PublicKey.
PublicKeys []crypto.PublicKey
}
// VerifySignature compares the signature against a static set of public keys.
func (s *StaticKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
// Algorithms are already checked by Verifier, so this parse method accepts
// any algorithm.
jws, err := jose.ParseSigned(jwt, allAlgs)
if err != nil {
return nil, fmt.Errorf("parsing jwt: %v", err)
}
for _, pub := range s.PublicKeys {
switch pub.(type) {
case *rsa.PublicKey:
case *ecdsa.PublicKey:
case ed25519.PublicKey:
default:
return nil, fmt.Errorf("invalid public key type provided: %T", pub)
}
payload, err := jws.Verify(pub)
if err != nil {
continue
}
return payload, nil
}
return nil, fmt.Errorf("no public keys able to verify jwt")
}
// NewRemoteKeySet returns a KeySet that can validate JSON web tokens by using HTTP
// GETs to fetch JSON web token sets hosted at a remote URL. This is automatically
// used by NewProvider using the URLs returned by OpenID Connect discovery, but is
// exposed for providers that don't support discovery or to prevent round trips to the
// discovery URL.
//
// The returned KeySet is a long lived verifier that caches keys in memory,
// re-fetching from the remote URL when it encounters a key ID it hasn't seen.
// Reuse a single remote key set rather than creating a new one for each
// verification.
func NewRemoteKeySet(ctx context.Context, jwksURL string) *RemoteKeySet {
return newRemoteKeySet(ctx, jwksURL)
}
func newRemoteKeySet(ctx context.Context, jwksURL string) *RemoteKeySet {
return &RemoteKeySet{
jwksURL: jwksURL,
// For historical reasons, this package uses contexts for configuration, not just
// cancellation. In hindsight, this was a bad idea.
//
// Attemps to reason about how cancels should work with background requests have
// largely lead to confusion. Use the context here as a config bag-of-values and
// ignore the cancel function.
ctx: context.WithoutCancel(ctx),
}
}
// RemoteKeySet is a KeySet implementation that validates JSON web tokens against
// a jwks_uri endpoint.
type RemoteKeySet struct {
jwksURL string
// Used for configuration. Cancelation is ignored.
ctx context.Context
// guard all other fields
mu sync.RWMutex
// inflight suppresses parallel execution of updateKeys and allows
// multiple goroutines to wait for its result.
inflight *inflight
// A set of cached keys.
cachedKeys []jose.JSONWebKey
}
// inflight is used to wait on some in-flight request from multiple goroutines.
type inflight struct {
doneCh chan struct{}
keys []jose.JSONWebKey
err error
}
func newInflight() *inflight {
return &inflight{doneCh: make(chan struct{})}
}
// wait returns a channel that multiple goroutines can receive on. Once it returns
// a value, the inflight request is done and result() can be inspected.
func (i *inflight) wait() <-chan struct{} {
return i.doneCh
}
// done can only be called by a single goroutine. It records the result of the
// inflight request and signals other goroutines that the result is safe to
// inspect.
func (i *inflight) done(keys []jose.JSONWebKey, err error) {
i.keys = keys
i.err = err
close(i.doneCh)
}
// result cannot be called until the wait() channel has returned a value.
func (i *inflight) result() ([]jose.JSONWebKey, error) {
return i.keys, i.err
}
// parsedJWTKey is a context key that allows common setups to avoid parsing the
// JWT twice. It holds a *jose.JSONWebSignature value.
var parsedJWTKey contextKey
// VerifySignature validates a payload against a signature from the jwks_uri.
//
// Users MUST NOT call this method directly and should use an IDTokenVerifier
// instead. This method skips critical validations such as 'alg' values and is
// only exported to implement the KeySet interface.
func (r *RemoteKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
jws, ok := ctx.Value(parsedJWTKey).(*jose.JSONWebSignature)
if !ok {
// The algorithm values are already enforced by the Validator, which also sets
// the context value above to pre-parsed signature.
//
// Practically, this codepath isn't called in normal use of this package, but
// if it is, the algorithms have already been checked.
var err error
jws, err = jose.ParseSigned(jwt, allAlgs)
if err != nil {
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
}
}
return r.verify(ctx, jws)
}
func (r *RemoteKeySet) verify(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
// We don't support JWTs signed with multiple signatures.
keyID := ""
for _, sig := range jws.Signatures {
keyID = sig.Header.KeyID
break
}
keys := r.keysFromCache()
for _, key := range keys {
if keyID == "" || key.KeyID == keyID {
if payload, err := jws.Verify(&key); err == nil {
return payload, nil
}
}
}
// If the kid doesn't match, check for new keys from the remote. This is the
// strategy recommended by the spec.
//
// https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys
keys, err := r.keysFromRemote(ctx)
if err != nil {
return nil, fmt.Errorf("fetching keys %w", err)
}
for _, key := range keys {
if keyID == "" || key.KeyID == keyID {
if payload, err := jws.Verify(&key); err == nil {
return payload, nil
}
}
}
return nil, errors.New("failed to verify id token signature")
}
func (r *RemoteKeySet) keysFromCache() (keys []jose.JSONWebKey) {
r.mu.RLock()
defer r.mu.RUnlock()
return r.cachedKeys
}
// keysFromRemote syncs the key set from the remote set, records the values in the
// cache, and returns the key set.
func (r *RemoteKeySet) keysFromRemote(ctx context.Context) ([]jose.JSONWebKey, error) {
// Need to lock to inspect the inflight request field.
r.mu.Lock()
// If there's not a current inflight request, create one.
if r.inflight == nil {
r.inflight = newInflight()
// This goroutine has exclusive ownership over the current inflight
// request. It releases the resource by nil'ing the inflight field
// once the goroutine is done.
go func() {
// Sync keys and finish inflight when that's done.
keys, err := r.updateKeys()
r.inflight.done(keys, err)
// Lock to update the keys and indicate that there is no longer an
// inflight request.
r.mu.Lock()
defer r.mu.Unlock()
if err == nil {
r.cachedKeys = keys
}
// Free inflight so a different request can run.
r.inflight = nil
}()
}
inflight := r.inflight
r.mu.Unlock()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-inflight.wait():
return inflight.result()
}
}
// jwkJSON implements custom logic for unmarshaling JSON Web Key Sets.
type jwkJSON struct {
Keys []jose.JSONWebKey
}
func (j *jwkJSON) UnmarshalJSON(data []byte) error {
var raw struct {
Keys []json.RawMessage `json:"keys"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
// For each key, attempt to determine if the algorithm is supported before
// unmarshaling the key. This allows us to ignore keys with unsupported
// algorithms, rather than failing to load the entire set.
for _, key := range raw.Keys {
var metadata struct {
Alg string `json:"alg"`
}
if err := json.Unmarshal(key, &metadata); err != nil {
return err
}
if metadata.Alg != "" {
// Algorithms are technically optional, but if the key advertises an
// algorithm we don't support, ignore it rather than failing to load
// the entire key set.
//
// https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
//
// This skip is currently implemented due to secp256k1, which some
// providers use for signing with "ES256K". While we could also
// ignore the curve, this seems like a more general check in case
// providers start throwing in post-quantum algorithims or something
// like that.
//
// https://github.com/coreos/go-oidc/issues/490
if !supportedJOSEAlgs[metadata.Alg] {
continue
}
}
var jwk jose.JSONWebKey
if err := json.Unmarshal(key, &jwk); err != nil {
// Ignore keys with types that go-jose doesn't support, such as
// OKP keys with Ed448 or X448 curves. Some providers include
// them in their key sets without an "alg" value, so the check
// above doesn't catch them.
//
// https://datatracker.ietf.org/doc/html/rfc7517#section-5
//
// Implementations SHOULD ignore JWKs within a JWK Set that use
// "kty" (key type) values that are not understood by them, that
// are missing required members, or for which values are out of
// the supported ranges.
if errors.Is(err, jose.ErrUnsupportedKeyType) {
continue
}
return err
}
j.Keys = append(j.Keys, jwk)
}
return nil
}
func (r *RemoteKeySet) updateKeys() ([]jose.JSONWebKey, error) {
req, err := http.NewRequest("GET", r.jwksURL, nil)
if err != nil {
return nil, fmt.Errorf("oidc: can't create request: %v", err)
}
req.Header.Set("Cache-Control", "no-cache")
resp, err := doRequest(r.ctx, req)
if err != nil {
return nil, fmt.Errorf("oidc: get keys failed %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("oidc: get keys failed: %s %s", resp.Status, body)
}
var keySet jwkJSON
err = unmarshalResp(resp, body, &keySet)
if err != nil {
return nil, fmt.Errorf("oidc: failed to decode keys: %v %s", err, body)
}
return keySet.Keys, nil
}
+188
View File
@@ -0,0 +1,188 @@
package oidc
import (
"context"
"encoding/json"
"errors"
"fmt"
"slices"
"time"
)
// LogoutToken represents a verified token from a Back-Channel Logout. Use
// [IDTokenVerifier.VerifyLogout] within a POST handler to receive and validate
// a token.
//
// See the ./example/logout at the top-level of this repo for a full example
// application.
type LogoutToken struct {
// The required token ID claim ("jti"). When processing this token for
// logout, applications should validate that no recent token with the same
// value has been processed.
TokenID string
// The unique identifier of the user that this logout token is for. This
// will match the subject value of the ID Token.
//
// If not set, SessionID will be.
Subject string
// The "iss" claim. This is validated against the provider URL unless
// explicitly skipped through SkipIssuerCheck.
Issuer string
// The Client ID this logout token is for. Validated against the config
// unless SkipClientIDCheck is provided.
Audience []string
// When this token was issued.
IssuedAt time.Time
// When this token expires. Validated unless SkipExpiryCheck is provided.
Expiry time.Time
// Optional session ID claim ("sid").
//
// The exact semantics of session IDs vary between identity providers. Use
// your provider's documentation to determine what this correlates to and
// how it should be handled.
SessionID string
claims []byte
}
// Claims unmarshals the raw JSON payload of the Logout Token into a provided
// struct. This can be used to access field not exposed by the LogoutToken
// fields.
//
// logoutToken, err := idTokenVerifier.VerifyLogout(ctx, rawLogoutToken)
// if err != nil{
// // ...
// }
// var claims struct {
// TraceID string `json:"trace_id"`
// }
// if err := logoutToken.Claims(&claims); err != nil {
// // ...
// }
func (l *LogoutToken) Claims(v any) error {
if l.claims == nil {
return errors.New("oidc: claims not set")
}
return json.Unmarshal(l.claims, v)
}
// https://openid.net/specs/openid-connect-backchannel-1_0.html#LogoutToken
type logoutTokenJSON struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience audience `json:"aud"`
Expiry jsonTime `json:"exp"`
IssuedAt jsonTime `json:"iat"`
JTI string `json:"jti"`
Events struct {
Logout json.RawMessage `json:"http://schemas.openid.net/event/backchannel-logout"`
}
SessionID string `json:"sid"`
// Nonce is parsed as a raw message so its mere presence can be detected.
// The spec requires logout tokens to not contain a nonce claim, regardless
// of its value.
Nonce json.RawMessage `json:"nonce"`
}
// VerifyLogout validates a back-channel logout token. Logout tokens are
// received by the relying party (this package) from the identity provider at a
// preconfigured "backchannel_logout_uri" through a POST. Then on certain
// events, such as RP-Initiated Logout, the identity provider will send a signed
// token indicating that sessions for a specific user should be terminated.
//
// To support back-channel logout within your app, register a POST endpoint and
// verify the token:
//
// oidcConfig := &oidc.Config{
// ClientID: clientID,
// }
// verifier := provider.Verifier(oidcConfig)
//
// mux.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Request) {
// rawLogoutToken := r.PostFormValue("logout_token")
// if rawLogoutToken == "" {
// // ...
// }
// logoutToken, err := verifier.VerifyLogout(r.Context(), rawLogoutToken)
// if err != nil {
// // ...
// }
// // Use fields in the logoutToken to determine what sessions to
// // terminate.
//
// })
//
// Back-channel logout spec: https://openid.net/specs/openid-connect-backchannel-1_0.html
//
// RP-initiated logout spec: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
func (v *IDTokenVerifier) VerifyLogout(ctx context.Context, rawLogoutToken string) (*LogoutToken, error) {
payload, _, err := v.verifyJWT(ctx, rawLogoutToken)
if err != nil {
return nil, err
}
var logoutToken logoutTokenJSON
if err := json.Unmarshal(payload, &logoutToken); err != nil {
return nil, fmt.Errorf("oidc: parsing logout token payload: %v", err)
}
if len(logoutToken.Events.Logout) == 0 {
return nil, fmt.Errorf("oidc: logout token missing required http://schemas.openid.net/event/backchannel-logout event")
}
// A logout token MUST NOT contain a nonce claim. This prevents an ID token
// from being replayed as a logout token.
if len(logoutToken.Nonce) != 0 {
return nil, fmt.Errorf("oidc: logout token must not contain a 'nonce' claim")
}
t := &LogoutToken{
Issuer: logoutToken.Issuer,
Subject: logoutToken.Subject,
Audience: logoutToken.Audience,
IssuedAt: time.Time(logoutToken.IssuedAt),
Expiry: time.Time(logoutToken.Expiry),
SessionID: logoutToken.SessionID,
TokenID: logoutToken.JTI,
claims: payload,
}
if t.TokenID == "" {
return nil, fmt.Errorf("oidc: logout token missing required claim 'jti'")
}
if t.Expiry.IsZero() {
return nil, fmt.Errorf("oidc: logout token must contain an 'exp' claim")
}
// A logout token MUST contain a 'sub' claim, a 'sid' claim, or both.
if t.Subject == "" && t.SessionID == "" {
return nil, fmt.Errorf("oidc: logout token must contain a 'sub' claim, a 'sid' claim, or both")
}
if !v.config.SkipIssuerCheck && t.Issuer != v.issuer {
return nil, fmt.Errorf("oidc: logout token issued by a different provider, expected %q, got %q", v.issuer, t.Issuer)
}
if !v.config.SkipClientIDCheck {
if v.config.ClientID != "" {
if !slices.Contains(t.Audience, v.config.ClientID) {
return nil, fmt.Errorf("oidc: expected logout token audience %q got %q", v.config.ClientID, t.Audience)
}
} else {
return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
}
}
// If a SkipExpiryCheck is false, make sure token is not expired.
if !v.config.SkipExpiryCheck {
now := time.Now
if v.config.Now != nil {
now = v.config.Now
}
nowTime := now()
if t.Expiry.Before(nowTime) {
return nil, &TokenExpiredError{Expiry: t.Expiry}
}
}
return t, nil
}
@@ -1,4 +1,3 @@
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
package oidc
import (
@@ -10,21 +9,40 @@ import (
"errors"
"fmt"
"hash"
"io/ioutil"
"io"
"mime"
"net/http"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/oauth2"
jose "gopkg.in/go-jose/go-jose.v2"
)
const (
// ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
ScopeOpenID = "openid"
// ScopeProfile can be used to request information about the user's profile,
// such as "name", "picture", etc.
//
// The exact set of claims supported by identity providers differs widely,
// though "name" and "picture" are commonly returned.
//
// See: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
ScopeProfile = "profile"
// ScopeEmail can be used to request the user's email address through the
// "email" and "email_verified" claims.
//
// What it means to verify an email isn't well defined. Clients can
// generally throw out emails when the "emvail_verified" claim is false, but
// should consult identity provider specific docs if attempting to ensure
// that the user controls the returned email address.
//
// See: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
ScopeEmail = "email"
// ScopeOfflineAccess is an optional scope defined by OpenID Connect for requesting
// OAuth2 refresh tokens.
//
@@ -41,6 +59,10 @@ var (
errInvalidAtHash = errors.New("access token hash does not match value in ID token")
)
type contextKey int
var issuerURLKey contextKey
// ClientContext returns a new Context that carries the provided HTTP client.
//
// This method sets the same context key used by the golang.org/x/oauth2 package,
@@ -55,40 +77,101 @@ func ClientContext(ctx context.Context, client *http.Client) context.Context {
return context.WithValue(ctx, oauth2.HTTPClient, client)
}
func getClient(ctx context.Context) *http.Client {
if c, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
return c
}
return nil
}
// InsecureIssuerURLContext allows discovery to work when the issuer_url reported
// by upstream is mismatched with the discovery URL. This is meant for integration
// with off-spec providers such as Azure.
//
// discoveryBaseURL := "https://login.microsoftonline.com/organizations/v2.0"
// issuerURL := "https://login.microsoftonline.com/my-tenantid/v2.0"
//
// ctx := oidc.InsecureIssuerURLContext(parentContext, issuerURL)
//
// // Provider will be discovered with the discoveryBaseURL, but use issuerURL
// // for future issuer validation.
// provider, err := oidc.NewProvider(ctx, discoveryBaseURL)
//
// This is insecure because validating the correct issuer is critical for multi-tenant
// providers. Any overrides here MUST be carefully reviewed.
func InsecureIssuerURLContext(ctx context.Context, issuerURL string) context.Context {
return context.WithValue(ctx, issuerURLKey, issuerURL)
}
func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
client := http.DefaultClient
if c, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
if c := getClient(ctx); c != nil {
client = c
}
return client.Do(req.WithContext(ctx))
}
// Provider represents an OpenID Connect server's configuration.
// Provider represents an OpenID Connect server's configuration, fetched from
// the discovery document.
//
// To access fields in the discovery document that aren't exposed directly
// through this package's API, use the [Provider.Claims] method. For example, to
// access the registration or end session endpoints:
//
// p, err := oidc.NewProvider(ctx, "https://issuer.example.com")
// if err != nil {
// // ...
// }
// var metadata struct {
// EndSessionEndpoint string `json:"end_session_endpoint"`
// RegistrationEndpoint string `json:"registration_endpoint"`
// }
// if err := p.Claims(&metadata); err != nil {
// // ...
// }
type Provider struct {
issuer string
authURL string
tokenURL string
userInfoURL string
algorithms []string
issuer string
authURL string
tokenURL string
deviceAuthURL string
userInfoURL string
jwksURL string
algorithms []string
// Raw claims returned by the server.
rawClaims []byte
remoteKeySet KeySet
// Guards all of the following fields.
mu sync.Mutex
// HTTP client specified from the initial NewProvider request. This is used
// when creating the common key set.
client *http.Client
// A key set that uses context.Background() and is shared between all code paths
// that don't have a convinent way of supplying a unique context.
commonRemoteKeySet KeySet
}
type cachedKeys struct {
keys []jose.JSONWebKey
expiry time.Time
func (p *Provider) remoteKeySet() KeySet {
p.mu.Lock()
defer p.mu.Unlock()
if p.commonRemoteKeySet == nil {
ctx := context.Background()
if p.client != nil {
ctx = ClientContext(ctx, p.client)
}
p.commonRemoteKeySet = NewRemoteKeySet(ctx, p.jwksURL)
}
return p.commonRemoteKeySet
}
type providerJSON struct {
Issuer string `json:"issuer"`
AuthURL string `json:"authorization_endpoint"`
TokenURL string `json:"token_endpoint"`
JWKSURL string `json:"jwks_uri"`
UserInfoURL string `json:"userinfo_endpoint"`
Algorithms []string `json:"id_token_signing_alg_values_supported"`
Issuer string `json:"issuer"`
AuthURL string `json:"authorization_endpoint"`
TokenURL string `json:"token_endpoint"`
DeviceAuthURL string `json:"device_authorization_endpoint"`
JWKSURL string `json:"jwks_uri"`
UserInfoURL string `json:"userinfo_endpoint"`
Algorithms []string `json:"id_token_signing_alg_values_supported"`
}
// supportedAlgorithms is a list of algorithms explicitly supported by this
@@ -104,12 +187,116 @@ var supportedAlgorithms = map[string]bool{
PS256: true,
PS384: true,
PS512: true,
EdDSA: true,
}
// ProviderConfig allows direct creation of a [Provider] from metadata
// configuration. This is intended for interop with providers that don't support
// discovery, or host the JSON discovery document at an off-spec path.
//
// The ProviderConfig struct specifies JSON struct tags to support document
// parsing.
//
// // Directly fetch the metadata document.
// resp, err := http.Get("https://login.example.com/custom-metadata-path")
// if err != nil {
// // ...
// }
// defer resp.Body.Close()
//
// // Parse config from JSON metadata.
// config := &oidc.ProviderConfig{}
// if err := json.NewDecoder(resp.Body).Decode(config); err != nil {
// // ...
// }
// p := config.NewProvider(context.Background())
//
// For providers that implement discovery, use [NewProvider] instead.
//
// See: https://openid.net/specs/openid-connect-discovery-1_0.html
type ProviderConfig struct {
// IssuerURL is the identity of the provider, and the string it uses to sign
// ID tokens with. For example "https://accounts.google.com". This value MUST
// match ID tokens exactly.
IssuerURL string `json:"issuer"`
// AuthURL is the endpoint used by the provider to support the OAuth 2.0
// authorization endpoint.
AuthURL string `json:"authorization_endpoint"`
// TokenURL is the endpoint used by the provider to support the OAuth 2.0
// token endpoint.
TokenURL string `json:"token_endpoint"`
// DeviceAuthURL is the endpoint used by the provider to support the OAuth 2.0
// device authorization endpoint.
DeviceAuthURL string `json:"device_authorization_endpoint"`
// UserInfoURL is the endpoint used by the provider to support the OpenID
// Connect UserInfo flow.
//
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
UserInfoURL string `json:"userinfo_endpoint"`
// JWKSURL is the endpoint used by the provider to advertise public keys to
// verify issued ID tokens. This endpoint is polled as new keys are made
// available.
JWKSURL string `json:"jwks_uri"`
// Algorithms, if provided, indicate a list of JWT algorithms allowed to sign
// ID tokens. If not provided, this defaults to the algorithms advertised by
// the JWK endpoint, then the set of algorithms supported by this package.
Algorithms []string `json:"id_token_signing_alg_values_supported"`
}
// NewProvider initializes a provider from a set of endpoints, rather than
// through discovery.
//
// The provided context is only used for [http.Client] configuration through
// [ClientContext], not cancelation.
//
// For providers that implement discovery, use [NewProvider] instead.
func (p *ProviderConfig) NewProvider(ctx context.Context) *Provider {
return &Provider{
issuer: p.IssuerURL,
authURL: p.AuthURL,
tokenURL: p.TokenURL,
deviceAuthURL: p.DeviceAuthURL,
userInfoURL: p.UserInfoURL,
jwksURL: p.JWKSURL,
algorithms: p.Algorithms,
client: getClient(ctx),
}
}
// IssuerMismatchError is returned by [NewProvider] when the "iss" value
// reported by the upstream is different than the expected value.
//
// Issuer mismatches can occur due to trailing slashes ("https://example.com"
// vs. "https://example.com/") or represent significant misconfiguration for
// multi-tenant issuers.
//
// Issuers must match exactly as they are also used to validate ID Tokens.
//
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
type IssuerMismatchError struct {
// The value provided to this package. The expected value.
Provided string
// The value advertised by the discovery document.
Discovered string
}
func (e *IssuerMismatchError) Error() string {
return fmt.Sprintf("oidc: issuer URL provided to client (%q) did not match the issuer URL returned by provider (%q)", e.Provided, e.Discovered)
}
// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
//
// The issuer is the URL identifier for the service. For example: "https://accounts.google.com"
// or "https://login.salesforce.com".
//
// If the "iss" value returned in the discovery document doesn't match the value
// provided here, [IssuerMismatchError] is returned.
//
// OpenID Connect providers that don't implement discovery or host the discovery
// document at a non-spec compliant path (such as requiring a URL parameter),
// should use [ProviderConfig] instead.
//
// See: https://openid.net/specs/openid-connect-discovery-1_0.html
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
req, err := http.NewRequest("GET", wellKnown, nil)
@@ -122,7 +309,7 @@ func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read response body: %v", err)
}
@@ -137,8 +324,15 @@ func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
}
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)
issuerURL, skipIssuerValidation := ctx.Value(issuerURLKey).(string)
if !skipIssuerValidation {
issuerURL = issuer
}
if p.Issuer != issuerURL && !skipIssuerValidation {
return nil, &IssuerMismatchError{
Provided: issuerURL,
Discovered: p.Issuer,
}
}
var algs []string
for _, a := range p.Algorithms {
@@ -147,13 +341,15 @@ func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
}
}
return &Provider{
issuer: p.Issuer,
authURL: p.AuthURL,
tokenURL: p.TokenURL,
userInfoURL: p.UserInfoURL,
algorithms: algs,
rawClaims: body,
remoteKeySet: NewRemoteKeySet(ctx, p.JWKSURL),
issuer: issuerURL,
authURL: p.AuthURL,
tokenURL: p.TokenURL,
deviceAuthURL: p.DeviceAuthURL,
userInfoURL: p.UserInfoURL,
jwksURL: p.JWKSURL,
algorithms: algs,
rawClaims: body,
client: getClient(ctx),
}, nil
}
@@ -170,7 +366,7 @@ func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
//
// For a list of fields defined by the OpenID Connect spec see:
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
func (p *Provider) Claims(v interface{}) error {
func (p *Provider) Claims(v any) error {
if p.rawClaims == nil {
return errors.New("oidc: claims not set")
}
@@ -179,7 +375,13 @@ func (p *Provider) Claims(v interface{}) error {
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
func (p *Provider) Endpoint() oauth2.Endpoint {
return oauth2.Endpoint{AuthURL: p.authURL, TokenURL: p.tokenURL}
return oauth2.Endpoint{AuthURL: p.authURL, DeviceAuthURL: p.deviceAuthURL, TokenURL: p.tokenURL}
}
// UserInfoEndpoint returns the OpenID Connect userinfo endpoint for the given
// provider.
func (p *Provider) UserInfoEndpoint() string {
return p.userInfoURL
}
// UserInfo represents the OpenID Connect userinfo claims.
@@ -203,7 +405,7 @@ type userInfoRaw struct {
}
// Claims unmarshals the raw JSON object claims into the provided object.
func (u *UserInfo) Claims(v interface{}) error {
func (u *UserInfo) Claims(v any) error {
if u.claims == nil {
return errors.New("oidc: claims not set")
}
@@ -211,6 +413,44 @@ func (u *UserInfo) Claims(v interface{}) error {
}
// UserInfo uses the token source to query the provider's user info endpoint.
//
// It's fewer round trips and better supported to validate the ID Token with
// [Provider.Verifier], rather than using the UserInfo endpoint. The ID Token
// contains all information [UserInfo] provides:
//
// p, err := oidc.NewProvider(ctx, "https://issuer.example.com")
// if err != nil {
// // ...
// }
// config := &oidc.Config{
// ClientID: clientID,
// }
// v := p.Verifier(config)
// http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
// oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
// if err != nil {
// // ...
// }
// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
// if !ok {
// // ...
// }
// idToken, err := verifier.Verify(ctx, rawIDToken)
// if err != nil {
// // ...
// }
// // https://openid.net/specs/openid-connect-core-1_0.html#Claims
// var claims struct {
// Email string `json:"email"`
// EmailVerified bool `json:"email_verified"`
// Name string `json:"name"`
// Picture string `json:"picture"`
// }
// if err := idToken.Claims(&claims); err != nil {
// // ...
// }
// // Use claims...
// })
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
if p.userInfoURL == "" {
return nil, errors.New("oidc: user info endpoint is not supported by this provider")
@@ -232,7 +472,7 @@ func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource)
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@@ -243,7 +483,7 @@ func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource)
ct := resp.Header.Get("Content-Type")
mediaType, _, parseErr := mime.ParseMediaType(ct)
if parseErr == nil && mediaType == "application/jwt" {
payload, err := p.remoteKeySet.VerifySignature(ctx, string(body))
payload, err := p.remoteKeySet().VerifySignature(ctx, string(body))
if err != nil {
return nil, fmt.Errorf("oidc: invalid userinfo jwt signature %v", err)
}
@@ -288,7 +528,7 @@ type IDToken struct {
// A unique string which identifies the end user.
Subject string
// Expiry of the token. Ths package will not process tokens that have
// Expiry of the token. This package will not process tokens that have
// expired unless that validation is explicitly turned off.
Expiry time.Time
// When the token was issued by the provider.
@@ -296,7 +536,7 @@ type IDToken struct {
// Initial nonce provided during the authentication redirect.
//
// This package does NOT provided verification on the value of this field
// This package does NOT provide verification on the value of this field
// and it's the user's responsibility to ensure it contains a valid value.
Nonce string
@@ -328,15 +568,15 @@ type IDToken struct {
// if err := idToken.Claims(&claims); err != nil {
// // handle error
// }
func (i *IDToken) Claims(v interface{}) error {
func (i *IDToken) Claims(v any) error {
if i.claims == nil {
return errors.New("oidc: claims not set")
}
return json.Unmarshal(i.claims, v)
}
// VerifyAccessToken verifies that the hash of the access token that corresponds to the iD token
// matches the hash in the id token. It returns an error if the hashes don't match.
// VerifyAccessToken verifies that the hash of the access token that corresponds to the ID token
// matches the hash in the ID token. It returns an error if the hashes don't match.
// It is the caller's responsibility to ensure that the optional access token hash is present for the ID token
// before calling this method. See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
func (i *IDToken) VerifyAccessToken(accessToken string) error {
@@ -349,7 +589,7 @@ func (i *IDToken) VerifyAccessToken(accessToken string) error {
h = sha256.New()
case RS384, ES384, PS384:
h = sha512.New384()
case RS512, ES512, PS512:
case RS512, ES512, PS512, EdDSA:
h = sha512.New()
default:
return fmt.Errorf("oidc: unsupported signing algorithm %q", i.sigAlgorithm)
@@ -384,22 +624,14 @@ type claimSource struct {
type stringAsBool bool
func (sb *stringAsBool) UnmarshalJSON(b []byte) error {
var result bool
err := json.Unmarshal(b, &result)
if err == nil {
*sb = stringAsBool(result)
return nil
switch string(b) {
case "true", `"true"`:
*sb = true
case "false", `"false"`:
*sb = false
default:
return errors.New("invalid value for boolean")
}
var s string
err = json.Unmarshal(b, &s)
if err != nil {
return err
}
result, err = strconv.ParseBool(s)
if err != nil {
return err
}
*sb = stringAsBool(result)
return nil
}
@@ -415,7 +647,7 @@ func (a *audience) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, &auds); err != nil {
return err
}
*a = audience(auds)
*a = auds
return nil
}
@@ -441,7 +673,7 @@ func (j *jsonTime) UnmarshalJSON(b []byte) error {
return nil
}
func unmarshalResp(r *http.Response, body []byte, v interface{}) error {
func unmarshalResp(r *http.Response, body []byte, v any) error {
err := json.Unmarshal(body, &v)
if err == nil {
return nil
@@ -2,16 +2,15 @@ package oidc
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
"slices"
"time"
jose "github.com/go-jose/go-jose/v4"
"golang.org/x/oauth2"
jose "gopkg.in/go-jose/go-jose.v2"
)
const (
@@ -19,7 +18,20 @@ const (
issuerGoogleAccountsNoScheme = "accounts.google.com"
)
// KeySet is a set of publc JSON Web Keys that can be used to validate the signature
// TokenExpiredError indicates that [IDTokenVerifier.Verify] or
// [IDTokenVerifier.VerifyLogout] failed because the token was expired. This
// error does NOT indicate that the token is not also invalid for other reasons.
// Other checks might have failed if the expiration check had not failed.
type TokenExpiredError struct {
// Expiry is the time when the token expired.
Expiry time.Time
}
func (e *TokenExpiredError) Error() string {
return fmt.Sprintf("oidc: token is expired (Token Expiry: %v)", e.Expiry)
}
// KeySet is a set of public JSON Web Keys that can be used to validate the signature
// of JSON web tokens. This is expected to be backed by a remote key set through
// provider metadata discovery or an in-memory set of keys delivered out-of-band.
type KeySet interface {
@@ -34,7 +46,7 @@ type KeySet interface {
VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
}
// IDTokenVerifier provides verification for ID Tokens.
// IDTokenVerifier provides verification for ID Tokens and Logout Tokens.
type IDTokenVerifier struct {
keySet KeySet
config *Config
@@ -44,8 +56,8 @@ type IDTokenVerifier struct {
// NewVerifier returns a verifier manually constructed from a key set and issuer URL.
//
// It's easier to use provider discovery to construct an IDTokenVerifier than creating
// one directly. This method is intended to be used with provider that don't support
// metadata discovery, or avoiding round trips when the key set URL is already known.
// one directly. This method is intended to be used with providers that don't support
// metadata discovery, or to avoid round trips when the key set URL is already known.
//
// This constructor can be used to create a verifier directly using the issuer URL and
// JSON Web Key Set URL without using discovery:
@@ -53,15 +65,10 @@ type IDTokenVerifier struct {
// keySet := oidc.NewRemoteKeySet(ctx, "https://www.googleapis.com/oauth2/v3/certs")
// verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
//
// Since KeySet is an interface, this constructor can also be used to supply custom
// public key sources. For example, if a user wanted to supply public keys out-of-band
// and hold them statically in-memory:
// Or a static key set (e.g. for testing):
//
// // Custom KeySet implementation.
// keySet := newStatisKeySet(publicKeys...)
//
// // Verifier uses the custom KeySet implementation.
// verifier := oidc.NewVerifier("https://auth.example.com", keySet, config)
// keySet := &oidc.StaticKeySet{PublicKeys: []crypto.PublicKey{pub1, pub2}}
// verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier {
return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL}
}
@@ -76,8 +83,8 @@ type Config struct {
ClientID string
// If specified, only this set of algorithms may be used to sign the JWT.
//
// If the IDTokenVerifier is created from a provider with (*Provider).Verifier, this
// defaults to the set of algorithms the provider supports. Otherwise this values
// If the IDTokenVerifier is created from a provider with [Provider.Verifier], this
// defaults to the set of algorithms the provider supports. Otherwise this value
// defaults to RS256.
SupportedSigningAlgs []string
@@ -86,7 +93,7 @@ type Config struct {
// If true, token expiry is not checked.
SkipExpiryCheck bool
// SkipIssuerCheck is intended for specialized cases where the the caller wishes to
// SkipIssuerCheck is intended for specialized cases where the caller wishes to
// defer issuer validation. When enabled, callers MUST independently verify the Token's
// Issuer is a known good value.
//
@@ -97,13 +104,36 @@ type Config struct {
// Time function to check Token expiry. Defaults to time.Now
Now func() time.Time
// InsecureSkipSignatureCheck causes this package to skip JWT signature validation.
// It's intended for special cases where providers (such as Azure), use the "none"
// algorithm.
//
// This option can only be enabled safely when the ID Token is received directly
// from the provider after the token exchange.
//
// This option MUST NOT be used when receiving an ID Token from sources other
// than the token endpoint.
InsecureSkipSignatureCheck bool
}
// VerifierContext returns an IDTokenVerifier that uses the provider's key set to
// verify JWTs. As opposed to [Provider.Verifier], the context is used to configure
// requests to the upstream JWKs endpoint. The provided context's cancellation is
// ignored.
func (p *Provider) VerifierContext(ctx context.Context, config *Config) *IDTokenVerifier {
return p.newVerifier(NewRemoteKeySet(ctx, p.jwksURL), config)
}
// Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
//
// The returned IDTokenVerifier is tied to the Provider's context and its behavior is
// undefined once the Provider's context is canceled.
// The returned verifier uses a background context for all requests to the upstream
// JWKs endpoint. To control that context, use [Provider.VerifierContext] instead.
func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
return p.newVerifier(p.remoteKeySet(), config)
}
func (p *Provider) newVerifier(keySet KeySet, 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{}
@@ -111,28 +141,7 @@ func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
cp.SupportedSigningAlgs = p.algorithms
config = cp
}
return NewVerifier(p.issuer, p.remoteKeySet, config)
}
func parseJWT(p string) ([]byte, error) {
parts := strings.Split(p, ".")
if len(parts) < 2 {
return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
}
return payload, nil
}
func contains(sli []string, ele string) bool {
for _, s := range sli {
if s == ele {
return true
}
}
return false
return NewVerifier(p.issuer, keySet, config)
}
// Returns the Claims from the distributed JWT token
@@ -151,7 +160,7 @@ func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read response body: %v", err)
}
@@ -168,24 +177,10 @@ func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src
return token.claims, nil
}
func parseClaim(raw []byte, name string, v interface{}) error {
var parsed map[string]json.RawMessage
if err := json.Unmarshal(raw, &parsed); err != nil {
return err
}
val, ok := parsed[name]
if !ok {
return fmt.Errorf("claim doesn't exist: %s", name)
}
return json.Unmarshal([]byte(val), v)
}
// Verify parses a raw ID Token, verifies it's been signed by the provider, performs
// any additional checks depending on the Config, and returns the payload.
//
// Verify does NOT do nonce validation, which is the callers responsibility.
// Verify does NOT do nonce validation, which is the caller's responsibility.
//
// See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
//
@@ -202,43 +197,10 @@ func parseClaim(raw []byte, name string, v interface{}) error {
//
// token, err := verifier.Verify(ctx, rawIDToken)
func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
if strings.Count(rawIDToken, ".") > 2 {
// Older versions of go-jose are vulnerable to a scenario where
// strings.Cut(token, ".") can lead to excessive allocations. Since the
// "v2" branch of this repo explicitly doesn't update its dependencies
// at the request of Kubernetes, add a check here first.
//
// https://github.com/go-jose/go-jose/security/advisories/GHSA-c6gw-w398-hv78
return nil, fmt.Errorf("oidc: malformed jwt")
}
jws, err := jose.ParseSigned(rawIDToken)
payload, sig, err := v.verifyJWT(ctx, rawIDToken)
if err != nil {
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
return nil, err
}
switch len(jws.Signatures) {
case 0:
return nil, fmt.Errorf("oidc: id token not signed")
case 1:
default:
return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
}
sig := jws.Signatures[0]
supportedSigAlgs := v.config.SupportedSigningAlgs
if len(supportedSigAlgs) == 0 {
supportedSigAlgs = []string{RS256}
}
if !contains(supportedSigAlgs, sig.Header.Algorithm) {
return nil, fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm)
}
payload, err := v.keySet.VerifySignature(ctx, rawIDToken)
if err != nil {
return nil, fmt.Errorf("failed to verify signature: %v", err)
}
var token idToken
if err := json.Unmarshal(payload, &token); err != nil {
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
@@ -268,6 +230,7 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok
AccessTokenHash: token.AtHash,
claims: payload,
distributedClaims: distributedClaims,
sigAlgorithm: sig.Header.Algorithm,
}
// Check issuer.
@@ -287,7 +250,7 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok
// This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
if !v.config.SkipClientIDCheck {
if v.config.ClientID != "" {
if !contains(t.Audience, v.config.ClientID) {
if !slices.Contains(t.Audience, v.config.ClientID) {
return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
}
} else {
@@ -304,13 +267,15 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok
nowTime := now()
if t.Expiry.Before(nowTime) {
return nil, fmt.Errorf("oidc: token is expired (Token Expiry: %v)", t.Expiry)
return nil, &TokenExpiredError{Expiry: t.Expiry}
}
// If nbf claim is provided in token, ensure that it is indeed in the past.
if token.NotBefore != nil {
nbfTime := time.Time(*token.NotBefore)
leeway := 1 * time.Minute
// Set to 5 minutes since this is what other OpenID Connect providers do to deal with clock skew.
// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/6.12.2/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs#L149-L153
leeway := 5 * time.Minute
if nowTime.Add(leeway).Before(nbfTime) {
return nil, fmt.Errorf("oidc: current time %v before the nbf (not before) time: %v", nowTime, nbfTime)
@@ -318,7 +283,6 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok
}
}
t.sigAlgorithm = sig.Header.Algorithm
return t, nil
}
@@ -327,3 +291,50 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok
func Nonce(nonce string) oauth2.AuthCodeOption {
return oauth2.SetAuthURLParam("nonce", nonce)
}
func (v *IDTokenVerifier) verifyJWT(ctx context.Context, rawIDToken string) ([]byte, jose.Signature, error) {
var supportedSigAlgs []jose.SignatureAlgorithm
for _, alg := range v.config.SupportedSigningAlgs {
supportedSigAlgs = append(supportedSigAlgs, jose.SignatureAlgorithm(alg))
}
if len(supportedSigAlgs) == 0 {
// If no algorithms were specified by both the config and discovery, default
// to the one mandatory algorithm "RS256".
supportedSigAlgs = []jose.SignatureAlgorithm{jose.RS256}
}
if v.config.InsecureSkipSignatureCheck {
// "none" is a required value to even parse a JWT with the "none" algorithm
// using go-jose.
supportedSigAlgs = append(supportedSigAlgs, "none")
}
// Parse and verify the signature first. This at least forces the user to have
// a valid, signed ID token before we do any other processing.
jws, err := jose.ParseSigned(rawIDToken, supportedSigAlgs)
if err != nil {
return nil, jose.Signature{}, fmt.Errorf("oidc: malformed jwt: %v", err)
}
switch len(jws.Signatures) {
case 0:
return nil, jose.Signature{}, fmt.Errorf("oidc: id token not signed")
case 1:
default:
return nil, jose.Signature{}, fmt.Errorf("oidc: multiple signatures on id token not supported")
}
sig := jws.Signatures[0]
var payload []byte
if v.config.InsecureSkipSignatureCheck {
// Yolo mode.
payload = jws.UnsafePayloadWithoutVerification()
} else {
// The JWT is attached here for the happy path to avoid the verifier from
// having to parse the JWT twice.
ctx = context.WithValue(ctx, parsedJWTKey, jws)
payload, err = v.keySet.VerifySignature(ctx, rawIDToken)
if err != nil {
return nil, jose.Signature{}, fmt.Errorf("failed to verify signature: %v", err)
}
}
return payload, sig, nil
}
@@ -1,8 +1,2 @@
*~
.*.swp
*.out
*.test
*.pem
*.cov
jose-util/jose-util
jose-util.t.err
+53
View File
@@ -0,0 +1,53 @@
# https://github.com/golangci/golangci-lint
run:
skip-files:
- doc_test.go
modules-download-mode: readonly
linters:
enable-all: true
disable:
- gochecknoglobals
- goconst
- lll
- maligned
- nakedret
- scopelint
- unparam
- funlen # added in 1.18 (requires go-jose changes before it can be enabled)
linters-settings:
gocyclo:
min-complexity: 35
issues:
exclude-rules:
- text: "don't use ALL_CAPS in Go names"
linters:
- golint
- text: "hardcoded credentials"
linters:
- gosec
- text: "weak cryptographic primitive"
linters:
- gosec
- path: json/
linters:
- dupl
- errcheck
- gocritic
- gocyclo
- golint
- govet
- ineffassign
- staticcheck
- structcheck
- stylecheck
- unused
- path: _test\.go
linters:
- scopelint
- path: jwk.go
linters:
- gocyclo
+33
View File
@@ -0,0 +1,33 @@
language: go
matrix:
fast_finish: true
allow_failures:
- go: tip
go:
- "1.13.x"
- "1.14.x"
- tip
before_script:
- export PATH=$HOME/.local/bin:$PATH
before_install:
- go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0
- pip install cram --user
script:
- go test -v -covermode=count -coverprofile=profile.cov .
- go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner
- go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher
- go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt
- go test -v ./json # no coverage for forked encoding/json package
- golangci-lint run
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util
- cd ..
after_success:
- gocovmerge *.cov */*.cov > merged.coverprofile
- goveralls -coverprofile merged.coverprofile -service=travis-ci
@@ -7,8 +7,3 @@ When submitting code, please make every effort to follow existing conventions
and style in order to keep the code as readable as possible. Please also make
sure all tests pass by running `go test`, and format your code with `go fmt`.
We also recommend using `golint` and `errcheck`.
Before your code can be accepted into the project you must also sign the
[Individual Contributor License Agreement][1].
[1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
File renamed without changes.
+108
View File
@@ -0,0 +1,108 @@
# Go JOSE
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
[![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. This includes support for JSON Web Encryption,
JSON Web Signature, and JSON Web Token standards.
## Overview
The implementation follows the
[JSON Web Encryption](https://dx.doi.org/10.17487/RFC7516) (RFC 7516),
[JSON Web Signature](https://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
[JSON Web Token](https://dx.doi.org/10.17487/RFC7519) (RFC 7519) specifications.
Tables of supported algorithms are shown below. The library supports both
the compact and JWS/JWE JSON Serialization formats, and has optional support for
multiple recipients. It also comes with a small command-line utility
([`jose-util`](https://pkg.go.dev/github.com/go-jose/go-jose/jose-util))
for dealing with JOSE messages in a shell.
**Note**: We use a forked version of the `encoding/json` package from the Go
standard library which uses case-sensitive matching for member names (instead
of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
This is to avoid differences in interpretation of messages between go-jose and
libraries in other languages.
### Versions
The forthcoming Version 5 will be released with several breaking API changes,
and will require Golang's `encoding/json/v2`, which is currently requires
Go 1.25 built with GOEXPERIMENT=jsonv2.
Version 4 is the current stable version:
import "github.com/go-jose/go-jose/v4"
It supports at least the current and previous Golang release. Currently it
requires Golang 1.24.
Version 3 is only receiving critical security updates. Migration to Version 4 is recommended.
Versions 1 and 2 are obsolete, but can be found in the old repository, [square/go-jose](https://github.com/square/go-jose).
### Supported algorithms
See below for a table of supported algorithms. Algorithm identifiers match
the names in the [JSON Web Algorithms](https://dx.doi.org/10.17487/RFC7518)
standard where possible. The Godoc reference has a list of constants.
| Key encryption | Algorithm identifier(s) |
|:-----------------------|:-----------------------------------------------|
| RSA-PKCS#1v1.5 | RSA1_5 |
| RSA-OAEP | RSA-OAEP, RSA-OAEP-256 |
| AES key wrap | A128KW, A192KW, A256KW |
| AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW |
| ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW |
| ECDH-ES (direct) | ECDH-ES<sup>1</sup> |
| Direct encryption | dir<sup>1</sup> |
<sup>1. Not supported in multi-recipient mode</sup>
| Signing / MAC | Algorithm identifier(s) |
|:------------------|:------------------------|
| RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 |
| RSASSA-PSS | PS256, PS384, PS512 |
| HMAC | HS256, HS384, HS512 |
| ECDSA | ES256, ES384, ES512 |
| Ed25519 | EdDSA<sup>2</sup> |
<sup>2. Only available in version 2 of the package</sup>
| Content encryption | Algorithm identifier(s) |
|:-------------------|:--------------------------------------------|
| AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 |
| AES-GCM | A128GCM, A192GCM, A256GCM |
| Compression | Algorithm identifiers(s) |
|:-------------------|--------------------------|
| DEFLATE (RFC 1951) | DEF |
### Supported key types
See below for a table of supported key types. These are understood by the
library, and can be passed to corresponding functions such as `NewEncrypter` or
`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
allows attaching a key id.
| Algorithm(s) | Corresponding types |
|:------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) |
| ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) |
| EdDSA<sup>1</sup> | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) |
| AES, HMAC | []byte |
<sup>1. Only available in version 2 or later of the package</sup>
## Examples
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
Examples can be found in the Godoc
reference for this package. The
[`jose-util`](https://github.com/go-jose/go-jose/tree/main/jose-util)
subdirectory also contains a small command-line utility which might be useful
as an example as well.
+13
View File
@@ -0,0 +1,13 @@
# Security Policy
This document explains how to contact the Let's Encrypt security team to report security vulnerabilities.
## Supported Versions
| Version | Supported |
| ------- | ----------|
| >= v3 | &check; |
| v2 | &cross; |
| v1 | &cross; |
## Reporting a vulnerability
Please see [https://letsencrypt.org/contact/#security](https://letsencrypt.org/contact/#security) for the email address to report a vulnerability. Ensure that the subject line for your report contains the word `vulnerability` and is descriptive. Your email should be acknowledged within 24 hours. If you do not receive a response within 24 hours, please follow-up again with another email.
@@ -20,6 +20,8 @@ import (
"crypto"
"crypto/aes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
@@ -28,9 +30,8 @@ import (
"fmt"
"math/big"
"golang.org/x/crypto/ed25519"
josecipher "gopkg.in/go-jose/go-jose.v2/cipher"
"gopkg.in/go-jose/go-jose.v2/json"
josecipher "github.com/go-jose/go-jose/v4/cipher"
"github.com/go-jose/go-jose/v4/json"
)
// A generic RSA-based encrypter/verifier
@@ -195,11 +196,11 @@ func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipi
func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) {
switch alg {
case RSA1_5:
return rsa.EncryptPKCS1v15(RandReader, ctx.publicKey, cek)
return rsa.EncryptPKCS1v15(randReader, ctx.publicKey, cek)
case RSA_OAEP:
return rsa.EncryptOAEP(sha1.New(), RandReader, ctx.publicKey, cek, []byte{})
return rsa.EncryptOAEP(sha1.New(), randReader, ctx.publicKey, cek, []byte{})
case RSA_OAEP_256:
return rsa.EncryptOAEP(sha256.New(), RandReader, ctx.publicKey, cek, []byte{})
return rsa.EncryptOAEP(sha256.New(), randReader, ctx.publicKey, cek, []byte{})
}
return nil, ErrUnsupportedAlgorithm
@@ -288,9 +289,9 @@ func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm
// TODO(https://github.com/go-jose/go-jose/issues/40): As of go1.20, the
// random parameter is legacy and ignored, and it can be nil.
// https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/rsa/pkcs1v15.go;l=263;bpv=0;bpt=1
out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed)
out, err = rsa.SignPKCS1v15(randReader, ctx.privateKey, hash, hashed)
case PS256, PS384, PS512:
out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
out, err = rsa.SignPSS(randReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
})
}
@@ -391,7 +392,7 @@ func (ctx ecKeyGenerator) keySize() int {
// Get a content encryption key for ECDH-ES
func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, RandReader)
priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, randReader)
if err != nil {
return nil, rawHeader{}, err
}
@@ -414,6 +415,9 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
// Decrypt the given payload and return the content encryption key.
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
if recipient == nil {
return nil, errors.New("go-jose/go-jose: missing recipient")
}
epk, err := headers.getEPK()
if err != nil {
return nil, errors.New("go-jose/go-jose: invalid epk header")
@@ -461,13 +465,18 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
return nil, ErrUnsupportedAlgorithm
}
encryptedKey := recipient.encryptedKey
if len(encryptedKey) == 0 {
return nil, errors.New("go-jose/go-jose: missing JWE Encrypted Key")
}
key := deriveKey(string(algorithm), keySize)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return josecipher.KeyUnwrap(block, recipient.encryptedKey)
return josecipher.KeyUnwrap(block, encryptedKey)
}
func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
@@ -475,7 +484,7 @@ func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
return Signature{}, ErrUnsupportedAlgorithm
}
sig, err := ctx.privateKey.Sign(RandReader, payload, crypto.Hash(0))
sig, err := ctx.privateKey.Sign(randReader, payload, crypto.Hash(0))
if err != nil {
return Signature{}, err
}
@@ -525,7 +534,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
_, _ = hasher.Write(payload)
hashed := hasher.Sum(nil)
r, s, err := ecdsa.Sign(RandReader, ctx.privateKey, hashed)
r, s, err := ecdsa.Sign(randReader, ctx.privateKey, hashed)
if err != nil {
return Signature{}, err
}
@@ -563,12 +572,21 @@ func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
case ES256:
keySize = 32
hash = crypto.SHA256
if ctx.publicKey.Curve != elliptic.P256() {
return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key")
}
case ES384:
keySize = 48
hash = crypto.SHA384
if ctx.publicKey.Curve != elliptic.P384() {
return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key")
}
case ES512:
keySize = 66
hash = crypto.SHA512
if ctx.publicKey.Curve != elliptic.P521() {
return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key")
}
default:
return ErrUnsupportedAlgorithm
}
@@ -51,6 +51,8 @@ func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (
hash = sha512.New384
case 32:
hash = sha512.New
default:
return nil, errors.New("go-jose/go-jose: invalid key size for CBC-HMAC")
}
return &cbcAEAD{
@@ -114,7 +116,7 @@ func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
// Make copy of ciphertext buffer, don't want to modify in place
buffer := append([]byte{}, []byte(ciphertext[:offset])...)
buffer := append([]byte{}, ciphertext[:offset]...)
if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
return nil, errors.New("go-jose/go-jose: invalid ciphertext (invalid length)")
@@ -176,7 +178,9 @@ func padBuffer(buffer []byte, blockSize int) []byte {
// Remove padding
func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
if len(buffer)%blockSize != 0 {
// A padded buffer can't be empty because an empty input is padded with
// `blockSize` bytes, resulting in a non-empty ciphertext.
if len(buffer) == 0 || len(buffer)%blockSize != 0 {
return nil, errors.New("go-jose/go-jose: invalid padding")
}
@@ -51,7 +51,7 @@ func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := 0; i < 8; i++ {
buffer[i] = buffer[i] ^ tBytes[i]
buffer[i] ^= tBytes[i]
}
copy(r[t%n], buffer[8:])
}
@@ -66,12 +66,20 @@ func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
}
// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
//
// https://datatracker.ietf.org/doc/html/rfc7518#section-4.4
// https://datatracker.ietf.org/doc/html/rfc7518#section-4.6
// https://datatracker.ietf.org/doc/html/rfc7518#section-4.8
func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
n := (len(ciphertext) / 8) - 1
if n <= 0 {
return nil, errors.New("go-jose/go-jose: JWE Encrypted Key too short")
}
if len(ciphertext)%8 != 0 {
return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks")
}
n := (len(ciphertext) / 8) - 1
r := make([][]byte, n)
for i := range r {
@@ -87,7 +95,7 @@ func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := 0; i < 8; i++ {
buffer[i] = buffer[i] ^ tBytes[i]
buffer[i] ^= tBytes[i]
}
copy(buffer[8:], r[t%n])
@@ -21,9 +21,8 @@ import (
"crypto/rsa"
"errors"
"fmt"
"reflect"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
)
// Encrypter represents an encrypter which produces an encrypted JWE object.
@@ -76,14 +75,24 @@ type recipientKeyInfo struct {
type EncrypterOptions struct {
Compression CompressionAlgorithm
// Optional map of additional keys to be inserted into the protected header
// of a JWS object. Some specifications which make use of JWS like to insert
// additional values here. All values must be JSON-serializable.
// Optional map of name/value pairs to be inserted into the protected
// header of a JWS object. Some specifications which make use of
// JWS require additional values here.
//
// Values will be serialized by [json.Marshal] and must be valid inputs to
// that function.
//
// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
ExtraHeaders map[HeaderKey]interface{}
}
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
// if necessary. It returns itself and so can be used in a fluent style.
// if necessary, and returns the updated EncrypterOptions.
//
// The v parameter will be serialized by [json.Marshal] and must be a valid
// input to that function.
//
// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions {
if eo.ExtraHeaders == nil {
eo.ExtraHeaders = map[HeaderKey]interface{}{}
@@ -111,7 +120,17 @@ func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions {
// default of 100000 will be used for the count and a 128-bit random salt will
// be generated.
type Recipient struct {
Algorithm KeyAlgorithm
Algorithm KeyAlgorithm
// Key must have one of these types:
// - ed25519.PublicKey
// - *ecdsa.PublicKey
// - *rsa.PublicKey
// - *JSONWebKey
// - JSONWebKey
// - []byte (a symmetric key)
// - Any type that satisfies the OpaqueKeyEncrypter interface
//
// The type of Key must match the value of Algorithm.
Key interface{}
KeyID string
PBES2Count int
@@ -150,16 +169,17 @@ func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions)
switch rcpt.Algorithm {
case DIRECT:
// Direct encryption mode must be treated differently
if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
keyBytes, ok := rawKey.([]byte)
if !ok {
return nil, ErrUnsupportedKeyType
}
if encrypter.cipher.keySize() != len(rawKey.([]byte)) {
if encrypter.cipher.keySize() != len(keyBytes) {
return nil, ErrInvalidKeySize
}
encrypter.keyGenerator = staticKeyGenerator{
key: rawKey.([]byte),
key: keyBytes,
}
recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, rawKey.([]byte))
recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, keyBytes)
recipientInfo.keyID = keyID
if rcpt.KeyID != "" {
recipientInfo.keyID = rcpt.KeyID
@@ -168,16 +188,16 @@ func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions)
return encrypter, nil
case ECDH_ES:
// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
typeOf := reflect.TypeOf(rawKey)
if typeOf != reflect.TypeOf(&ecdsa.PublicKey{}) {
keyDSA, ok := rawKey.(*ecdsa.PublicKey)
if !ok {
return nil, ErrUnsupportedKeyType
}
encrypter.keyGenerator = ecKeyGenerator{
size: encrypter.cipher.keySize(),
algID: string(enc),
publicKey: rawKey.(*ecdsa.PublicKey),
publicKey: keyDSA,
}
recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, rawKey.(*ecdsa.PublicKey))
recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, keyDSA)
recipientInfo.keyID = keyID
if rcpt.KeyID != "" {
recipientInfo.keyID = rcpt.KeyID
@@ -201,7 +221,7 @@ func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *Encrypter
if cipher == nil {
return nil, ErrUnsupportedAlgorithm
}
if rcpts == nil || len(rcpts) == 0 {
if len(rcpts) == 0 {
return nil, fmt.Errorf("go-jose/go-jose: recipients is nil or empty")
}
@@ -238,6 +258,10 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
}
recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
if err != nil {
return err
}
if recipient.KeyID != "" {
recipientInfo.keyID = recipient.KeyID
}
@@ -250,10 +274,8 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
}
}
if err == nil {
ctx.recipients = append(ctx.recipients, recipientInfo)
}
return err
ctx.recipients = append(ctx.recipients, recipientInfo)
return nil
}
func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) {
@@ -266,13 +288,16 @@ func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKey
return newSymmetricRecipient(alg, encryptionKey)
case string:
return newSymmetricRecipient(alg, []byte(encryptionKey))
case JSONWebKey:
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
recipient.keyID = encryptionKey.KeyID
return recipient, err
case *JSONWebKey:
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
recipient.keyID = encryptionKey.KeyID
return recipient, err
}
if encrypter, ok := encryptionKey.(OpaqueKeyEncrypter); ok {
return newOpaqueKeyEncrypter(alg, encrypter)
case OpaqueKeyEncrypter:
return newOpaqueKeyEncrypter(alg, encryptionKey)
}
return recipientKeyInfo{}, ErrUnsupportedKeyType
}
@@ -300,11 +325,11 @@ func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
return newDecrypter(decryptionKey.Key)
case *JSONWebKey:
return newDecrypter(decryptionKey.Key)
case OpaqueKeyDecrypter:
return &opaqueKeyDecrypter{decrypter: decryptionKey}, nil
default:
return nil, ErrUnsupportedKeyType
}
if okd, ok := decryptionKey.(OpaqueKeyDecrypter); ok {
return &opaqueKeyDecrypter{decrypter: okd}, nil
}
return nil, ErrUnsupportedKeyType
}
// Implementation of encrypt method producing a JWE object.
@@ -403,10 +428,25 @@ func (ctx *genericEncrypter) Options() EncrypterOptions {
}
}
// Decrypt and validate the object and return the plaintext. Note that this
// function does not support multi-recipient, if you desire multi-recipient
// Decrypt and validate the object and return the plaintext. This
// function does not support multi-recipient. If you desire multi-recipient
// decryption use DecryptMulti instead.
//
// The decryptionKey argument must contain a private or symmetric key
// and must have one of these types:
// - *ecdsa.PrivateKey
// - *rsa.PrivateKey
// - *JSONWebKey
// - JSONWebKey
// - *JSONWebKeySet
// - JSONWebKeySet
// - []byte (a symmetric key)
// - string (a symmetric key)
// - Any type that satisfies the OpaqueKeyDecrypter interface.
//
// Note that ed25519 is only available for signatures, not encryption, so is
// not an option here.
//
// Automatically decompresses plaintext, but returns an error if the decompressed
// data would be >250kB or >10x the size of the compressed data, whichever is larger.
func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
@@ -416,16 +456,16 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
return nil, errors.New("go-jose/go-jose: too many recipients in payload; expecting only one")
}
critical, err := headers.getCritical()
err := headers.checkNoCritical()
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
return nil, err
}
if len(critical) > 0 {
return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
key, err := tryJWKS(decryptionKey, obj.Header)
if err != nil {
return nil, err
}
decrypter, err := newDecrypter(decryptionKey)
decrypter, err := newDecrypter(key)
if err != nil {
return nil, err
}
@@ -452,21 +492,22 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
recipientHeaders := obj.mergedHeaders(&recipient)
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
if err == nil {
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
}
if plaintext == nil {
if err != nil {
return nil, ErrCryptoFailure
}
// The "zip" header parameter may only be present in the protected header.
if comp := obj.protected.getCompression(); comp != "" {
plaintext, err = decompress(comp, plaintext)
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
if err != nil {
return nil, ErrCryptoFailure
}
return plaintext, err
plaintext, err = obj.decompress(plaintext)
if err != nil {
return nil, err
}
return plaintext, nil
}
// DecryptMulti decrypts and validates the object and returns the plaintexts,
@@ -474,21 +515,24 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
// for which the decryption was successful, the merged headers for that recipient,
// and the plaintext.
//
// The decryptionKey argument must have one of the types allowed for the
// decryptionKey argument of Decrypt().
//
// Automatically decompresses plaintext, but returns an error if the decompressed
// data would be >250kB or >3x the size of the compressed data, whichever is larger.
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
globalHeaders := obj.mergedHeaders(nil)
critical, err := globalHeaders.getCritical()
err := globalHeaders.checkNoCritical()
if err != nil {
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
return -1, Header{}, nil, err
}
if len(critical) > 0 {
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
key, err := tryJWKS(decryptionKey, obj.Header)
if err != nil {
return -1, Header{}, nil, err
}
decrypter, err := newDecrypter(decryptionKey)
decrypter, err := newDecrypter(key)
if err != nil {
return -1, Header{}, nil, err
}
@@ -515,28 +559,38 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
var plaintext []byte
var headers rawHeader
if len(obj.recipients) == 0 {
return -1, Header{}, nil, errors.New("go-jose/go-jose: no recipients")
}
// Loop sets `err` in the function scope; don't shadow it.
for i, recipient := range obj.recipients {
recipientHeaders := obj.mergedHeaders(&recipient)
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
if err == nil {
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
if err == nil {
index = i
headers = recipientHeaders
break
}
var cek []byte
cek, err = decrypter.decryptKey(recipientHeaders, &recipient, generator)
if err != nil {
continue
}
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
if err != nil {
continue
}
index = i
headers = recipientHeaders
break
}
if plaintext == nil || err != nil {
if err != nil {
return -1, Header{}, nil, ErrCryptoFailure
}
// The "zip" header parameter may only be present in the protected header.
if comp := obj.protected.getCompression(); comp != "" {
plaintext, err = decompress(comp, plaintext)
plaintext, err = obj.decompress(plaintext)
if err != nil {
return -1, Header{}, nil, err
}
sanitized, err := headers.sanitized()
@@ -546,3 +600,22 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
return index, sanitized, plaintext, err
}
// decompress decompresses plaintext using the protected "zip" header, if present.
// It returns plaintext unchanged when there is no protected header or "zip" value.
func (obj JSONWebEncryption) decompress(plaintext []byte) ([]byte, error) {
if obj.protected == nil {
return plaintext, nil
}
comp := obj.protected.getCompression()
if comp == "" {
return plaintext, nil
}
plaintext, err := decompress(comp, plaintext)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err)
}
return plaintext, nil
}
@@ -15,13 +15,11 @@
*/
/*
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. It implements encryption and signing based on
the JSON Web Encryption and JSON Web Signature standards, with optional JSON
Web Token support available in a sub-package. The library supports both the
compact and full serialization formats, and has optional support for multiple
the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web
Token support available in a sub-package. The library supports both the compact
and JWS/JWE JSON Serialization formats, and has optional support for multiple
recipients.
*/
package jose
@@ -27,7 +27,7 @@ import (
"strings"
"unicode"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
)
// Helper function to serialize known-good objects.
@@ -106,10 +106,7 @@ func inflate(input []byte) ([]byte, error) {
output := new(bytes.Buffer)
reader := flate.NewReader(bytes.NewBuffer(input))
maxCompressedSize := 10 * int64(len(input))
if maxCompressedSize < 250000 {
maxCompressedSize = 250000
}
maxCompressedSize := max(250_000, 10*int64(len(input)))
limit := maxCompressedSize + 1
n, err := io.CopyN(output, reader, limit)
@@ -196,3 +193,36 @@ func (b byteBuffer) bigInt() *big.Int {
func (b byteBuffer) toInt() int {
return int(b.bigInt().Int64())
}
func base64EncodeLen(sl []byte) int {
return base64.RawURLEncoding.EncodedLen(len(sl))
}
func base64JoinWithDots(inputs ...[]byte) string {
if len(inputs) == 0 {
return ""
}
// Count of dots.
totalCount := len(inputs) - 1
for _, input := range inputs {
totalCount += base64EncodeLen(input)
}
out := make([]byte, totalCount)
startEncode := 0
for i, input := range inputs {
base64.RawURLEncoding.Encode(out[startEncode:], input)
if i == len(inputs)-1 {
continue
}
startEncode += base64EncodeLen(input)
out[startEncode] = '.'
startEncode++
}
return string(out)
}
File renamed without changes.
File renamed without changes.
@@ -75,14 +75,13 @@ import (
//
// The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean
// ``not present,'' unmarshaling a JSON null into any other Go type has no effect
// “not present,” unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error.
//
// When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement
// character U+FFFD.
//
func Unmarshal(data []byte, v interface{}) error {
// Check for well-formedness.
// Avoids filling out half a data structure
@@ -58,6 +58,7 @@ import (
// becomes a member of the object unless
// - the field's tag is "-", or
// - the field is empty and its tag specifies the "omitempty" option.
//
// The empty values are false, 0, any
// nil pointer or interface value, and any array, slice, map, or string of
// length zero. The object's default key string is the struct field name
@@ -65,28 +66,28 @@ import (
// the struct field's tag value is the key name, followed by an optional comma
// and options. Examples:
//
// // Field is ignored by this package.
// Field int `json:"-"`
// // Field is ignored by this package.
// Field int `json:"-"`
//
// // Field appears in JSON as key "myName".
// Field int `json:"myName"`
// // Field appears in JSON as key "myName".
// Field int `json:"myName"`
//
// // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty,
// // as defined above.
// Field int `json:"myName,omitempty"`
// // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty,
// // as defined above.
// Field int `json:"myName,omitempty"`
//
// // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty.
// // Note the leading comma.
// Field int `json:",omitempty"`
// // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty.
// // Note the leading comma.
// Field int `json:",omitempty"`
//
// The "string" option signals that a field is stored as JSON inside a
// JSON-encoded string. It applies only to fields of string, floating point,
// integer, or boolean types. This extra level of encoding is sometimes used
// when communicating with JavaScript programs:
//
// Int64String int64 `json:",string"`
// Int64String int64 `json:",string"`
//
// The key name will be used if it's a non-empty string consisting of
// only Unicode letters, digits, dollar signs, percent signs, hyphens,
@@ -133,7 +134,6 @@ import (
// JSON cannot represent cyclic data structures and Marshal does not
// handle them. Passing cyclic structures to Marshal will result in
// an infinite recursion.
//
func Marshal(v interface{}) ([]byte, error) {
e := &encodeState{}
err := e.marshal(v)
@@ -648,7 +648,7 @@ func encodeByteSlice(e *encodeState, v reflect.Value, _ bool) {
// for large buffers, avoid unnecessary extra temporary
// buffer space.
enc := base64.NewEncoder(base64.StdEncoding, e)
enc.Write(s)
_, _ = enc.Write(s)
enc.Close()
}
e.WriteByte('"')
File renamed without changes.
@@ -140,6 +140,10 @@ const (
parseArrayValue // parsing array value
)
// This limits the max nesting depth to prevent stack overflow.
// This is permitted by https://tools.ietf.org/html/rfc7159#section-9
const maxNestingDepth = 10000
// reset prepares the scanner for use.
// It must be called before calling s.step.
func (s *scanner) reset() {
@@ -170,8 +174,13 @@ func (s *scanner) eof() int {
}
// pushParseState pushes a new parse state p onto the parse stack.
func (s *scanner) pushParseState(p int) {
s.parseState = append(s.parseState, p)
// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned.
func (s *scanner) pushParseState(c byte, newParseState int, successState int) int {
s.parseState = append(s.parseState, newParseState)
if len(s.parseState) <= maxNestingDepth {
return successState
}
return s.error(c, "exceeded max depth")
}
// popParseState pops a parse state (already obtained) off the stack
@@ -211,12 +220,10 @@ func stateBeginValue(s *scanner, c byte) int {
switch c {
case '{':
s.step = stateBeginStringOrEmpty
s.pushParseState(parseObjectKey)
return scanBeginObject
return s.pushParseState(c, parseObjectKey, scanBeginObject)
case '[':
s.step = stateBeginValueOrEmpty
s.pushParseState(parseArrayValue)
return scanBeginArray
return s.pushParseState(c, parseArrayValue, scanBeginArray)
case '"':
s.step = stateInString
return scanBeginLiteral
@@ -240,7 +240,6 @@ var _ Unmarshaler = (*RawMessage)(nil)
// Number, for JSON numbers
// string, for JSON string literals
// nil, for JSON null
//
type Token interface{}
const (
File renamed without changes.
@@ -18,10 +18,11 @@ package jose
import (
"encoding/base64"
"errors"
"fmt"
"strings"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
)
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
@@ -86,11 +87,12 @@ func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
func (obj JSONWebEncryption) computeAuthData() []byte {
var protected string
if obj.original != nil && obj.original.Protected != nil {
switch {
case obj.original != nil && obj.original.Protected != nil:
protected = obj.original.Protected.base64()
} else if obj.protected != nil {
case obj.protected != nil:
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
} else {
default:
protected = ""
}
@@ -103,29 +105,79 @@ func (obj JSONWebEncryption) computeAuthData() []byte {
return output
}
// ParseEncrypted parses an encrypted message in compact or full serialization format.
func ParseEncrypted(input string) (*JSONWebEncryption, error) {
input = stripWhitespace(input)
if strings.HasPrefix(input, "{") {
return parseEncryptedFull(input)
func containsKeyAlgorithm(haystack []KeyAlgorithm, needle KeyAlgorithm) bool {
for _, algorithm := range haystack {
if algorithm == needle {
return true
}
}
return parseEncryptedCompact(input)
return false
}
// parseEncryptedFull parses a message in compact format.
func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
func containsContentEncryption(haystack []ContentEncryption, needle ContentEncryption) bool {
for _, algorithm := range haystack {
if algorithm == needle {
return true
}
}
return false
}
// ParseEncrypted parses an encrypted message in JWE Compact or JWE JSON Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
//
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
// parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
func ParseEncrypted(input string,
keyEncryptionAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
input = stripWhitespace(input)
if strings.HasPrefix(input, "{") {
return ParseEncryptedJSON(input, keyEncryptionAlgorithms, contentEncryption)
}
return ParseEncryptedCompact(input, keyEncryptionAlgorithms, contentEncryption)
}
// ParseEncryptedJSON parses a message in JWE JSON Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
func ParseEncryptedJSON(
input string,
keyEncryptionAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
if len(input) == 0 {
return nil, errEmptyInput
}
var parsed rawJSONWebEncryption
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
return nil, err
}
return parsed.sanitized()
return parsed.sanitized(keyEncryptionAlgorithms, contentEncryption)
}
// sanitized produces a cleaned-up JWE object from the raw JSON.
func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
func (parsed *rawJSONWebEncryption) sanitized(
keyEncryptionAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
if len(keyEncryptionAlgorithms) == 0 {
return nil, errors.New("go-jose/go-jose: no key algorithms provided")
}
if len(contentEncryption) == 0 {
return nil, errors.New("go-jose/go-jose: no content encryption algorithms provided")
}
obj := &JSONWebEncryption{
original: parsed,
unprotected: parsed.Unprotected,
@@ -184,10 +236,31 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
}
}
for _, recipient := range obj.recipients {
for i, recipient := range obj.recipients {
headers := obj.mergedHeaders(&recipient)
if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
return nil, fmt.Errorf("go-jose/go-jose: message is missing alg/enc headers")
if headers.getAlgorithm() == "" {
return nil, fmt.Errorf(`go-jose/go-jose: recipient %d: missing header "alg"`, i)
}
if headers.getEncryption() == "" {
return nil, fmt.Errorf(`go-jose/go-jose: recipient %d: missing header "enc"`, i)
}
err := validateAlgEnc(headers, keyEncryptionAlgorithms, contentEncryption)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: recipient %d: %s", i, err)
}
}
if obj.protected != nil {
err := validateAlgEnc(*obj.protected, keyEncryptionAlgorithms, contentEncryption)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: protected header: %s", err)
}
}
if obj.unprotected != nil {
err := validateAlgEnc(*obj.unprotected, keyEncryptionAlgorithms, contentEncryption)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: unprotected header: %s", err)
}
}
@@ -199,12 +272,44 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
return obj, nil
}
// parseEncryptedCompact parses a message in compact format.
func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
parts := strings.Split(input, ".")
if len(parts) != 5 {
return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts")
func validateAlgEnc(headers rawHeader, keyAlgorithms []KeyAlgorithm, contentEncryption []ContentEncryption) error {
alg := headers.getAlgorithm()
enc := headers.getEncryption()
if alg != "" && !containsKeyAlgorithm(keyAlgorithms, alg) {
return fmt.Errorf("unexpected key algorithm %q; expected %q", alg, keyAlgorithms)
}
if enc != "" && !containsContentEncryption(contentEncryption, enc) {
return fmt.Errorf("unexpected content encryption algorithm %q; expected %q", enc, contentEncryption)
}
return nil
}
// ParseEncryptedCompact parses a message in JWE Compact Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
func ParseEncryptedCompact(
input string,
keyAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
var parts [5]string
var ok bool
if len(input) == 0 {
return nil, errEmptyInput
}
for i := range 4 {
parts[i], input, ok = strings.Cut(input, ".")
if !ok {
return nil, errors.New("go-jose/go-jose: compact JWE format must have five parts")
}
}
// Validate that the last part does not contain more dots
if strings.ContainsRune(input, '.') {
return nil, errors.New("go-jose/go-jose: compact JWE format must have five parts")
}
parts[4] = input
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
@@ -239,7 +344,7 @@ func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
Tag: newBuffer(tag),
}
return raw.sanitized()
return raw.sanitized(keyAlgorithms, contentEncryption)
}
// CompactSerialize serializes an object using the compact serialization format.
@@ -251,13 +356,13 @@ func (obj JSONWebEncryption) CompactSerialize() (string, error) {
serializedProtected := mustSerializeJSON(obj.protected)
return fmt.Sprintf(
"%s.%s.%s.%s.%s",
base64.RawURLEncoding.EncodeToString(serializedProtected),
base64.RawURLEncoding.EncodeToString(obj.recipients[0].encryptedKey),
base64.RawURLEncoding.EncodeToString(obj.iv),
base64.RawURLEncoding.EncodeToString(obj.ciphertext),
base64.RawURLEncoding.EncodeToString(obj.tag)), nil
return base64JoinWithDots(
serializedProtected,
obj.recipients[0].encryptedKey,
obj.iv,
obj.ciphertext,
obj.tag,
), nil
}
// FullSerialize serializes an object using the full JSON serialization format.
@@ -20,6 +20,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/sha1"
@@ -34,9 +35,7 @@ import (
"reflect"
"strings"
"golang.org/x/crypto/ed25519"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
)
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
@@ -63,14 +62,26 @@ type rawJSONWebKey struct {
Qi *byteBuffer `json:"qi,omitempty"`
// Certificates
X5c []string `json:"x5c,omitempty"`
X5u *url.URL `json:"x5u,omitempty"`
X5u string `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. It can be
// marshaled into JSON and unmarshaled from JSON.
type JSONWebKey struct {
// Cryptographic key, can be a symmetric or asymmetric key.
// Key is the Go in-memory representation of this key. It must have one
// of these types:
// - ed25519.PublicKey
// - ed25519.PrivateKey
// - *ecdsa.PublicKey
// - *ecdsa.PrivateKey
// - *rsa.PublicKey
// - *rsa.PrivateKey
// - []byte (a symmetric key)
//
// When marshaling this JSONWebKey into JSON, the "kty" header parameter
// will be automatically set based on the type of this field.
Key interface{}
// Key identifier, parsed from `kid` header.
KeyID string
@@ -96,7 +107,7 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
switch key := k.Key.(type) {
case ed25519.PublicKey:
raw = fromEdPublicKey(key)
raw, err = fromEdPublicKey(key)
case *ecdsa.PublicKey:
raw, err = fromEcPublicKey(key)
case *rsa.PublicKey:
@@ -156,12 +167,16 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
}
}
raw.X5u = k.CertificatesURL
if k.CertificatesURL != nil {
raw.X5u = k.CertificatesURL.String()
}
return json.Marshal(raw)
}
// UnmarshalJSON reads a key from its JSON representation.
//
// Returns ErrUnsupportedKeyType for unrecognized or unsupported "kty" header values.
func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
var raw rawJSONWebKey
err = json.Unmarshal(data, &raw)
@@ -192,21 +207,29 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
case "EC":
if raw.D != nil {
key, err = raw.ecPrivateKey()
if err == nil {
keyPub = key.(*ecdsa.PrivateKey).Public()
if err != nil {
return err
}
keyPub = key.(*ecdsa.PrivateKey).Public()
} else {
key, err = raw.ecPublicKey()
if err != nil {
return err
}
keyPub = key
}
case "RSA":
if raw.D != nil {
key, err = raw.rsaPrivateKey()
if err == nil {
keyPub = key.(*rsa.PrivateKey).Public()
if err != nil {
return err
}
keyPub = key.(*rsa.PrivateKey).Public()
} else {
key, err = raw.rsaPublicKey()
if err != nil {
return err
}
keyPub = key
}
case "oct":
@@ -214,37 +237,55 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
}
key, err = raw.symmetricKey()
if err != nil {
return err
}
case "OKP":
if raw.Crv == "Ed25519" && raw.X != nil {
if raw.Crv == "Ed25519" {
if raw.D != nil {
key, err = raw.edPrivateKey()
if err == nil {
keyPub = key.(ed25519.PrivateKey).Public()
if err != nil {
return err
}
keyPub = key.(ed25519.PrivateKey).Public()
} else {
key, err = raw.edPublicKey()
if err != nil {
return err
}
keyPub = key
}
} else {
err = fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv)
}
default:
err = fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty)
case "":
// kty MUST be present
return fmt.Errorf("go-jose/go-jose: missing json web key type")
}
if err != nil {
return
if key == nil {
// RFC 7517:
// 5. JWK Set Format
// ...
// Implementations SHOULD ignore JWKs within a JWK Set that use "kty"
// (key type) values that are not understood by them, that are missing
// required members, or for which values are out of the supported
// ranges.
return ErrUnsupportedKeyType
}
if certPub != nil && keyPub != nil {
if !reflect.DeepEqual(certPub, keyPub) {
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields to not match")
return errors.New("go-jose/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
if raw.X5u != "" {
k.CertificatesURL, err = url.Parse(raw.X5u)
if err != nil {
return fmt.Errorf("go-jose/go-jose: invalid JWK, x5u header is invalid URL: %w", err)
}
}
// x5t parameters are base64url-encoded SHA thumbprints
// See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
@@ -358,11 +399,12 @@ func rsaThumbprintInput(n *big.Int, e int) (string, error) {
func edThumbprintInput(ed ed25519.PublicKey) (string, error) {
crv := "Ed25519"
if len(ed) > 32 {
return "", errors.New("go-jose/go-jose: invalid elliptic key (too large)")
err := validateEd25519PublicKey(ed)
if err != nil {
return "", err
}
return fmt.Sprintf(edThumbprintTemplate, crv,
newFixedSizeBuffer(ed, 32).base64()), nil
newFixedSizeBuffer(ed, ed25519.PublicKeySize).base64()), nil
}
// Thumbprint computes the JWK Thumbprint of a key using the
@@ -383,6 +425,8 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
input, err = rsaThumbprintInput(key.N, key.E)
case ed25519.PrivateKey:
input, err = edThumbprintInput(ed25519.PublicKey(key[32:]))
case OpaqueSigner:
return key.Public().Thumbprint(hash)
default:
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
}
@@ -392,7 +436,7 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
}
h := hash.New()
h.Write([]byte(input))
_, _ = h.Write([]byte(input))
return h.Sum(nil), nil
}
@@ -472,12 +516,16 @@ func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
}, nil
}
func fromEdPublicKey(pub ed25519.PublicKey) *rawJSONWebKey {
func fromEdPublicKey(pub ed25519.PublicKey) (*rawJSONWebKey, error) {
err := validateEd25519PublicKey(pub)
if err != nil {
return nil, err
}
return &rawJSONWebKey{
Kty: "OKP",
Crv: "Ed25519",
X: newBuffer(pub),
}
}, nil
}
func fromRsaPublicKey(pub *rsa.PublicKey) *rawJSONWebKey {
@@ -561,10 +609,10 @@ func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
var missing []string
switch {
case key.D == nil:
if key.D == nil {
missing = append(missing, "D")
case key.X == nil:
}
if key.X == nil {
missing = append(missing, "X")
}
@@ -572,38 +620,55 @@ func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
}
privateKey := make([]byte, ed25519.PrivateKeySize)
copy(privateKey[0:32], key.D.bytes())
copy(privateKey[32:], key.X.bytes())
rv := ed25519.PrivateKey(privateKey)
return rv, nil
publicKey := key.X.bytes()
err := validateEd25519PublicKey(publicKey)
if err != nil {
return nil, err
}
seed := key.D.bytes()
if len(seed) != ed25519.SeedSize {
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, wrong length for d")
}
privateKey := ed25519.NewKeyFromSeed(seed)
derivedPublicKey := privateKey.Public().(ed25519.PublicKey)
if !bytes.Equal(derivedPublicKey, publicKey) {
return nil, errors.New("go-jose/go-jose: invalid Ed25519 private key, x does not match d")
}
return privateKey, nil
}
func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
if key.X == nil {
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed key, missing x value")
}
publicKey := make([]byte, ed25519.PublicKeySize)
copy(publicKey[0:32], key.X.bytes())
rv := ed25519.PublicKey(publicKey)
return rv, nil
publicKey := key.X.bytes()
err := validateEd25519PublicKey(publicKey)
if err != nil {
return nil, err
}
return ed25519.PublicKey(bytes.Clone(publicKey)), nil
}
func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
var missing []string
switch {
case key.N == nil:
if key.N == nil {
missing = append(missing, "N")
case key.E == nil:
}
if key.E == nil {
missing = append(missing, "E")
case key.D == nil:
}
if key.D == nil {
missing = append(missing, "D")
case key.P == nil:
}
if key.P == nil {
missing = append(missing, "P")
case key.Q == nil:
}
if key.Q == nil {
missing = append(missing, "Q")
}
if len(missing) > 0 {
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
}
@@ -635,9 +700,17 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
}
func fromEdPrivateKey(ed ed25519.PrivateKey) (*rawJSONWebKey, error) {
raw := fromEdPublicKey(ed25519.PublicKey(ed[32:]))
if len(ed) != ed25519.PrivateKeySize {
return nil, errors.New("go-jose/go-jose: invalid Ed25519 private key length")
}
raw.D = newBuffer(ed[0:32])
publicKey := ed.Public().(ed25519.PublicKey)
raw, err := fromEdPublicKey(publicKey)
if err != nil {
return nil, err
}
raw.D = newBuffer(ed.Seed())
return raw, nil
}
@@ -678,8 +751,19 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
}
if key.X == nil || key.Y == nil || key.D == nil {
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, missing x/y/d values")
var missing []string
if key.X == nil {
missing = append(missing, "X")
}
if key.Y == nil {
missing = append(missing, "Y")
}
if key.D == nil {
missing = append(missing, "D")
}
if len(missing) > 0 {
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, missing %s value(s)", strings.Join(missing, ", "))
}
// The length of this octet string MUST be the full size of a coordinate for
@@ -740,7 +824,7 @@ func dSize(curve elliptic.Curve) int {
bitLen := order.BitLen()
size := bitLen / 8
if bitLen%8 != 0 {
size = size + 1
size++
}
return size
}
@@ -758,3 +842,142 @@ func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
}
return key.K.bytes(), nil
}
var (
// ErrJWKSKidNotFound is returned when a JWKS does not contain a JWK with a
// key ID which matches one in the provided tokens headers.
ErrJWKSKidNotFound = errors.New("go-jose/go-jose: JWK with matching kid not found in JWK Set")
)
func tryJWKS(key interface{}, header Header) (interface{}, error) {
var jwks JSONWebKeySet
switch jwksType := key.(type) {
case *JSONWebKeySet:
jwks = *jwksType
case JSONWebKeySet:
jwks = jwksType
default:
// If the specified key is not a JWKS, return as is.
return key, nil
}
// If no KID is specified in the header, reject.
kid := header.KeyID
if kid == "" {
return nil, ErrJWKSKidNotFound
}
// Find the JWK with the matching KID. If no JWK with the specified KID is
// found, reject.
keys := jwks.Key(kid)
if len(keys) == 0 {
return nil, ErrJWKSKidNotFound
}
return keys[0].Key, nil
}
// weakEd25519PublicKeys contains the low-order Ed25519 encodings accepted by
// Go's verifier that must not be accepted as JOSE verification keys.
var weakEd25519PublicKeys = map[[ed25519.PublicKeySize]byte]struct{}{
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}: {},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
}: {},
{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}: {},
{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
}: {},
{
0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05,
}: {},
{
0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85,
}: {},
{
0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a,
}: {},
{
0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa,
}: {},
{
0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
}: {},
{
0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}: {},
{
0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
}: {},
{
0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}: {},
{
0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
}: {},
{
0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}: {},
}
func validateEd25519PublicKey(publicKey ed25519.PublicKey) error {
if len(publicKey) != ed25519.PublicKeySize {
return fmt.Errorf("go-jose/go-jose: invalid Ed25519 public key, wrong length for x")
}
var encoded [ed25519.PublicKeySize]byte
copy(encoded[:], publicKey)
_, ok := weakEd25519PublicKeys[encoded]
if ok {
return errors.New("go-jose/go-jose: invalid Ed25519 public key, low-order point")
}
return nil
}
@@ -22,8 +22,9 @@ import (
"errors"
"fmt"
"strings"
"unicode"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
)
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
@@ -75,22 +76,64 @@ type Signature struct {
original *rawSignatureInfo
}
// ParseSigned parses a signed message in compact or full serialization format.
func ParseSigned(signature string) (*JSONWebSignature, error) {
signature = stripWhitespace(signature)
if strings.HasPrefix(signature, "{") {
return parseSignedFull(signature)
// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization. Validation fails if
// the JWS is signed with an algorithm that isn't in the provided list of signature algorithms.
// Applications should decide for themselves which signature algorithms are acceptable. If you're
// not sure which signature algorithms your application might receive, consult the documentation of
// the program which provides them or the protocol that you are implementing. You can also try
// getting an example JWS and decoding it with a tool like https://jwt.io to see what its "alg"
// header parameter indicates. The signature on the JWS does not get validated during parsing. Call
// Verify() after parsing to validate the signature and obtain the payload.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7
func ParseSigned(
signature string,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
trimmed := strings.TrimLeftFunc(signature, unicode.IsSpace)
if strings.HasPrefix(trimmed, "{") {
return ParseSignedJSON(signature, signatureAlgorithms)
}
return parseSignedCompact(signature, nil)
return parseSignedCompact(stripWhitespace(signature), nil, signatureAlgorithms)
}
// ParseSignedCompact parses a message in JWS Compact Serialization. Validation fails if the JWS is
// signed with an algorithm that isn't in the provided list of signature algorithms. Applications
// should decide for themselves which signature algorithms are acceptable.If you're not sure which
// signature algorithms your application might receive, consult the documentation of the program
// which provides them or the protocol that you are implementing. You can also try getting an
// example JWS and decoding it with a tool like https://jwt.io to see what its "alg" header
// parameter indicates. The signature on the JWS does not get validated during parsing. Call
// Verify() after parsing to validate the signature and obtain the payload.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.1
func ParseSignedCompact(
signature string,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
return parseSignedCompact(signature, nil, signatureAlgorithms)
}
// ParseDetached parses a signed message in compact serialization format with detached payload.
func ParseDetached(signature string, payload []byte) (*JSONWebSignature, error) {
// Validation fails if the JWS is signed with an algorithm that isn't in the provided list of
// signature algorithms. Applications should decide for themselves which signature algorithms are
// acceptable. If you're not sure which signature algorithms your application might receive, consult
// the documentation of the program which provides them or the protocol that you are implementing.
// You can also try getting an example JWS and decoding it with a tool like https://jwt.io to see
// what its "alg" header parameter indicates. The signature on the JWS does not get validated during
// parsing. Call Verify() after parsing to validate the signature and obtain the payload.
//
// https://datatracker.ietf.org/doc/html/rfc7515#appendix-F
func ParseDetached(
signature string,
payload []byte,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
if payload == nil {
return nil, errors.New("go-jose/go-jose: nil payload")
}
return parseSignedCompact(stripWhitespace(signature), payload)
return parseSignedCompact(stripWhitespace(signature), payload, signatureAlgorithms)
}
// Get a header value
@@ -137,19 +180,59 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
return authData.Bytes(), nil
}
// parseSignedFull parses a message in full format.
func parseSignedFull(input string) (*JSONWebSignature, error) {
// ParseSignedJSON parses a message in JWS JSON Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.2
func ParseSignedJSON(
input string,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
if len(input) == 0 {
return nil, errEmptyInput
}
var parsed rawJSONWebSignature
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
return nil, err
}
return parsed.sanitized()
return parsed.sanitized(signatureAlgorithms)
}
func containsSignatureAlgorithm(haystack []SignatureAlgorithm, needle SignatureAlgorithm) bool {
for _, algorithm := range haystack {
if algorithm == needle {
return true
}
}
return false
}
// ErrUnexpectedSignatureAlgorithm is returned when the signature algorithm in
// the JWS header does not match one of the expected algorithms.
type ErrUnexpectedSignatureAlgorithm struct {
// Got is the signature algorithm found in the JWS header.
Got SignatureAlgorithm
expected []SignatureAlgorithm
}
func (e *ErrUnexpectedSignatureAlgorithm) Error() string {
return fmt.Sprintf("unexpected signature algorithm %q; expected %q", e.Got, e.expected)
}
func newErrUnexpectedSignatureAlgorithm(got SignatureAlgorithm, expected []SignatureAlgorithm) error {
return &ErrUnexpectedSignatureAlgorithm{
Got: got,
expected: expected,
}
}
// sanitized produces a cleaned-up JWS object from the raw JSON.
func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgorithm) (*JSONWebSignature, error) {
if len(signatureAlgorithms) == 0 {
return nil, errors.New("go-jose/go-jose: no signature algorithms specified")
}
if parsed.Payload == nil {
return nil, fmt.Errorf("go-jose/go-jose: missing payload in JWS message")
}
@@ -198,6 +281,11 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
return nil, err
}
alg := SignatureAlgorithm(signature.Header.Algorithm)
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
}
if signature.header != nil {
signature.Unprotected, err = signature.header.sanitized()
if err != nil {
@@ -241,6 +329,11 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
return nil, err
}
alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm)
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
}
if obj.Signatures[i].header != nil {
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
if err != nil {
@@ -273,30 +366,47 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
return obj, nil
}
const tokenDelim = "."
// parseSignedCompact parses a message in compact format.
func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error) {
parts := strings.Split(input, ".")
if len(parts) != 3 {
func parseSignedCompact(
input string,
payload []byte,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
if len(input) == 0 {
return nil, errEmptyInput
}
protected, s, ok := strings.Cut(input, tokenDelim)
if !ok { // no period found
return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
}
claims, sig, ok := strings.Cut(s, tokenDelim)
if !ok { // only one period found
return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
}
if strings.ContainsRune(sig, '.') { // too many periods found
return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
}
if parts[1] != "" && payload != nil {
if claims != "" && payload != nil {
return nil, fmt.Errorf("go-jose/go-jose: payload is not detached")
}
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
rawProtected, err := base64.RawURLEncoding.DecodeString(protected)
if err != nil {
return nil, err
}
if payload == nil {
payload, err = base64.RawURLEncoding.DecodeString(parts[1])
payload, err = base64.RawURLEncoding.DecodeString(claims)
if err != nil {
return nil, err
}
}
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
signature, err := base64.RawURLEncoding.DecodeString(sig)
if err != nil {
return nil, err
}
@@ -306,7 +416,7 @@ func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error)
Protected: newBuffer(rawProtected),
Signature: newBuffer(signature),
}
return raw.sanitized()
return raw.sanitized(signatureAlgorithms)
}
func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
@@ -314,15 +424,18 @@ func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
return "", ErrNotSupported
}
serializedProtected := base64.RawURLEncoding.EncodeToString(mustSerializeJSON(obj.Signatures[0].protected))
payload := ""
signature := base64.RawURLEncoding.EncodeToString(obj.Signatures[0].Signature)
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
var payload []byte
if !detached {
payload = base64.RawURLEncoding.EncodeToString(obj.payload)
payload = obj.payload
}
return fmt.Sprintf("%s.%s.%s", serializedProtected, payload, signature), nil
return base64JoinWithDots(
serializedProtected,
payload,
obj.Signatures[0].Signature,
), nil
}
// CompactSerialize serializes an object using the compact serialization format.
@@ -21,13 +21,13 @@ import (
"bytes"
"reflect"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
"gopkg.in/go-jose/go-jose.v2"
"github.com/go-jose/go-jose/v4"
)
// Builder is a utility for making JSON Web Tokens. Calls can be chained, and
// errors are accumulated until the final call to CompactSerialize/FullSerialize.
// errors are accumulated until the final call to Serialize.
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
@@ -36,15 +36,13 @@ type Builder interface {
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)
// Serialize serializes a token.
Serialize() (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.
// Serialize.
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
@@ -53,10 +51,8 @@ type NestedBuilder interface {
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)
// Serialize serializes a token.
Serialize() (string, error)
}
type builder struct {
@@ -194,7 +190,7 @@ func (b *signedBuilder) Token() (*JSONWebToken, error) {
return b.builder.token(sig.Verify, h)
}
func (b *signedBuilder) CompactSerialize() (string, error) {
func (b *signedBuilder) Serialize() (string, error) {
sig, err := b.sign()
if err != nil {
return "", err
@@ -203,15 +199,6 @@ func (b *signedBuilder) CompactSerialize() (string, error) {
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
@@ -232,7 +219,7 @@ func (b *encryptedBuilder) Claims(i interface{}) Builder {
}
}
func (b *encryptedBuilder) CompactSerialize() (string, error) {
func (b *encryptedBuilder) Serialize() (string, error) {
enc, err := b.encrypt()
if err != nil {
return "", err
@@ -241,15 +228,6 @@ func (b *encryptedBuilder) CompactSerialize() (string, error) {
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 {
@@ -280,6 +258,8 @@ func (b *nestedBuilder) Claims(i interface{}) NestedBuilder {
}
}
// Token produced a token suitable for serialization. It cannot be decrypted
// without serializing and then deserializing.
func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
enc, err := b.signAndEncrypt()
if err != nil {
@@ -287,12 +267,13 @@ func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
}
return &NestedJSONWebToken{
enc: enc,
Headers: []jose.Header{enc.Header},
allowedSignatureAlgorithms: nil,
enc: enc,
Headers: []jose.Header{enc.Header},
}, nil
}
func (b *nestedBuilder) CompactSerialize() (string, error) {
func (b *nestedBuilder) Serialize() (string, error) {
enc, err := b.signAndEncrypt()
if err != nil {
return "", err
@@ -21,9 +21,14 @@ import (
"strconv"
"time"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
)
// maxNumericDate bounds an accepted NumericDate (in seconds). It is far past
// any real timestamp but safely below where an int64 conversion or time.Unix
// would overflow and wrap Time() to a bogus instant.
const maxNumericDate = 1 << 62
// Claims represents public claim values (as specified in RFC 7519).
type Claims struct {
Issuer string `json:"iss,omitempty"`
@@ -69,6 +74,16 @@ func (n *NumericDate) UnmarshalJSON(b []byte) error {
return ErrUnmarshalNumericDate
}
// Reject values large enough to overflow either the int64 conversion below
// or time.Unix in Time(). time.Unix adds a ~62e9-second offset internally,
// so a value near the int64 limit wraps and compares as a time in the
// past; for "nbf" that lets a not-yet-valid token pass the not-before
// check instead of being rejected. maxNumericDate (2^62 seconds) is far
// beyond any real timestamp yet safely below both overflow points.
if f >= maxNumericDate || f <= -maxNumericDate {
return ErrUnmarshalNumericDate
}
*n = NumericDate(f)
return nil
}
@@ -111,6 +126,15 @@ func (s *Audience) UnmarshalJSON(b []byte) error {
return nil
}
// MarshalJSON converts audience to json representation.
func (s Audience) MarshalJSON() ([]byte, error) {
if len(s) == 1 {
return json.Marshal(s[0])
}
return json.Marshal([]string(s))
}
// Contains checks whether a given string is included in the Audience
func (s Audience) Contains(v string) bool {
for _, a := range s {
if a == v {
@@ -15,8 +15,6 @@
*/
/*
Package jwt provides an implementation of the JSON Web Token standard.
*/
package jwt
File renamed without changes.
+198
View File
@@ -0,0 +1,198 @@
/*-
* 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 "github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/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
// Used when parsing and decrypting an input
allowedSignatureAlgorithms []jose.SignatureAlgorithm
}
// Claims deserializes a JSONWebToken into dest using the provided key.
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
b, err := t.payload(key)
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("go-jose/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) {
b, err := t.enc.Decrypt(decryptionKey)
if err != nil {
return nil, err
}
sig, err := ParseSigned(string(b), t.allowedSignatureAlgorithms)
if err != nil {
return nil, err
}
return sig, nil
}
// ParseSigned parses token from JWS form.
func ParseSigned(s string, signatureAlgorithms []jose.SignatureAlgorithm) (*JSONWebToken, error) {
sig, err := jose.ParseSignedCompact(s, signatureAlgorithms)
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
}
func validateKeyEncryptionAlgorithm(algs []jose.KeyAlgorithm) error {
for _, alg := range algs {
switch alg {
case jose.ED25519,
jose.RSA1_5,
jose.RSA_OAEP,
jose.RSA_OAEP_256,
jose.ECDH_ES,
jose.ECDH_ES_A128KW,
jose.ECDH_ES_A192KW,
jose.ECDH_ES_A256KW:
return fmt.Errorf("asymmetric encryption algorithms not supported for JWT: "+
"invalid key encryption algorithm: %s", alg)
case jose.PBES2_HS256_A128KW,
jose.PBES2_HS384_A192KW,
jose.PBES2_HS512_A256KW:
return fmt.Errorf("password-based encryption not supported for JWT: "+
"invalid key encryption algorithm: %s", alg)
}
}
return nil
}
func parseEncryptedCompact(
s string,
keyAlgorithms []jose.KeyAlgorithm,
contentEncryption []jose.ContentEncryption,
) (*jose.JSONWebEncryption, error) {
err := validateKeyEncryptionAlgorithm(keyAlgorithms)
if err != nil {
return nil, err
}
enc, err := jose.ParseEncryptedCompact(s, keyAlgorithms, contentEncryption)
if err != nil {
return nil, err
}
return enc, nil
}
// ParseEncrypted parses token from JWE form.
//
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
// parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
func ParseEncrypted(s string,
keyAlgorithms []jose.KeyAlgorithm,
contentEncryption []jose.ContentEncryption,
) (*JSONWebToken, error) {
enc, err := parseEncryptedCompact(s, keyAlgorithms, contentEncryption)
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.
//
// The encryptionKeyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters, respectively, of the outer JWE. They must be nonempty, and each "alg" or "enc"
// header in parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
//
// The signatureAlgorithms parameter is used to validate the "alg" header parameter of the
// inner JWS. It must be nonempty, and the "alg" header in the inner JWS must contain a value
// that is present in the parameter.
func ParseSignedAndEncrypted(s string,
encryptionKeyAlgorithms []jose.KeyAlgorithm,
contentEncryption []jose.ContentEncryption,
signatureAlgorithms []jose.SignatureAlgorithm,
) (*NestedJSONWebToken, error) {
enc, err := parseEncryptedCompact(s, encryptionKeyAlgorithms, contentEncryption)
if err != nil {
return nil, err
}
contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
if strings.ToUpper(contentType) != "JWT" {
return nil, ErrInvalidContentType
}
return &NestedJSONWebToken{
allowedSignatureAlgorithms: signatureAlgorithms,
enc: enc,
Headers: []jose.Header{enc.Header},
}, nil
}
@@ -25,14 +25,17 @@ const (
)
// Expected defines values used for protected claims validation.
// If field has zero value then validation is skipped.
// If field has zero value then validation is skipped, with the exception of
// Time, where the zero value means "now." To skip validating them, set the
// corresponding field in the Claims struct to nil.
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
// AnyAudience matches if there is a non-empty intersection between
// its values and the values in the "aud" claim.
AnyAudience Audience
// ID matches the "jti" claim exactly.
ID string
// Time matches the "exp", "nbf" and "iat" claims with leeway.
@@ -61,7 +64,7 @@ func (c Claims) Validate(e Expected) error {
// 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
// zero value to check time values with no leeway, but you should note that
// numeric date values are rounded to the nearest second and sub-second
// precision is not supported.
//
@@ -86,28 +89,38 @@ func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
return ErrInvalidID
}
if len(e.Audience) != 0 {
for _, v := range e.Audience {
if !c.Audience.Contains(v) {
return ErrInvalidAudience
if len(e.AnyAudience) != 0 {
var intersection bool
for _, v := range e.AnyAudience {
if c.Audience.Contains(v) {
intersection = true
break
}
}
if !intersection {
return ErrInvalidAudience
}
}
if !e.Time.IsZero() {
if c.NotBefore != nil && e.Time.Add(leeway).Before(c.NotBefore.Time()) {
return ErrNotValidYet
}
// validate using the e.Time, or time.Now if not provided
validationTime := e.Time
if validationTime.IsZero() {
validationTime = time.Now()
}
if c.Expiry != nil && e.Time.Add(-leeway).After(c.Expiry.Time()) {
return ErrExpired
}
if c.NotBefore != nil && validationTime.Add(leeway).Before(c.NotBefore.Time()) {
return ErrNotValidYet
}
// 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
}
if c.Expiry != nil && validationTime.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 && validationTime.Add(leeway).Before(c.IssuedAt.Time()) {
return ErrIssuedInTheFuture
}
return nil
@@ -46,6 +46,11 @@ func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigI
return recipientSigInfo{}, ErrUnsupportedAlgorithm
}
pk := signer.Public()
if pk != nil && !pk.IsPublic() {
return recipientSigInfo{}, ErrNotPublic
}
return recipientSigInfo{
sigAlg: alg,
publicKey: signer.Public,
@@ -83,6 +88,9 @@ func (o *opaqueVerifier) verifyPayload(payload []byte, signature []byte, alg Sig
}
// OpaqueKeyEncrypter is an interface that supports encrypting keys with an opaque key.
//
// Note: this cannot currently be implemented outside this package because of its
// unexported method.
type OpaqueKeyEncrypter interface {
// KeyID returns the kid
KeyID() string
@@ -121,7 +129,7 @@ func (oke *opaqueKeyEncrypter) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
return oke.encrypter.encryptKey(cek, alg)
}
//OpaqueKeyDecrypter is an interface that supports decrypting keys with an opaque key.
// OpaqueKeyDecrypter is an interface that supports decrypting keys with an opaque key.
type OpaqueKeyDecrypter interface {
DecryptKey(encryptedKey []byte, header Header) ([]byte, error)
}
@@ -23,7 +23,7 @@ import (
"errors"
"fmt"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
)
// KeyAlgorithm represents a key management algorithm.
@@ -71,6 +71,21 @@ var (
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
// nonce header parameter was included in an unprotected header object.
ErrUnprotectedNonce = errors.New("go-jose/go-jose: Nonce parameter included in unprotected header")
// ErrMissingX5cHeader indicates that the JWT header is missing x5c headers.
ErrMissingX5cHeader = errors.New("go-jose/go-jose: no x5c header present in message")
// ErrUnsupportedEllipticCurve indicates unsupported or unknown elliptic curve has been found.
ErrUnsupportedEllipticCurve = errors.New("go-jose/go-jose: unsupported/unknown elliptic curve")
// ErrUnsupportedCriticalHeader is returned when a header is marked critical but not supported by go-jose.
ErrUnsupportedCriticalHeader = errors.New("go-jose/go-jose: unsupported critical header")
// errEmptyInput is returned when go-jose was asked to parse an empty string.
errEmptyInput = errors.New("go-jose/go-jose: empty input")
// ErrNotPublic indicates a private key was passed where a public key was expected
ErrNotPublic = errors.New("go-jose/go-jose: public key was unexpectedly not public")
)
// Key management algorithms
@@ -133,8 +148,8 @@ const (
type HeaderKey string
const (
HeaderType HeaderKey = "typ" // string
HeaderContentType = "cty" // string
HeaderType = "typ" // string
HeaderContentType = "cty" // string
// These are set by go-jose and shouldn't need to be set by consumers of the
// library.
@@ -161,8 +176,8 @@ const (
)
// supportedCritical is the set of supported extensions that are understood and processed.
var supportedCritical = map[string]bool{
headerB64: true,
var supportedCritical = map[string]struct{}{
headerB64: {},
}
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
@@ -183,8 +198,13 @@ type Header struct {
// Unverified certificate chain parsed from x5c header.
certificates []*x509.Certificate
// Any headers not recognised above get unmarshalled
// from JSON in a generic manner and placed in this map.
// At parse time, each header parameter with a name other than "kid",
// "jwk", "alg", "nonce", or "x5c" will have its value passed to
// [json.Unmarshal] to unmarshal it into an interface value.
// The resulting value will be stored in this map, with the header
// parameter name as the key.
//
// [json.Unmarshal]: https://pkg.go.dev/encoding/json#Unmarshal
ExtraHeaders map[HeaderKey]interface{}
}
@@ -194,7 +214,7 @@ type Header struct {
// not be validated with the given verify options.
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
if len(h.certificates) == 0 {
return nil, errors.New("go-jose/go-jose: no x5c header present in message")
return nil, ErrMissingX5cHeader
}
leaf := h.certificates[0]
@@ -335,6 +355,32 @@ func (parsed rawHeader) getCritical() ([]string, error) {
return q, nil
}
// checkNoCritical verifies there are no critical headers present.
func (parsed rawHeader) checkNoCritical() error {
if _, ok := parsed[headerCritical]; ok {
return ErrUnsupportedCriticalHeader
}
return nil
}
// checkSupportedCritical verifies there are no unsupported critical headers.
// Supported headers are passed in as a set: map of names to empty structs
func (parsed rawHeader) checkSupportedCritical(supported map[string]struct{}) error {
crit, err := parsed.getCritical()
if err != nil {
return err
}
for _, name := range crit {
if _, ok := supported[name]; !ok {
return ErrUnsupportedCriticalHeader
}
}
return nil
}
// getS2C extracts parsed "p2c" from the raw JSON.
func (parsed rawHeader) getP2C() (int, error) {
v := parsed[headerP2C]
@@ -452,8 +498,8 @@ func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
return out, nil
}
func (dst rawHeader) isSet(k HeaderKey) bool {
dvr := dst[k]
func (parsed rawHeader) isSet(k HeaderKey) bool {
dvr := parsed[k]
if dvr == nil {
return false
}
@@ -472,17 +518,17 @@ func (dst rawHeader) isSet(k HeaderKey) bool {
}
// Merge headers from src into dst, giving precedence to headers from l.
func (dst rawHeader) merge(src *rawHeader) {
func (parsed rawHeader) merge(src *rawHeader) {
if src == nil {
return
}
for k, v := range *src {
if dst.isSet(k) {
if parsed.isSet(k) {
continue
}
dst[k] = v
parsed[k] = v
}
}
@@ -496,7 +542,7 @@ func curveName(crv elliptic.Curve) (string, error) {
case elliptic.P521():
return "P-521", nil
default:
return "", fmt.Errorf("go-jose/go-jose: unsupported/unknown elliptic curve")
return "", ErrUnsupportedEllipticCurve
}
}
@@ -19,14 +19,13 @@ package jose
import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"encoding/base64"
"errors"
"fmt"
"golang.org/x/crypto/ed25519"
"gopkg.in/go-jose/go-jose.v2/json"
"github.com/go-jose/go-jose/v4/json"
)
// NonceSource represents a source of random nonces to go into JWS objects
@@ -41,6 +40,20 @@ type Signer interface {
}
// SigningKey represents an algorithm/key used to sign a message.
//
// Key must have one of these types:
// - ed25519.PrivateKey
// - *ecdsa.PrivateKey
// - *rsa.PrivateKey
// - *JSONWebKey
// - JSONWebKey
// - []byte (an HMAC key)
// - Any type that satisfies the OpaqueSigner interface
//
// If the key is an HMAC key, it must have at least as many bytes as the relevant hash output:
// - HS256: 32 bytes
// - HS384: 48 bytes
// - HS512: 64 bytes
type SigningKey struct {
Algorithm SignatureAlgorithm
Key interface{}
@@ -53,12 +66,22 @@ type SignerOptions struct {
// Optional map of additional keys to be inserted into the protected header
// of a JWS object. Some specifications which make use of JWS like to insert
// additional values here. All values must be JSON-serializable.
// additional values here.
//
// Values will be serialized by [json.Marshal] and must be valid inputs to
// that function.
//
// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
ExtraHeaders map[HeaderKey]interface{}
}
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
// if necessary. It returns itself and so can be used in a fluent style.
// if necessary, and returns the updated SignerOptions.
//
// The v argument will be serialized by [json.Marshal] and must be a valid
// input to that function.
//
// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal
func (so *SignerOptions) WithHeader(k HeaderKey, v interface{}) *SignerOptions {
if so.ExtraHeaders == nil {
so.ExtraHeaders = map[HeaderKey]interface{}{}
@@ -107,6 +130,8 @@ type payloadVerifier interface {
verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
}
var errInvalidVerificationKey = errors.New("go-jose/go-jose: invalid verification key")
type genericSigner struct {
recipients []recipientSigInfo
nonceSource NonceSource
@@ -115,11 +140,21 @@ type genericSigner struct {
}
type recipientSigInfo struct {
sigAlg SignatureAlgorithm
sigAlg SignatureAlgorithm
// publicKey returns a synthetic JSONWebKey for the signer.
// For opaque signers, it calls OpaqueSigner.Public().
publicKey func() *JSONWebKey
signer payloadSigner
}
// getPublicKey gets the public key, with a nil check on the func.
func (r recipientSigInfo) getPublicKey() *JSONWebKey {
if r.publicKey == nil {
return nil
}
return r.publicKey()
}
func staticPublicKey(jwk *JSONWebKey) func() *JSONWebKey {
return func() *JSONWebKey {
return jwk
@@ -155,14 +190,23 @@ func NewMultiSigner(sigs []SigningKey, opts *SignerOptions) (Signer, error) {
func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
switch verificationKey := verificationKey.(type) {
case ed25519.PublicKey:
if len(verificationKey) == 0 {
return nil, errInvalidVerificationKey
}
return &edEncrypterVerifier{
publicKey: verificationKey,
}, nil
case *rsa.PublicKey:
if verificationKey == nil {
return nil, errInvalidVerificationKey
}
return &rsaEncrypterVerifier{
publicKey: verificationKey,
}, nil
case *ecdsa.PublicKey:
if verificationKey == nil {
return nil, errInvalidVerificationKey
}
return &ecEncrypterVerifier{
publicKey: verificationKey,
}, nil
@@ -173,12 +217,15 @@ func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
case JSONWebKey:
return newVerifier(verificationKey.Key)
case *JSONWebKey:
if verificationKey == nil {
return nil, errInvalidVerificationKey
}
return newVerifier(verificationKey.Key)
case OpaqueVerifier:
return &opaqueVerifier{verifier: verificationKey}, nil
default:
return nil, ErrUnsupportedKeyType
}
if ov, ok := verificationKey.(OpaqueVerifier); ok {
return &opaqueVerifier{verifier: ov}, nil
}
return nil, ErrUnsupportedKeyType
}
func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
@@ -205,11 +252,11 @@ func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipient
return newJWKSigner(alg, signingKey)
case *JSONWebKey:
return newJWKSigner(alg, *signingKey)
case OpaqueSigner:
return newOpaqueSigner(alg, signingKey)
default:
return recipientSigInfo{}, ErrUnsupportedKeyType
}
if signer, ok := signingKey.(OpaqueSigner); ok {
return newOpaqueSigner(alg, signer)
}
return recipientSigInfo{}, ErrUnsupportedKeyType
}
func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) {
@@ -217,18 +264,18 @@ func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigIn
if err != nil {
return recipientSigInfo{}, err
}
if recipient.publicKey != nil && recipient.publicKey() != nil {
if recipientPubKey := recipient.getPublicKey(); recipientPubKey != nil {
// This should be impossible, but let's check anyway.
if !recipientPubKey.IsPublic() {
return recipientSigInfo{}, ErrNotPublic
}
// recipient.publicKey is a JWK synthesized for embedding when recipientSigInfo
// was created for the inner key (such as a RSA or ECDSA public key). It contains
// the pub key for embedding, but doesn't have extra params like key id.
publicKey := signingKey
publicKey.Key = recipient.publicKey().Key
publicKey.Key = recipientPubKey.Key
recipient.publicKey = staticPublicKey(&publicKey)
// This should be impossible, but let's check anyway.
if !recipient.publicKey().IsPublic() {
return recipientSigInfo{}, errors.New("go-jose/go-jose: public key was unexpectedly not public")
}
}
return recipient, nil
}
@@ -243,7 +290,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
headerAlgorithm: string(recipient.sigAlg),
}
if recipient.publicKey != nil && recipient.publicKey() != nil {
if recipientPubKey := recipient.getPublicKey(); recipientPubKey != nil {
// We want to embed the JWK or set the kid header, but not both. Having a protected
// header that contains an embedded JWK while also simultaneously containing the kid
// header is confusing, and at least in ACME the two are considered to be mutually
@@ -251,11 +298,11 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
// result of the JOSE spec. We've decided that this library will only include one or
// the other to avoid this confusion.
//
// See https://github.com/go-jose/go-jose/issues/157 for more context.
// See https://github.com/square/go-jose/issues/157 for more context.
if ctx.embedJWK {
protected[headerJWK] = recipient.publicKey()
protected[headerJWK] = recipientPubKey
} else {
keyID := recipient.publicKey().KeyID
keyID := recipientPubKey.KeyID
if keyID != "" {
protected[headerKeyID] = keyID
}
@@ -322,12 +369,28 @@ func (ctx *genericSigner) Options() SignerOptions {
}
// Verify validates the signature on the object and returns the payload.
// This function does not support multi-signature, if you desire multi-sig
// This function does not support multi-signature. If you desire multi-signature
// verification use VerifyMulti instead.
//
// Be careful when verifying signatures based on embedded JWKs inside the
// payload header. You cannot assume that the key received in a payload is
// trusted.
//
// The verificationKey argument must have one of these types:
// - ed25519.PublicKey
// - *ecdsa.PublicKey
// - *rsa.PublicKey
// - *JSONWebKey
// - JSONWebKey
// - *JSONWebKeySet
// - JSONWebKeySet
// - []byte (an HMAC key)
// - Any type that implements the OpaqueVerifier interface.
//
// If the key is an HMAC key, it must have at least as many bytes as the relevant hash output:
// - HS256: 32 bytes
// - HS384: 48 bytes
// - HS512: 64 bytes
func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
err := obj.DetachedVerify(obj.payload, verificationKey)
if err != nil {
@@ -347,26 +410,41 @@ func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte {
// most cases, you will probably want to use Verify instead. DetachedVerify
// is only useful if you have a payload and signature that are separated from
// each other.
//
// The verificationKey argument must have one of the types allowed for the
// verificationKey argument of JSONWebSignature.Verify().
func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
verifier, err := newVerifier(verificationKey)
if err != nil {
return err
}
if len(obj.Signatures) > 1 {
return errors.New("go-jose/go-jose: too many signatures in payload; expecting only one")
}
signature := obj.Signatures[0]
headers := signature.mergedHeaders()
critical, err := headers.getCritical()
key, err := tryJWKS(verificationKey, signature.Header)
if err != nil {
return err
}
verifier, err := newVerifier(key)
if err != nil {
return err
}
for _, name := range critical {
if !supportedCritical[name] {
return ErrCryptoFailure
if signature.header != nil {
// Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11,
// 4.1.11. "crit" (Critical) Header Parameter
// "When used, this Header Parameter MUST be integrity
// protected; therefore, it MUST occur only within the JWS
// Protected Header."
err = signature.header.checkNoCritical()
if err != nil {
return err
}
}
if signature.protected != nil {
err = signature.protected.checkSupportedCritical(supportedCritical)
if err != nil {
return err
}
}
@@ -375,19 +453,23 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter
return ErrCryptoFailure
}
headers := signature.mergedHeaders()
alg := headers.getSignatureAlgorithm()
err = verifier.verifyPayload(input, signature.Signature, alg)
if err == nil {
return nil
if err != nil {
return ErrCryptoFailure
}
return ErrCryptoFailure
return nil
}
// VerifyMulti validates (one of the multiple) signatures on the object and
// returns the index of the signature that was verified, along with the signature
// object and the payload. We return the signature and index to guarantee that
// callers are getting the verified value.
//
// The verificationKey argument must have one of the types allowed for the
// verificationKey argument of JSONWebSignature.Verify().
func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
idx, sig, err := obj.DetachedVerifyMulti(obj.payload, verificationKey)
if err != nil {
@@ -405,24 +487,40 @@ func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signa
// DetachedVerifyMulti is only useful if you have a payload and signature that are
// separated from each other, and the signature can have multiple signers at the
// same time.
//
// The verificationKey argument must have one of the types allowed for the
// verificationKey argument of JSONWebSignature.Verify().
func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
verifier, err := newVerifier(verificationKey)
if err != nil {
return -1, Signature{}, err
}
outer:
for i, signature := range obj.Signatures {
headers := signature.mergedHeaders()
critical, err := headers.getCritical()
if signature.header != nil {
// Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11,
// 4.1.11. "crit" (Critical) Header Parameter
// "When used, this Header Parameter MUST be integrity
// protected; therefore, it MUST occur only within the JWS
// Protected Header."
err := signature.header.checkNoCritical()
if err != nil {
continue
}
}
if signature.protected != nil {
// Check for only supported critical headers
err := signature.protected.checkSupportedCritical(supportedCritical)
if err != nil {
continue
}
}
// If the verification key is a JWK Set, pick a key based on this signature's
// "kid" header. If no match, skip this signature.
key, err := tryJWKS(verificationKey, signature.Header)
if err != nil {
continue
}
for _, name := range critical {
if !supportedCritical[name] {
continue outer
}
verifier, err := newVerifier(key)
if err != nil {
continue
}
input, err := obj.computeAuthData(payload, &signature)
@@ -430,11 +528,14 @@ outer:
continue
}
headers := signature.mergedHeaders()
alg := headers.getSignatureAlgorithm()
err = verifier.verifyPayload(input, signature.Signature, alg)
if err == nil {
return i, signature, nil
if err != nil {
continue
}
return i, signature, nil
}
return -1, Signature{}, ErrCryptoFailure
@@ -21,6 +21,7 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/pbkdf2"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
@@ -30,21 +31,25 @@ import (
"hash"
"io"
"golang.org/x/crypto/pbkdf2"
"gopkg.in/go-jose/go-jose.v2/cipher"
josecipher "github.com/go-jose/go-jose/v4/cipher"
)
// Random reader (stubbed out in tests)
var RandReader = rand.Reader
// randReader is a cryptographically secure random number generator (stubbed out in tests).
var randReader = rand.Reader
const (
// RFC7518 recommends a minimum of 1,000 iterations:
// https://tools.ietf.org/html/rfc7518#section-4.8.1.2
// - https://tools.ietf.org/html/rfc7518#section-4.8.1.2
//
// NIST recommends a minimum of 10,000:
// https://pages.nist.gov/800-63-3/sp800-63b.html
// 1Password uses 100,000:
// https://support.1password.com/pbkdf2/
defaultP2C = 100000
// - https://pages.nist.gov/800-63-3/sp800-63b.html
//
// 1Password increased in 2023 from 100,000 to 650,000:
// - https://support.1password.com/pbkdf2/
//
// OWASP recommended 600,000 in Dec 2022:
// - https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
defaultP2C = 600000
// Default salt size: 128 bits
defaultP2SSize = 16
)
@@ -148,7 +153,7 @@ func getPbkdf2Params(alg KeyAlgorithm) (int, func() hash.Hash) {
// getRandomSalt generates a new salt of the given size.
func getRandomSalt(size int) ([]byte, error) {
salt := make([]byte, size)
_, err := io.ReadFull(RandReader, salt)
_, err := io.ReadFull(randReader, salt)
if err != nil {
return nil, err
}
@@ -193,7 +198,7 @@ func newSymmetricSigner(sigAlg SignatureAlgorithm, key []byte) (recipientSigInfo
// Generate a random key for the given content cipher
func (ctx randomKeyGenerator) genKey() ([]byte, rawHeader, error) {
key := make([]byte, ctx.size)
_, err := io.ReadFull(RandReader, key)
_, err := io.ReadFull(randReader, key)
if err != nil {
return nil, rawHeader{}, err
}
@@ -233,7 +238,7 @@ func (ctx aeadContentCipher) encrypt(key, aad, pt []byte) (*aeadParts, error) {
// Initialize a new nonce
iv := make([]byte, aead.NonceSize())
_, err = io.ReadFull(RandReader, iv)
_, err = io.ReadFull(randReader, iv)
if err != nil {
return nil, err
}
@@ -278,8 +283,14 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
}
header := &rawHeader{}
header.set(headerIV, newBuffer(parts.iv))
header.set(headerTag, newBuffer(parts.tag))
if err = header.set(headerIV, newBuffer(parts.iv)); err != nil {
return recipientInfo{}, err
}
if err = header.set(headerTag, newBuffer(parts.tag)); err != nil {
return recipientInfo{}, err
}
return recipientInfo{
header: header,
@@ -318,7 +329,10 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
// derive key
keyLen, h := getPbkdf2Params(alg)
key := pbkdf2.Key(ctx.key, salt, ctx.p2c, keyLen, h)
key, err := pbkdf2.Key(h, string(ctx.key), salt, ctx.p2c, keyLen)
if err != nil {
return recipientInfo{}, nil
}
// use AES cipher with derived key
block, err := aes.NewCipher(key)
@@ -332,8 +346,14 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
}
header := &rawHeader{}
header.set(headerP2C, ctx.p2c)
header.set(headerP2S, newBuffer(ctx.p2s))
if err = header.set(headerP2C, ctx.p2c); err != nil {
return recipientInfo{}, err
}
if err = header.set(headerP2S, newBuffer(ctx.p2s)); err != nil {
return recipientInfo{}, err
}
return recipientInfo{
encryptedKey: jek,
@@ -346,11 +366,21 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
// Decrypt the content encryption key.
func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
switch headers.getAlgorithm() {
case DIRECT:
cek := make([]byte, len(ctx.key))
copy(cek, ctx.key)
return cek, nil
if recipient == nil {
return nil, fmt.Errorf("go-jose/go-jose: missing recipient")
}
alg := headers.getAlgorithm()
if alg == DIRECT {
return bytes.Clone(ctx.key), nil
}
encryptedKey := recipient.encryptedKey
if len(encryptedKey) == 0 {
return nil, fmt.Errorf("go-jose/go-jose: missing JWE Encrypted Key")
}
switch alg {
case A128GCMKW, A192GCMKW, A256GCMKW:
aead := newAESGCM(len(ctx.key))
@@ -365,7 +395,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
parts := &aeadParts{
iv: iv.bytes(),
ciphertext: recipient.encryptedKey,
ciphertext: encryptedKey,
tag: tag.bytes(),
}
@@ -381,7 +411,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
return nil, err
}
cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
cek, err := josecipher.KeyUnwrap(block, encryptedKey)
if err != nil {
return nil, err
}
@@ -414,7 +444,10 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
// derive key
keyLen, h := getPbkdf2Params(alg)
key := pbkdf2.Key(ctx.key, salt, p2c, keyLen, h)
key, err := pbkdf2.Key(h, string(ctx.key), salt, p2c, keyLen)
if err != nil {
return nil, err
}
// use AES cipher with derived key
block, err := aes.NewCipher(key)
@@ -422,7 +455,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
return nil, err
}
cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
cek, err := josecipher.KeyUnwrap(block, encryptedKey)
if err != nil {
return nil, err
}
@@ -436,7 +469,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
mac, err := ctx.hmac(payload, alg)
if err != nil {
return Signature{}, errors.New("go-jose/go-jose: failed to compute hmac")
return Signature{}, err
}
return Signature{
@@ -468,12 +501,24 @@ func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureA
func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
var hash func() hash.Hash
// https://datatracker.ietf.org/doc/html/rfc7518#section-3.2
// A key of the same size as the hash output (for instance, 256 bits for
// "HS256") or larger MUST be used
switch alg {
case HS256:
if len(ctx.key)*8 < 256 {
return nil, ErrInvalidKeySize
}
hash = sha256.New
case HS384:
if len(ctx.key)*8 < 384 {
return nil, ErrInvalidKeySize
}
hash = sha512.New384
case HS512:
if len(ctx.key)*8 < 512 {
return nil, ErrInvalidKeySize
}
hash = sha512.New
default:
return nil, ErrUnsupportedAlgorithm
-8
View File
@@ -1,8 +0,0 @@
arch:
- amd64
- ppc64le
language: go
go:
- "1.15"
- "1.16"
-107
View File
@@ -1,107 +0,0 @@
# cachecontrol: HTTP Caching Parser and Interpretation
[![PkgGoDev](https://pkg.go.dev/badge/github.com/pquerna/cachecontrol?tab=doc)](https://pkg.go.dev/github.com/pquerna/cachecontrol?tab=doc)[![Build Status](https://travis-ci.org/pquerna/cachecontrol.svg?branch=master)](https://travis-ci.org/pquerna/cachecontrol)
`cachecontrol` implements [RFC 7234](http://tools.ietf.org/html/rfc7234) __Hypertext Transfer Protocol (HTTP/1.1): Caching__. It does this by parsing the `Cache-Control` and other headers, providing information about requests and responses -- but `cachecontrol` does not implement an actual cache backend, just the control plane to make decisions about if a particular response is cachable.
# Usage
`cachecontrol.CachableResponse` returns an array of [reasons](https://godoc.org/github.com/pquerna/cachecontrol/cacheobject#Reason) why a response should not be cached and when it expires. In the case that `len(reasons) == 0`, the response is cachable according to the RFC. However, some people want non-compliant caches for various business use cases, so each reason is specifically named, so if your cache wants to cache `POST` requests, it can easily do that, but still be RFC compliant in other situations.
# Examples
## Can you cache Example.com?
```go
package main
import (
"github.com/pquerna/cachecontrol"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "http://www.example.com/", nil)
res, _ := http.DefaultClient.Do(req)
_, _ = ioutil.ReadAll(res.Body)
reasons, expires, _ := cachecontrol.CachableResponse(req, res, cachecontrol.Options{})
fmt.Println("Reasons to not cache: ", reasons)
fmt.Println("Expiration: ", expires.String())
}
```
## Can I use this in a high performance caching server?
`cachecontrol` is divided into two packages: `cachecontrol` with a high level API, and a lower level `cacheobject` package. Use [Object](https://godoc.org/github.com/pquerna/cachecontrol/cacheobject#Object) in a high performance use case where you have previously parsed headers containing dates or would like to avoid memory allocations.
```go
package main
import (
"github.com/pquerna/cachecontrol/cacheobject"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "http://www.example.com/", nil)
res, _ := http.DefaultClient.Do(req)
_, _ = ioutil.ReadAll(res.Body)
reqDir, _ := cacheobject.ParseRequestCacheControl(req.Header.Get("Cache-Control"))
resDir, _ := cacheobject.ParseResponseCacheControl(res.Header.Get("Cache-Control"))
expiresHeader, _ := http.ParseTime(res.Header.Get("Expires"))
dateHeader, _ := http.ParseTime(res.Header.Get("Date"))
lastModifiedHeader, _ := http.ParseTime(res.Header.Get("Last-Modified"))
obj := cacheobject.Object{
RespDirectives: resDir,
RespHeaders: res.Header,
RespStatusCode: res.StatusCode,
RespExpiresHeader: expiresHeader,
RespDateHeader: dateHeader,
RespLastModifiedHeader: lastModifiedHeader,
ReqDirectives: reqDir,
ReqHeaders: req.Header,
ReqMethod: req.Method,
NowUTC: time.Now().UTC(),
}
rv := cacheobject.ObjectResults{}
cacheobject.CachableObject(&obj, &rv)
cacheobject.ExpirationObject(&obj, &rv)
fmt.Println("Errors: ", rv.OutErr)
fmt.Println("Reasons to not cache: ", rv.OutReasons)
fmt.Println("Warning headers to add: ", rv.OutWarnings)
fmt.Println("Expiration: ", rv.OutExpirationTime.String())
}
```
## Improvements, bugs, adding features, and taking cachecontrol new directions!
Please [open issues in Github](https://github.com/pquerna/cachecontrol/issues) for ideas, bugs, and general thoughts. Pull requests are of course preferred :)
# Credits
`cachecontrol` has recieved significant contributions from:
* [Paul Querna](https://github.com/pquerna)
## License
`cachecontrol` is licensed under the [Apache License, Version 2.0](./LICENSE)
-48
View File
@@ -1,48 +0,0 @@
/**
* Copyright 2015 Paul Querna
*
* 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 cachecontrol
import (
"github.com/pquerna/cachecontrol/cacheobject"
"net/http"
"time"
)
type Options struct {
// Set to True for a private cache, which is not shared among users (eg, in a browser)
// Set to False for a "shared" cache, which is more common in a server context.
PrivateCache bool
}
// Given an HTTP Request, the future Status Code, and an ResponseWriter,
// determine the possible reasons a response SHOULD NOT be cached.
func CachableResponseWriter(req *http.Request,
statusCode int,
resp http.ResponseWriter,
opts Options) ([]cacheobject.Reason, time.Time, error) {
return cacheobject.UsingRequestResponse(req, statusCode, resp.Header(), opts.PrivateCache)
}
// Given an HTTP Request and Response, determine the possible reasons a response SHOULD NOT
// be cached.
func CachableResponse(req *http.Request,
resp *http.Response,
opts Options) ([]cacheobject.Reason, time.Time, error) {
return cacheobject.UsingRequestResponse(req, resp.StatusCode, resp.Header, opts.PrivateCache)
}
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff. Show more