diff --git a/.dependency_license b/.dependency_license index 954fadeff7..48ce53ee44 100644 --- a/.dependency_license +++ b/.dependency_license @@ -107,6 +107,8 @@ traffic_portal/app/src/assets/js/jsonformatter\..*, Apache traffic_portal/app/src/assets/js/fast-json-patch\..*, MIT traffic_portal/app/src/assets/css/colReorder.dataTables\..*, MIT traffic_portal/app/src/assets/js/colReorder.dataTables\..*, MIT +traffic_ops/traffic_ops_golang/vendor/github\.com/dgrijalva/.*, MIT +traffic_ops/traffic_ops_golang/vendor/github\.com/lestrrat-go/.*, MIT # Ignored - Do not report. \.DS_Store, Ignore # Created automatically OSX. diff --git a/CHANGELOG.md b/CHANGELOG.md index a9494faafc..904dfbd03b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - /api/1.4/cdns/dnsseckeys/refresh `GET` - /api/1.1/cdns/name/:name/dnsseckeys `GET` - /api/1.4/cdns/name/:name/dnsseckeys `GET` + - /api/1.4/user/login/oauth `POST` - To support reusing a single riak cluster connection, an optional parameter is added to riak.conf: "HealthCheckInterval". This options takes a 'Duration' value (ie: 10s, 5m) which affects how often the riak cluster is health checked. Default is currently set to: "HealthCheckInterval": "5s". - Added a new Go db/admin binary to replace the Perl db/admin.pl script which is now deprecated and will be removed in a future release. The new db/admin binary is essentially a drop-in replacement for db/admin.pl since it supports all of the same commands and options; therefore, it should be used in place of db/admin.pl for all the same tasks. - Added an API 1.4 endpoint, /api/1.4/cdns/dnsseckeys/refresh, to perform necessary behavior previously served outside the API under `/internal`. @@ -32,6 +33,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - In Traffic Portal, provides the ability to clone delivery service assignments from one cache to another cache of the same type. Issue #2963. - Traffic Ops now allows each delivery service to have a set of query parameter keys to be retained for consistent hash generation by Traffic Router. - In Traffic Portal, delivery service table columns can now be rearranged and their visibility toggled on/off as desired by the user. Hidden table columns are excluded from the table search. These settings are persisted in the browser. +- Added an API 1.4 endpoint, /api/1.4/user/login/oauth to handle SSO login using OAuth. +- Added /#!/sso page to Traffic Portal to catch redirects back from OAuth provider and POST token into the API. ### Changed - Traffic Router, added TLS certificate validation on certificates imported from Traffic Ops @@ -58,6 +61,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Issue #3750: Fixed Grove access log fractional seconds. - Issue #3646: Fixed Traffic Monitor Thresholds. - Modified Traffic Router API to be available via HTTPS. +- Added fields to traffic_portal_properties.json to configure SSO through OAuth. +- Added field to cdn.conf to configure whitelisted URLs for Json Key Set URL returned from OAuth provider. ## [3.0.0] - 2018-10-30 ### Added diff --git a/LICENSE b/LICENSE index 829423faab..4e351e790d 100644 --- a/LICENSE +++ b/LICENSE @@ -432,3 +432,11 @@ The modern-go/concurrent component is used under the Apache 2.0 license: The modern-go/reflect2 component is used under the Apache 2.0 license: @vendor/github.com/modern-go/reflect2/* ./vendor/github.com/modern-go/reflect2/LICENSE + +For the lestrrat-go/jwx (commit e35178a) component: +@traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/* +./traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/LICENSE + +For the dgrijalva/jwt-go (commit 5e25c22) component: +@traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/* +./traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/LICENSE diff --git a/docs/source/admin/quick_howto/index.rst b/docs/source/admin/quick_howto/index.rst index 01a46c880f..1ea4fdb209 100644 --- a/docs/source/admin/quick_howto/index.rst +++ b/docs/source/admin/quick_howto/index.rst @@ -27,6 +27,7 @@ Traffic Control is a complicated system, and documenting it is not trivial. Some ds_requests federations multi_site + oauth_login regionalgeo static_dns steering diff --git a/docs/source/admin/quick_howto/oauth_login.rst b/docs/source/admin/quick_howto/oauth_login.rst new file mode 100644 index 0000000000..1a3731b35f --- /dev/null +++ b/docs/source/admin/quick_howto/oauth_login.rst @@ -0,0 +1,92 @@ +.. +.. +.. 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. +.. +.. _oauth_login: + +********************* +Configure OAuth Login +********************* + +An opt-in configuration for SSO using OAuth is supported and can be configured through the :file:`/opt/traffic_portal/public/traffic_portal_properties.json` and :file:`/opt/traffic_ops/app/conf/cdn.conf` files. OAuth uses a third party provider to authenticate the user. Once enabled, the Traffic Portal Login page will no longer accept username and password but instead will authenticate using OAuth. This will redirect to the ``oAuthUrl`` from :file:`/opt/traffic_portal/public/traffic_portal_properties.json` which will authenticate the user then redirect to the new ``/sso`` page with an authorization code. The new ``/sso`` page will then construct the full URL to exchange the authorization code for a JSON Web Token, and ``POST`` this information to the :ref:`to-api-user-login-oauth` API endpoint. The :ref:`to-api-user-login-oauth` API endpoint will ``POST`` to the URL provided and receive JSON Web Token. The :ref:`to-api-user-login-oauth` API endpoint will decode the token, validate that it is between the issued time and the expiration time, and validate that the public key set URL is allowed by the list of whitelisted URLs read from :file:`/opt/traffic_ops/app/conf/cdn.conf`. It will then authorize the user from the database and return a mojolicious cookie as per the normal login workflow. + +.. Note:: Ensure that the user names in the Traffic Ops database match the value returned in the `sub` field in the response from the OAuth provider when setting up with the OAuth provider. The `sub` field is used to reference the roles in the Traffic Ops database in order to authorize the user. + +.. Note:: OAuth providers sometimes do not return the public key set URL but instead require a locally stored key. This functionality is not currently supported and will require further development. + +.. Note:: The ``POST`` from the API to the OAuth provider to exchange the code for a token expects the response to have the token in JSON format with `access_token` as the desired field (and can include other fields). It also supports a response with just the token itself as the body. Further development work will need to be done to allow other resposne forms or other response fields. + +.. Note:: Users must exist in both Traffic Ops as well as in the OAuth provider's system. The user's rights are defined by the :term:`role` assigned to the user. + +To configure OAuth login: + +- Set up authentication with a third party OAuth provider. + +- Update :file:`/opt/traffic_portal/public/traffic_portal_properties.json` and ensure the following properties are set up correctly: + + .. table:: OAuth Configuration Property Definitions In traffic_portal_properties.json + + +------------------------------+------------+-------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Type | Description | + +==============================+============+===========================================================================================================================================+ + | enabled | boolean | Allow OAuth SSO login | + +------------------------------+------------+-------------------------------------------------------------------------------------------------------------------------------------------+ + | oAuthUrl | string | URL to your OAuth provider | + +------------------------------+------------+-------------------------------------------------------------------------------------------------------------------------------------------+ + | redirectUriParameterOverride | string | Query parameter override if the oAuth provider requires a different key for the redirect_uri parameter, defaults to ``redirect_uri`` | + +------------------------------+------------+-------------------------------------------------------------------------------------------------------------------------------------------+ + | clientId | string | Client id registered with OAuth provider, passed in with `client_id` parameter | + +------------------------------+------------+-------------------------------------------------------------------------------------------------------------------------------------------+ + | oAuthCodeTokenUrl | string | URL to your OAuth provider's endpoint for exchanging the code (from oAuthUrl) for a token | + +------------------------------+------------+-------------------------------------------------------------------------------------------------------------------------------------------+ + + + .. code-block:: json + :caption: Example OAuth Configuration Properties In traffic_portal_properties.json + + { + "oAuth": { + "_comment": "Opt-in OAuth properties for SSO login. See http://traffic-control-cdn.readthedocs.io/en/release-4.0.0/admin/quick_howto/oauth_login.html for more details. redirectUriParameterOverride defaults to redirect_uri if left blank.", + "enabled": true, + "oAuthUrl": "example.oauth.com", + "redirectUriParameterOverride": "", + "clientId": "", + "oAuthCodeTokenUrl": "example.oauth.com/oauth/token" + } + } + +- Update :file:`/opt/traffic_ops/app/conf/cdn.conf` property traffic_ops_golang.whitelisted_oauth_urls to contain all allowed domains for the JSON key set (Use ``*`` for wildcard): + + .. table:: OAuth Configuration Property Definitions In cdn.conf + + +--------------------------+--------------------+-----------------------------------------------------------------------------------------------------------------+ + | Name | Type | Description | + +==========================+====================+=================================================================================================================+ + | whitelisted_oauth_urls | Array of strings | List of whitelisted URLs for the JSON public key set returned by OAuth provider. Can contain ``*`` wildcards. | + +--------------------------+--------------------+-----------------------------------------------------------------------------------------------------------------+ + | oauth_client_secret | string | Client secret registered with OAuth provider to verify client, passed in with `client_secret` parameter | + +--------------------------+--------------------+-----------------------------------------------------------------------------------------------------------------+ + + + .. code-block:: json + :caption: Example OAuth Configuration Properties In cdn.conf + + { + "traffic_ops_golang": { + "whitelisted_oauth_urls": [ + "oauth.example.com", + "*.example.com" + ], + "oauth_client_secret": "secret" + } + } \ No newline at end of file diff --git a/docs/source/admin/traffic_portal/installation.rst b/docs/source/admin/traffic_portal/installation.rst index e357cafe0c..7220f21d6a 100644 --- a/docs/source/admin/traffic_portal/installation.rst +++ b/docs/source/admin/traffic_portal/installation.rst @@ -41,6 +41,11 @@ Configuring Traffic Portal - Optional: update :file:`/opt/traffic_portal/public/resources/assets/css/custom.css` to customize Traffic Portal styling. +Configuring OAuth Through Traffic Portal +======================================== +See :ref:`oauth_login`. + + Starting Traffic Portal ======================= The Traffic Portal RPM comes with a :manpage:`systemd(1)` unit file, so under normal circumstances Traffic Portal may be started with :manpage:`systemctl(1)`. diff --git a/docs/source/admin/traffic_portal/usingtrafficportal.rst b/docs/source/admin/traffic_portal/usingtrafficportal.rst index 8e4a879059..0199f8ac3e 100644 --- a/docs/source/admin/traffic_portal/usingtrafficportal.rst +++ b/docs/source/admin/traffic_portal/usingtrafficportal.rst @@ -652,6 +652,8 @@ User management includes the ability to (where applicable): - update an existing user - view :term:`Delivery Service`\ s visible to a user +.. Note:: If OAuth is enabled, the username must exist both here as well as with the OAuth provider. A user's rights are defined by the :term:`role` assigned to the user in Traffic Ops. Creating/deleting a user here will update the user's :term:`role` but the user needs to be created/deleted with the OAuth provider as well. + Tenants ------- Each entry in the table of :term:`Tenant`\ s on this page has the following entries: diff --git a/docs/source/api/user_login_oauth.rst b/docs/source/api/user_login_oauth.rst new file mode 100644 index 0000000000..44a6e29c21 --- /dev/null +++ b/docs/source/api/user_login_oauth.rst @@ -0,0 +1,79 @@ +.. +.. +.. 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. +.. + +.. _to-api-user-login-oauth: + +******************** +``user/login/oauth`` +******************** +.. versionadded:: 1.4 + +``POST`` +======== + +Authentication of a user by exchanging a code for an encrypted JSON Web Token from an OAuth service. Traffic Ops will ``POST`` to the authCodeTokenUrl to exchange the code for an encrypted JSON Web Token. It will then decode and validate the token, validate the key set domain, and send back a session cookie. + +:Auth. Required: No +:Roles Required: None +:Response Type: ``undefined`` + +Request Structure +----------------- +:authCodeTokenUrl: URL for code-to-token conversion +:code: Code +:clientId: Client Id +:redirectUri: Redirect URI + +.. code-block:: http + :caption: Request Example + + POST /api/1.4/user/login/oauth HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 26 + Content-Type: application/json + + { + "authCodeTokenUrl": "https://url-to-convert-code-to-token.example.com", + "code": "AbCd123", + "clientId": "oauthClientId", + "redirectUri": "https://traffic-portal.example.com/sso" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Thu, 13 Dec 2018 21:21:33 GMT; HttpOnly + Whole-Content-Sha512: UdO6T3tMNctnVusDXzRjVwwYOnD7jmnBzPEB9PvOt2bHajTv3SKTPiIZjDzvhU6EX4p+JoG4fA5wlhgxpsejIw== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 13 Dec 2018 15:21:33 GMT + Content-Length: 65 + + { "alerts": [ + { + "text": "Successfully logged in.", + "level": "success" + } + ]} diff --git a/infrastructure/cdn-in-a-box/traffic_ops/config.sh b/infrastructure/cdn-in-a-box/traffic_ops/config.sh index b1fae8c938..323ab27825 100755 --- a/infrastructure/cdn-in-a-box/traffic_ops/config.sh +++ b/infrastructure/cdn-in-a-box/traffic_ops/config.sh @@ -80,7 +80,7 @@ cat <<-EOF >/opt/traffic_ops/app/conf/cdn.conf "workers" : 12 }, "traffic_ops_golang" : { - "insecure": true, + "insecure": true, "port" : "$TO_PORT", "proxy_timeout" : 60, "proxy_keep_alive" : 60, @@ -98,7 +98,9 @@ cat <<-EOF >/opt/traffic_ops/app/conf/cdn.conf "max_db_connections": 20, "backend_max_connections": { "mojolicious": 4 - } + }, + "whitelisted_oauth_urls": [], + "oauth_client_secret": "" }, "cors" : { "access_control_allow_origin" : "*" diff --git a/traffic_ops/app/conf/cdn.conf b/traffic_ops/app/conf/cdn.conf index 8852291f7e..d389b850c6 100644 --- a/traffic_ops/app/conf/cdn.conf +++ b/traffic_ops/app/conf/cdn.conf @@ -31,7 +31,9 @@ "backend_max_connections": { "mojolicious": 4 }, - "profiling_enabled": false + "whitelisted_oauth_urls": [], + "oauth_client_secret": "", + "profiling_enabled": false }, "cors" : { "access_control_allow_origin" : "*" diff --git a/traffic_ops/app/db/seeds.sql b/traffic_ops/app/db/seeds.sql index 3c53325540..5bdb7d65e9 100644 --- a/traffic_ops/app/db/seeds.sql +++ b/traffic_ops/app/db/seeds.sql @@ -397,6 +397,7 @@ INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHER -- auth insert into api_capability (http_method, route, capability) values ('POST', 'user/login', 'auth') ON CONFLICT (http_method, route, capability) DO NOTHING; +insert into api_capability (http_method, route, capability) values ('POST', 'user/login/oauth', 'auth') ON CONFLICT (http_method, route, capability) DO NOTHING; insert into api_capability (http_method, route, capability) values ('POST', 'user/login/token', 'auth') ON CONFLICT (http_method, route, capability) DO NOTHING; insert into api_capability (http_method, route, capability) values ('POST', 'user/logout', 'auth') ON CONFLICT (http_method, route, capability) DO NOTHING; insert into api_capability (http_method, route, capability) values ('POST', 'user/reset_password', 'auth') ON CONFLICT (http_method, route, capability) DO NOTHING; diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go index ea4e931eea..759753a2a1 100644 --- a/traffic_ops/traffic_ops_golang/config/config.go +++ b/traffic_ops/traffic_ops_golang/config/config.go @@ -87,6 +87,8 @@ type ConfigTrafficOpsGolang struct { ProfilingEnabled bool `json:"profiling_enabled"` ProfilingLocation string `json:"profiling_location"` RiakPort *uint `json:"riak_port"` + WhitelistedOAuthUrls []string `json:"whitelisted_oauth_urls"` + OAuthClientSecret string `json:"oauth_client_secret"` // CRConfigUseRequestHost is whether to use the client request host header in the CRConfig. If false, uses the tm.url parameter. // This defaults to false. Traffic Ops used to always use the host header, setting this true will resume that legacy behavior. diff --git a/traffic_ops/traffic_ops_golang/login/login.go b/traffic_ops/traffic_ops_golang/login/login.go index 1a59722da7..07575f70c5 100644 --- a/traffic_ops/traffic_ops_golang/login/login.go +++ b/traffic_ops/traffic_ops_golang/login/login.go @@ -20,9 +20,15 @@ package login */ import ( + "bytes" "encoding/json" + "errors" "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/lestrrat-go/jwx/jwk" "net/http" + "net/url" + "path/filepath" "time" "github.com/apache/trafficcontrol/lib/go-log" @@ -104,3 +110,185 @@ func LoginHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc { fmt.Fprintf(w, "%s", respBts) } } + +// OauthLoginHandler accepts a JSON web token previously obtained from an OAuth provider, decodes it, validates it, authorizes the user against the database, and returns the login result as either an error or success message +func OauthLoginHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + handleErrs := tc.GetHandleErrorsFunc(w, r) + defer r.Body.Close() + authenticated := false + resp := struct { + tc.Alerts + }{} + + form := auth.PasswordForm{} + parameters := struct { + AuthCodeTokenUrl string `json:"authCodeTokenUrl"` + Code string `json:"code"` + ClientId string `json:"clientId"` + RedirectUri string `json:"redirectUri"` + }{} + + if err := json.NewDecoder(r.Body).Decode(¶meters); err != nil { + handleErrs(http.StatusBadRequest, err) + return + } + + data := url.Values{} + data.Add("code", parameters.Code) + data.Add("client_id", parameters.ClientId) + data.Add("client_secret", cfg.ConfigTrafficOpsGolang.OAuthClientSecret) + data.Add("grant_type", "authorization_code") // Required by RFC6749 section 4.1.3 + data.Add("redirect_uri", parameters.RedirectUri) + + req, err := http.NewRequest(http.MethodPost, parameters.AuthCodeTokenUrl, bytes.NewBufferString(data.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if err != nil { + log.Errorf("obtaining token using code from oauth provider: %s", err.Error()) + return + } + + client := http.Client{ + Timeout: 30 * time.Second, + } + response, err := client.Do(req) + if err != nil { + log.Errorf("getting an http client: %s", err.Error()) + return + } + defer response.Body.Close() + + buf := new(bytes.Buffer) + buf.ReadFrom(response.Body) + encodedToken := "" + + var result map[string]interface{} + if err := json.Unmarshal(buf.Bytes(), &result); err != nil { + log.Warnf("Error parsing JSON response from oAuth: %s", err.Error()) + encodedToken = buf.String() + } else if _, ok := result["access_token"]; !ok { + sysErr := fmt.Errorf("Missing access token in response: %s\n", buf.String()) + usrErr := errors.New("Bad response from OAuth2.0 provider") + api.HandleErr(w, r, nil, http.StatusBadGateway, usrErr, sysErr) + return + } else { + switch t := result["access_token"].(type) { + case string: + encodedToken = result["access_token"].(string) + default: + sysErr := fmt.Errorf("Incorrect type of access_token! Expected 'string', got '%v'\n", t) + usrErr := errors.New("Bad response from OAuth2.0 provider") + api.HandleErr(w, r, nil, http.StatusBadGateway, usrErr, sysErr) + return + } + } + + if encodedToken == "" { + log.Errorf("Token not found in request but is required") + handleErrs(http.StatusBadRequest, errors.New("Token not found in request but is required")) + return + } + + decodedToken, err := jwt.Parse(encodedToken, func(unverifiedToken *jwt.Token) (interface{}, error) { + publicKeyUrl := unverifiedToken.Header["jku"].(string) + publicKeyId := unverifiedToken.Header["kid"].(string) + + matched, err := VerifyUrlOnWhiteList(publicKeyUrl, cfg.ConfigTrafficOpsGolang.WhitelistedOAuthUrls) + if err != nil { + return nil, err + } + if !matched { + return nil, errors.New("Key URL from token is not included in the whitelisted urls. Received: " + publicKeyUrl) + } + + keys, err := jwk.FetchHTTP(publicKeyUrl) + if err != nil { + return nil, errors.New("Error fetching JSON key set with message: " + err.Error()) + } + + keyById := keys.LookupKeyID(publicKeyId) + if len(keyById) == 0 { + return nil, errors.New("No public key found for id: " + publicKeyId + " at url: " + publicKeyUrl) + } + + selectedKey, err := keyById[0].Materialize() + if err != nil { + return nil, errors.New("Error materializing key from JSON key set with message: " + err.Error()) + } + + return selectedKey, nil + }) + if err != nil { + handleErrs(http.StatusInternalServerError, errors.New("Error decoding token with message: "+err.Error())) + log.Errorf("Error decoding token: %s\n", err.Error()) + return + } + + authenticated = decodedToken.Valid + + userId := decodedToken.Claims.(jwt.MapClaims)["sub"].(string) + form.Username = userId + + userAllowed, err, blockingErr := auth.CheckLocalUserIsAllowed(form, db, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second) + if blockingErr != nil { + api.HandleErr(w, r, nil, http.StatusServiceUnavailable, nil, fmt.Errorf("error checking local user password: %s\n", blockingErr.Error())) + return + } + if err != nil { + log.Errorf("checking local user: %s\n", err.Error()) + } + + if userAllowed && authenticated { + expiry := time.Now().Add(time.Hour * 6) + cookie := tocookie.New(userId, expiry, cfg.Secrets[0]) + httpCookie := http.Cookie{Name: "mojolicious", Value: cookie, Path: "/", Expires: expiry, HttpOnly: true} + http.SetCookie(w, &httpCookie) + resp = struct { + tc.Alerts + }{tc.CreateAlerts(tc.SuccessLevel, "Successfully logged in.")} + } else { + resp = struct { + tc.Alerts + }{tc.CreateAlerts(tc.ErrorLevel, "Invalid username or password.")} + } + + respBts, err := json.Marshal(resp) + if err != nil { + handleErrs(http.StatusInternalServerError, err) + return + } + w.Header().Set(tc.ContentType, tc.ApplicationJson) + if !authenticated { + w.WriteHeader(http.StatusUnauthorized) + } + if !userAllowed { + w.WriteHeader(http.StatusForbidden) + } + fmt.Fprintf(w, "%s", respBts) + + } +} + +func VerifyUrlOnWhiteList(urlString string, whiteListedUrls []string) (bool, error) { + + for _, listing := range whiteListedUrls { + if listing == "" { + continue + } + + urlParsed, err := url.Parse(urlString) + if err != nil { + return false, err + } + + matched, err := filepath.Match(listing, urlParsed.Hostname()) + if err != nil { + return false, err + } + + if matched { + return true, nil + } + } + return false, nil +} diff --git a/traffic_ops/traffic_ops_golang/login/login_test.go b/traffic_ops/traffic_ops_golang/login/login_test.go new file mode 100644 index 0000000000..cd522a8f8c --- /dev/null +++ b/traffic_ops/traffic_ops_golang/login/login_test.go @@ -0,0 +1,52 @@ +package login + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import "testing" + +func TestVerifyUrlOnWhiteList(t *testing.T) { + type TestResult struct { + Whitelist []string + ExpectedResult bool + } + + completeTestResults := struct { + Results []TestResult + }{} + + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{}, ExpectedResult: false}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{""}, ExpectedResult: false}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{"*"}, ExpectedResult: true}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{"test.wrong"}, ExpectedResult: false}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{"test.right.com"}, ExpectedResult: true}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{"*.right.com"}, ExpectedResult: true}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{"test.wrong", "test.right.com"}, ExpectedResult: true}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{"test.wrong", "*.right.*"}, ExpectedResult: true}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{"test.wrong", "*right*"}, ExpectedResult: true}) + completeTestResults.Results = append(completeTestResults.Results, TestResult{Whitelist: []string{"test.wrong", "*right"}, ExpectedResult: false}) + + url := "https://test.right.com/other/parts" + + for _, result := range completeTestResults.Results { + if matched, _ := VerifyUrlOnWhiteList(url, result.Whitelist); matched != result.ExpectedResult { + t.Errorf("for whitelist: %v, expected: %v, actual: %v", result.Whitelist, result.ExpectedResult, matched) + } + } +} diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index 3734340f0d..f85dfcb1f5 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -162,6 +162,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {1.1, http.MethodGet, `users/{id}/deliveryservices/?(\.json)?$`, user.GetDSes, auth.PrivLevelReadOnly, Authenticated, nil}, {1.1, http.MethodGet, `user/{id}/deliveryservices/available/?(\.json)?$`, user.GetAvailableDSes, auth.PrivLevelReadOnly, Authenticated, nil}, {1.1, http.MethodPost, `user/login/?$`, login.LoginHandler(d.DB, d.Config), 0, NoAuth, nil}, + {1.4, http.MethodPost, `user/login/oauth/?$`, login.OauthLoginHandler(d.DB, d.Config), 0, NoAuth, nil}, //User: CRUD {1.1, http.MethodGet, `users/?(\.json)?$`, api.ReadHandler(&user.TOUser{}), auth.PrivLevelReadOnly, Authenticated, nil}, diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/.travis.yml b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/.travis.yml new file mode 100644 index 0000000000..1027f56cd9 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/.travis.yml @@ -0,0 +1,13 @@ +language: go + +script: + - go vet ./... + - go test -v ./... + +go: + - 1.3 + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - tip diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/LICENSE b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/LICENSE new file mode 100644 index 0000000000..df83a9c2f0 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/LICENSE @@ -0,0 +1,8 @@ +Copyright (c) 2012 Dave Grijalva + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md new file mode 100644 index 0000000000..7fc1f793cb --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md @@ -0,0 +1,97 @@ +## Migration Guide from v2 -> v3 + +Version 3 adds several new, frequently requested features. To do so, it introduces a few breaking changes. We've worked to keep these as minimal as possible. This guide explains the breaking changes and how you can quickly update your code. + +### `Token.Claims` is now an interface type + +The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`. We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`. + +`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior. It is the default claims type when using `Parse`. The usage is unchanged except you must type cast the claims property. + +The old example for parsing a token looked like this.. + +```go + if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil { + fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) + } +``` + +is now directly mapped to... + +```go + if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil { + claims := token.Claims.(jwt.MapClaims) + fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) + } +``` + +`StandardClaims` is designed to be embedded in your custom type. You can supply a custom claims type with the new `ParseWithClaims` function. Here's an example of using a custom claims type. + +```go + type MyCustomClaims struct { + User string + *StandardClaims + } + + if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil { + claims := token.Claims.(*MyCustomClaims) + fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt) + } +``` + +### `ParseFromRequest` has been moved + +To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`. The method signatues have also been augmented to receive a new argument: `Extractor`. + +`Extractors` do the work of picking the token string out of a request. The interface is simple and composable. + +This simple parsing example: + +```go + if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil { + fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) + } +``` + +is directly mapped to: + +```go + if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil { + claims := token.Claims.(jwt.MapClaims) + fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) + } +``` + +There are several concrete `Extractor` types provided for your convenience: + +* `HeaderExtractor` will search a list of headers until one contains content. +* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content. +* `MultiExtractor` will try a list of `Extractors` in order until one returns content. +* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token. +* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument +* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed. A simple example is stripping the `Bearer ` text from a header + + +### RSA signing methods no longer accept `[]byte` keys + +Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse. + +To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`. These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types. + +```go + func keyLookupFunc(*Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + + // Look up key + key, err := lookupPublicKey(token.Header["kid"]) + if err != nil { + return nil, err + } + + // Unpack key from PEM encoded PKCS8 + return jwt.ParseRSAPublicKeyFromPEM(key) + } +``` diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/README.md b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/README.md new file mode 100644 index 0000000000..d7749077fd --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/README.md @@ -0,0 +1,104 @@ +# jwt-go + +[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go) +[![GoDoc](https://godoc.org/github.com/dgrijalva/jwt-go?status.svg)](https://godoc.org/github.com/dgrijalva/jwt-go) + +A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) + +**NEW VERSION COMING:** There have been a lot of improvements suggested since the version 3.0.0 released in 2016. I'm working now on cutting two different releases: 3.2.0 will contain any non-breaking changes or enhancements. 4.0.0 will follow shortly which will include breaking changes. See the 4.0.0 milestone to get an idea of what's coming. If you have other ideas, or would like to participate in 4.0.0, now's the time. If you depend on this library and don't want to be interrupted, I recommend you use your dependency mangement tool to pin to version 3. + +**SECURITY NOTICE:** Some older versions of Go have a security issue in the cryotp/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue #216 for more detail. + +**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided. + +## What the heck is a JWT? + +JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens. + +In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way. + +The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used. + +The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) for information about reserved keys and the proper way to add your own. + +## What's in the box? + +This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own. + +## Examples + +See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage: + +* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac) +* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac) +* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples) + +## Extensions + +This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`. + +Here's an example of an extension that integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS): https://github.com/someone1/gcp-jwt-go + +## Compliance + +This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences: + +* In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key. + +## Project Status & Versioning + +This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason). + +This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases). + +While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v3`. It will do the right thing WRT semantic versioning. + +**BREAKING CHANGES:*** +* Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code. + +## Usage Tips + +### Signing vs Encryption + +A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data: + +* The author of the token was in the possession of the signing secret +* The data has not been modified since it was signed + +It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library. + +### Choosing a Signing Method + +There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric. + +Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation. + +Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification. + +### Signing Methods and Key Types + +Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones: + +* The [HMAC signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation +* The [RSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation +* The [ECDSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation + +### JWT and OAuth + +It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication. + +Without going too far down the rabbit hole, here's a description of the interaction of these technologies: + +* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth. +* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token. +* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL. + +### Troubleshooting + +This library uses descriptive error messages whenever possible. If you are not getting the expected result, have a look at the errors. The most common place people get stuck is providing the correct type of key to the parser. See the above section on signing methods and key types. + +## More + +Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). + +The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation. diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md new file mode 100644 index 0000000000..6370298313 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md @@ -0,0 +1,118 @@ +## `jwt-go` Version History + +#### 3.2.0 + +* Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation +* HMAC signing method returns `ErrInvalidKeyType` instead of `ErrInvalidKey` where appropriate +* Added options to `request.ParseFromRequest`, which allows for an arbitrary list of modifiers to parsing behavior. Initial set include `WithClaims` and `WithParser`. Existing usage of this function will continue to work as before. +* Deprecated `ParseFromRequestWithClaims` to simplify API in the future. + +#### 3.1.0 + +* Improvements to `jwt` command line tool +* Added `SkipClaimsValidation` option to `Parser` +* Documentation updates + +#### 3.0.0 + +* **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code + * Dropped support for `[]byte` keys when using RSA signing methods. This convenience feature could contribute to security vulnerabilities involving mismatched key types with signing methods. + * `ParseFromRequest` has been moved to `request` subpackage and usage has changed + * The `Claims` property on `Token` is now type `Claims` instead of `map[string]interface{}`. The default value is type `MapClaims`, which is an alias to `map[string]interface{}`. This makes it possible to use a custom type when decoding claims. +* Other Additions and Changes + * Added `Claims` interface type to allow users to decode the claims into a custom type + * Added `ParseWithClaims`, which takes a third argument of type `Claims`. Use this function instead of `Parse` if you have a custom type you'd like to decode into. + * Dramatically improved the functionality and flexibility of `ParseFromRequest`, which is now in the `request` subpackage + * Added `ParseFromRequestWithClaims` which is the `FromRequest` equivalent of `ParseWithClaims` + * Added new interface type `Extractor`, which is used for extracting JWT strings from http requests. Used with `ParseFromRequest` and `ParseFromRequestWithClaims`. + * Added several new, more specific, validation errors to error type bitmask + * Moved examples from README to executable example files + * Signing method registry is now thread safe + * Added new property to `ValidationError`, which contains the raw error returned by calls made by parse/verify (such as those returned by keyfunc or json parser) + +#### 2.7.0 + +This will likely be the last backwards compatible release before 3.0.0, excluding essential bug fixes. + +* Added new option `-show` to the `jwt` command that will just output the decoded token without verifying +* Error text for expired tokens includes how long it's been expired +* Fixed incorrect error returned from `ParseRSAPublicKeyFromPEM` +* Documentation updates + +#### 2.6.0 + +* Exposed inner error within ValidationError +* Fixed validation errors when using UseJSONNumber flag +* Added several unit tests + +#### 2.5.0 + +* Added support for signing method none. You shouldn't use this. The API tries to make this clear. +* Updated/fixed some documentation +* Added more helpful error message when trying to parse tokens that begin with `BEARER ` + +#### 2.4.0 + +* Added new type, Parser, to allow for configuration of various parsing parameters + * You can now specify a list of valid signing methods. Anything outside this set will be rejected. + * You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON +* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go) +* Fixed some bugs with ECDSA parsing + +#### 2.3.0 + +* Added support for ECDSA signing methods +* Added support for RSA PSS signing methods (requires go v1.4) + +#### 2.2.0 + +* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic. + +#### 2.1.0 + +Backwards compatible API change that was missed in 2.0.0. + +* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte` + +#### 2.0.0 + +There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change. + +The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`. + +It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`. + +* **Compatibility Breaking Changes** + * `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct` + * `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct` + * `KeyFunc` now returns `interface{}` instead of `[]byte` + * `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key + * `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key +* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type. + * Added public package global `SigningMethodHS256` + * Added public package global `SigningMethodHS384` + * Added public package global `SigningMethodHS512` +* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type. + * Added public package global `SigningMethodRS256` + * Added public package global `SigningMethodRS384` + * Added public package global `SigningMethodRS512` +* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged. +* Refactored the RSA implementation to be easier to read +* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM` + +#### 1.0.2 + +* Fixed bug in parsing public keys from certificates +* Added more tests around the parsing of keys for RS256 +* Code refactoring in RS256 implementation. No functional changes + +#### 1.0.1 + +* Fixed panic if RS256 signing method was passed an invalid key + +#### 1.0.0 + +* First versioned release +* API stabilized +* Supports creating, signing, parsing, and validating JWT tokens +* Supports RS256 and HS256 signing methods \ No newline at end of file diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/claims.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/claims.go new file mode 100644 index 0000000000..f0228f02e0 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/claims.go @@ -0,0 +1,134 @@ +package jwt + +import ( + "crypto/subtle" + "fmt" + "time" +) + +// For a type to be a Claims object, it must just have a Valid method that determines +// if the token is invalid for any supported reason +type Claims interface { + Valid() error +} + +// Structured version of Claims Section, as referenced at +// https://tools.ietf.org/html/rfc7519#section-4.1 +// See examples for how to use this with your own claim types +type StandardClaims struct { + Audience string `json:"aud,omitempty"` + ExpiresAt int64 `json:"exp,omitempty"` + Id string `json:"jti,omitempty"` + IssuedAt int64 `json:"iat,omitempty"` + Issuer string `json:"iss,omitempty"` + NotBefore int64 `json:"nbf,omitempty"` + Subject string `json:"sub,omitempty"` +} + +// Validates time based claims "exp, iat, nbf". +// There is no accounting for clock skew. +// As well, if any of the above claims are not in the token, it will still +// be considered a valid claim. +func (c StandardClaims) Valid() error { + vErr := new(ValidationError) + now := TimeFunc().Unix() + + // The claims below are optional, by default, so if they are set to the + // default value in Go, let's not fail the verification for them. + if c.VerifyExpiresAt(now, false) == false { + delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) + vErr.Inner = fmt.Errorf("token is expired by %v", delta) + vErr.Errors |= ValidationErrorExpired + } + + if c.VerifyIssuedAt(now, false) == false { + vErr.Inner = fmt.Errorf("Token used before issued") + vErr.Errors |= ValidationErrorIssuedAt + } + + if c.VerifyNotBefore(now, false) == false { + vErr.Inner = fmt.Errorf("token is not valid yet") + vErr.Errors |= ValidationErrorNotValidYet + } + + if vErr.valid() { + return nil + } + + return vErr +} + +// Compares the aud claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { + return verifyAud(c.Audience, cmp, req) +} + +// Compares the exp claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool { + return verifyExp(c.ExpiresAt, cmp, req) +} + +// Compares the iat claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { + return verifyIat(c.IssuedAt, cmp, req) +} + +// Compares the iss claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool { + return verifyIss(c.Issuer, cmp, req) +} + +// Compares the nbf claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { + return verifyNbf(c.NotBefore, cmp, req) +} + +// ----- helpers + +func verifyAud(aud string, cmp string, required bool) bool { + if aud == "" { + return !required + } + if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 { + return true + } else { + return false + } +} + +func verifyExp(exp int64, now int64, required bool) bool { + if exp == 0 { + return !required + } + return now <= exp +} + +func verifyIat(iat int64, now int64, required bool) bool { + if iat == 0 { + return !required + } + return now >= iat +} + +func verifyIss(iss string, cmp string, required bool) bool { + if iss == "" { + return !required + } + if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 { + return true + } else { + return false + } +} + +func verifyNbf(nbf int64, now int64, required bool) bool { + if nbf == 0 { + return !required + } + return now >= nbf +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/README.md b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/README.md new file mode 100644 index 0000000000..41eb89da55 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/README.md @@ -0,0 +1,18 @@ +`jwt` command-line tool +======================= + +This is a simple tool to sign, verify and show JSON Web Tokens from +the command line. + +The following will create and sign a token, then verify it and output the original claims: + + echo {\"foo\":\"bar\"} | ./jwt -key ../../test/sample_key -alg RS256 -sign - | ./jwt -key ../../test/sample_key.pub -alg RS256 -verify - + +To simply display a token, use: + + echo $JWT | ./jwt -show - + +You can install this tool with the following command: + + go install github.com/dgrijalva/jwt-go/cmd/jwt + diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/app.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/app.go new file mode 100644 index 0000000000..cdf2500832 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/app.go @@ -0,0 +1,282 @@ +// A useful example app. You can use this to debug your tokens on the command line. +// This is also a great place to look at how you might use this library. +// +// Example usage: +// The following will create and sign a token, then verify it and output the original claims. +// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify - +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "regexp" + "strings" + + jwt "github.com/dgrijalva/jwt-go" +) + +var ( + // Options + flagAlg = flag.String("alg", "", "signing algorithm identifier") + flagKey = flag.String("key", "", "path to key file or '-' to read from stdin") + flagCompact = flag.Bool("compact", false, "output compact JSON") + flagDebug = flag.Bool("debug", false, "print out all kinds of debug data") + flagClaims = make(ArgList) + flagHead = make(ArgList) + + // Modes - exactly one of these is required + flagSign = flag.String("sign", "", "path to claims object to sign, '-' to read from stdin, or '+' to use only -claim args") + flagVerify = flag.String("verify", "", "path to JWT token to verify or '-' to read from stdin") + flagShow = flag.String("show", "", "path to JWT file or '-' to read from stdin") +) + +func main() { + // Plug in Var flags + flag.Var(flagClaims, "claim", "add additional claims. may be used more than once") + flag.Var(flagHead, "header", "add additional header params. may be used more than once") + + // Usage message if you ask for -help or if you mess up inputs. + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " One of the following flags is required: sign, verify\n") + flag.PrintDefaults() + } + + // Parse command line options + flag.Parse() + + // Do the thing. If something goes wrong, print error to stderr + // and exit with a non-zero status code + if err := start(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +// Figure out which thing to do and then do that +func start() error { + if *flagSign != "" { + return signToken() + } else if *flagVerify != "" { + return verifyToken() + } else if *flagShow != "" { + return showToken() + } else { + flag.Usage() + return fmt.Errorf("None of the required flags are present. What do you want me to do?") + } +} + +// Helper func: Read input from specified file or stdin +func loadData(p string) ([]byte, error) { + if p == "" { + return nil, fmt.Errorf("No path specified") + } + + var rdr io.Reader + if p == "-" { + rdr = os.Stdin + } else if p == "+" { + return []byte("{}"), nil + } else { + if f, err := os.Open(p); err == nil { + rdr = f + defer f.Close() + } else { + return nil, err + } + } + return ioutil.ReadAll(rdr) +} + +// Print a json object in accordance with the prophecy (or the command line options) +func printJSON(j interface{}) error { + var out []byte + var err error + + if *flagCompact == false { + out, err = json.MarshalIndent(j, "", " ") + } else { + out, err = json.Marshal(j) + } + + if err == nil { + fmt.Println(string(out)) + } + + return err +} + +// Verify a token and output the claims. This is a great example +// of how to verify and view a token. +func verifyToken() error { + // get the token + tokData, err := loadData(*flagVerify) + if err != nil { + return fmt.Errorf("Couldn't read token: %v", err) + } + + // trim possible whitespace from token + tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{}) + if *flagDebug { + fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData)) + } + + // Parse the token. Load the key from command line option + token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) { + data, err := loadData(*flagKey) + if err != nil { + return nil, err + } + if isEs() { + return jwt.ParseECPublicKeyFromPEM(data) + } else if isRs() { + return jwt.ParseRSAPublicKeyFromPEM(data) + } + return data, nil + }) + + // Print some debug data + if *flagDebug && token != nil { + fmt.Fprintf(os.Stderr, "Header:\n%v\n", token.Header) + fmt.Fprintf(os.Stderr, "Claims:\n%v\n", token.Claims) + } + + // Print an error if we can't parse for some reason + if err != nil { + return fmt.Errorf("Couldn't parse token: %v", err) + } + + // Is token invalid? + if !token.Valid { + return fmt.Errorf("Token is invalid") + } + + // Print the token details + if err := printJSON(token.Claims); err != nil { + return fmt.Errorf("Failed to output claims: %v", err) + } + + return nil +} + +// Create, sign, and output a token. This is a great, simple example of +// how to use this library to create and sign a token. +func signToken() error { + // get the token data from command line arguments + tokData, err := loadData(*flagSign) + if err != nil { + return fmt.Errorf("Couldn't read token: %v", err) + } else if *flagDebug { + fmt.Fprintf(os.Stderr, "Token: %v bytes", len(tokData)) + } + + // parse the JSON of the claims + var claims jwt.MapClaims + if err := json.Unmarshal(tokData, &claims); err != nil { + return fmt.Errorf("Couldn't parse claims JSON: %v", err) + } + + // add command line claims + if len(flagClaims) > 0 { + for k, v := range flagClaims { + claims[k] = v + } + } + + // get the key + var key interface{} + key, err = loadData(*flagKey) + if err != nil { + return fmt.Errorf("Couldn't read key: %v", err) + } + + // get the signing alg + alg := jwt.GetSigningMethod(*flagAlg) + if alg == nil { + return fmt.Errorf("Couldn't find signing method: %v", *flagAlg) + } + + // create a new token + token := jwt.NewWithClaims(alg, claims) + + // add command line headers + if len(flagHead) > 0 { + for k, v := range flagHead { + token.Header[k] = v + } + } + + if isEs() { + if k, ok := key.([]byte); !ok { + return fmt.Errorf("Couldn't convert key data to key") + } else { + key, err = jwt.ParseECPrivateKeyFromPEM(k) + if err != nil { + return err + } + } + } else if isRs() { + if k, ok := key.([]byte); !ok { + return fmt.Errorf("Couldn't convert key data to key") + } else { + key, err = jwt.ParseRSAPrivateKeyFromPEM(k) + if err != nil { + return err + } + } + } + + if out, err := token.SignedString(key); err == nil { + fmt.Println(out) + } else { + return fmt.Errorf("Error signing token: %v", err) + } + + return nil +} + +// showToken pretty-prints the token on the command line. +func showToken() error { + // get the token + tokData, err := loadData(*flagShow) + if err != nil { + return fmt.Errorf("Couldn't read token: %v", err) + } + + // trim possible whitespace from token + tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{}) + if *flagDebug { + fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData)) + } + + token, err := jwt.Parse(string(tokData), nil) + if token == nil { + return fmt.Errorf("malformed token: %v", err) + } + + // Print the token details + fmt.Println("Header:") + if err := printJSON(token.Header); err != nil { + return fmt.Errorf("Failed to output header: %v", err) + } + + fmt.Println("Claims:") + if err := printJSON(token.Claims); err != nil { + return fmt.Errorf("Failed to output claims: %v", err) + } + + return nil +} + +func isEs() bool { + return strings.HasPrefix(*flagAlg, "ES") +} + +func isRs() bool { + return strings.HasPrefix(*flagAlg, "RS") || strings.HasPrefix(*flagAlg, "PS") +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/args.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/args.go new file mode 100644 index 0000000000..a5bba5b10c --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/cmd/jwt/args.go @@ -0,0 +1,23 @@ +package main + +import ( + "encoding/json" + "fmt" + "strings" +) + +type ArgList map[string]string + +func (l ArgList) String() string { + data, _ := json.Marshal(l) + return string(data) +} + +func (l ArgList) Set(arg string) error { + parts := strings.SplitN(arg, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("Invalid argument '%v'. Must use format 'key=value'. %v", arg, parts) + } + l[parts[0]] = parts[1] + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/doc.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/doc.go new file mode 100644 index 0000000000..a86dc1a3b3 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/doc.go @@ -0,0 +1,4 @@ +// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html +// +// See README.md for more info. +package jwt diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa.go new file mode 100644 index 0000000000..f977381240 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa.go @@ -0,0 +1,148 @@ +package jwt + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "errors" + "math/big" +) + +var ( + // Sadly this is missing from crypto/ecdsa compared to crypto/rsa + ErrECDSAVerification = errors.New("crypto/ecdsa: verification error") +) + +// Implements the ECDSA family of signing methods signing methods +// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification +type SigningMethodECDSA struct { + Name string + Hash crypto.Hash + KeySize int + CurveBits int +} + +// Specific instances for EC256 and company +var ( + SigningMethodES256 *SigningMethodECDSA + SigningMethodES384 *SigningMethodECDSA + SigningMethodES512 *SigningMethodECDSA +) + +func init() { + // ES256 + SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256} + RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod { + return SigningMethodES256 + }) + + // ES384 + SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384} + RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod { + return SigningMethodES384 + }) + + // ES512 + SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521} + RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod { + return SigningMethodES512 + }) +} + +func (m *SigningMethodECDSA) Alg() string { + return m.Name +} + +// Implements the Verify method from SigningMethod +// For this verify method, key must be an ecdsa.PublicKey struct +func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error { + var err error + + // Decode the signature + var sig []byte + if sig, err = DecodeSegment(signature); err != nil { + return err + } + + // Get the key + var ecdsaKey *ecdsa.PublicKey + switch k := key.(type) { + case *ecdsa.PublicKey: + ecdsaKey = k + default: + return ErrInvalidKeyType + } + + if len(sig) != 2*m.KeySize { + return ErrECDSAVerification + } + + r := big.NewInt(0).SetBytes(sig[:m.KeySize]) + s := big.NewInt(0).SetBytes(sig[m.KeySize:]) + + // Create hasher + if !m.Hash.Available() { + return ErrHashUnavailable + } + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Verify the signature + if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true { + return nil + } else { + return ErrECDSAVerification + } +} + +// Implements the Sign method from SigningMethod +// For this signing method, key must be an ecdsa.PrivateKey struct +func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) { + // Get the key + var ecdsaKey *ecdsa.PrivateKey + switch k := key.(type) { + case *ecdsa.PrivateKey: + ecdsaKey = k + default: + return "", ErrInvalidKeyType + } + + // Create the hasher + if !m.Hash.Available() { + return "", ErrHashUnavailable + } + + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Sign the string and return r, s + if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil { + curveBits := ecdsaKey.Curve.Params().BitSize + + if m.CurveBits != curveBits { + return "", ErrInvalidKey + } + + keyBytes := curveBits / 8 + if curveBits%8 > 0 { + keyBytes += 1 + } + + // We serialize the outpus (r and s) into big-endian byte arrays and pad + // them with zeros on the left to make sure the sizes work out. Both arrays + // must be keyBytes long, and the output must be 2*keyBytes long. + rBytes := r.Bytes() + rBytesPadded := make([]byte, keyBytes) + copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) + + sBytes := s.Bytes() + sBytesPadded := make([]byte, keyBytes) + copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) + + out := append(rBytesPadded, sBytesPadded...) + + return EncodeSegment(out), nil + } else { + return "", err + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa_test.go new file mode 100644 index 0000000000..753047b1ec --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa_test.go @@ -0,0 +1,100 @@ +package jwt_test + +import ( + "crypto/ecdsa" + "io/ioutil" + "strings" + "testing" + + "github.com/dgrijalva/jwt-go" +) + +var ecdsaTestData = []struct { + name string + keys map[string]string + tokenString string + alg string + claims map[string]interface{} + valid bool +}{ + { + "Basic ES256", + map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ", + "ES256", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "Basic ES384", + map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"}, + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld", + "ES384", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "Basic ES512", + map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"}, + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5", + "ES512", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "basic ES256 invalid: foo => bar", + map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W", + "ES256", + map[string]interface{}{"foo": "bar"}, + false, + }, +} + +func TestECDSAVerify(t *testing.T) { + for _, data := range ecdsaTestData { + var err error + + key, _ := ioutil.ReadFile(data.keys["public"]) + + var ecdsaKey *ecdsa.PublicKey + if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil { + t.Errorf("Unable to parse ECDSA public key: %v", err) + } + + parts := strings.Split(data.tokenString, ".") + + method := jwt.GetSigningMethod(data.alg) + err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey) + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying key: %v", data.name, err) + } + if !data.valid && err == nil { + t.Errorf("[%v] Invalid key passed validation", data.name) + } + } +} + +func TestECDSASign(t *testing.T) { + for _, data := range ecdsaTestData { + var err error + key, _ := ioutil.ReadFile(data.keys["private"]) + + var ecdsaKey *ecdsa.PrivateKey + if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil { + t.Errorf("Unable to parse ECDSA private key: %v", err) + } + + if data.valid { + parts := strings.Split(data.tokenString, ".") + method := jwt.GetSigningMethod(data.alg) + sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey) + if err != nil { + t.Errorf("[%v] Error signing token: %v", data.name, err) + } + if sig == parts[2] { + t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig) + } + } + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go new file mode 100644 index 0000000000..d19624b726 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go @@ -0,0 +1,67 @@ +package jwt + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +var ( + ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key") + ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key") +) + +// Parse PEM encoded Elliptic Curve Private Key Structure +func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil { + return nil, err + } + + var pkey *ecdsa.PrivateKey + var ok bool + if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { + return nil, ErrNotECPrivateKey + } + + return pkey, nil +} + +// Parse PEM encoded PKCS1 or PKCS8 public key +func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { + if cert, err := x509.ParseCertificate(block.Bytes); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + var pkey *ecdsa.PublicKey + var ok bool + if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok { + return nil, ErrNotECPublicKey + } + + return pkey, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/errors.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/errors.go new file mode 100644 index 0000000000..1c93024aad --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/errors.go @@ -0,0 +1,59 @@ +package jwt + +import ( + "errors" +) + +// Error constants +var ( + ErrInvalidKey = errors.New("key is invalid") + ErrInvalidKeyType = errors.New("key is of invalid type") + ErrHashUnavailable = errors.New("the requested hash function is unavailable") +) + +// The errors that might occur when parsing and validating a token +const ( + ValidationErrorMalformed uint32 = 1 << iota // Token is malformed + ValidationErrorUnverifiable // Token could not be verified because of signing problems + ValidationErrorSignatureInvalid // Signature validation failed + + // Standard Claim validation errors + ValidationErrorAudience // AUD validation failed + ValidationErrorExpired // EXP validation failed + ValidationErrorIssuedAt // IAT validation failed + ValidationErrorIssuer // ISS validation failed + ValidationErrorNotValidYet // NBF validation failed + ValidationErrorId // JTI validation failed + ValidationErrorClaimsInvalid // Generic claims validation error +) + +// Helper for constructing a ValidationError with a string error message +func NewValidationError(errorText string, errorFlags uint32) *ValidationError { + return &ValidationError{ + text: errorText, + Errors: errorFlags, + } +} + +// The error from Parse if token is not valid +type ValidationError struct { + Inner error // stores the error returned by external dependencies, i.e.: KeyFunc + Errors uint32 // bitfield. see ValidationError... constants + text string // errors that do not have a valid error just have text +} + +// Validation error is an error type +func (e ValidationError) Error() string { + if e.Inner != nil { + return e.Inner.Error() + } else if e.text != "" { + return e.text + } else { + return "token is invalid" + } +} + +// No errors +func (e *ValidationError) valid() bool { + return e.Errors == 0 +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/example_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/example_test.go new file mode 100644 index 0000000000..ae8b788a0b --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/example_test.go @@ -0,0 +1,114 @@ +package jwt_test + +import ( + "fmt" + "github.com/dgrijalva/jwt-go" + "time" +) + +// Example (atypical) using the StandardClaims type by itself to parse a token. +// The StandardClaims type is designed to be embedded into your custom types +// to provide standard validation features. You can use it alone, but there's +// no way to retrieve other fields after parsing. +// See the CustomClaimsType example for intended usage. +func ExampleNewWithClaims_standardClaims() { + mySigningKey := []byte("AllYourBase") + + // Create the Claims + claims := &jwt.StandardClaims{ + ExpiresAt: 15000, + Issuer: "test", + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + ss, err := token.SignedString(mySigningKey) + fmt.Printf("%v %v", ss, err) + //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.QsODzZu3lUZMVdhbO76u3Jv02iYCvEHcYVUI1kOWEU0 +} + +// Example creating a token using a custom claims type. The StandardClaim is embedded +// in the custom type to allow for easy encoding, parsing and validation of standard claims. +func ExampleNewWithClaims_customClaimsType() { + mySigningKey := []byte("AllYourBase") + + type MyCustomClaims struct { + Foo string `json:"foo"` + jwt.StandardClaims + } + + // Create the Claims + claims := MyCustomClaims{ + "bar", + jwt.StandardClaims{ + ExpiresAt: 15000, + Issuer: "test", + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + ss, err := token.SignedString(mySigningKey) + fmt.Printf("%v %v", ss, err) + //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c +} + +// Example creating a token using a custom claims type. The StandardClaim is embedded +// in the custom type to allow for easy encoding, parsing and validation of standard claims. +func ExampleParseWithClaims_customClaimsType() { + tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c" + + type MyCustomClaims struct { + Foo string `json:"foo"` + jwt.StandardClaims + } + + // sample token is expired. override time so it parses as valid + at(time.Unix(0, 0), func() { + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte("AllYourBase"), nil + }) + + if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { + fmt.Printf("%v %v", claims.Foo, claims.StandardClaims.ExpiresAt) + } else { + fmt.Println(err) + } + }) + + // Output: bar 15000 +} + +// Override time value for tests. Restore default value after. +func at(t time.Time, f func()) { + jwt.TimeFunc = func() time.Time { + return t + } + f() + jwt.TimeFunc = time.Now +} + +// An example of parsing the error types using bitfield checks +func ExampleParse_errorChecking() { + // Token from another example. This token is expired + var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c" + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return []byte("AllYourBase"), nil + }) + + if token.Valid { + fmt.Println("You look nice today") + } else if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + fmt.Println("That's not even a token") + } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { + // Token is either expired or not active yet + fmt.Println("Timing is everything") + } else { + fmt.Println("Couldn't handle this token:", err) + } + } else { + fmt.Println("Couldn't handle this token:", err) + } + + // Output: Timing is everything +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac.go new file mode 100644 index 0000000000..addbe5d401 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac.go @@ -0,0 +1,95 @@ +package jwt + +import ( + "crypto" + "crypto/hmac" + "errors" +) + +// Implements the HMAC-SHA family of signing methods signing methods +// Expects key type of []byte for both signing and validation +type SigningMethodHMAC struct { + Name string + Hash crypto.Hash +} + +// Specific instances for HS256 and company +var ( + SigningMethodHS256 *SigningMethodHMAC + SigningMethodHS384 *SigningMethodHMAC + SigningMethodHS512 *SigningMethodHMAC + ErrSignatureInvalid = errors.New("signature is invalid") +) + +func init() { + // HS256 + SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256} + RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod { + return SigningMethodHS256 + }) + + // HS384 + SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384} + RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod { + return SigningMethodHS384 + }) + + // HS512 + SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512} + RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod { + return SigningMethodHS512 + }) +} + +func (m *SigningMethodHMAC) Alg() string { + return m.Name +} + +// Verify the signature of HSXXX tokens. Returns nil if the signature is valid. +func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error { + // Verify the key is the right type + keyBytes, ok := key.([]byte) + if !ok { + return ErrInvalidKeyType + } + + // Decode signature, for comparison + sig, err := DecodeSegment(signature) + if err != nil { + return err + } + + // Can we use the specified hashing method? + if !m.Hash.Available() { + return ErrHashUnavailable + } + + // This signing method is symmetric, so we validate the signature + // by reproducing the signature from the signing string and key, then + // comparing that against the provided signature. + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write([]byte(signingString)) + if !hmac.Equal(sig, hasher.Sum(nil)) { + return ErrSignatureInvalid + } + + // No validation errors. Signature is good. + return nil +} + +// Implements the Sign method from SigningMethod for this signing method. +// Key must be []byte +func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) { + if keyBytes, ok := key.([]byte); ok { + if !m.Hash.Available() { + return "", ErrHashUnavailable + } + + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write([]byte(signingString)) + + return EncodeSegment(hasher.Sum(nil)), nil + } + + return "", ErrInvalidKeyType +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac_example_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac_example_test.go new file mode 100644 index 0000000000..0027831474 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac_example_test.go @@ -0,0 +1,66 @@ +package jwt_test + +import ( + "fmt" + "github.com/dgrijalva/jwt-go" + "io/ioutil" + "time" +) + +// For HMAC signing method, the key can be any []byte. It is recommended to generate +// a key using crypto/rand or something equivalent. You need the same key for signing +// and validating. +var hmacSampleSecret []byte + +func init() { + // Load sample key data + if keyData, e := ioutil.ReadFile("test/hmacTestKey"); e == nil { + hmacSampleSecret = keyData + } else { + panic(e) + } +} + +// Example creating, signing, and encoding a JWT token using the HMAC signing method +func ExampleNew_hmac() { + // Create a new token object, specifying signing method and the claims + // you would like it to contain. + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "foo": "bar", + "nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(), + }) + + // Sign and get the complete encoded token as a string using the secret + tokenString, err := token.SignedString(hmacSampleSecret) + + fmt.Println(tokenString, err) + // Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU +} + +// Example parsing and validating a token using the HMAC signing method +func ExampleParse_hmac() { + // sample token string taken from the New example + tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU" + + // Parse takes the token string and a function for looking up the key. The latter is especially + // useful if you use multiple keys for your application. The standard is to use 'kid' in the + // head of the token to identify which key to use, but the parsed token (head and claims) is provided + // to the callback, providing flexibility. + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + + // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") + return hmacSampleSecret, nil + }) + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + fmt.Println(claims["foo"], claims["nbf"]) + } else { + fmt.Println(err) + } + + // Output: bar 1.4444784e+09 +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac_test.go new file mode 100644 index 0000000000..c7e114f4f9 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/hmac_test.go @@ -0,0 +1,91 @@ +package jwt_test + +import ( + "github.com/dgrijalva/jwt-go" + "io/ioutil" + "strings" + "testing" +) + +var hmacTestData = []struct { + name string + tokenString string + alg string + claims map[string]interface{} + valid bool +}{ + { + "web sample", + "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", + "HS256", + map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, + true, + }, + { + "HS384", + "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy", + "HS384", + map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, + true, + }, + { + "HS512", + "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw", + "HS512", + map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, + true, + }, + { + "web sample: invalid", + "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo", + "HS256", + map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, + false, + }, +} + +// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1 +var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey") + +func TestHMACVerify(t *testing.T) { + for _, data := range hmacTestData { + parts := strings.Split(data.tokenString, ".") + + method := jwt.GetSigningMethod(data.alg) + err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey) + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying key: %v", data.name, err) + } + if !data.valid && err == nil { + t.Errorf("[%v] Invalid key passed validation", data.name) + } + } +} + +func TestHMACSign(t *testing.T) { + for _, data := range hmacTestData { + if data.valid { + parts := strings.Split(data.tokenString, ".") + method := jwt.GetSigningMethod(data.alg) + sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey) + if err != nil { + t.Errorf("[%v] Error signing token: %v", data.name, err) + } + if sig != parts[2] { + t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2]) + } + } + } +} + +func BenchmarkHS256Signing(b *testing.B) { + benchmarkSigning(b, jwt.SigningMethodHS256, hmacTestKey) +} + +func BenchmarkHS384Signing(b *testing.B) { + benchmarkSigning(b, jwt.SigningMethodHS384, hmacTestKey) +} + +func BenchmarkHS512Signing(b *testing.B) { + benchmarkSigning(b, jwt.SigningMethodHS512, hmacTestKey) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/http_example_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/http_example_test.go new file mode 100644 index 0000000000..82e9c50a41 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/http_example_test.go @@ -0,0 +1,216 @@ +package jwt_test + +// Example HTTP auth using asymmetric crypto/RSA keys +// This is based on a (now outdated) example at https://gist.github.com/cryptix/45c33ecf0ae54828e63b + +import ( + "bytes" + "crypto/rsa" + "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/dgrijalva/jwt-go/request" + "io" + "io/ioutil" + "log" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +// location of the files used for signing and verification +const ( + privKeyPath = "test/sample_key" // openssl genrsa -out app.rsa keysize + pubKeyPath = "test/sample_key.pub" // openssl rsa -in app.rsa -pubout > app.rsa.pub +) + +var ( + verifyKey *rsa.PublicKey + signKey *rsa.PrivateKey + serverPort int + // storing sample username/password pairs + // don't do this on a real server + users = map[string]string{ + "test": "known", + } +) + +// read the key files before starting http handlers +func init() { + signBytes, err := ioutil.ReadFile(privKeyPath) + fatal(err) + + signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes) + fatal(err) + + verifyBytes, err := ioutil.ReadFile(pubKeyPath) + fatal(err) + + verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes) + fatal(err) + + http.HandleFunc("/authenticate", authHandler) + http.HandleFunc("/restricted", restrictedHandler) + + // Setup listener + listener, err := net.ListenTCP("tcp", &net.TCPAddr{}) + serverPort = listener.Addr().(*net.TCPAddr).Port + + log.Println("Listening...") + go func() { + fatal(http.Serve(listener, nil)) + }() +} + +var start func() + +func fatal(err error) { + if err != nil { + log.Fatal(err) + } +} + +// Define some custom types were going to use within our tokens +type CustomerInfo struct { + Name string + Kind string +} + +type CustomClaimsExample struct { + *jwt.StandardClaims + TokenType string + CustomerInfo +} + +func Example_getTokenViaHTTP() { + // See func authHandler for an example auth handler that produces a token + res, err := http.PostForm(fmt.Sprintf("http://localhost:%v/authenticate", serverPort), url.Values{ + "user": {"test"}, + "pass": {"known"}, + }) + if err != nil { + fatal(err) + } + + if res.StatusCode != 200 { + fmt.Println("Unexpected status code", res.StatusCode) + } + + // Read the token out of the response body + buf := new(bytes.Buffer) + io.Copy(buf, res.Body) + res.Body.Close() + tokenString := strings.TrimSpace(buf.String()) + + // Parse the token + token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) { + // since we only use the one private key to sign the tokens, + // we also only use its public counter part to verify + return verifyKey, nil + }) + fatal(err) + + claims := token.Claims.(*CustomClaimsExample) + fmt.Println(claims.CustomerInfo.Name) + + //Output: test +} + +func Example_useTokenViaHTTP() { + + // Make a sample token + // In a real world situation, this token will have been acquired from + // some other API call (see Example_getTokenViaHTTP) + token, err := createToken("foo") + fatal(err) + + // Make request. See func restrictedHandler for example request processor + req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%v/restricted", serverPort), nil) + fatal(err) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token)) + res, err := http.DefaultClient.Do(req) + fatal(err) + + // Read the response body + buf := new(bytes.Buffer) + io.Copy(buf, res.Body) + res.Body.Close() + fmt.Println(buf.String()) + + // Output: Welcome, foo +} + +func createToken(user string) (string, error) { + // create a signer for rsa 256 + t := jwt.New(jwt.GetSigningMethod("RS256")) + + // set our claims + t.Claims = &CustomClaimsExample{ + &jwt.StandardClaims{ + // set the expire time + // see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4 + ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), + }, + "level1", + CustomerInfo{user, "human"}, + } + + // Creat token string + return t.SignedString(signKey) +} + +// reads the form values, checks them and creates the token +func authHandler(w http.ResponseWriter, r *http.Request) { + // make sure its post + if r.Method != "POST" { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, "No POST", r.Method) + return + } + + user := r.FormValue("user") + pass := r.FormValue("pass") + + log.Printf("Authenticate: user[%s] pass[%s]\n", user, pass) + + // check values + if user != "test" || pass != "known" { + w.WriteHeader(http.StatusForbidden) + fmt.Fprintln(w, "Wrong info") + return + } + + tokenString, err := createToken(user) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintln(w, "Sorry, error while Signing Token!") + log.Printf("Token Signing error: %v\n", err) + return + } + + w.Header().Set("Content-Type", "application/jwt") + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, tokenString) +} + +// only accessible with a valid token +func restrictedHandler(w http.ResponseWriter, r *http.Request) { + // Get token from request + token, err := request.ParseFromRequestWithClaims(r, request.OAuth2Extractor, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) { + // since we only use the one private key to sign the tokens, + // we also only use its public counter part to verify + return verifyKey, nil + }) + + // If the token is missing or invalid, return error + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + fmt.Fprintln(w, "Invalid token:", err) + return + } + + // Token is valid + fmt.Fprintln(w, "Welcome,", token.Claims.(*CustomClaimsExample).Name) + return +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/map_claims.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/map_claims.go new file mode 100644 index 0000000000..291213c460 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/map_claims.go @@ -0,0 +1,94 @@ +package jwt + +import ( + "encoding/json" + "errors" + // "fmt" +) + +// Claims type that uses the map[string]interface{} for JSON decoding +// This is the default claims type if you don't supply one +type MapClaims map[string]interface{} + +// Compares the aud claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaims) VerifyAudience(cmp string, req bool) bool { + aud, _ := m["aud"].(string) + return verifyAud(aud, cmp, req) +} + +// Compares the exp claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool { + switch exp := m["exp"].(type) { + case float64: + return verifyExp(int64(exp), cmp, req) + case json.Number: + v, _ := exp.Int64() + return verifyExp(v, cmp, req) + } + return req == false +} + +// Compares the iat claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { + switch iat := m["iat"].(type) { + case float64: + return verifyIat(int64(iat), cmp, req) + case json.Number: + v, _ := iat.Int64() + return verifyIat(v, cmp, req) + } + return req == false +} + +// Compares the iss claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { + iss, _ := m["iss"].(string) + return verifyIss(iss, cmp, req) +} + +// Compares the nbf claim against cmp. +// If required is false, this method will return true if the value matches or is unset +func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool { + switch nbf := m["nbf"].(type) { + case float64: + return verifyNbf(int64(nbf), cmp, req) + case json.Number: + v, _ := nbf.Int64() + return verifyNbf(v, cmp, req) + } + return req == false +} + +// Validates time based claims "exp, iat, nbf". +// There is no accounting for clock skew. +// As well, if any of the above claims are not in the token, it will still +// be considered a valid claim. +func (m MapClaims) Valid() error { + vErr := new(ValidationError) + now := TimeFunc().Unix() + + if m.VerifyExpiresAt(now, false) == false { + vErr.Inner = errors.New("Token is expired") + vErr.Errors |= ValidationErrorExpired + } + + if m.VerifyIssuedAt(now, false) == false { + vErr.Inner = errors.New("Token used before issued") + vErr.Errors |= ValidationErrorIssuedAt + } + + if m.VerifyNotBefore(now, false) == false { + vErr.Inner = errors.New("Token is not valid yet") + vErr.Errors |= ValidationErrorNotValidYet + } + + if vErr.valid() { + return nil + } + + return vErr +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/none.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/none.go new file mode 100644 index 0000000000..f04d189d06 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/none.go @@ -0,0 +1,52 @@ +package jwt + +// Implements the none signing method. This is required by the spec +// but you probably should never use it. +var SigningMethodNone *signingMethodNone + +const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" + +var NoneSignatureTypeDisallowedError error + +type signingMethodNone struct{} +type unsafeNoneMagicConstant string + +func init() { + SigningMethodNone = &signingMethodNone{} + NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid) + + RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod { + return SigningMethodNone + }) +} + +func (m *signingMethodNone) Alg() string { + return "none" +} + +// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key +func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) { + // Key must be UnsafeAllowNoneSignatureType to prevent accidentally + // accepting 'none' signing method + if _, ok := key.(unsafeNoneMagicConstant); !ok { + return NoneSignatureTypeDisallowedError + } + // If signing method is none, signature must be an empty string + if signature != "" { + return NewValidationError( + "'none' signing method with non-empty signature", + ValidationErrorSignatureInvalid, + ) + } + + // Accept 'none' signing method. + return nil +} + +// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key +func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) { + if _, ok := key.(unsafeNoneMagicConstant); ok { + return "", nil + } + return "", NoneSignatureTypeDisallowedError +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/none_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/none_test.go new file mode 100644 index 0000000000..29a69efef7 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/none_test.go @@ -0,0 +1,72 @@ +package jwt_test + +import ( + "github.com/dgrijalva/jwt-go" + "strings" + "testing" +) + +var noneTestData = []struct { + name string + tokenString string + alg string + key interface{} + claims map[string]interface{} + valid bool +}{ + { + "Basic", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.", + "none", + jwt.UnsafeAllowNoneSignatureType, + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "Basic - no key", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.", + "none", + nil, + map[string]interface{}{"foo": "bar"}, + false, + }, + { + "Signed", + "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw", + "none", + jwt.UnsafeAllowNoneSignatureType, + map[string]interface{}{"foo": "bar"}, + false, + }, +} + +func TestNoneVerify(t *testing.T) { + for _, data := range noneTestData { + parts := strings.Split(data.tokenString, ".") + + method := jwt.GetSigningMethod(data.alg) + err := method.Verify(strings.Join(parts[0:2], "."), parts[2], data.key) + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying key: %v", data.name, err) + } + if !data.valid && err == nil { + t.Errorf("[%v] Invalid key passed validation", data.name) + } + } +} + +func TestNoneSign(t *testing.T) { + for _, data := range noneTestData { + if data.valid { + parts := strings.Split(data.tokenString, ".") + method := jwt.GetSigningMethod(data.alg) + sig, err := method.Sign(strings.Join(parts[0:2], "."), data.key) + if err != nil { + t.Errorf("[%v] Error signing token: %v", data.name, err) + } + if sig != parts[2] { + t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2]) + } + } + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/parser.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/parser.go new file mode 100644 index 0000000000..d6901d9adb --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/parser.go @@ -0,0 +1,148 @@ +package jwt + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +type Parser struct { + ValidMethods []string // If populated, only these methods will be considered valid + UseJSONNumber bool // Use JSON Number format in JSON decoder + SkipClaimsValidation bool // Skip claims validation during token parsing +} + +// Parse, validate, and return a token. +// keyFunc will receive the parsed token and should return the key for validating. +// If everything is kosher, err will be nil +func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { + return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc) +} + +func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { + token, parts, err := p.ParseUnverified(tokenString, claims) + if err != nil { + return token, err + } + + // Verify signing method is in the required set + if p.ValidMethods != nil { + var signingMethodValid = false + var alg = token.Method.Alg() + for _, m := range p.ValidMethods { + if m == alg { + signingMethodValid = true + break + } + } + if !signingMethodValid { + // signing method is not in the listed set + return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid) + } + } + + // Lookup key + var key interface{} + if keyFunc == nil { + // keyFunc was not provided. short circuiting validation + return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable) + } + if key, err = keyFunc(token); err != nil { + // keyFunc returned an error + if ve, ok := err.(*ValidationError); ok { + return token, ve + } + return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} + } + + vErr := &ValidationError{} + + // Validate Claims + if !p.SkipClaimsValidation { + if err := token.Claims.Valid(); err != nil { + + // If the Claims Valid returned an error, check if it is a validation error, + // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set + if e, ok := err.(*ValidationError); !ok { + vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} + } else { + vErr = e + } + } + } + + // Perform validation + token.Signature = parts[2] + if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { + vErr.Inner = err + vErr.Errors |= ValidationErrorSignatureInvalid + } + + if vErr.valid() { + token.Valid = true + return token, nil + } + + return token, vErr +} + +// WARNING: Don't use this method unless you know what you're doing +// +// This method parses the token but doesn't validate the signature. It's only +// ever useful in cases where you know the signature is valid (because it has +// been checked previously in the stack) and you want to extract values from +// it. +func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) { + parts = strings.Split(tokenString, ".") + if len(parts) != 3 { + return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) + } + + token = &Token{Raw: tokenString} + + // parse Header + var headerBytes []byte + if headerBytes, err = DecodeSegment(parts[0]); err != nil { + if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { + return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed) + } + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + if err = json.Unmarshal(headerBytes, &token.Header); err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + + // parse Claims + var claimBytes []byte + token.Claims = claims + + if claimBytes, err = DecodeSegment(parts[1]); err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) + if p.UseJSONNumber { + dec.UseNumber() + } + // JSON Decode. Special case for map type to avoid weird pointer behavior + if c, ok := token.Claims.(MapClaims); ok { + err = dec.Decode(&c) + } else { + err = dec.Decode(&claims) + } + // Handle decode error + if err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + + // Lookup signature method + if method, ok := token.Header["alg"].(string); ok { + if token.Method = GetSigningMethod(method); token.Method == nil { + return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable) + } + } else { + return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable) + } + + return token, parts, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/parser_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/parser_test.go new file mode 100644 index 0000000000..390779785d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/parser_test.go @@ -0,0 +1,301 @@ +package jwt_test + +import ( + "crypto/rsa" + "encoding/json" + "fmt" + "reflect" + "testing" + "time" + + "github.com/dgrijalva/jwt-go" + "github.com/dgrijalva/jwt-go/test" +) + +var keyFuncError error = fmt.Errorf("error loading key") + +var ( + jwtTestDefaultKey *rsa.PublicKey + defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil } + emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil } + errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, keyFuncError } + nilKeyFunc jwt.Keyfunc = nil +) + +func init() { + jwtTestDefaultKey = test.LoadRSAPublicKeyFromDisk("test/sample_key.pub") +} + +var jwtTestData = []struct { + name string + tokenString string + keyfunc jwt.Keyfunc + claims jwt.Claims + valid bool + errors uint32 + parser *jwt.Parser +}{ + { + "basic", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + defaultKeyFunc, + jwt.MapClaims{"foo": "bar"}, + true, + 0, + nil, + }, + { + "basic expired", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, + false, + jwt.ValidationErrorExpired, + nil, + }, + { + "basic nbf", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, + false, + jwt.ValidationErrorNotValidYet, + nil, + }, + { + "expired and nbf", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)}, + false, + jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, + nil, + }, + { + "basic invalid", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + defaultKeyFunc, + jwt.MapClaims{"foo": "bar"}, + false, + jwt.ValidationErrorSignatureInvalid, + nil, + }, + { + "basic nokeyfunc", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + nilKeyFunc, + jwt.MapClaims{"foo": "bar"}, + false, + jwt.ValidationErrorUnverifiable, + nil, + }, + { + "basic nokey", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + emptyKeyFunc, + jwt.MapClaims{"foo": "bar"}, + false, + jwt.ValidationErrorSignatureInvalid, + nil, + }, + { + "basic errorkey", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + errorKeyFunc, + jwt.MapClaims{"foo": "bar"}, + false, + jwt.ValidationErrorUnverifiable, + nil, + }, + { + "invalid signing method", + "", + defaultKeyFunc, + jwt.MapClaims{"foo": "bar"}, + false, + jwt.ValidationErrorSignatureInvalid, + &jwt.Parser{ValidMethods: []string{"HS256"}}, + }, + { + "valid signing method", + "", + defaultKeyFunc, + jwt.MapClaims{"foo": "bar"}, + true, + 0, + &jwt.Parser{ValidMethods: []string{"RS256", "HS256"}}, + }, + { + "JSON Number", + "", + defaultKeyFunc, + jwt.MapClaims{"foo": json.Number("123.4")}, + true, + 0, + &jwt.Parser{UseJSONNumber: true}, + }, + { + "Standard Claims", + "", + defaultKeyFunc, + &jwt.StandardClaims{ + ExpiresAt: time.Now().Add(time.Second * 10).Unix(), + }, + true, + 0, + &jwt.Parser{UseJSONNumber: true}, + }, + { + "JSON Number - basic expired", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))}, + false, + jwt.ValidationErrorExpired, + &jwt.Parser{UseJSONNumber: true}, + }, + { + "JSON Number - basic nbf", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))}, + false, + jwt.ValidationErrorNotValidYet, + &jwt.Parser{UseJSONNumber: true}, + }, + { + "JSON Number - expired and nbf", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100)), "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))}, + false, + jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, + &jwt.Parser{UseJSONNumber: true}, + }, + { + "SkipClaimsValidation during token parsing", + "", // autogen + defaultKeyFunc, + jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))}, + true, + 0, + &jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true}, + }, +} + +func TestParser_Parse(t *testing.T) { + privateKey := test.LoadRSAPrivateKeyFromDisk("test/sample_key") + + // Iterate over test data set and run tests + for _, data := range jwtTestData { + // If the token string is blank, use helper function to generate string + if data.tokenString == "" { + data.tokenString = test.MakeSampleToken(data.claims, privateKey) + } + + // Parse the token + var token *jwt.Token + var err error + var parser = data.parser + if parser == nil { + parser = new(jwt.Parser) + } + // Figure out correct claims type + switch data.claims.(type) { + case jwt.MapClaims: + token, err = parser.ParseWithClaims(data.tokenString, jwt.MapClaims{}, data.keyfunc) + case *jwt.StandardClaims: + token, err = parser.ParseWithClaims(data.tokenString, &jwt.StandardClaims{}, data.keyfunc) + } + + // Verify result matches expectation + if !reflect.DeepEqual(data.claims, token.Claims) { + t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) + } + + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err) + } + + if !data.valid && err == nil { + t.Errorf("[%v] Invalid token passed validation", data.name) + } + + if (err == nil && !token.Valid) || (err != nil && token.Valid) { + t.Errorf("[%v] Inconsistent behavior between returned error and token.Valid", data.name) + } + + if data.errors != 0 { + if err == nil { + t.Errorf("[%v] Expecting error. Didn't get one.", data.name) + } else { + + ve := err.(*jwt.ValidationError) + // compare the bitfield part of the error + if e := ve.Errors; e != data.errors { + t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors) + } + + if err.Error() == keyFuncError.Error() && ve.Inner != keyFuncError { + t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, keyFuncError) + } + } + } + if data.valid && token.Signature == "" { + t.Errorf("[%v] Signature is left unpopulated after parsing", data.name) + } + } +} + +func TestParser_ParseUnverified(t *testing.T) { + privateKey := test.LoadRSAPrivateKeyFromDisk("test/sample_key") + + // Iterate over test data set and run tests + for _, data := range jwtTestData { + // If the token string is blank, use helper function to generate string + if data.tokenString == "" { + data.tokenString = test.MakeSampleToken(data.claims, privateKey) + } + + // Parse the token + var token *jwt.Token + var err error + var parser = data.parser + if parser == nil { + parser = new(jwt.Parser) + } + // Figure out correct claims type + switch data.claims.(type) { + case jwt.MapClaims: + token, _, err = parser.ParseUnverified(data.tokenString, jwt.MapClaims{}) + case *jwt.StandardClaims: + token, _, err = parser.ParseUnverified(data.tokenString, &jwt.StandardClaims{}) + } + + if err != nil { + t.Errorf("[%v] Invalid token", data.name) + } + + // Verify result matches expectation + if !reflect.DeepEqual(data.claims, token.Claims) { + t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) + } + + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err) + } + } +} + +// Helper method for benchmarking various methods +func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) { + t := jwt.New(method) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := t.SignedString(key); err != nil { + b.Fatal(err) + } + } + }) + +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/doc.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/doc.go new file mode 100644 index 0000000000..c01069c984 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/doc.go @@ -0,0 +1,7 @@ +// Utility package for extracting JWT tokens from +// HTTP requests. +// +// The main function is ParseFromRequest and it's WithClaims variant. +// See examples for how to use the various Extractor implementations +// or roll your own. +package request diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor.go new file mode 100644 index 0000000000..14414fe2f2 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor.go @@ -0,0 +1,81 @@ +package request + +import ( + "errors" + "net/http" +) + +// Errors +var ( + ErrNoTokenInRequest = errors.New("no token present in request") +) + +// Interface for extracting a token from an HTTP request. +// The ExtractToken method should return a token string or an error. +// If no token is present, you must return ErrNoTokenInRequest. +type Extractor interface { + ExtractToken(*http.Request) (string, error) +} + +// Extractor for finding a token in a header. Looks at each specified +// header in order until there's a match +type HeaderExtractor []string + +func (e HeaderExtractor) ExtractToken(req *http.Request) (string, error) { + // loop over header names and return the first one that contains data + for _, header := range e { + if ah := req.Header.Get(header); ah != "" { + return ah, nil + } + } + return "", ErrNoTokenInRequest +} + +// Extract token from request arguments. This includes a POSTed form or +// GET URL arguments. Argument names are tried in order until there's a match. +// This extractor calls `ParseMultipartForm` on the request +type ArgumentExtractor []string + +func (e ArgumentExtractor) ExtractToken(req *http.Request) (string, error) { + // Make sure form is parsed + req.ParseMultipartForm(10e6) + + // loop over arg names and return the first one that contains data + for _, arg := range e { + if ah := req.Form.Get(arg); ah != "" { + return ah, nil + } + } + + return "", ErrNoTokenInRequest +} + +// Tries Extractors in order until one returns a token string or an error occurs +type MultiExtractor []Extractor + +func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) { + // loop over header names and return the first one that contains data + for _, extractor := range e { + if tok, err := extractor.ExtractToken(req); tok != "" { + return tok, nil + } else if err != ErrNoTokenInRequest { + return "", err + } + } + return "", ErrNoTokenInRequest +} + +// Wrap an Extractor in this to post-process the value before it's handed off. +// See AuthorizationHeaderExtractor for an example +type PostExtractionFilter struct { + Extractor + Filter func(string) (string, error) +} + +func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) { + if tok, err := e.Extractor.ExtractToken(req); tok != "" { + return e.Filter(tok) + } else { + return "", err + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor_example_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor_example_test.go new file mode 100644 index 0000000000..a994ffe586 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor_example_test.go @@ -0,0 +1,32 @@ +package request + +import ( + "fmt" + "net/url" +) + +const ( + exampleTokenA = "A" +) + +func ExampleHeaderExtractor() { + req := makeExampleRequest("GET", "/", map[string]string{"Token": exampleTokenA}, nil) + tokenString, err := HeaderExtractor{"Token"}.ExtractToken(req) + if err == nil { + fmt.Println(tokenString) + } else { + fmt.Println(err) + } + //Output: A +} + +func ExampleArgumentExtractor() { + req := makeExampleRequest("GET", "/", nil, url.Values{"token": {extractorTestTokenA}}) + tokenString, err := ArgumentExtractor{"token"}.ExtractToken(req) + if err == nil { + fmt.Println(tokenString) + } else { + fmt.Println(err) + } + //Output: A +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor_test.go new file mode 100644 index 0000000000..e3bbb0a3eb --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/extractor_test.go @@ -0,0 +1,91 @@ +package request + +import ( + "fmt" + "net/http" + "net/url" + "testing" +) + +var extractorTestTokenA = "A" +var extractorTestTokenB = "B" + +var extractorTestData = []struct { + name string + extractor Extractor + headers map[string]string + query url.Values + token string + err error +}{ + { + name: "simple header", + extractor: HeaderExtractor{"Foo"}, + headers: map[string]string{"Foo": extractorTestTokenA}, + query: nil, + token: extractorTestTokenA, + err: nil, + }, + { + name: "simple argument", + extractor: ArgumentExtractor{"token"}, + headers: map[string]string{}, + query: url.Values{"token": {extractorTestTokenA}}, + token: extractorTestTokenA, + err: nil, + }, + { + name: "multiple extractors", + extractor: MultiExtractor{ + HeaderExtractor{"Foo"}, + ArgumentExtractor{"token"}, + }, + headers: map[string]string{"Foo": extractorTestTokenA}, + query: url.Values{"token": {extractorTestTokenB}}, + token: extractorTestTokenA, + err: nil, + }, + { + name: "simple miss", + extractor: HeaderExtractor{"This-Header-Is-Not-Set"}, + headers: map[string]string{"Foo": extractorTestTokenA}, + query: nil, + token: "", + err: ErrNoTokenInRequest, + }, + { + name: "filter", + extractor: AuthorizationHeaderExtractor, + headers: map[string]string{"Authorization": "Bearer " + extractorTestTokenA}, + query: nil, + token: extractorTestTokenA, + err: nil, + }, +} + +func TestExtractor(t *testing.T) { + // Bearer token request + for _, data := range extractorTestData { + // Make request from test struct + r := makeExampleRequest("GET", "/", data.headers, data.query) + + // Test extractor + token, err := data.extractor.ExtractToken(r) + if token != data.token { + t.Errorf("[%v] Expected token '%v'. Got '%v'", data.name, data.token, token) + continue + } + if err != data.err { + t.Errorf("[%v] Expected error '%v'. Got '%v'", data.name, data.err, err) + continue + } + } +} + +func makeExampleRequest(method, path string, headers map[string]string, urlArgs url.Values) *http.Request { + r, _ := http.NewRequest(method, fmt.Sprintf("%v?%v", path, urlArgs.Encode()), nil) + for k, v := range headers { + r.Header.Set(k, v) + } + return r +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/oauth2.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/oauth2.go new file mode 100644 index 0000000000..5948694a51 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/oauth2.go @@ -0,0 +1,28 @@ +package request + +import ( + "strings" +) + +// Strips 'Bearer ' prefix from bearer token string +func stripBearerPrefixFromTokenString(tok string) (string, error) { + // Should be a bearer token + if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " { + return tok[7:], nil + } + return tok, nil +} + +// Extract bearer token from Authorization header +// Uses PostExtractionFilter to strip "Bearer " prefix from header +var AuthorizationHeaderExtractor = &PostExtractionFilter{ + HeaderExtractor{"Authorization"}, + stripBearerPrefixFromTokenString, +} + +// Extractor for OAuth2 access tokens. Looks in 'Authorization' +// header then 'access_token' argument for a token. +var OAuth2Extractor = &MultiExtractor{ + AuthorizationHeaderExtractor, + ArgumentExtractor{"access_token"}, +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/request.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/request.go new file mode 100644 index 0000000000..70525cface --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/request.go @@ -0,0 +1,68 @@ +package request + +import ( + "github.com/dgrijalva/jwt-go" + "net/http" +) + +// Extract and parse a JWT token from an HTTP request. +// This behaves the same as Parse, but accepts a request and an extractor +// instead of a token string. The Extractor interface allows you to define +// the logic for extracting a token. Several useful implementations are provided. +// +// You can provide options to modify parsing behavior +func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc, options ...ParseFromRequestOption) (token *jwt.Token, err error) { + // Create basic parser struct + p := &fromRequestParser{req, extractor, nil, nil} + + // Handle options + for _, option := range options { + option(p) + } + + // Set defaults + if p.claims == nil { + p.claims = jwt.MapClaims{} + } + if p.parser == nil { + p.parser = &jwt.Parser{} + } + + // perform extract + tokenString, err := p.extractor.ExtractToken(req) + if err != nil { + return nil, err + } + + // perform parse + return p.parser.ParseWithClaims(tokenString, p.claims, keyFunc) +} + +// ParseFromRequest but with custom Claims type +// DEPRECATED: use ParseFromRequest and the WithClaims option +func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc) (token *jwt.Token, err error) { + return ParseFromRequest(req, extractor, keyFunc, WithClaims(claims)) +} + +type fromRequestParser struct { + req *http.Request + extractor Extractor + claims jwt.Claims + parser *jwt.Parser +} + +type ParseFromRequestOption func(*fromRequestParser) + +// Parse with custom claims +func WithClaims(claims jwt.Claims) ParseFromRequestOption { + return func(p *fromRequestParser) { + p.claims = claims + } +} + +// Parse using a custom parser +func WithParser(parser *jwt.Parser) ParseFromRequestOption { + return func(p *fromRequestParser) { + p.parser = parser + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/request_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/request_test.go new file mode 100644 index 0000000000..b4365cd869 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/request/request_test.go @@ -0,0 +1,103 @@ +package request + +import ( + "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/dgrijalva/jwt-go/test" + "net/http" + "net/url" + "reflect" + "strings" + "testing" +) + +var requestTestData = []struct { + name string + claims jwt.MapClaims + extractor Extractor + headers map[string]string + query url.Values + valid bool +}{ + { + "authorization bearer token", + jwt.MapClaims{"foo": "bar"}, + AuthorizationHeaderExtractor, + map[string]string{"Authorization": "Bearer %v"}, + url.Values{}, + true, + }, + { + "oauth bearer token - header", + jwt.MapClaims{"foo": "bar"}, + OAuth2Extractor, + map[string]string{"Authorization": "Bearer %v"}, + url.Values{}, + true, + }, + { + "oauth bearer token - url", + jwt.MapClaims{"foo": "bar"}, + OAuth2Extractor, + map[string]string{}, + url.Values{"access_token": {"%v"}}, + true, + }, + { + "url token", + jwt.MapClaims{"foo": "bar"}, + ArgumentExtractor{"token"}, + map[string]string{}, + url.Values{"token": {"%v"}}, + true, + }, +} + +func TestParseRequest(t *testing.T) { + // load keys from disk + privateKey := test.LoadRSAPrivateKeyFromDisk("../test/sample_key") + publicKey := test.LoadRSAPublicKeyFromDisk("../test/sample_key.pub") + keyfunc := func(*jwt.Token) (interface{}, error) { + return publicKey, nil + } + + // Bearer token request + for _, data := range requestTestData { + // Make token from claims + tokenString := test.MakeSampleToken(data.claims, privateKey) + + // Make query string + for k, vv := range data.query { + for i, v := range vv { + if strings.Contains(v, "%v") { + data.query[k][i] = fmt.Sprintf(v, tokenString) + } + } + } + + // Make request from test struct + r, _ := http.NewRequest("GET", fmt.Sprintf("/?%v", data.query.Encode()), nil) + for k, v := range data.headers { + if strings.Contains(v, "%v") { + r.Header.Set(k, fmt.Sprintf(v, tokenString)) + } else { + r.Header.Set(k, tokenString) + } + } + token, err := ParseFromRequestWithClaims(r, data.extractor, jwt.MapClaims{}, keyfunc) + + if token == nil { + t.Errorf("[%v] Token was not found: %v", data.name, err) + continue + } + if !reflect.DeepEqual(data.claims, token.Claims) { + t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) + } + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying token: %v", data.name, err) + } + if !data.valid && err == nil { + t.Errorf("[%v] Invalid token passed validation", data.name) + } + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa.go new file mode 100644 index 0000000000..e4caf1ca4a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa.go @@ -0,0 +1,101 @@ +package jwt + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" +) + +// Implements the RSA family of signing methods signing methods +// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation +type SigningMethodRSA struct { + Name string + Hash crypto.Hash +} + +// Specific instances for RS256 and company +var ( + SigningMethodRS256 *SigningMethodRSA + SigningMethodRS384 *SigningMethodRSA + SigningMethodRS512 *SigningMethodRSA +) + +func init() { + // RS256 + SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256} + RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod { + return SigningMethodRS256 + }) + + // RS384 + SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384} + RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod { + return SigningMethodRS384 + }) + + // RS512 + SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512} + RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod { + return SigningMethodRS512 + }) +} + +func (m *SigningMethodRSA) Alg() string { + return m.Name +} + +// Implements the Verify method from SigningMethod +// For this signing method, must be an *rsa.PublicKey structure. +func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error { + var err error + + // Decode the signature + var sig []byte + if sig, err = DecodeSegment(signature); err != nil { + return err + } + + var rsaKey *rsa.PublicKey + var ok bool + + if rsaKey, ok = key.(*rsa.PublicKey); !ok { + return ErrInvalidKeyType + } + + // Create hasher + if !m.Hash.Available() { + return ErrHashUnavailable + } + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Verify the signature + return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig) +} + +// Implements the Sign method from SigningMethod +// For this signing method, must be an *rsa.PrivateKey structure. +func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) { + var rsaKey *rsa.PrivateKey + var ok bool + + // Validate type of key + if rsaKey, ok = key.(*rsa.PrivateKey); !ok { + return "", ErrInvalidKey + } + + // Create the hasher + if !m.Hash.Available() { + return "", ErrHashUnavailable + } + + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Sign the string and return the encoded bytes + if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil { + return EncodeSegment(sigBytes), nil + } else { + return "", err + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_pss.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_pss.go new file mode 100644 index 0000000000..10ee9db8a4 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_pss.go @@ -0,0 +1,126 @@ +// +build go1.4 + +package jwt + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" +) + +// Implements the RSAPSS family of signing methods signing methods +type SigningMethodRSAPSS struct { + *SigningMethodRSA + Options *rsa.PSSOptions +} + +// Specific instances for RS/PS and company +var ( + SigningMethodPS256 *SigningMethodRSAPSS + SigningMethodPS384 *SigningMethodRSAPSS + SigningMethodPS512 *SigningMethodRSAPSS +) + +func init() { + // PS256 + SigningMethodPS256 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS256", + Hash: crypto.SHA256, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA256, + }, + } + RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod { + return SigningMethodPS256 + }) + + // PS384 + SigningMethodPS384 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS384", + Hash: crypto.SHA384, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA384, + }, + } + RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod { + return SigningMethodPS384 + }) + + // PS512 + SigningMethodPS512 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS512", + Hash: crypto.SHA512, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA512, + }, + } + RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod { + return SigningMethodPS512 + }) +} + +// Implements the Verify method from SigningMethod +// For this verify method, key must be an rsa.PublicKey struct +func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error { + var err error + + // Decode the signature + var sig []byte + if sig, err = DecodeSegment(signature); err != nil { + return err + } + + var rsaKey *rsa.PublicKey + switch k := key.(type) { + case *rsa.PublicKey: + rsaKey = k + default: + return ErrInvalidKey + } + + // Create hasher + if !m.Hash.Available() { + return ErrHashUnavailable + } + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options) +} + +// Implements the Sign method from SigningMethod +// For this signing method, key must be an rsa.PrivateKey struct +func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) { + var rsaKey *rsa.PrivateKey + + switch k := key.(type) { + case *rsa.PrivateKey: + rsaKey = k + default: + return "", ErrInvalidKeyType + } + + // Create the hasher + if !m.Hash.Available() { + return "", ErrHashUnavailable + } + + hasher := m.Hash.New() + hasher.Write([]byte(signingString)) + + // Sign the string and return the encoded bytes + if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil { + return EncodeSegment(sigBytes), nil + } else { + return "", err + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_pss_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_pss_test.go new file mode 100644 index 0000000000..9045aaf349 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_pss_test.go @@ -0,0 +1,96 @@ +// +build go1.4 + +package jwt_test + +import ( + "crypto/rsa" + "io/ioutil" + "strings" + "testing" + + "github.com/dgrijalva/jwt-go" +) + +var rsaPSSTestData = []struct { + name string + tokenString string + alg string + claims map[string]interface{} + valid bool +}{ + { + "Basic PS256", + "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9w", + "PS256", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "Basic PS384", + "eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.w7-qqgj97gK4fJsq_DCqdYQiylJjzWONvD0qWWWhqEOFk2P1eDULPnqHRnjgTXoO4HAw4YIWCsZPet7nR3Xxq4ZhMqvKW8b7KlfRTb9cH8zqFvzMmybQ4jv2hKc3bXYqVow3AoR7hN_CWXI3Dv6Kd2X5xhtxRHI6IL39oTVDUQ74LACe-9t4c3QRPuj6Pq1H4FAT2E2kW_0KOc6EQhCLWEhm2Z2__OZskDC8AiPpP8Kv4k2vB7l0IKQu8Pr4RcNBlqJdq8dA5D3hk5TLxP8V5nG1Ib80MOMMqoS3FQvSLyolFX-R_jZ3-zfq6Ebsqr0yEb0AH2CfsECF7935Pa0FKQ", + "PS384", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "Basic PS512", + "eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.GX1HWGzFaJevuSLavqqFYaW8_TpvcjQ8KfC5fXiSDzSiT9UD9nB_ikSmDNyDILNdtjZLSvVKfXxZJqCfefxAtiozEDDdJthZ-F0uO4SPFHlGiXszvKeodh7BuTWRI2wL9-ZO4mFa8nq3GMeQAfo9cx11i7nfN8n2YNQ9SHGovG7_T_AvaMZB_jT6jkDHpwGR9mz7x1sycckEo6teLdHRnH_ZdlHlxqknmyTu8Odr5Xh0sJFOL8BepWbbvIIn-P161rRHHiDWFv6nhlHwZnVzjx7HQrWSGb6-s2cdLie9QL_8XaMcUpjLkfOMKkDOfHo6AvpL7Jbwi83Z2ZTHjJWB-A", + "PS512", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "basic PS256 invalid: foo => bar", + "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9W", + "PS256", + map[string]interface{}{"foo": "bar"}, + false, + }, +} + +func TestRSAPSSVerify(t *testing.T) { + var err error + + key, _ := ioutil.ReadFile("test/sample_key.pub") + var rsaPSSKey *rsa.PublicKey + if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil { + t.Errorf("Unable to parse RSA public key: %v", err) + } + + for _, data := range rsaPSSTestData { + parts := strings.Split(data.tokenString, ".") + + method := jwt.GetSigningMethod(data.alg) + err := method.Verify(strings.Join(parts[0:2], "."), parts[2], rsaPSSKey) + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying key: %v", data.name, err) + } + if !data.valid && err == nil { + t.Errorf("[%v] Invalid key passed validation", data.name) + } + } +} + +func TestRSAPSSSign(t *testing.T) { + var err error + + key, _ := ioutil.ReadFile("test/sample_key") + var rsaPSSKey *rsa.PrivateKey + if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil { + t.Errorf("Unable to parse RSA private key: %v", err) + } + + for _, data := range rsaPSSTestData { + if data.valid { + parts := strings.Split(data.tokenString, ".") + method := jwt.GetSigningMethod(data.alg) + sig, err := method.Sign(strings.Join(parts[0:2], "."), rsaPSSKey) + if err != nil { + t.Errorf("[%v] Error signing token: %v", data.name, err) + } + if sig == parts[2] { + t.Errorf("[%v] Signatures shouldn't match\nnew:\n%v\noriginal:\n%v", data.name, sig, parts[2]) + } + } + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_test.go new file mode 100644 index 0000000000..7f67c5db15 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_test.go @@ -0,0 +1,185 @@ +package jwt_test + +import ( + "github.com/dgrijalva/jwt-go" + "io/ioutil" + "strings" + "testing" +) + +var rsaTestData = []struct { + name string + tokenString string + alg string + claims map[string]interface{} + valid bool +}{ + { + "Basic RS256", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + "RS256", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "Basic RS384", + "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw", + "RS384", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "Basic RS512", + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ", + "RS512", + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "basic invalid: foo => bar", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + "RS256", + map[string]interface{}{"foo": "bar"}, + false, + }, +} + +func TestRSAVerify(t *testing.T) { + keyData, _ := ioutil.ReadFile("test/sample_key.pub") + key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData) + + for _, data := range rsaTestData { + parts := strings.Split(data.tokenString, ".") + + method := jwt.GetSigningMethod(data.alg) + err := method.Verify(strings.Join(parts[0:2], "."), parts[2], key) + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying key: %v", data.name, err) + } + if !data.valid && err == nil { + t.Errorf("[%v] Invalid key passed validation", data.name) + } + } +} + +func TestRSASign(t *testing.T) { + keyData, _ := ioutil.ReadFile("test/sample_key") + key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData) + + for _, data := range rsaTestData { + if data.valid { + parts := strings.Split(data.tokenString, ".") + method := jwt.GetSigningMethod(data.alg) + sig, err := method.Sign(strings.Join(parts[0:2], "."), key) + if err != nil { + t.Errorf("[%v] Error signing token: %v", data.name, err) + } + if sig != parts[2] { + t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2]) + } + } + } +} + +func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) { + key, _ := ioutil.ReadFile("test/sample_key.pub") + parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key) + if err != nil { + t.Fatal(err) + } + testData := rsaTestData[0] + parts := strings.Split(testData.tokenString, ".") + err = jwt.SigningMethodRS256.Verify(strings.Join(parts[0:2], "."), parts[2], parsedKey) + if err != nil { + t.Errorf("[%v] Error while verifying key: %v", testData.name, err) + } +} + +func TestRSAWithPreParsedPrivateKey(t *testing.T) { + key, _ := ioutil.ReadFile("test/sample_key") + parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) + if err != nil { + t.Fatal(err) + } + testData := rsaTestData[0] + parts := strings.Split(testData.tokenString, ".") + sig, err := jwt.SigningMethodRS256.Sign(strings.Join(parts[0:2], "."), parsedKey) + if err != nil { + t.Errorf("[%v] Error signing token: %v", testData.name, err) + } + if sig != parts[2] { + t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", testData.name, sig, parts[2]) + } +} + +func TestRSAKeyParsing(t *testing.T) { + key, _ := ioutil.ReadFile("test/sample_key") + secureKey, _ := ioutil.ReadFile("test/privateSecure.pem") + pubKey, _ := ioutil.ReadFile("test/sample_key.pub") + badKey := []byte("All your base are belong to key") + + // Test parsePrivateKey + if _, e := jwt.ParseRSAPrivateKeyFromPEM(key); e != nil { + t.Errorf("Failed to parse valid private key: %v", e) + } + + if k, e := jwt.ParseRSAPrivateKeyFromPEM(pubKey); e == nil { + t.Errorf("Parsed public key as valid private key: %v", k) + } + + if k, e := jwt.ParseRSAPrivateKeyFromPEM(badKey); e == nil { + t.Errorf("Parsed invalid key as valid private key: %v", k) + } + + if _, e := jwt.ParseRSAPrivateKeyFromPEMWithPassword(secureKey, "password"); e != nil { + t.Errorf("Failed to parse valid private key with password: %v", e) + } + + if k, e := jwt.ParseRSAPrivateKeyFromPEMWithPassword(secureKey, "123132"); e == nil { + t.Errorf("Parsed private key with invalid password %v", k) + } + + // Test parsePublicKey + if _, e := jwt.ParseRSAPublicKeyFromPEM(pubKey); e != nil { + t.Errorf("Failed to parse valid public key: %v", e) + } + + if k, e := jwt.ParseRSAPublicKeyFromPEM(key); e == nil { + t.Errorf("Parsed private key as valid public key: %v", k) + } + + if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil { + t.Errorf("Parsed invalid key as valid private key: %v", k) + } + +} + +func BenchmarkRS256Signing(b *testing.B) { + key, _ := ioutil.ReadFile("test/sample_key") + parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) + if err != nil { + b.Fatal(err) + } + + benchmarkSigning(b, jwt.SigningMethodRS256, parsedKey) +} + +func BenchmarkRS384Signing(b *testing.B) { + key, _ := ioutil.ReadFile("test/sample_key") + parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) + if err != nil { + b.Fatal(err) + } + + benchmarkSigning(b, jwt.SigningMethodRS384, parsedKey) +} + +func BenchmarkRS512Signing(b *testing.B) { + key, _ := ioutil.ReadFile("test/sample_key") + parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) + if err != nil { + b.Fatal(err) + } + + benchmarkSigning(b, jwt.SigningMethodRS512, parsedKey) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go new file mode 100644 index 0000000000..a5ababf956 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go @@ -0,0 +1,101 @@ +package jwt + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +var ( + ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") + ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key") + ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key") +) + +// Parse PEM encoded PKCS1 or PKCS8 private key +func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + var parsedKey interface{} + if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { + if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { + return nil, err + } + } + + var pkey *rsa.PrivateKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { + return nil, ErrNotRSAPrivateKey + } + + return pkey, nil +} + +// Parse PEM encoded PKCS1 or PKCS8 private key protected with password +func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + var parsedKey interface{} + + var blockDecrypted []byte + if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil { + return nil, err + } + + if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil { + if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil { + return nil, err + } + } + + var pkey *rsa.PrivateKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { + return nil, ErrNotRSAPrivateKey + } + + return pkey, nil +} + +// Parse PEM encoded PKCS1 or PKCS8 public key +func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { + if cert, err := x509.ParseCertificate(block.Bytes); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + var pkey *rsa.PublicKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { + return nil, ErrNotRSAPublicKey + } + + return pkey, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/signing_method.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/signing_method.go new file mode 100644 index 0000000000..ed1f212b21 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/signing_method.go @@ -0,0 +1,35 @@ +package jwt + +import ( + "sync" +) + +var signingMethods = map[string]func() SigningMethod{} +var signingMethodLock = new(sync.RWMutex) + +// Implement SigningMethod to add new methods for signing or verifying tokens. +type SigningMethod interface { + Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid + Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error + Alg() string // returns the alg identifier for this method (example: 'HS256') +} + +// Register the "alg" name and a factory function for signing method. +// This is typically done during init() in the method's implementation +func RegisterSigningMethod(alg string, f func() SigningMethod) { + signingMethodLock.Lock() + defer signingMethodLock.Unlock() + + signingMethods[alg] = f +} + +// Get a signing method from an "alg" string +func GetSigningMethod(alg string) (method SigningMethod) { + signingMethodLock.RLock() + defer signingMethodLock.RUnlock() + + if methodF, ok := signingMethods[alg]; ok { + method = methodF() + } + return +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec256-private.pem b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec256-private.pem new file mode 100644 index 0000000000..a6882b3e53 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec256-private.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIAh5qA3rmqQQuu0vbKV/+zouz/y/Iy2pLpIcWUSyImSwoAoGCCqGSM49 +AwEHoUQDQgAEYD54V/vp+54P9DXarYqx4MPcm+HKRIQzNasYSoRQHQ/6S6Ps8tpM +cT+KvIIC8W/e9k0W7Cm72M1P9jU7SLf/vg== +-----END EC PRIVATE KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec256-public.pem b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec256-public.pem new file mode 100644 index 0000000000..7191361e72 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec256-public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYD54V/vp+54P9DXarYqx4MPcm+HK +RIQzNasYSoRQHQ/6S6Ps8tpMcT+KvIIC8W/e9k0W7Cm72M1P9jU7SLf/vg== +-----END PUBLIC KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec384-private.pem b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec384-private.pem new file mode 100644 index 0000000000..a86c823e56 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec384-private.pem @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCaCvMHKhcG/qT7xsNLYnDT7sE/D+TtWIol1ROdaK1a564vx5pHbsRy +SEKcIxISi1igBwYFK4EEACKhZANiAATYa7rJaU7feLMqrAx6adZFNQOpaUH/Uylb +ZLriOLON5YFVwtVUpO1FfEXZUIQpptRPtc5ixIPY658yhBSb6irfIJUSP9aYTflJ +GKk/mDkK4t8mWBzhiD5B6jg9cEGhGgA= +-----END EC PRIVATE KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec384-public.pem b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec384-public.pem new file mode 100644 index 0000000000..e80d005644 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec384-public.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2Gu6yWlO33izKqwMemnWRTUDqWlB/1Mp +W2S64jizjeWBVcLVVKTtRXxF2VCEKabUT7XOYsSD2OufMoQUm+oq3yCVEj/WmE35 +SRipP5g5CuLfJlgc4Yg+Qeo4PXBBoRoA +-----END PUBLIC KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec512-private.pem b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec512-private.pem new file mode 100644 index 0000000000..213afaf13c --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec512-private.pem @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB0pE4uFaWRx7t03BsYlYvF1YvKaBGyvoakxnodm9ou0R9wC+sJAjH +QZZJikOg4SwNqgQ/hyrOuDK2oAVHhgVGcYmgBwYFK4EEACOhgYkDgYYABAAJXIuw +12MUzpHggia9POBFYXSxaOGKGbMjIyDI+6q7wi7LMw3HgbaOmgIqFG72o8JBQwYN +4IbXHf+f86CRY1AA2wHzbHvt6IhkCXTNxBEffa1yMUgu8n9cKKF2iLgyQKcKqW33 +8fGOw/n3Rm2Yd/EB56u2rnD29qS+nOM9eGS+gy39OQ== +-----END EC PRIVATE KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec512-public.pem b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec512-public.pem new file mode 100644 index 0000000000..02ea022031 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/ec512-public.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQACVyLsNdjFM6R4IImvTzgRWF0sWjh +ihmzIyMgyPuqu8IuyzMNx4G2jpoCKhRu9qPCQUMGDeCG1x3/n/OgkWNQANsB82x7 +7eiIZAl0zcQRH32tcjFILvJ/XCihdoi4MkCnCqlt9/HxjsP590ZtmHfxAeertq5w +9vakvpzjPXhkvoMt/Tk= +-----END PUBLIC KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/helpers.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/helpers.go new file mode 100644 index 0000000000..f84c3ef63e --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/helpers.go @@ -0,0 +1,42 @@ +package test + +import ( + "crypto/rsa" + "github.com/dgrijalva/jwt-go" + "io/ioutil" +) + +func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey { + keyData, e := ioutil.ReadFile(location) + if e != nil { + panic(e.Error()) + } + key, e := jwt.ParseRSAPrivateKeyFromPEM(keyData) + if e != nil { + panic(e.Error()) + } + return key +} + +func LoadRSAPublicKeyFromDisk(location string) *rsa.PublicKey { + keyData, e := ioutil.ReadFile(location) + if e != nil { + panic(e.Error()) + } + key, e := jwt.ParseRSAPublicKeyFromPEM(keyData) + if e != nil { + panic(e.Error()) + } + return key +} + +func MakeSampleToken(c jwt.Claims, key interface{}) string { + token := jwt.NewWithClaims(jwt.SigningMethodRS256, c) + s, e := token.SignedString(key) + + if e != nil { + panic(e.Error()) + } + + return s +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/hmacTestKey b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/hmacTestKey new file mode 100644 index 0000000000..435b8ddb37 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/hmacTestKey @@ -0,0 +1 @@ +#5K+¥¼ƒ~ew{¦Z³(æðTÉ(©„²ÒP.¿ÓûZ’ÒGï–Š´Ãwb="=.!r.OÀÍšõgЀ£ \ No newline at end of file diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/privateSecure.pem b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/privateSecure.pem new file mode 100644 index 0000000000..8537e07437 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/privateSecure.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,7487BB8910A3741B + +iL7m48mbFSIy1Y5xbXWwPTR07ufxu7o+myGUE+AdDeWWISkd5W6Gl44oX/jgXldS +mL/ntUXoZzQz2WKEYLwssAtSTGF+QgSIMvV5faiP+pLYvWgk0oVr42po00CvADFL +eDAJC7LgagYifS1l4EAK4MY8RGCHyJWEN5JAr0fc/Haa3WfWZ009kOWAp8MDuYxB +hQlCKUmnUpXCp5c6jwbjlyinLj8XwzzjZ/rVRsY+t2Z0Vcd5qzR5BV8IJCqbG5Py +z15/EFgMG2N2eYMsiEKgdXeKW2H5XIoWyun/3pBigWaDnTtiWSt9kz2MplqYfIT7 +F+0XE3gdDGalAeN3YwFPHCkxxBmcI+s6lQG9INmf2/gkJQ+MOZBVXKmGLv6Qis3l +0eyUz1yZvNzf0zlcUBjiPulLF3peThHMEzhSsATfPomyg5NJ0X7ttd0ybnq+sPe4 +qg2OJ8qNhYrqnx7Xlvj61+B2NAZVHvIioma1FzqX8DxQYrnR5S6DJExDqvzNxEz6 +5VPQlH2Ig4hTvNzla84WgJ6USc/2SS4ehCReiNvfeNG9sPZKQnr/Ss8KPIYsKGcC +Pz/vEqbWDmJwHb7KixCQKPt1EbD+/uf0YnhskOWM15YiFbYAOZKJ5rcbz2Zu66vg +GAmqcBsHeFR3s/bObEzjxOmMfSr1vzvr4ActNJWVtfNKZNobSehZiMSHL54AXAZW +Yj48pwTbf7b1sbF0FeCuwTFiYxM+yiZVO5ciYOfmo4HUg53PjknKpcKtEFSj02P1 +8JRBSb++V0IeMDyZLl12zgURDsvualbJMMBBR8emIpF13h0qdyah431gDhHGBnnC +J5UDGq21/flFjzz0x/Okjwf7mPK5pcmF+uW7AxtHqws6m93yD5+RFmfZ8cb/8CL8 +jmsQslj+OIE64ykkRoJWpNBKyQjL3CnPnLmAB6TQKxegR94C7/hP1FvRW+W0AgZy +g2QczKQU3KBQP18Ui1HTbkOUJT0Lsy4FnmJFCB/STPRo6NlJiATKHq/cqHWQUvZd +d4oTMb1opKfs7AI9wiJBuskpGAECdRnVduml3dT4p//3BiP6K9ImWMSJeFpjFAFs +AbBMKyitMs0Fyn9AJRPl23TKVQ3cYeSTxus4wLmx5ECSsHRV6g06nYjBp4GWEqSX +RVclXF3zmy3b1+O5s2chJN6TrypzYSEYXJb1vvQLK0lNXqwxZAFV7Roi6xSG0fSY +EAtdUifLonu43EkrLh55KEwkXdVV8xneUjh+TF8VgJKMnqDFfeHFdmN53YYh3n3F +kpYSmVLRzQmLbH9dY+7kqvnsQm8y76vjug3p4IbEbHp/fNGf+gv7KDng1HyCl9A+ +Ow/Hlr0NqCAIhminScbRsZ4SgbRTRgGEYZXvyOtQa/uL6I8t2NR4W7ynispMs0QL +RD61i3++bQXuTi4i8dg3yqIfe9S22NHSzZY/lAHAmmc3r5NrQ1TM1hsSxXawT5CU +anWFjbH6YQ/QplkkAqZMpropWn6ZdNDg/+BUjukDs0HZrbdGy846WxQUvE7G2bAw +IFQ1SymBZBtfnZXhfAXOHoWh017p6HsIkb2xmFrigMj7Jh10VVhdWg== +-----END RSA PRIVATE KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/sample_key b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/sample_key new file mode 100644 index 0000000000..abdbade312 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/sample_key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA4f5wg5l2hKsTeNem/V41fGnJm6gOdrj8ym3rFkEU/wT8RDtn +SgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0i +cqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhC +PUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR+1DcKJzQBSTAGnpYVaqpsAR +ap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKA +Rdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7wIDAQABAoIBAQCwia1k7+2oZ2d3 +n6agCAbqIE1QXfCmh41ZqJHbOY3oRQG3X1wpcGH4Gk+O+zDVTV2JszdcOt7E5dAy +MaomETAhRxB7hlIOnEN7WKm+dGNrKRvV0wDU5ReFMRHg31/Lnu8c+5BvGjZX+ky9 +POIhFFYJqwCRlopGSUIxmVj5rSgtzk3iWOQXr+ah1bjEXvlxDOWkHN6YfpV5ThdE +KdBIPGEVqa63r9n2h+qazKrtiRqJqGnOrHzOECYbRFYhexsNFz7YT02xdfSHn7gM +IvabDDP/Qp0PjE1jdouiMaFHYnLBbgvlnZW9yuVf/rpXTUq/njxIXMmvmEyyvSDn +FcFikB8pAoGBAPF77hK4m3/rdGT7X8a/gwvZ2R121aBcdPwEaUhvj/36dx596zvY +mEOjrWfZhF083/nYWE2kVquj2wjs+otCLfifEEgXcVPTnEOPO9Zg3uNSL0nNQghj +FuD3iGLTUBCtM66oTe0jLSslHe8gLGEQqyMzHOzYxNqibxcOZIe8Qt0NAoGBAO+U +I5+XWjWEgDmvyC3TrOSf/KCGjtu0TSv30ipv27bDLMrpvPmD/5lpptTFwcxvVhCs +2b+chCjlghFSWFbBULBrfci2FtliClOVMYrlNBdUSJhf3aYSG2Doe6Bgt1n2CpNn +/iu37Y3NfemZBJA7hNl4dYe+f+uzM87cdQ214+jrAoGAXA0XxX8ll2+ToOLJsaNT +OvNB9h9Uc5qK5X5w+7G7O998BN2PC/MWp8H+2fVqpXgNENpNXttkRm1hk1dych86 +EunfdPuqsX+as44oCyJGFHVBnWpm33eWQw9YqANRI+pCJzP08I5WK3osnPiwshd+ +hR54yjgfYhBFNI7B95PmEQkCgYBzFSz7h1+s34Ycr8SvxsOBWxymG5zaCsUbPsL0 +4aCgLScCHb9J+E86aVbbVFdglYa5Id7DPTL61ixhl7WZjujspeXZGSbmq0Kcnckb +mDgqkLECiOJW2NHP/j0McAkDLL4tysF8TLDO8gvuvzNC+WQ6drO2ThrypLVZQ+ry +eBIPmwKBgEZxhqa0gVvHQG/7Od69KWj4eJP28kq13RhKay8JOoN0vPmspXJo1HY3 +CKuHRG+AP579dncdUnOMvfXOtkdM4vk0+hWASBQzM9xzVcztCa+koAugjVaLS9A+ +9uQoqEeVNTckxx0S2bYevRy7hGQmUJTyQm3j1zEUR5jpdbL83Fbq +-----END RSA PRIVATE KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/sample_key.pub b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/sample_key.pub new file mode 100644 index 0000000000..03dc982acb --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/test/sample_key.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4f5wg5l2hKsTeNem/V41 +fGnJm6gOdrj8ym3rFkEU/wT8RDtnSgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7 +mCpz9Er5qLaMXJwZxzHzAahlfA0icqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBp +HssPnpYGIn20ZZuNlX2BrClciHhCPUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2 +XrHhR+1DcKJzQBSTAGnpYVaqpsARap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3b +ODIRe1AuTyHceAbewn8b462yEWKARdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy +7wIDAQAB +-----END PUBLIC KEY----- diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/token.go b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/token.go new file mode 100644 index 0000000000..d637e0867c --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/dgrijalva/jwt-go/token.go @@ -0,0 +1,108 @@ +package jwt + +import ( + "encoding/base64" + "encoding/json" + "strings" + "time" +) + +// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time). +// You can override it to use another time value. This is useful for testing or if your +// server uses a different time zone than your tokens. +var TimeFunc = time.Now + +// Parse methods use this callback function to supply +// the key for verification. The function receives the parsed, +// but unverified Token. This allows you to use properties in the +// Header of the token (such as `kid`) to identify which key to use. +type Keyfunc func(*Token) (interface{}, error) + +// A JWT Token. Different fields will be used depending on whether you're +// creating or parsing/verifying a token. +type Token struct { + Raw string // The raw token. Populated when you Parse a token + Method SigningMethod // The signing method used or to be used + Header map[string]interface{} // The first segment of the token + Claims Claims // The second segment of the token + Signature string // The third segment of the token. Populated when you Parse a token + Valid bool // Is the token valid? Populated when you Parse/Verify a token +} + +// Create a new Token. Takes a signing method +func New(method SigningMethod) *Token { + return NewWithClaims(method, MapClaims{}) +} + +func NewWithClaims(method SigningMethod, claims Claims) *Token { + return &Token{ + Header: map[string]interface{}{ + "typ": "JWT", + "alg": method.Alg(), + }, + Claims: claims, + Method: method, + } +} + +// Get the complete, signed token +func (t *Token) SignedString(key interface{}) (string, error) { + var sig, sstr string + var err error + if sstr, err = t.SigningString(); err != nil { + return "", err + } + if sig, err = t.Method.Sign(sstr, key); err != nil { + return "", err + } + return strings.Join([]string{sstr, sig}, "."), nil +} + +// Generate the signing string. This is the +// most expensive part of the whole deal. Unless you +// need this for something special, just go straight for +// the SignedString. +func (t *Token) SigningString() (string, error) { + var err error + parts := make([]string, 2) + for i, _ := range parts { + var jsonValue []byte + if i == 0 { + if jsonValue, err = json.Marshal(t.Header); err != nil { + return "", err + } + } else { + if jsonValue, err = json.Marshal(t.Claims); err != nil { + return "", err + } + } + + parts[i] = EncodeSegment(jsonValue) + } + return strings.Join(parts, "."), nil +} + +// Parse, validate, and return a token. +// keyFunc will receive the parsed token and should return the key for validating. +// If everything is kosher, err will be nil +func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { + return new(Parser).Parse(tokenString, keyFunc) +} + +func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { + return new(Parser).ParseWithClaims(tokenString, claims, keyFunc) +} + +// Encode JWT specific base64url encoding with padding stripped +func EncodeSegment(seg []byte) string { + return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") +} + +// Decode JWT specific base64url encoding with padding stripped +func DecodeSegment(seg string) ([]byte, error) { + if l := len(seg) % 4; l > 0 { + seg += strings.Repeat("=", 4-l) + } + + return base64.URLEncoding.DecodeString(seg) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/.travis.yml b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/.travis.yml new file mode 100644 index 0000000000..921806e55d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/.travis.yml @@ -0,0 +1,12 @@ +language: go +sudo: false +before_script: + - go get -t -u ./... +script: + - make generate + - go test -v ./... + - ./scripts/check-diff.sh +go: + - 1.11.x + - 1.12.x + - tip diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/Changes b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/Changes new file mode 100644 index 0000000000..eddd7e2d2f --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/Changes @@ -0,0 +1,5 @@ +Changes +======= + +v0.9.0 - 22 May 2019 + * Start tagging versions for good measure. diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/LICENSE b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/LICENSE new file mode 100644 index 0000000000..205e33a7f1 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 lestrrat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/Makefile b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/Makefile new file mode 100644 index 0000000000..b6a96ab4df --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/Makefile @@ -0,0 +1,16 @@ +.PHONY: generate realclean cover viewcover + +generate: + @$(MAKE) generate-jwa generate-jwk generate-jws generate-jwt + +generate-%: + @cd $(patsubst generate-%,%,$@); go generate + +realclean: + rm coverage.out + +cover: + go test -v -coverpkg=./... -coverprofile=coverage.out ./... + +viewcover: + go tool cover -html=coverage.out diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/README.md b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/README.md new file mode 100644 index 0000000000..f2db982586 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/README.md @@ -0,0 +1,295 @@ +# jwx + +Implementation of various JWx technologies + +[![Build Status](https://travis-ci.org/lestrrat-go/jwx.svg?branch=master)](https://travis-ci.org/lestrrat-go/jwx) +[![GoDoc](https://godoc.org/github.com/lestrrat-go/jwx?status.svg)](https://godoc.org/github.com/lestrrat-go/jwx) + +## Status + +### Done + +PR/issues welcome. + +| Package name | Notes | +|-----------------------------------------------------------|-------------------------------------------------| +| [jwt](https://github.com/lestrrat-go/jwx/tree/master/jwt) | [RFC 7519](https://tools.ietf.org/html/rfc7519) | +| [jwk](https://github.com/lestrrat-go/jwx/tree/master/jwk) | [RFC 7517](https://tools.ietf.org/html/rfc7517) + [RFC 7638](https://tools.ietf.org/html/rfc7638) | +| [jwa](https://github.com/lestrrat-go/jwx/tree/master/jwa) | [RFC 7518](https://tools.ietf.org/html/rfc7518) | +| [jws](https://github.com/lestrrat-go/jwx/tree/master/jws) | [RFC 7515](https://tools.ietf.org/html/rfc7515) | +| [jwe](https://github.com/lestrrat-go/jwx/tree/master/jwe) | [RFC 7516](https://tools.ietf.org/html/rfc7516) | + +### In progress: + +* jwe - more algorithms + +## Why? + +My goal was to write a server that heavily uses JWK and JWT. At first glance +the libraries that already exist seemed sufficient, but soon I realized that + +1. To completely implement the protocols, I needed the entire JWT, JWK, JWS, JWE (and JWA, by necessity). +2. Most of the libraries that existed only deal with a subset of the various JWx specifications that were necessary to implement their specific needs + +For example, a certain library looks like it had most of JWS, JWE, JWK covered, but then it lacked the ability to include private claims in its JWT responses. Another library had support of all the private claims, but completely lacked in its flexibility to generate various different response formats. + +Because I was writing the server side (and the client side for testing), I needed the *entire* JOSE toolset to properly implement my server, **and** they needed to be *flexible* enough to fulfill the entire spec that I was writing. + +So here's go-jwx. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it. + +As of this writing (Nov 2015), it's still lacking a few of the algorithms for JWE that are described in JWA (which I believe to be less frequently used), but in general you should be able to do pretty much everything allowed in the specifications. + +## Synopsis + +### JWT + +See the examples here as well: [https://github.com/lestrrat-go/jwx/jwt](./jwt/README.md) + +```go +func ExampleJWT() { + const aLongLongTimeAgo = 233431200 + + t := jwt.New() + t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`) + t.Set(jwt.AudienceKey, `Golang Users`) + t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0)) + t.Set(`privateClaimKey`, `Hello, World!`) + + buf, err := json.MarshalIndent(t, "", " ") + if err != nil { + fmt.Printf("failed to generate JSON: %s\n", err) + return + } + + fmt.Printf("%s\n", buf) + fmt.Printf("aud -> '%s'\n", t.Audience()) + fmt.Printf("iat -> '%s'\n", t.IssuedAt().Format(time.RFC3339)) + if v, ok := t.Get(`privateClaimKey`); ok { + fmt.Printf("privateClaimKey -> '%s'\n", v) + } + fmt.Printf("sub -> '%s'\n", t.Subject()) +} +``` + +### JWK + +See the examples here as well: https://godoc.org/github.com/lestrrat-go/jwx/jwk#pkg-examples + +Create a JWK file from RSA public key: + +```go +import( + "crypto/rand" + "crypto/rsa" + "encoding/json" + "log" + "os" + + "github.com/lestrrat-go/jwx/jwk" +) + +func main() { + privkey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Printf("failed to generate private key: %s", err) + return + } + + key, err := jwk.New(&privkey.PublicKey) + if err != nil { + log.Printf("failed to create JWK: %s", err) + return + } + + jsonbuf, err := json.MarshalIndent(key, "", " ") + if err != nil { + log.Printf("failed to generate JSON: %s", err) + return + } + + os.Stdout.Write(jsonbuf) +} +``` + +Parse and use a JWK key: + +```go +import( + "log" + + "github.com/lestrrat-go/jwx/jwk" +) + +func main() { + set, err := jwk.Fetch("https://foobar.domain/jwk.json") + if err != nil { + log.Printf("failed to parse JWK: %s", err) + return + } + + // If you KNOW you have exactly one key, you can just + // use set.Keys[0] + keys := set.LookupKeyID("mykey") + if len(keys) == 0 { + log.Printf("failed to lookup key: %s", err) + return + } + + key, err := keys[0].Materialize() + if err != nil { + log.Printf("failed to create public key: %s", err) + return + } + + // Use key for jws.Verify() or whatever +} +``` + +### JWS + +See also `VerifyWithJWK` and `VerifyWithJKU` + +```go +import( + "crypto/rand" + "crypto/rsa" + "log" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jws" +) + +func main() { + privkey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Printf("failed to generate private key: %s", err) + return + } + + buf, err := jws.Sign([]byte("Lorem ipsum"), jwa.RS256, privkey) + if err != nil { + log.Printf("failed to created JWS message: %s", err) + return + } + + // When you received a JWS message, you can verify the signature + // and grab the payload sent in the message in one go: + verified, err := jws.Verify(buf, jwa.RS256, &privkey.PublicKey) + if err != nil { + log.Printf("failed to verify message: %s", err) + return + } + + log.Printf("signed message verified! -> %s", verified) +} +``` + +Supported signature algorithms: + +| Algorithm | Supported? | Constant in go-jwx | +|:----------------------------------------|:-----------|:-------------------| +| HMAC using SHA-256 | YES | jwa.HS256 | +| HMAC using SHA-384 | YES | jwa.HS384 | +| HMAC using SHA-512 | YES | jwa.HS512 | +| RSASSA-PKCS-v1.5 using SHA-256 | YES | jwa.RS256 | +| RSASSA-PKCS-v1.5 using SHA-384 | YES | jwa.RS384 | +| RSASSA-PKCS-v1.5 using SHA-512 | YES | jwa.RS512 | +| ECDSA using P-256 and SHA-256 | YES | jwa.ES256 | +| ECDSA using P-384 and SHA-384 | YES | jwa.ES384 | +| ECDSA using P-521 and SHA-512 | YES | jwa.ES512 | +| RSASSA-PSS using SHA256 and MGF1-SHA256 | YES | jwa.PS256 | +| RSASSA-PSS using SHA384 and MGF1-SHA384 | YES | jwa.PS384 | +| RSASSA-PSS using SHA512 and MGF1-SHA512 | YES | jwa.PS512 | + +### JWE + +See the examples here as well: https://godoc.org/github.com/lestrrat-go/jwx/jwe#pkg-examples + +```go +import( + "crypto/rand" + "crypto/rsa" + "log" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwe" +) + +func main() { + privkey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Printf("failed to generate private key: %s", err) + return + } + + payload := []byte("Lorem Ipsum") + + encrypted, err := jwe.Encrypt(payload, jwa.RSA1_5, &privkey.PublicKey, jwa.A128CBC_HS256, jwa.NoCompress) + if err != nil { + log.Printf("failed to encrypt payload: %s", err) + return + } + + decrypted, err := jwe.Decrypt(encrypted, jwa.RSA1_5, privkey) + if err != nil { + log.Printf("failed to decrypt: %s", err) + return + } + + if string(decrypted) != "Lorem Ipsum" { + log.Printf("WHAT?!") + return + } +} +``` + +Supported key encryption algorithm: + +| Algorithm | Supported? | Constant in go-jwx | +|:-----------------------------------------|:-----------|:-----------------------| +| RSA-PKCS1v1.5 | YES | jwa.RSA1_5 | +| RSA-OAEP-SHA1 | YES | jwa.RSA_OAEP | +| RSA-OAEP-SHA256 | YES | jwa.RSA_OAEP_256 | +| AES key wrap (128) | YES | jwa.A128KW | +| AES key wrap (192) | YES | jwa.A192KW | +| AES key wrap (256) | YES | jwa.A256KW | +| Direct encryption | NO | jwa.DIRECT | +| ECDH-ES | YES | jwa.ECDH_ES | +| ECDH-ES + AES key wrap (128) | YES | jwa.ECDH_ES_A128KW | +| ECDH-ES + AES key wrap (192) | YES | jwa.ECDH_ES_A192KW | +| ECDH-ES + AES key wrap (256) | YES | jwa.ECDH_ES_A256KW | +| AES-GCM key wrap (128) | NO | jwa.A128GCMKW | +| AES-GCM key wrap (192) | NO | jwa.A192GCMKW | +| AES-GCM key wrap (256) | NO | jwa.A256GCMKW | +| PBES2 + HMAC-SHA256 + AES key wrap (128) | NO | jwa.PBES2_HS256_A128KW | +| PBES2 + HMAC-SHA384 + AES key wrap (192) | NO | jwa.PBES2_HS384_A192KW | +| PBES2 + HMAC-SHA512 + AES key wrap (256) | NO | jwa.PBES2_HS512_A256KW | + +Supported content encryption algorithm: + +| Algorithm | Supported? | Constant in go-jwx | +|:----------------------------|:-----------|:-----------------------| +| AES-CBC + HMAC-SHA256 (128) | YES | jwa.A128CBC_HS256 | +| AES-CBC + HMAC-SHA384 (192) | YES | jwa.A192CBC_HS384 | +| AES-CBC + HMAC-SHA512 (256) | YES | jwa.A256CBC_HS512 | +| AES-GCM (128) | YES | jwa.A128GCM | +| AES-GCM (192) | YES | jwa.A192GCM | +| AES-GCM (256) | YES | jwa.A256GCM | + +PRs welcome to support missing algorithms! + +## Other related libraries: + +* https://github.com/dgrijalva/jwt-go +* https://github.com/square/go-jose +* https://github.com/coreos/oidc +* https://golang.org/x/oauth2 + +## Contributions + +PRs welcome! + +## Credits + +* Work on this library was generously sponsored by HDE Inc (https://www.hde.co.jp) +* Lots of code, especially JWE was taken from go-jose library (https://github.com/square/go-jose) diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/buffer/buffer.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/buffer/buffer.go new file mode 100644 index 0000000000..fbef9a4f59 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/buffer/buffer.go @@ -0,0 +1,118 @@ +// Package buffer provides a very thin wrapper around []byte buffer called +// `Buffer`, to provide functionalities that are often used within the jwx +// related packages +package buffer + +import ( + "encoding/base64" + "encoding/binary" + "encoding/json" + + "github.com/pkg/errors" +) + +// Buffer wraps `[]byte` and provides functions that are often used in +// the jwx related packages. One notable difference is that while +// encoding/json marshalls `[]byte` using base64.StdEncoding, this +// module uses base64.RawURLEncoding as mandated by the spec +type Buffer []byte + +// FromUint creates a `Buffer` from an unsigned int +func FromUint(v uint64) Buffer { + data := make([]byte, 8) + binary.BigEndian.PutUint64(data, v) + + i := 0 + for ; i < len(data); i++ { + if data[i] != 0x0 { + break + } + } + return Buffer(data[i:]) +} + +// FromBase64 constructs a new Buffer from a base64 encoded data +func FromBase64(v []byte) (Buffer, error) { + b := Buffer{} + if err := b.Base64Decode(v); err != nil { + return Buffer(nil), errors.Wrap(err, "failed to decode from base64") + } + + return b, nil +} + +// FromNData constructs a new Buffer from a "n:data" format +// (I made that name up) +func FromNData(v []byte) (Buffer, error) { + size := binary.BigEndian.Uint32(v) + buf := make([]byte, int(size)) + copy(buf, v[4:4+size]) + return Buffer(buf), nil +} + +// Bytes returns the raw bytes that comprises the Buffer +func (b Buffer) Bytes() []byte { + return []byte(b) +} + +// NData returns Datalen || Data, where Datalen is a 32 bit counter for +// the length of the following data, and Data is the octets that comprise +// the buffer data +func (b Buffer) NData() []byte { + buf := make([]byte, 4+b.Len()) + binary.BigEndian.PutUint32(buf, uint32(b.Len())) + + copy(buf[4:], b.Bytes()) + return buf +} + +// Len returns the number of bytes that the Buffer holds +func (b Buffer) Len() int { + return len(b) +} + +func (b *Buffer) SetBytes(b2 []byte) { + *b = make([]byte, len(b2)) + copy(*b, b2) +} + +// Base64Encode encodes the contents of the Buffer using base64.RawURLEncoding +func (b Buffer) Base64Encode() ([]byte, error) { + enc := base64.RawURLEncoding + out := make([]byte, enc.EncodedLen(len(b))) + enc.Encode(out, b) + return out, nil +} + +// Base64Decode decodes the contents of the Buffer using base64.RawURLEncoding +func (b *Buffer) Base64Decode(v []byte) error { + enc := base64.RawURLEncoding + out := make([]byte, enc.DecodedLen(len(v))) + n, err := enc.Decode(out, v) + if err != nil { + return errors.Wrap(err, "failed to decode from base64") + } + out = out[:n] + *b = Buffer(out) + return nil +} + +// MarshalJSON marshals the buffer into JSON format after encoding the buffer +// with base64.RawURLEncoding +func (b Buffer) MarshalJSON() ([]byte, error) { + v, err := b.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to encode to base64") + } + return json.Marshal(string(v)) +} + +// UnmarshalJSON unmarshals from a JSON string into a Buffer, after decoding it +// with base64.RawURLEncoding +func (b *Buffer) UnmarshalJSON(data []byte) error { + var x string + if err := json.Unmarshal(data, &x); err != nil { + return errors.Wrap(err, "failed to unmarshal JSON") + } + return b.Base64Decode([]byte(x)) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/buffer/buffer_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/buffer/buffer_test.go new file mode 100644 index 0000000000..4889c0a104 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/buffer/buffer_test.go @@ -0,0 +1,93 @@ +package buffer + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuffer_FromUint(t *testing.T) { + b := FromUint(1) + if !assert.Equal(t, []byte{1}, b.Bytes(), "should be left trimmed") { + return + } +} + +func TestBuffer_Convert(t *testing.T) { + v1 := []byte{'a', 'b', 'c'} + b := Buffer(v1) + + if !assert.Equal(t, v1, b.Bytes()) { + return + } + + v2 := "abc" + b = Buffer(v2) + if !assert.Equal(t, []byte(v2), b.Bytes()) { + return + } + +} + +func TestBuffer_Base64Encode(t *testing.T) { + b := Buffer{'a', 'b', 'c'} + v, err := b.Base64Encode() + if !assert.NoError(t, err, "Base64 encode is successful") { + return + } + if !assert.Equal(t, []byte{'Y', 'W', 'J', 'j'}, v) { + return + } +} + +func TestJSON(t *testing.T) { + b1 := Buffer{'a', 'b', 'c'} + + jsontxt, err := json.Marshal(b1) + if !assert.NoError(t, err) { + return + } + + if !assert.Equal(t, `"YWJj"`, string(jsontxt)) { + return + } + + var b2 Buffer + if !assert.NoError(t, json.Unmarshal(jsontxt, &b2)) { + return + } + + if !assert.Equal(t, b1, b2) { + return + } +} + +func TestFunky(t *testing.T) { + s := `QD4_B3ghg0PNu-c_EAlXn3Xlb0gzAFPJSYQSI1cZZ8sPIxISgPMtNJTzgncC281IaKDXLV1aEnYuH5eH-4u4f383zlyBCGKSKSQWmqKNE7xcIqleFVNsfzOucTL4QRxfbcyHcli_symC_RGWJ6GdocE0VgyYN8t9_0sm_Nq5lcwtYEQs_hNlf1ileCjjdsUfC05zTbbrLpMjgI3IK5_QxOU81FLei4LMx3iQ1kqrIGH5FxxQMKGdx_fDaRQ-YBAA2YVqn7rs3TcwQ7NUjjz8JyDE168NlMV1WxoDC9nwOe0O6K4NzFuWpoGHTh0M-0lT5M3dy9kEBYgPtWoe_u9dogA` + b := Buffer{} + if !assert.NoError(t, b.Base64Decode([]byte(s)), "Base64Decode should work") { + return + } + + if !assert.Equal(t, 257, b.Len(), "Should 257 bytes") { + return + } +} + +func TestBuffer_NData(t *testing.T) { + payload := []byte("Alice") + nd := Buffer(payload).NData() + if !assert.Equal(t, []byte{0, 0, 0, 5, 65, 108, 105, 99, 101}, nd, "NData mathces") { + return + } + + b1, err := FromNData(nd) + if !assert.NoError(t, err, "FromNData succeeds") { + return + } + + if !assert.Equal(t, payload, b1.Bytes(), "payload matches") { + return + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/cmd/jwx/jwx.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/cmd/jwx/jwx.go new file mode 100644 index 0000000000..8c9d2d8fc1 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/cmd/jwx/jwx.go @@ -0,0 +1,152 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "log" + "os" + + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jws" + "github.com/pkg/errors" +) + +func main() { + os.Exit(_main()) +} + +type JWKConfig struct { + JWKLocation string + Payload string +} + +type JWEConfig struct { + Algorithm string +} + +func _main() int { + var f func() int + + if len(os.Args) < 2 { + f = doHelp + } else { + switch os.Args[1] { + case "jwk": + f = doJWK + case "jwe": + f = doJWE + default: + f = doHelp + } + + os.Args = os.Args[1:] + } + return f() +} + +func doHelp() int { + fmt.Println(`jwx [command] [args]`) + return 0 +} + +func doJWE() int { + c := JWEConfig{} + flag.StringVar(&c.Algorithm, "alg", "", "Key encryption algorithm") + flag.Parse() + + return 0 +} + +func doJWK() int { + c := JWKConfig{} + flag.StringVar(&c.JWKLocation, "jwk", "", "JWK location, either a local file or a URL") + flag.Parse() + + if c.JWKLocation == "" { + fmt.Printf("-jwk must be specified\n") + return 1 + } + + key, err := jwk.Fetch(c.JWKLocation) + if err != nil { + log.Printf("%s", err) + return 0 + } + + keybuf, err := json.MarshalIndent(key, "", " ") + if err != nil { + log.Printf("%s", err) + return 0 + } + log.Printf("=== JWK ===") + for _, l := range bytes.Split(keybuf, []byte{'\n'}) { + log.Printf("%s", l) + } + + // TODO make it flexible + pubkey, err := (key.Keys[0]).(*jwk.RSAPublicKey).Materialize() + if err != nil { + log.Printf("%s", err) + return 0 + } + + var src io.Reader + if c.Payload == "" { + src = os.Stdin + } else { + f, err := os.Open(c.Payload) + if err != nil { + log.Printf("%s", errors.Wrap(err, "failed to open file "+c.Payload)) + return 1 + } + src = f + defer f.Close() + } + + var buf bytes.Buffer + src = io.TeeReader(src, &buf) + + message, err := jws.Parse(src) + if err != nil { + log.Printf("%s", err) + return 0 + } + + log.Printf("=== Payload ===") + // See if this is JSON. if it is, display it nicely + m := map[string]interface{}{} + if err := json.Unmarshal(message.Payload(), &m); err == nil { + payloadbuf, err := json.MarshalIndent(m, "", " ") + if err != nil { + log.Printf("%s", errors.Wrap(err, "failed to marshal payload")) + return 0 + } + for _, l := range bytes.Split(payloadbuf, []byte{'\n'}) { + log.Printf("%s", l) + } + } else { + log.Printf("%s", message.Payload()) + } + + for i, sig := range message.Signatures() { + log.Printf("=== Signature %d ===", i) + sigbuf, err := json.MarshalIndent(sig, "", " ") + if err != nil { + log.Printf("%s", errors.Wrap(err, "failed to marshal signature as JSON")) + return 0 + } + for _, l := range bytes.Split(sigbuf, []byte{'\n'}) { + log.Printf("%s", l) + } + + alg := sig.ProtectedHeaders().Algorithm() + if _, err := jws.Verify(buf.Bytes(), alg, pubkey); err == nil { + log.Printf("=== Verified with signature %d! ===", i) + } + } + + return 1 +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/base64/base64.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/base64/base64.go new file mode 100644 index 0000000000..59aef5329e --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/base64/base64.go @@ -0,0 +1,28 @@ +package base64 + +import ( + "encoding/base64" + "encoding/binary" +) + +func EncodeToString(src []byte) string { + return base64.RawURLEncoding.EncodeToString(src) +} + +func EncodeUint64ToString(v uint64) string { + data := make([]byte, 8) + binary.BigEndian.PutUint64(data, v) + + i := 0 + for ; i < len(data); i++ { + if data[i] != 0x0 { + break + } + } + + return EncodeToString(data[i:]) +} + +func DecodeString(src string) ([]byte, error) { + return base64.RawURLEncoding.DecodeString(src) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/concatkdf/concatkdf.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/concatkdf/concatkdf.go new file mode 100644 index 0000000000..5a89b55f35 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/concatkdf/concatkdf.go @@ -0,0 +1,55 @@ +package concatkdf + +import ( + "crypto" + "encoding/binary" + "hash" + + "github.com/lestrrat-go/jwx/buffer" +) + +type KDF struct { + buf []byte + hash hash.Hash + otherinfo []byte + round uint32 + z []byte +} + +func New(hash crypto.Hash, alg, Z, apu, apv, pubinfo, privinfo []byte) *KDF { + algbuf := buffer.Buffer(alg).NData() + apubuf := buffer.Buffer(apu).NData() + apvbuf := buffer.Buffer(apv).NData() + + concat := make([]byte, len(algbuf)+len(apubuf)+len(apvbuf)+len(pubinfo)+len(privinfo)) + n := copy(concat, algbuf) + n += copy(concat[n:], apubuf) + n += copy(concat[n:], apvbuf) + n += copy(concat[n:], pubinfo) + n += copy(concat[n:], privinfo) + + return &KDF{ + hash: hash.New(), + otherinfo: concat, + round: 1, + z: Z, + } +} + +func (k *KDF) Read(buf []byte) (int, error) { + h := k.hash + for len(buf) > len(k.buf) { + h.Reset() + + binary.Write(h, binary.BigEndian, k.round) + h.Write(k.z) + h.Write(k.otherinfo) + + k.buf = append(k.buf, h.Sum(nil)...) + k.round++ + } + + n := copy(buf, k.buf[:len(buf)]) + k.buf = k.buf[len(buf):] + return n, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/concatkdf/concatkdf_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/concatkdf/concatkdf_test.go new file mode 100644 index 0000000000..f5c1bc76d6 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/concatkdf/concatkdf_test.go @@ -0,0 +1,42 @@ +package concatkdf + +import ( + "crypto" + "testing" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/stretchr/testify/assert" +) + +// https://tools.ietf.org/html/rfc7518#appendix-C +func TestAppendix(t *testing.T) { + z := []byte{158, 86, 217, 29, 129, 113, 53, 211, 114, 131, 66, 131, 191, 132, + 38, 156, 251, 49, 110, 163, 218, 128, 106, 72, 246, 218, 167, 121, + 140, 254, 144, 196} + alg := []byte(jwa.A128GCM.String()) + apu := []byte{65, 108, 105, 99, 101} + apv := []byte{66, 111, 98} + pub := []byte{0, 0, 0, 128} + priv := []byte(nil) + expected := []byte{86, 170, 141, 234, 248, 35, 109, 32, 92, 34, 40, 205, 113, 167, 16, 26} + + kdf := New(crypto.SHA256, alg, z, apu, apv, pub, priv) + + out := make([]byte, 16) // 128bits + + n, err := kdf.Read(out[:5]) + if !assert.Equal(t, 5, n, "first read bytes matches") || + !assert.NoError(t, err, "first read successful") { + return + } + + n, err = kdf.Read(out[5:]) + if !assert.Equal(t, 11, n, "second read bytes matches") || + !assert.NoError(t, err, "second read successful") { + return + } + + if !assert.Equal(t, expected, out, "generated value matches") { + return + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/debug/debug_off.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/debug/debug_off.go new file mode 100644 index 0000000000..2c0b7f6f2a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/debug/debug_off.go @@ -0,0 +1,8 @@ +//+build !debug + +package debug + +const Enabled = false + +// Printf is no op unless you compile with the `debug` tag +func Printf(f string, args ...interface{}) {} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/debug/debug_on.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/debug/debug_on.go new file mode 100644 index 0000000000..72257f5254 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/debug/debug_on.go @@ -0,0 +1,16 @@ +//+build debug + +package debug + +import ( + "log" + "os" +) + +const Enabled = true + +var logger = log.New(os.Stdout, "|DEBUG| ", 0) + +func Printf(f string, args ...interface{}) { + logger.Printf(f, args...) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/ecdsautil/ecdsautil.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/ecdsautil/ecdsautil.go new file mode 100644 index 0000000000..7b97bcd2ac --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/ecdsautil/ecdsautil.go @@ -0,0 +1,94 @@ +package ecdsautil + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "encoding/json" + "github.com/lestrrat-go/jwx/buffer" + "github.com/pkg/errors" + "math/big" +) + +type curve struct { + elliptic.Curve +} + +type rawkey struct { + Curve curve `json:"crv"` + D buffer.Buffer `json:"d"` + X buffer.Buffer `json:"x"` + Y buffer.Buffer `json:"y"` +} + +func (c *curve) UnmarshalJSON(data []byte) error { + var name string + if err := json.Unmarshal(data, &name); err != nil { + return errors.Wrap(err, `failed to unmarshal ecdsa curve`) + } + + switch name { + case "P-256": + *c = curve{elliptic.P256()} + case "P-384": + *c = curve{elliptic.P384()} + case "P-521": + *c = curve{elliptic.P521()} + default: + return errors.New("Unsupported curve") + } + return nil +} + +func NewRawKeyFromPublicKey(pubkey *ecdsa.PublicKey) *rawkey { + r := &rawkey{} + r.Curve = curve{pubkey.Curve} + r.X = buffer.Buffer(pubkey.X.Bytes()) + r.Y = buffer.Buffer(pubkey.Y.Bytes()) + return r +} + +func NewRawKeyFromPrivateKey(privkey *ecdsa.PrivateKey) *rawkey { + r := NewRawKeyFromPublicKey(&privkey.PublicKey) + r.D = buffer.Buffer(privkey.D.Bytes()) + return r +} + +func PublicKeyFromJSON(data []byte) (*ecdsa.PublicKey, error) { + r := rawkey{} + if err := json.Unmarshal(data, &r); err != nil { + return nil, errors.Wrap(err, `failed to unmarshal ecdsa public key`) + } + + return r.GeneratePublicKey() +} + +func PrivateKeyFromJSON(data []byte) (*ecdsa.PrivateKey, error) { + r := rawkey{} + if err := json.Unmarshal(data, &r); err != nil { + return nil, errors.Wrap(err, `failed to unmarshal ecdsa private key`) + } + + return r.GeneratePrivateKey() +} + +func (r rawkey) GeneratePublicKey() (*ecdsa.PublicKey, error) { + return &ecdsa.PublicKey{ + Curve: r.Curve.Curve, + X: (&big.Int{}).SetBytes(r.X.Bytes()), + Y: (&big.Int{}).SetBytes(r.Y.Bytes()), + }, nil +} + +func (r rawkey) GeneratePrivateKey() (*ecdsa.PrivateKey, error) { + pubkey, err := r.GeneratePublicKey() + if err != nil { + return nil, errors.Wrap(err, `failed to generate public key`) + } + + privkey := &ecdsa.PrivateKey{ + PublicKey: *pubkey, + D: (&big.Int{}).SetBytes(r.D.Bytes()), + } + + return privkey, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/ecdsautil/ecdsautil_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/ecdsautil/ecdsautil_test.go new file mode 100644 index 0000000000..1deebbcc51 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/ecdsautil/ecdsautil_test.go @@ -0,0 +1,38 @@ +package ecdsautil + +import ( + "bytes" + "crypto/x509" + "encoding/json" + "encoding/pem" + "testing" +) + +func TestKeyConversions(t *testing.T) { + + const ECPrivateKey = `-----BEGIN PRIVATE KEY----- +MHcCAQEEIModxWofWpbtNo6KlPEUzX6M5BoqVhcriHM/jtQYcDMDoAoGCCqGSM49 +AwEHoUQDQgAE2mFHYH1k9QGmpzTirHWgGtRRKmFh8deqKNZVUuxEH4sIQrj2zOkP +7YzeEixA9G+d7ZEPp221fqA5i0u+PchowA== +-----END PRIVATE KEY-----` + + expectedECRawKeyBytes := []byte{123, 34, 99, 114, 118, 34, 58, 123, 34, 67, 117, 114, 118, 101, 34, 58, 123, 34, 80, 34, 58, 49, 49, 53, 55, 57, 50, 48, 56, 57, 50, 49, 48, 51, 53, 54, 50, 52, 56, 55, 54, 50, 54, 57, 55, 52, 52, 54, 57, 52, 57, 52, 48, 55, 53, 55, 51, 53, 51, 48, 48, 56, 54, 49, 52, 51, 52, 49, 53, 50, 57, 48, 51, 49, 52, 49, 57, 53, 53, 51, 51, 54, 51, 49, 51, 48, 56, 56, 54, 55, 48, 57, 55, 56, 53, 51, 57, 53, 49, 44, 34, 78, 34, 58, 49, 49, 53, 55, 57, 50, 48, 56, 57, 50, 49, 48, 51, 53, 54, 50, 52, 56, 55, 54, 50, 54, 57, 55, 52, 52, 54, 57, 52, 57, 52, 48, 55, 53, 55, 51, 53, 50, 57, 57, 57, 54, 57, 53, 53, 50, 50, 52, 49, 51, 53, 55, 54, 48, 51, 52, 50, 52, 50, 50, 50, 53, 57, 48, 54, 49, 48, 54, 56, 53, 49, 50, 48, 52, 52, 51, 54, 57, 44, 34, 66, 34, 58, 52, 49, 48, 53, 56, 51, 54, 51, 55, 50, 53, 49, 53, 50, 49, 52, 50, 49, 50, 57, 51, 50, 54, 49, 50, 57, 55, 56, 48, 48, 52, 55, 50, 54, 56, 52, 48, 57, 49, 49, 52, 52, 52, 49, 48, 49, 53, 57, 57, 51, 55, 50, 53, 53, 53, 52, 56, 51, 53, 50, 53, 54, 51, 49, 52, 48, 51, 57, 52, 54, 55, 52, 48, 49, 50, 57, 49, 44, 34, 71, 120, 34, 58, 52, 56, 52, 51, 57, 53, 54, 49, 50, 57, 51, 57, 48, 54, 52, 53, 49, 55, 53, 57, 48, 53, 50, 53, 56, 53, 50, 53, 50, 55, 57, 55, 57, 49, 52, 50, 48, 50, 55, 54, 50, 57, 52, 57, 53, 50, 54, 48, 52, 49, 55, 52, 55, 57, 57, 53, 56, 52, 52, 48, 56, 48, 55, 49, 55, 48, 56, 50, 52, 48, 52, 54, 51, 53, 50, 56, 54, 44, 34, 71, 121, 34, 58, 51, 54, 49, 51, 52, 50, 53, 48, 57, 53, 54, 55, 52, 57, 55, 57, 53, 55, 57, 56, 53, 56, 53, 49, 50, 55, 57, 49, 57, 53, 56, 55, 56, 56, 49, 57, 53, 54, 54, 49, 49, 49, 48, 54, 54, 55, 50, 57, 56, 53, 48, 49, 53, 48, 55, 49, 56, 55, 55, 49, 57, 56, 50, 53, 51, 53, 54, 56, 52, 49, 52, 52, 48, 53, 49, 48, 57, 44, 34, 66, 105, 116, 83, 105, 122, 101, 34, 58, 50, 53, 54, 44, 34, 78, 97, 109, 101, 34, 58, 34, 80, 45, 50, 53, 54, 34, 125, 125, 44, 34, 100, 34, 58, 34, 121, 104, 51, 70, 97, 104, 57, 97, 108, 117, 48, 50, 106, 111, 113, 85, 56, 82, 84, 78, 102, 111, 122, 107, 71, 105, 112, 87, 70, 121, 117, 73, 99, 122, 45, 79, 49, 66, 104, 119, 77, 119, 77, 34, 44, 34, 120, 34, 58, 34, 50, 109, 70, 72, 89, 72, 49, 107, 57, 81, 71, 109, 112, 122, 84, 105, 114, 72, 87, 103, 71, 116, 82, 82, 75, 109, 70, 104, 56, 100, 101, 113, 75, 78, 90, 86, 85, 117, 120, 69, 72, 52, 115, 34, 44, 34, 121, 34, 58, 34, 67, 69, 75, 52, 57, 115, 122, 112, 68, 45, 50, 77, 51, 104, 73, 115, 81, 80, 82, 118, 110, 101, 50, 82, 68, 54, 100, 116, 116, 88, 54, 103, 79, 89, 116, 76, 118, 106, 51, 73, 97, 77, 65, 34, 125} + + t.Run("RawKeyFromPrivateKey", func(t *testing.T) { + + block, _ := pem.Decode([]byte(ECPrivateKey)) + privateKey, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + t.Fatal("Failed to parse EC Private Key") + } + rawKey := NewRawKeyFromPrivateKey(privateKey) + rawKeyBytes, err := json.Marshal(rawKey) + if err != nil { + t.Fatal("Failed to json marshal EC Raw Key") + } + if bytes.Compare(expectedECRawKeyBytes, rawKeyBytes) != 0 { + t.Fatal("Keys do not match") + } + }) + +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/option/option.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/option/option.go new file mode 100644 index 0000000000..9259dc51b4 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/option/option.go @@ -0,0 +1,25 @@ +package option + +type Interface interface { + Name() string + Value() interface{} +} + +type Option struct { + name string + value interface{} +} + +func New(name string, value interface{}) *Option { + return &Option{ + name: name, + value: value, + } +} + +func (o *Option) Name() string { + return o.name +} +func (o *Option) Value() interface{} { + return o.value +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/padbuf/padbuf.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/padbuf/padbuf.go new file mode 100644 index 0000000000..f35598e8a4 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/padbuf/padbuf.go @@ -0,0 +1,61 @@ +// Package padbuf implements a simple buffer that knows how to pad/unpad +// itself so that the buffer size aligns with an arbitrary block size. +package padbuf + +import "errors" + +type PadBuffer []byte + +func (pb PadBuffer) Len() int { + return len(pb) +} + +func (pb PadBuffer) Pad(n int) PadBuffer { + rem := n - pb.Len()%n + if rem == 0 { + return pb + } + + newpb := pb.Resize(pb.Len() + rem) + + pad := make([]byte, rem) + for i := 0; i < rem; i++ { + pad[i] = byte(rem) + } + copy(newpb[pb.Len():], pad) + + return newpb +} + +func (pb PadBuffer) Resize(newlen int) PadBuffer { + if pb.Len() == newlen { + return pb + } + + buf := make([]byte, newlen) + copy(buf, pb) + return PadBuffer(buf) +} + +func (pb PadBuffer) Unpad(n int) (PadBuffer, error) { + rem := pb.Len() % n + if rem != 0 { + return pb, errors.New("buffer should be multiple block size") + } + + last := pb[pb.Len()-1] + + count := 0 + for i := pb.Len() - 1; i >= 0; i-- { + if pb[i] != last { + break + } + count++ + } + + if count != int(last) { + return pb, errors.New("invalid padding") + } + + return PadBuffer(pb[:pb.Len()-int(last)]), nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/padbuf/padbuf_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/padbuf/padbuf_test.go new file mode 100644 index 0000000000..f6049c173a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/padbuf/padbuf_test.go @@ -0,0 +1,29 @@ +package padbuf + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPadBuffer(t *testing.T) { + for i := 0; i < 256; i++ { + buf := make([]byte, i) + pb := PadBuffer(buf) + + pb = pb.Pad(16) + + if !assert.Equal(t, pb.Len()%16, 0, "pb should be multiple of 16") { + return + } + + pb, err := pb.Unpad(16) + if !assert.NoError(t, err, "Unpad return successfully") { + return + } + + if !assert.Len(t, pb, i, "Unpad should result in len = %d", i) { + return + } + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/rsautil/rsautil.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/rsautil/rsautil.go new file mode 100644 index 0000000000..5a99341330 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/rsautil/rsautil.go @@ -0,0 +1,98 @@ +package rsautil + +import ( + "crypto/rsa" + "encoding/json" + "math/big" + + "github.com/lestrrat-go/jwx/buffer" + "github.com/pkg/errors" +) + +type rawkey struct { + N buffer.Buffer `json:"n"` + E buffer.Buffer `json:"e"` + D buffer.Buffer `json:"d"` + P buffer.Buffer `json:"p"` + Q buffer.Buffer `json:"q"` + Dp buffer.Buffer `json:"dp"` + Dq buffer.Buffer `json:"dq"` + Qi buffer.Buffer `json:"qi"` + CRTValues []rsa.CRTValue `json:"crtvalues"` +} + +func NewRawKeyFromPublicKey(pubkey *rsa.PublicKey) *rawkey { + r := &rawkey{} + r.N = buffer.Buffer(pubkey.N.Bytes()) + r.E = buffer.FromUint(uint64(pubkey.E)) + return r +} + +func NewRawKeyFromPrivateKey(privkey *rsa.PrivateKey) *rawkey { + r := NewRawKeyFromPublicKey(&privkey.PublicKey) + r.D = buffer.Buffer(privkey.D.Bytes()) + r.P = buffer.Buffer(privkey.Primes[0].Bytes()) + r.Q = buffer.Buffer(privkey.Primes[1].Bytes()) + r.Dp = buffer.Buffer(privkey.Precomputed.Dp.Bytes()) + r.Dq = buffer.Buffer(privkey.Precomputed.Dq.Bytes()) + r.Qi = buffer.Buffer(privkey.Precomputed.Qinv.Bytes()) + r.CRTValues = make([]rsa.CRTValue, len(privkey.Precomputed.CRTValues)) + copy(r.CRTValues, privkey.Precomputed.CRTValues) + return r +} + +func PublicKeyFromJSON(data []byte) (*rsa.PublicKey, error) { + r := rawkey{} + if err := json.Unmarshal(data, &r); err != nil { + return nil, errors.Wrap(err, `failed to unmarshal public key`) + } + + return r.GeneratePublicKey() +} + +func PrivateKeyFromJSON(data []byte) (*rsa.PrivateKey, error) { + r := rawkey{} + if err := json.Unmarshal(data, &r); err != nil { + return nil, errors.Wrap(err, `failed to unmarshal private key`) + } + + return r.GeneratePrivateKey() +} + +func (r rawkey) GeneratePublicKey() (*rsa.PublicKey, error) { + return &rsa.PublicKey{ + N: (&big.Int{}).SetBytes(r.N.Bytes()), + E: int((&big.Int{}).SetBytes(r.E.Bytes()).Int64()), + }, nil +} + +func (r rawkey) GeneratePrivateKey() (*rsa.PrivateKey, error) { + pubkey, err := r.GeneratePublicKey() + if err != nil { + return nil, errors.Wrap(err, `failed to generate public key`) + } + + privkey := &rsa.PrivateKey{ + PublicKey: *pubkey, + D: (&big.Int{}).SetBytes(r.D.Bytes()), + Primes: []*big.Int{ + (&big.Int{}).SetBytes(r.P.Bytes()), + (&big.Int{}).SetBytes(r.Q.Bytes()), + }, + } + + if r.Dp.Len() > 0 { + privkey.Precomputed.Dp = (&big.Int{}).SetBytes(r.Dp.Bytes()) + } + if r.Dq.Len() > 0 { + privkey.Precomputed.Dq = (&big.Int{}).SetBytes(r.Dq.Bytes()) + } + if r.Qi.Len() > 0 { + privkey.Precomputed.Qinv = (&big.Int{}).SetBytes(r.Qi.Bytes()) + } + + privkey.Precomputed.CRTValues = make([]rsa.CRTValue, len(r.CRTValues)) + copy(privkey.Precomputed.CRTValues, r.CRTValues) + + return privkey, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/rsautil/rsautil_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/rsautil/rsautil_test.go new file mode 100644 index 0000000000..17b429bc0d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/internal/rsautil/rsautil_test.go @@ -0,0 +1,59 @@ +package rsautil + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "reflect" + "testing" +) + +func TestRSAUtil(t *testing.T) { + + t.Run("RoundTripNewRawKeyFromPrivateKey", func(t *testing.T) { + + privateKey, err := rsa.GenerateKey(rand.Reader, 512) + if err != nil { + t.Fatalf("Error generating private key: %s", err.Error()) + } + rawKey := NewRawKeyFromPrivateKey(privateKey) + keyBytes, err := json.Marshal(rawKey) + realizedPrivateKey, err := PrivateKeyFromJSON(keyBytes) + if !reflect.DeepEqual(realizedPrivateKey, privateKey) { + t.Fatalf("Mismatched private keys") + } + }) + + t.Run("PublicKeyFromJSON", func(t *testing.T) { + const jwkPublicKey = `{ + "e":"AQAB", + "kty":"RSA", + "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" + }` + + const expectedPEM = `-----BEGIN PUBLIC KEY----- +MIIBCgKCAQEA0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4 +cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc/BJECPebWKRXjBZCiFV4n3oknjhMst +n64tZ/2W+5JsGY4Hc5n9yBXArwl93lqt7/RN5w6Cf0h4QyQ5v+65YGjQR0/FDW2Q +vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS +D08qNLyrdkt+bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ+G/xBniIqbw +0Ls1jF44+csFCur+kEgU8awapJzKnqDKgwIDAQAB +-----END PUBLIC KEY----- +` + + publicKey := []byte(jwkPublicKey) + rsaPublicKey, err := PublicKeyFromJSON(publicKey) + if err != nil { + t.Fatalf("Failed to construct RSA public key from JSON: %s", err.Error()) + } + publicKeyBytes := x509.MarshalPKCS1PublicKey(rsaPublicKey) + pemBlock := &pem.Block{Type: "PUBLIC KEY", Bytes: publicKeyBytes} + realizedPublicKeyPem := pem.EncodeToMemory(pemBlock) + if !reflect.DeepEqual(realizedPublicKeyPem, []byte(expectedPEM)) { + t.Fatal("Mismatched public keys") + } + }) + +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/compression.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/compression.go new file mode 100644 index 0000000000..8cd23f1a4d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/compression.go @@ -0,0 +1,43 @@ +// this file was auto-generated by internal/cmd/gentypes/main.go: DO NOT EDIT + +package jwa + +import ( + "github.com/pkg/errors" +) + +// CompressionAlgorithm represents the compression algorithms as described in https://tools.ietf.org/html/rfc7518#section-7.3 +type CompressionAlgorithm string + +// Supported values for CompressionAlgorithm +const ( + Deflate CompressionAlgorithm = "DEF" // DEFLATE (RFC 1951) + NoCompress CompressionAlgorithm = "" // No compression +) + +// Accept is used when conversion from values given by +// outside sources (such as JSON payloads) is required +func (v *CompressionAlgorithm) Accept(value interface{}) error { + var tmp CompressionAlgorithm + switch x := value.(type) { + case string: + tmp = CompressionAlgorithm(x) + case CompressionAlgorithm: + tmp = x + default: + return errors.Errorf(`invalid type for jwa.CompressionAlgorithm: %T`, value) + } + switch tmp { + case Deflate, NoCompress: + default: + return errors.Errorf(`invalid jwa.CompressionAlgorithm value`) + } + + *v = tmp + return nil +} + +// String returns the string representation of a CompressionAlgorithm +func (v CompressionAlgorithm) String() string { + return string(v) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/content_encryption.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/content_encryption.go new file mode 100644 index 0000000000..9c18086771 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/content_encryption.go @@ -0,0 +1,47 @@ +// this file was auto-generated by internal/cmd/gentypes/main.go: DO NOT EDIT + +package jwa + +import ( + "github.com/pkg/errors" +) + +// ContentEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-5 +type ContentEncryptionAlgorithm string + +// Supported values for ContentEncryptionAlgorithm +const ( + A128CBC_HS256 ContentEncryptionAlgorithm = "A128CBC-HS256" // AES-CBC + HMAC-SHA256 (128) + A128GCM ContentEncryptionAlgorithm = "A128GCM" // AES-GCM (128) + A192CBC_HS384 ContentEncryptionAlgorithm = "A192CBC-HS384" // AES-CBC + HMAC-SHA384 (192) + A192GCM ContentEncryptionAlgorithm = "A192GCM" // AES-GCM (192) + A256CBC_HS512 ContentEncryptionAlgorithm = "A256CBC-HS512" // AES-CBC + HMAC-SHA512 (256) + A256GCM ContentEncryptionAlgorithm = "A256GCM" // AES-GCM (256) +) + +// Accept is used when conversion from values given by +// outside sources (such as JSON payloads) is required +func (v *ContentEncryptionAlgorithm) Accept(value interface{}) error { + var tmp ContentEncryptionAlgorithm + switch x := value.(type) { + case string: + tmp = ContentEncryptionAlgorithm(x) + case ContentEncryptionAlgorithm: + tmp = x + default: + return errors.Errorf(`invalid type for jwa.ContentEncryptionAlgorithm: %T`, value) + } + switch tmp { + case A128CBC_HS256, A128GCM, A192CBC_HS384, A192GCM, A256CBC_HS512, A256GCM: + default: + return errors.Errorf(`invalid jwa.ContentEncryptionAlgorithm value`) + } + + *v = tmp + return nil +} + +// String returns the string representation of a ContentEncryptionAlgorithm +func (v ContentEncryptionAlgorithm) String() string { + return string(v) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/elliptic.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/elliptic.go new file mode 100644 index 0000000000..e9d8779107 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/elliptic.go @@ -0,0 +1,44 @@ +// this file was auto-generated by internal/cmd/gentypes/main.go: DO NOT EDIT + +package jwa + +import ( + "github.com/pkg/errors" +) + +// EllipticCurveAlgorithm represents the algorithms used for EC keys +type EllipticCurveAlgorithm string + +// Supported values for EllipticCurveAlgorithm +const ( + P256 EllipticCurveAlgorithm = "P-256" + P384 EllipticCurveAlgorithm = "P-384" + P521 EllipticCurveAlgorithm = "P-521" +) + +// Accept is used when conversion from values given by +// outside sources (such as JSON payloads) is required +func (v *EllipticCurveAlgorithm) Accept(value interface{}) error { + var tmp EllipticCurveAlgorithm + switch x := value.(type) { + case string: + tmp = EllipticCurveAlgorithm(x) + case EllipticCurveAlgorithm: + tmp = x + default: + return errors.Errorf(`invalid type for jwa.EllipticCurveAlgorithm: %T`, value) + } + switch tmp { + case P256, P384, P521: + default: + return errors.Errorf(`invalid jwa.EllipticCurveAlgorithm value`) + } + + *v = tmp + return nil +} + +// String returns the string representation of a EllipticCurveAlgorithm +func (v EllipticCurveAlgorithm) String() string { + return string(v) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/internal/cmd/gentypes/main.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/internal/cmd/gentypes/main.go new file mode 100644 index 0000000000..aaf2a401dc --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/internal/cmd/gentypes/main.go @@ -0,0 +1,399 @@ +package main + +import ( + "bytes" + "fmt" + "go/format" + "log" + "os" + "sort" + "strconv" + + "github.com/pkg/errors" +) + +func main() { + if err := _main(); err != nil { + log.Printf("%s", err) + os.Exit(1) + } +} + +func _main() error { + typs := []typ{ + { + name: `CompressionAlgorithm`, + comment: `CompressionAlgorithm represents the compression algorithms as described in https://tools.ietf.org/html/rfc7518#section-7.3`, + filename: `compression.go`, + elements: []element{ + { + name: `NoCompress`, + value: ``, + comment: `No compression`, + }, + { + name: `Deflate`, + value: `DEF`, + comment: `DEFLATE (RFC 1951)`, + }, + }, + }, + { + name: `ContentEncryptionAlgorithm`, + comment: `ContentEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-5`, + filename: `content_encryption.go`, + elements: []element{ + { + name: `A128CBC_HS256`, + value: `A128CBC-HS256`, + comment: `AES-CBC + HMAC-SHA256 (128)`, + }, + { + name: `A192CBC_HS384`, + value: `A192CBC-HS384`, + comment: `AES-CBC + HMAC-SHA384 (192)`, + }, + { + name: `A256CBC_HS512`, + value: `A256CBC-HS512`, + comment: `AES-CBC + HMAC-SHA512 (256)`, + }, + { + name: `A128GCM`, + value: `A128GCM`, + comment: `AES-GCM (128)`, + }, + { + name: `A192GCM`, + value: `A192GCM`, + comment: `AES-GCM (192)`, + }, + { + name: `A256GCM`, + value: `A256GCM`, + comment: `AES-GCM (256)`, + }, + }, + }, + { + name: `KeyType`, + comment: `KeyType represents the key type ("kty") that are supported`, + filename: "key_type.go", + elements: []element{ + { + name: `InvalidKeyType`, + value: ``, + comment: `Invalid KeyType`, + invalid: true, + }, + { + name: `EC`, + value: `EC`, + comment: `Elliptic Curve`, + }, + { + name: `RSA`, + value: `RSA`, + comment: `RSA`, + }, + { + name: `OctetSeq`, + value: `oct`, + comment: `Octet sequence (used to represent symmetric keys)`, + }, + }, + }, + { + name: `EllipticCurveAlgorithm`, + comment: ` EllipticCurveAlgorithm represents the algorithms used for EC keys`, + filename: `elliptic.go`, + elements: []element{ + { + name: `P256`, + value: `P-256`, + }, + { + name: `P384`, + value: `P-384`, + }, + { + name: `P521`, + value: `P-521`, + }, + }, + }, + { + name: `SignatureAlgorithm`, + comment: `SignatureAlgorithm represents the various signature algorithms as described in https://tools.ietf.org/html/rfc7518#section-3.1`, + filename: `signature.go`, + elements: []element{ + { + name: `NoSignature`, + value: "none", + }, + { + name: `HS256`, + value: "HS256", + comment: `HMAC using SHA-256`, + }, + { + name: `HS384`, + value: `HS384`, + comment: `HMAC using SHA-384`, + }, + { + name: `HS512`, + value: "HS512", + comment: `HMAC using SHA-512`, + }, + { + name: `RS256`, + value: `RS256`, + comment: `RSASSA-PKCS-v1.5 using SHA-256`, + }, + { + name: `RS384`, + value: `RS384`, + comment: `RSASSA-PKCS-v1.5 using SHA-384`, + }, + { + name: `RS512`, + value: `RS512`, + comment: `RSASSA-PKCS-v1.5 using SHA-512`, + }, + { + name: `ES256`, + value: `ES256`, + comment: `ECDSA using P-256 and SHA-256`, + }, + { + name: `ES384`, + value: `ES384`, + comment: `ECDSA using P-384 and SHA-384`, + }, + { + name: `ES512`, + value: "ES512", + comment: `ECDSA using P-521 and SHA-512`, + }, + { + name: `PS256`, + value: `PS256`, + comment: `RSASSA-PSS using SHA256 and MGF1-SHA256`, + }, + { + name: `PS384`, + value: `PS384`, + comment: `RSASSA-PSS using SHA384 and MGF1-SHA384`, + }, + { + name: `PS512`, + value: `PS512`, + comment: `RSASSA-PSS using SHA512 and MGF1-SHA512`, + }, + }, + }, + { + name: `KeyEncryptionAlgorithm`, + comment: `KeyEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-4.1`, + filename: `key_encryption.go`, + elements: []element{ + { + name: `RSA1_5`, + value: "RSA1_5", + comment: `RSA-PKCS1v1.5`, + }, + { + name: `RSA_OAEP`, + value: "RSA-OAEP", + comment: `RSA-OAEP-SHA1`, + }, + { + name: `RSA_OAEP_256`, + value: "RSA-OAEP-256", + comment: `RSA-OAEP-SHA256`, + }, + { + name: `A128KW`, + value: "A128KW", + comment: `AES key wrap (128)`, + }, + { + name: `A192KW`, + value: "A192KW", + comment: `AES key wrap (192)`, + }, + { + name: `A256KW`, + value: "A256KW", + comment: `AES key wrap (256)`, + }, + { + name: `DIRECT`, + value: "dir", + comment: `Direct encryption`, + }, + { + name: `ECDH_ES`, + value: "ECDH-ES", + comment: `ECDH-ES`, + }, + { + name: `ECDH_ES_A128KW`, + value: "ECDH-ES+A128KW", + comment: `ECDH-ES + AES key wrap (128)`, + }, + { + name: `ECDH_ES_A192KW`, + value: "ECDH-ES+A192KW", + comment: `ECDH-ES + AES key wrap (192)`, + }, + { + name: `ECDH_ES_A256KW`, + value: "ECDH-ES+A256KW", + comment: `ECDH-ES + AES key wrap (256)`, + }, + { + name: `A128GCMKW`, + value: "A128GCMKW", + comment: `AES-GCM key wrap (128)`, + }, + { + name: `A192GCMKW`, + value: "A192GCMKW", + comment: `AES-GCM key wrap (192)`, + }, + { + name: `A256GCMKW`, + value: "A256GCMKW", + comment: `AES-GCM key wrap (256)`, + }, + { + name: `PBES2_HS256_A128KW`, + value: "PBES2-HS256+A128KW", + comment: `PBES2 + HMAC-SHA256 + AES key wrap (128)`, + }, + { + name: `PBES2_HS384_A192KW`, + value: "PBES2-HS384+A192KW", + comment: `PBES2 + HMAC-SHA384 + AES key wrap (192)`, + }, + { + name: `PBES2_HS512_A256KW`, + value: "PBES2-HS512+A256KW", + comment: `PBES2 + HMAC-SHA512 + AES key wrap (256)`, + }, + }, + }, + } + + sort.Slice(typs, func(i, j int) bool { + return typs[i].name < typs[j].name + }) + + for _, t := range typs { + sort.Slice(t.elements, func(i, j int) bool { + return t.elements[i].name < t.elements[j].name + }) + if err := t.Generate(); err != nil { + return errors.Wrap(err, `failed to generate file`) + } + } + return nil +} + +type typ struct { + name string + comment string + filename string + elements []element +} + +type element struct { + name string + value string + comment string + invalid bool +} + +func (t typ) Generate() error { + var buf bytes.Buffer + + fmt.Fprintf(&buf, "// this file was auto-generated by internal/cmd/gentypes/main.go: DO NOT EDIT") + fmt.Fprintf(&buf, "\n\npackage jwa") + fmt.Fprintf(&buf, "\n\nimport (") + for _, pkg := range []string{"github.com/pkg/errors"} { + fmt.Fprintf(&buf, "\n%s", strconv.Quote(pkg)) + } + fmt.Fprintf(&buf, "\n)") + fmt.Fprintf(&buf, "\n\n// %s", t.comment) + fmt.Fprintf(&buf, "\ntype %s string", t.name) + + fmt.Fprintf(&buf, "\n\n// Supported values for %s", t.name) + fmt.Fprintf(&buf, "\nconst (") + for _, e := range t.elements { + fmt.Fprintf(&buf, "\n%s %s = %s", e.name, t.name, strconv.Quote(e.value)) + if len(e.comment) > 0 { + fmt.Fprintf(&buf, " // %s", e.comment) + } + } + fmt.Fprintf(&buf, "\n)") // end const + + fmt.Fprintf(&buf, "\n\n// Accept is used when conversion from values given by") + fmt.Fprintf(&buf, "\n// outside sources (such as JSON payloads) is required") + fmt.Fprintf(&buf, "\nfunc (v *%s) Accept(value interface{}) error {", t.name) + fmt.Fprintf(&buf, "\nvar tmp %s", t.name) + fmt.Fprintf(&buf, "\nswitch x := value.(type) {") + fmt.Fprintf(&buf, "\ncase string:") + fmt.Fprintf(&buf, "\ntmp = %s(x)", t.name) + fmt.Fprintf(&buf, "\ncase %s:", t.name) + fmt.Fprintf(&buf, "\ntmp = x") + fmt.Fprintf(&buf, "\ndefault:") + fmt.Fprintf(&buf, "\nreturn errors.Errorf(`invalid type for jwa.%s: %%T`, value)", t.name) + fmt.Fprintf(&buf, "\n}") + + fmt.Fprintf(&buf, "\nswitch tmp {") + fmt.Fprintf(&buf, "\ncase ") + var valids []element + for _, e := range t.elements { + if e.invalid { + continue + } + valids = append(valids, e) + } + + for i, e := range valids { + fmt.Fprintf(&buf, "%s", e.name) + if i < len(valids)-1 { + fmt.Fprintf(&buf, ", ") + } + } + fmt.Fprintf(&buf, ":") + fmt.Fprintf(&buf, "\ndefault:") + fmt.Fprintf(&buf, "\nreturn errors.Errorf(`invalid jwa.%s value`)", t.name) + fmt.Fprintf(&buf, "\n}") + + fmt.Fprintf(&buf, "\n\n*v = tmp") + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") // func (v *%s) Accept(v interface{}) + + fmt.Fprintf(&buf, "\n\n// String returns the string representation of a %s", t.name) + fmt.Fprintf(&buf, "\nfunc (v %s) String() string {", t.name) + fmt.Fprintf(&buf, "\nreturn string(v)") + fmt.Fprintf(&buf, "\n}") + + formatted, err := format.Source(buf.Bytes()) + if err != nil { + os.Stdout.Write(buf.Bytes()) + return errors.Wrap(err, `failed to format source`) + } + + f, err := os.Create(t.filename) + if err != nil { + return errors.Wrapf(err, `failed to create %s`, t.filename) + } + defer f.Close() + f.Write(formatted) + + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/jwa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/jwa.go new file mode 100644 index 0000000000..5e2b351a7a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/jwa.go @@ -0,0 +1,17 @@ +//go:generate go run internal/cmd/gentypes/main.go + +// Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518 +package jwa + +// Size returns the size of the EllipticCurveAlgorithm +func (crv EllipticCurveAlgorithm) Size() int { + switch crv { + case P256: + return 32 + case P384: + return 48 + case P521: + return 66 + } + return 0 +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/key_encryption.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/key_encryption.go new file mode 100644 index 0000000000..dbb6eeddc5 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/key_encryption.go @@ -0,0 +1,58 @@ +// this file was auto-generated by internal/cmd/gentypes/main.go: DO NOT EDIT + +package jwa + +import ( + "github.com/pkg/errors" +) + +// KeyEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-4.1 +type KeyEncryptionAlgorithm string + +// Supported values for KeyEncryptionAlgorithm +const ( + A128GCMKW KeyEncryptionAlgorithm = "A128GCMKW" // AES-GCM key wrap (128) + A128KW KeyEncryptionAlgorithm = "A128KW" // AES key wrap (128) + A192GCMKW KeyEncryptionAlgorithm = "A192GCMKW" // AES-GCM key wrap (192) + A192KW KeyEncryptionAlgorithm = "A192KW" // AES key wrap (192) + A256GCMKW KeyEncryptionAlgorithm = "A256GCMKW" // AES-GCM key wrap (256) + A256KW KeyEncryptionAlgorithm = "A256KW" // AES key wrap (256) + DIRECT KeyEncryptionAlgorithm = "dir" // Direct encryption + ECDH_ES KeyEncryptionAlgorithm = "ECDH-ES" // ECDH-ES + ECDH_ES_A128KW KeyEncryptionAlgorithm = "ECDH-ES+A128KW" // ECDH-ES + AES key wrap (128) + ECDH_ES_A192KW KeyEncryptionAlgorithm = "ECDH-ES+A192KW" // ECDH-ES + AES key wrap (192) + ECDH_ES_A256KW KeyEncryptionAlgorithm = "ECDH-ES+A256KW" // ECDH-ES + AES key wrap (256) + PBES2_HS256_A128KW KeyEncryptionAlgorithm = "PBES2-HS256+A128KW" // PBES2 + HMAC-SHA256 + AES key wrap (128) + PBES2_HS384_A192KW KeyEncryptionAlgorithm = "PBES2-HS384+A192KW" // PBES2 + HMAC-SHA384 + AES key wrap (192) + PBES2_HS512_A256KW KeyEncryptionAlgorithm = "PBES2-HS512+A256KW" // PBES2 + HMAC-SHA512 + AES key wrap (256) + RSA1_5 KeyEncryptionAlgorithm = "RSA1_5" // RSA-PKCS1v1.5 + RSA_OAEP KeyEncryptionAlgorithm = "RSA-OAEP" // RSA-OAEP-SHA1 + RSA_OAEP_256 KeyEncryptionAlgorithm = "RSA-OAEP-256" // RSA-OAEP-SHA256 +) + +// Accept is used when conversion from values given by +// outside sources (such as JSON payloads) is required +func (v *KeyEncryptionAlgorithm) Accept(value interface{}) error { + var tmp KeyEncryptionAlgorithm + switch x := value.(type) { + case string: + tmp = KeyEncryptionAlgorithm(x) + case KeyEncryptionAlgorithm: + tmp = x + default: + return errors.Errorf(`invalid type for jwa.KeyEncryptionAlgorithm: %T`, value) + } + switch tmp { + case A128GCMKW, A128KW, A192GCMKW, A192KW, A256GCMKW, A256KW, DIRECT, ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW, PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW, RSA1_5, RSA_OAEP, RSA_OAEP_256: + default: + return errors.Errorf(`invalid jwa.KeyEncryptionAlgorithm value`) + } + + *v = tmp + return nil +} + +// String returns the string representation of a KeyEncryptionAlgorithm +func (v KeyEncryptionAlgorithm) String() string { + return string(v) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/key_type.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/key_type.go new file mode 100644 index 0000000000..747fe09b69 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/key_type.go @@ -0,0 +1,45 @@ +// this file was auto-generated by internal/cmd/gentypes/main.go: DO NOT EDIT + +package jwa + +import ( + "github.com/pkg/errors" +) + +// KeyType represents the key type ("kty") that are supported +type KeyType string + +// Supported values for KeyType +const ( + EC KeyType = "EC" // Elliptic Curve + InvalidKeyType KeyType = "" // Invalid KeyType + OctetSeq KeyType = "oct" // Octet sequence (used to represent symmetric keys) + RSA KeyType = "RSA" // RSA +) + +// Accept is used when conversion from values given by +// outside sources (such as JSON payloads) is required +func (v *KeyType) Accept(value interface{}) error { + var tmp KeyType + switch x := value.(type) { + case string: + tmp = KeyType(x) + case KeyType: + tmp = x + default: + return errors.Errorf(`invalid type for jwa.KeyType: %T`, value) + } + switch tmp { + case EC, OctetSeq, RSA: + default: + return errors.Errorf(`invalid jwa.KeyType value`) + } + + *v = tmp + return nil +} + +// String returns the string representation of a KeyType +func (v KeyType) String() string { + return string(v) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/signature.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/signature.go new file mode 100644 index 0000000000..e661fa7971 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwa/signature.go @@ -0,0 +1,54 @@ +// this file was auto-generated by internal/cmd/gentypes/main.go: DO NOT EDIT + +package jwa + +import ( + "github.com/pkg/errors" +) + +// SignatureAlgorithm represents the various signature algorithms as described in https://tools.ietf.org/html/rfc7518#section-3.1 +type SignatureAlgorithm string + +// Supported values for SignatureAlgorithm +const ( + ES256 SignatureAlgorithm = "ES256" // ECDSA using P-256 and SHA-256 + ES384 SignatureAlgorithm = "ES384" // ECDSA using P-384 and SHA-384 + ES512 SignatureAlgorithm = "ES512" // ECDSA using P-521 and SHA-512 + HS256 SignatureAlgorithm = "HS256" // HMAC using SHA-256 + HS384 SignatureAlgorithm = "HS384" // HMAC using SHA-384 + HS512 SignatureAlgorithm = "HS512" // HMAC using SHA-512 + NoSignature SignatureAlgorithm = "none" + PS256 SignatureAlgorithm = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256 + PS384 SignatureAlgorithm = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384 + PS512 SignatureAlgorithm = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512 + RS256 SignatureAlgorithm = "RS256" // RSASSA-PKCS-v1.5 using SHA-256 + RS384 SignatureAlgorithm = "RS384" // RSASSA-PKCS-v1.5 using SHA-384 + RS512 SignatureAlgorithm = "RS512" // RSASSA-PKCS-v1.5 using SHA-512 +) + +// Accept is used when conversion from values given by +// outside sources (such as JSON payloads) is required +func (v *SignatureAlgorithm) Accept(value interface{}) error { + var tmp SignatureAlgorithm + switch x := value.(type) { + case string: + tmp = SignatureAlgorithm(x) + case SignatureAlgorithm: + tmp = x + default: + return errors.Errorf(`invalid type for jwa.SignatureAlgorithm: %T`, value) + } + switch tmp { + case ES256, ES384, ES512, HS256, HS384, HS512, NoSignature, PS256, PS384, PS512, RS256, RS384, RS512: + default: + return errors.Errorf(`invalid jwa.SignatureAlgorithm value`) + } + + *v = tmp + return nil +} + +// String returns the string representation of a SignatureAlgorithm +func (v SignatureAlgorithm) String() string { + return string(v) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/aescbc/aescbc.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/aescbc/aescbc.go new file mode 100644 index 0000000000..3401367efa --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/aescbc/aescbc.go @@ -0,0 +1,183 @@ +package aescbc + +import ( + "crypto/cipher" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "crypto/subtle" + "encoding/binary" + "fmt" + "hash" + + "github.com/lestrrat-go/jwx/internal/debug" + "github.com/lestrrat-go/jwx/internal/padbuf" + "github.com/pkg/errors" +) + +const ( + NonceSize = 16 +) + +type AesCbcHmac struct { + blockCipher cipher.Block + hash func() hash.Hash + keysize int + tagsize int + integrityKey []byte +} + +type BlockCipherFunc func([]byte) (cipher.Block, error) + +func New(key []byte, f BlockCipherFunc) (*AesCbcHmac, error) { + keysize := len(key) / 2 + ikey := key[:keysize] + ekey := key[keysize:] + + if debug.Enabled { + debug.Printf("New: keysize = %d", keysize) + debug.Printf("New: cek (key) = %x (%d)\n", key, len(key)) + debug.Printf("New: ikey = %x (%d)\n", ikey, len(ikey)) + debug.Printf("New: ekey = %x (%d)\n", ekey, len(ekey)) + } + + bc, err := f(ekey) + if err != nil { + return nil, errors.Wrap(err, `failed to execute block cipher function`) + } + + var hfunc func() hash.Hash + switch keysize { + case 16: + hfunc = sha256.New + case 24: + hfunc = sha512.New384 + case 32: + hfunc = sha512.New + default: + return nil, errors.Errorf("unsupported key size %d", keysize) + } + + return &AesCbcHmac{ + blockCipher: bc, + hash: hfunc, + integrityKey: ikey, + keysize: keysize, + tagsize: NonceSize, + }, nil +} + +// NonceSize fulfills the crypto.AEAD interface +func (c AesCbcHmac) NonceSize() int { + return NonceSize +} + +// Overhead fulfills the crypto.AEAD interface +func (c AesCbcHmac) Overhead() int { + return c.blockCipher.BlockSize() + c.tagsize +} + +func (c AesCbcHmac) ComputeAuthTag(aad, nonce, ciphertext []byte) []byte { + if debug.Enabled { + debug.Printf("ComputeAuthTag: aad = %x (%d)\n", aad, len(aad)) + debug.Printf("ComputeAuthTag: ciphertext = %x (%d)\n", ciphertext, len(ciphertext)) + debug.Printf("ComputeAuthTag: iv (nonce) = %x (%d)\n", nonce, len(nonce)) + debug.Printf("ComputeAuthTag: integrity = %x (%d)\n", c.integrityKey, len(c.integrityKey)) + } + + buf := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8) + n := 0 + n += copy(buf, aad) + n += copy(buf[n:], nonce) + n += copy(buf[n:], ciphertext) + binary.BigEndian.PutUint64(buf[n:], uint64(len(aad)*8)) + + h := hmac.New(c.hash, c.integrityKey) + h.Write(buf) + s := h.Sum(nil) + if debug.Enabled { + debug.Printf("ComputeAuthTag: buf = %x (%d)\n", buf, len(buf)) + debug.Printf("ComputeAuthTag: computed = %x (%d)\n", s[:c.keysize], len(s[:c.keysize])) + } + return s[:c.tagsize] +} + +func ensureSize(dst []byte, n int) []byte { + // if the dst buffer has enough length just copy the relevant parts to it. + // Otherwise create a new slice that's big enough, and operate on that + // Note: I think go-jose has a bug in that it checks for cap(), but not len(). + ret := dst + if diff := n - len(dst); diff > 0 { + // dst is not big enough + ret = make([]byte, n) + copy(ret, dst) + } + return ret +} + +// Seal fulfills the crypto.AEAD interface +func (c AesCbcHmac) Seal(dst, nonce, plaintext, data []byte) []byte { + ctlen := len(plaintext) + ciphertext := make([]byte, ctlen+c.Overhead())[:ctlen] + copy(ciphertext, plaintext) + ciphertext = padbuf.PadBuffer(ciphertext).Pad(c.blockCipher.BlockSize()) + + cbc := cipher.NewCBCEncrypter(c.blockCipher, nonce) + cbc.CryptBlocks(ciphertext, ciphertext) + + authtag := c.ComputeAuthTag(data, nonce, ciphertext) + + retlen := len(dst) + len(ciphertext) + len(authtag) + + ret := ensureSize(dst, retlen) + out := ret[len(dst):] + n := copy(out, ciphertext) + n += copy(out[n:], authtag) + + if debug.Enabled { + debug.Printf("Seal: ciphertext = %x (%d)\n", ciphertext, len(ciphertext)) + debug.Printf("Seal: authtag = %x (%d)\n", authtag, len(authtag)) + debug.Printf("Seal: ret = %x (%d)\n", ret, len(ret)) + } + return ret +} + +// Open fulfills the crypto.AEAD interface +func (c AesCbcHmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + if len(ciphertext) < c.keysize { + return nil, errors.New("invalid ciphertext (too short)") + } + + tagOffset := len(ciphertext) - c.tagsize + if tagOffset%c.blockCipher.BlockSize() != 0 { + return nil, fmt.Errorf( + "invalid ciphertext (invalid length: %d %% %d != 0)", + tagOffset, + c.blockCipher.BlockSize(), + ) + } + tag := ciphertext[tagOffset:] + ciphertext = ciphertext[:tagOffset] + + expectedTag := c.ComputeAuthTag(data, nonce, ciphertext) + if subtle.ConstantTimeCompare(expectedTag, tag) != 1 { + if debug.Enabled { + debug.Printf("provided tag = %x\n", tag) + debug.Printf("expected tag = %x\n", expectedTag) + } + return nil, errors.New("invalid ciphertext (tag mismatch)") + } + + cbc := cipher.NewCBCDecrypter(c.blockCipher, nonce) + buf := make([]byte, tagOffset) + cbc.CryptBlocks(buf, ciphertext) + + plaintext, err := padbuf.PadBuffer(buf).Unpad(c.blockCipher.BlockSize()) + if err != nil { + return nil, errors.Wrap(err, `failed to generate plaintext from decrypted blocks`) + } + ret := ensureSize(dst, len(plaintext)) + out := ret[len(dst):] + copy(out, plaintext) + return ret, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/aescbc/aescbc_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/aescbc/aescbc_test.go new file mode 100644 index 0000000000..2e6f7b157d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/aescbc/aescbc_test.go @@ -0,0 +1,60 @@ +package aescbc + +import ( + "crypto/aes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVectorsAESCBC128(t *testing.T) { + // Source: http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-29#appendix-A.2 + plaintext := []byte{ + 76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, + 112, 114, 111, 115, 112, 101, 114, 46} + + aad := []byte{ + 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, + 120, 88, 122, 85, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, + 74, 66, 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, + 50, 73, 110, 48} + + ciphertext := []byte{ + 40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, + 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, + 112, 56, 102} + + authtag := []byte{ + 246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, + 191} + + key := []byte{ + 4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, + 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207} + + nonce := []byte{ + 3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101} + + enc, err := New(key, aes.NewCipher) + out := enc.Seal(nil, nonce, plaintext, aad) + if !assert.NoError(t, err, "enc.Seal") { + return + } + + if !assert.Equal(t, ciphertext, out[:len(out)-enc.keysize], "Ciphertext tag should match") { + return + } + + if !assert.Equal(t, authtag, out[len(out)-enc.keysize:], "Auth tag should match") { + return + } + + out, err = enc.Open(nil, nonce, out, aad) + if !assert.NoError(t, err, "Open should succeed") { + return + } + + if !assert.Equal(t, plaintext, out, "Open should get us original text") { + return + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/cipher.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/cipher.go new file mode 100644 index 0000000000..7e83658db2 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/cipher.go @@ -0,0 +1,188 @@ +package jwe + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rsa" + "fmt" + + "github.com/lestrrat-go/jwx/internal/debug" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwe/aescbc" + "github.com/pkg/errors" +) + +const ( + TagSize = 16 +) + +func (f AeadFetchFunc) AeadFetch(key []byte) (cipher.AEAD, error) { + return f(key) +} + +var GcmAeadFetch = AeadFetchFunc(func(key []byte) (cipher.AEAD, error) { + aescipher, err := aes.NewCipher(key) + if err != nil { + if debug.Enabled { + debug.Printf("GcmAeadFetch: failed to create cipher") + } + return nil, errors.Wrap(err, "cipher: failed to create AES cipher for GCM") + } + + aead, err := cipher.NewGCM(aescipher) + if err != nil { + return nil, errors.Wrap(err, `failed to create GCM for cipher`) + } + return aead, nil +}) +var CbcAeadFetch = AeadFetchFunc(func(key []byte) (cipher.AEAD, error) { + if debug.Enabled { + debug.Printf("CbcAeadFetch: fetching key (%d)", len(key)) + } + aead, err := aescbc.New(key, aes.NewCipher) + if err != nil { + if debug.Enabled { + debug.Printf("CbcAeadFetch: failed to create aead fetcher %v (%d): %s", key, len(key), err) + } + return nil, errors.Wrap(err, "cipher: failed to create AES cipher for CBC") + } + return aead, nil +}) + +func (c AesContentCipher) KeySize() int { + return c.keysize +} + +func (c AesContentCipher) TagSize() int { + return c.tagsize +} + +func NewAesContentCipher(alg jwa.ContentEncryptionAlgorithm) (*AesContentCipher, error) { + var keysize int + var fetcher AeadFetcher + switch alg { + case jwa.A128GCM: + keysize = 16 + fetcher = GcmAeadFetch + case jwa.A192GCM: + keysize = 24 + fetcher = GcmAeadFetch + case jwa.A256GCM: + keysize = 32 + fetcher = GcmAeadFetch + case jwa.A128CBC_HS256: + keysize = 16 * 2 + fetcher = CbcAeadFetch + case jwa.A192CBC_HS384: + keysize = 24 * 2 + fetcher = CbcAeadFetch + case jwa.A256CBC_HS512: + keysize = 32 * 2 + fetcher = CbcAeadFetch + default: + return nil, errors.Wrap(ErrUnsupportedAlgorithm, "failed to create AES content cipher") + } + + return &AesContentCipher{ + keysize: keysize, + tagsize: TagSize, + AeadFetcher: fetcher, + }, nil +} + +func (c AesContentCipher) encrypt(cek, plaintext, aad []byte) (iv, ciphertext, tag []byte, err error) { + var aead cipher.AEAD + aead, err = c.AeadFetch(cek) + if err != nil { + if debug.Enabled { + debug.Printf("AeadFetch failed: %s", err) + } + return nil, nil, nil, errors.Wrap(err, "failed to fetch AEAD") + } + + // Seal may panic (argh!), so protect ourselves from that + defer func() { + if e := recover(); e != nil { + switch e.(type) { + case error: + err = e.(error) + case string: + err = errors.New(e.(string)) + default: + err = fmt.Errorf("%s", e) + } + err = errors.Wrap(err, "failed to descrypt") + } + }() + + var bs ByteSource + if c.NonceGenerator == nil { + bs, err = NewRandomKeyGenerate(aead.NonceSize()).KeyGenerate() + } else { + bs, err = c.NonceGenerator.KeyGenerate() + } + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to generate nonce") + } + iv = bs.Bytes() + + combined := aead.Seal(nil, iv, plaintext, aad) + tagoffset := len(combined) - c.TagSize() + if debug.Enabled { + debug.Printf("tagsize = %d", c.TagSize()) + } + tag = combined[tagoffset:] + ciphertext = make([]byte, tagoffset) + copy(ciphertext, combined[:tagoffset]) + + if debug.Enabled { + debug.Printf("encrypt: combined = %x (%d)\n", combined, len(combined)) + debug.Printf("encrypt: ciphertext = %x (%d)\n", ciphertext, len(ciphertext)) + debug.Printf("encrypt: tag = %x (%d)\n", tag, len(tag)) + debug.Printf("finally ciphertext = %x\n", ciphertext) + } + return +} + +func (c AesContentCipher) decrypt(cek, iv, ciphertxt, tag, aad []byte) (plaintext []byte, err error) { + aead, err := c.AeadFetch(cek) + if err != nil { + if debug.Enabled { + debug.Printf("AeadFetch failed for %v: %s", cek, err) + } + return nil, errors.Wrap(err, "failed to fetch AEAD data") + } + + // Open may panic (argh!), so protect ourselves from that + defer func() { + if e := recover(); e != nil { + switch e.(type) { + case error: + err = e.(error) + case string: + err = errors.New(e.(string)) + default: + err = fmt.Errorf("%s", e) + } + err = errors.Wrap(err, "failed to decrypt") + return + } + }() + + combined := make([]byte, len(ciphertxt)+len(tag)) + copy(combined, ciphertxt) + copy(combined[len(ciphertxt):], tag) + + if debug.Enabled { + debug.Printf("AesContentCipher.decrypt: combined = %x (%d)", combined, len(combined)) + } + + plaintext, err = aead.Open(nil, iv, combined, aad) + return +} + +func NewRsaContentCipher(alg jwa.ContentEncryptionAlgorithm, pubkey *rsa.PublicKey) (*RsaContentCipher, error) { + return &RsaContentCipher{ + pubkey: pubkey, + }, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/cipher_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/cipher_test.go new file mode 100644 index 0000000000..660444746e --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/cipher_test.go @@ -0,0 +1,26 @@ +package jwe + +import ( + "testing" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/stretchr/testify/assert" +) + +func TestAesContentCipher(t *testing.T) { + algs := []jwa.ContentEncryptionAlgorithm{ + jwa.A128GCM, + jwa.A192GCM, + jwa.A256GCM, + jwa.A128CBC_HS256, + jwa.A192CBC_HS384, + jwa.A256CBC_HS512, + } + for _, alg := range algs { + c, err := NewAesContentCipher(alg) + if !assert.NoError(t, err, "BuildCipher for %s succeeds", alg) { + return + } + t.Logf("keysize = %d", c.KeySize()) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/content_crypt.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/content_crypt.go new file mode 100644 index 0000000000..3d0e15675a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/content_crypt.go @@ -0,0 +1,59 @@ +package jwe + +import ( + "github.com/lestrrat-go/jwx/internal/debug" + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +func (c GenericContentCrypt) Algorithm() jwa.ContentEncryptionAlgorithm { + return c.alg +} + +func (c GenericContentCrypt) Encrypt(cek, plaintext, aad []byte) ([]byte, []byte, []byte, error) { + if debug.Enabled { + debug.Printf("ContentCrypt.Encrypt: cek = %x (%d)", cek, len(cek)) + debug.Printf("ContentCrypt.Encrypt: ciphertext = %x (%d)", plaintext, len(plaintext)) + debug.Printf("ContentCrypt.Encrypt: aad = %x (%d)", aad, len(aad)) + } + iv, encrypted, tag, err := c.cipher.encrypt(cek, plaintext, aad) + if err != nil { + if debug.Enabled { + debug.Printf("cipher.encrypt failed") + } + + return nil, nil, nil, errors.Wrap(err, `failed to crypt content`) + } + + return iv, encrypted, tag, nil +} + +func (c GenericContentCrypt) Decrypt(cek, iv, ciphertext, tag, aad []byte) ([]byte, error) { + return c.cipher.decrypt(cek, iv, ciphertext, tag, aad) +} + +func NewAesCrypt(alg jwa.ContentEncryptionAlgorithm) (*GenericContentCrypt, error) { + if debug.Enabled { + debug.Printf("AES Crypt: alg = %s", alg) + } + cipher, err := NewAesContentCipher(alg) + if err != nil { + return nil, errors.Wrap(err, `aes crypt: failed to create content cipher`) + } + + if debug.Enabled { + debug.Printf("AES Crypt: cipher.keysize = %d", cipher.KeySize()) + } + + return &GenericContentCrypt{ + alg: alg, + cipher: cipher, + cekgen: NewRandomKeyGenerate(cipher.KeySize() * 2), + keysize: cipher.KeySize() * 2, + tagsize: 16, + }, nil +} + +func (c GenericContentCrypt) KeySize() int { + return c.keysize +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/doc_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/doc_test.go new file mode 100644 index 0000000000..08266f58c2 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/doc_test.go @@ -0,0 +1,36 @@ +package jwe + +import ( + "crypto/rand" + "crypto/rsa" + "log" + + "github.com/lestrrat-go/jwx/jwa" +) + +func ExampleEncrypt() { + privkey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Printf("failed to generate private key: %s", err) + return + } + + payload := []byte("Lorem Ipsum") + + encrypted, err := Encrypt(payload, jwa.RSA1_5, &privkey.PublicKey, jwa.A128CBC_HS256, jwa.NoCompress) + if err != nil { + log.Printf("failed to encrypt payload: %s", err) + return + } + + decrypted, err := Decrypt(encrypted, jwa.RSA1_5, privkey) + if err != nil { + log.Printf("failed to decrypt: %s", err) + return + } + + if string(decrypted) != "Lorem Ipsum" { + log.Printf("WHAT?!") + return + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/encrypt.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/encrypt.go new file mode 100644 index 0000000000..b6ef1dfbe6 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/encrypt.go @@ -0,0 +1,105 @@ +package jwe + +import ( + "github.com/lestrrat-go/jwx/internal/debug" + "github.com/pkg/errors" +) + +// NewMultiEncrypt creates a new Encrypt struct. The caller is responsible +// for instantiating valid inputs for ContentEncrypter, KeyGenerator, +// and KeyEncrypters. +func NewMultiEncrypt(cc ContentEncrypter, kg KeyGenerator, ke ...KeyEncrypter) *MultiEncrypt { + e := &MultiEncrypt{ + ContentEncrypter: cc, + KeyGenerator: kg, + KeyEncrypters: ke, + } + return e +} + +// Encrypt takes the plaintext and encrypts into a JWE message. +func (e MultiEncrypt) Encrypt(plaintext []byte) (*Message, error) { + bk, err := e.KeyGenerator.KeyGenerate() + if err != nil { + if debug.Enabled { + debug.Printf("Failed to generate key: %s", err) + } + return nil, errors.Wrap(err, "failed to generate key") + } + cek := bk.Bytes() + + if debug.Enabled { + debug.Printf("Encrypt: generated cek len = %d", len(cek)) + } + + protected := NewEncodedHeader() + protected.Set("enc", e.ContentEncrypter.Algorithm()) + + // In JWE, multiple recipients may exist -- they receive an + // encrypted version of the CEK, using their key encryption + // algorithm of choice. + recipients := make([]Recipient, len(e.KeyEncrypters)) + for i, enc := range e.KeyEncrypters { + r := NewRecipient() + r.Header.Set("alg", enc.Algorithm()) + if v := enc.Kid(); v != "" { + r.Header.Set("kid", v) + } + enckey, err := enc.KeyEncrypt(cek) + if err != nil { + if debug.Enabled { + debug.Printf("Failed to encrypt key: %s", err) + } + return nil, errors.Wrap(err, `failed to encrypt key`) + } + r.EncryptedKey = enckey.Bytes() + if hp, ok := enckey.(HeaderPopulater); ok { + hp.HeaderPopulate(r.Header) + } + if debug.Enabled { + debug.Printf("Encrypt: encrypted_key = %x (%d)", enckey.Bytes(), len(enckey.Bytes())) + } + recipients[i] = *r + } + + // If there's only one recipient, you want to include that in the + // protected header + if len(recipients) == 1 { + protected.Header, err = protected.Header.Merge(recipients[0].Header) + if err != nil { + return nil, errors.Wrap(err, "failed to merge protected headers") + } + } + + aad, err := protected.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to base64 encode protected headers") + } + + // ...on the other hand, there's only one content cipher. + iv, ciphertext, tag, err := e.ContentEncrypter.Encrypt(cek, plaintext, aad) + if err != nil { + if debug.Enabled { + debug.Printf("Failed to encrypt: %s", err) + } + return nil, errors.Wrap(err, "failed to encrypt payload") + } + + if debug.Enabled { + debug.Printf("Encrypt.Encrypt: cek = %x (%d)", cek, len(cek)) + debug.Printf("Encrypt.Encrypt: aad = %x", aad) + debug.Printf("Encrypt.Encrypt: ciphertext = %x", ciphertext) + debug.Printf("Encrypt.Encrypt: iv = %x", iv) + debug.Printf("Encrypt.Encrypt: tag = %x", tag) + } + + msg := NewMessage() + msg.AuthenticatedData.Base64Decode(aad) + msg.CipherText = ciphertext + msg.InitializationVector = iv + msg.ProtectedHeader = protected + msg.Recipients = recipients + msg.Tag = tag + + return msg, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/interface.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/interface.go new file mode 100644 index 0000000000..65d478df59 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/interface.go @@ -0,0 +1,280 @@ +package jwe + +import ( + "crypto/cipher" + "crypto/ecdsa" + "crypto/rsa" + "errors" + "fmt" + "net/url" + + "github.com/lestrrat-go/jwx/buffer" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" +) + +// Errors used in JWE +var ( + ErrInvalidBlockSize = errors.New("keywrap input must be 8 byte blocks") + ErrInvalidCompactPartsCount = errors.New("compact JWE format must have five parts") + ErrInvalidHeaderValue = errors.New("invalid value for header key") + ErrUnsupportedAlgorithm = errors.New("unsupported algorithm") + ErrMissingPrivateKey = errors.New("missing private key") +) + +type errUnsupportedAlgorithm struct { + alg string + purpose string +} + +// NewErrUnsupportedAlgorithm creates a new UnsupportedAlgorithm error +func NewErrUnsupportedAlgorithm(alg, purpose string) errUnsupportedAlgorithm { + return errUnsupportedAlgorithm{alg: alg, purpose: purpose} +} + +// Error returns the string representation of the error +func (e errUnsupportedAlgorithm) Error() string { + return fmt.Sprintf("unsupported algorithm '%s' for %s", e.alg, e.purpose) +} + +// EssentialHeader is a set of headers that are already defined in RFC 7516` +type EssentialHeader struct { + AgreementPartyUInfo buffer.Buffer `json:"apu,omitempty"` + AgreementPartyVInfo buffer.Buffer `json:"apv,omitempty"` + Algorithm jwa.KeyEncryptionAlgorithm `json:"alg,omitempty"` + ContentEncryption jwa.ContentEncryptionAlgorithm `json:"enc,omitempty"` + ContentType string `json:"cty,omitempty"` + Compression jwa.CompressionAlgorithm `json:"zip,omitempty"` + Critical []string `json:"crit,omitempty"` + EphemeralPublicKey *jwk.ECDSAPublicKey `json:"epk,omitempty"` + Jwk jwk.Key `json:"jwk,omitempty"` // public key + JwkSetURL *url.URL `json:"jku,omitempty"` + KeyID string `json:"kid,omitempty"` + Type string `json:"typ,omitempty"` // e.g. "JWT" + X509Url *url.URL `json:"x5u,omitempty"` + X509CertChain []string `json:"x5c,omitempty"` + X509CertThumbprint string `json:"x5t,omitempty"` + X509CertThumbprintS256 string `json:"x5t#S256,omitempty"` +} + +// Header represents a jws header. +type Header struct { + *EssentialHeader `json:"-"` + PrivateParams map[string]interface{} `json:"-"` +} + +// EncodedHeader represents a header value that is base64 encoded +// in JSON format +type EncodedHeader struct { + *Header +} + +// ByteSource is an interface for things that return a byte sequence. +// This is used for KeyGenerator so that the result of computations can +// carry more than just the generate byte sequence. +type ByteSource interface { + Bytes() []byte +} + +// KeyEncrypter is an interface for things that can encrypt keys +type KeyEncrypter interface { + Algorithm() jwa.KeyEncryptionAlgorithm + KeyEncrypt([]byte) (ByteSource, error) + // Kid returns the key id for this KeyEncrypter. This exists so that + // you can pass in a KeyEncrypter to MultiEncrypt, you can rest assured + // that the generated key will have the proper key ID. + Kid() string +} + +// KeyDecrypter is an interface for things that can decrypt keys +type KeyDecrypter interface { + Algorithm() jwa.KeyEncryptionAlgorithm + KeyDecrypt([]byte) ([]byte, error) +} + +// Recipient holds the encrypted key and hints to decrypt the key +type Recipient struct { + Header *Header `json:"header"` + EncryptedKey buffer.Buffer `json:"encrypted_key"` +} + +// Message contains the entire encrypted JWE message +type Message struct { + AuthenticatedData buffer.Buffer `json:"aad,omitempty"` + CipherText buffer.Buffer `json:"ciphertext"` + InitializationVector buffer.Buffer `json:"iv,omitempty"` + ProtectedHeader *EncodedHeader `json:"protected"` + Recipients []Recipient `json:"recipients"` + Tag buffer.Buffer `json:"tag,omitempty"` + UnprotectedHeader *Header `json:"unprotected,omitempty"` +} + +// Encrypter is the top level structure that encrypts the given +// payload to a JWE message +type Encrypter interface { + Encrypt([]byte) (*Message, error) +} + +// ContentEncrypter encrypts the content using the content using the +// encrypted key +type ContentEncrypter interface { + Algorithm() jwa.ContentEncryptionAlgorithm + Encrypt([]byte, []byte, []byte) ([]byte, []byte, []byte, error) +} + +// MultiEncrypt is the default Encrypter implementation. +type MultiEncrypt struct { + ContentEncrypter ContentEncrypter + KeyGenerator KeyGenerator // KeyGenerator creates the random CEK. + KeyEncrypters []KeyEncrypter +} + +// KeyWrapEncrypt encrypts content encryption keys using AES-CGM key wrap. +// Contrary to what the name implies, it also decrypt encrypted keys +type KeyWrapEncrypt struct { + alg jwa.KeyEncryptionAlgorithm + sharedkey []byte + KeyID string +} + +// EcdhesKeyWrapEncrypt encrypts content encryption keys using ECDH-ES. +type EcdhesKeyWrapEncrypt struct { + algorithm jwa.KeyEncryptionAlgorithm + generator KeyGenerator + KeyID string +} + +// EcdhesKeyWrapDecrypt decrypts keys using ECDH-ES. +type EcdhesKeyWrapDecrypt struct { + algorithm jwa.KeyEncryptionAlgorithm + apu []byte + apv []byte + privkey *ecdsa.PrivateKey + pubkey *ecdsa.PublicKey +} + +// ByteKey is a generated key that only has the key's byte buffer +// as its instance data. If a ke needs to do more, such as providing +// values to be set in a JWE header, that key type wraps a ByteKey +type ByteKey []byte + +// ByteWithECPrivateKey holds the EC-DSA private key that generated +// the key along witht he key itself. This is required to set the +// proper values in the JWE headers +type ByteWithECPrivateKey struct { + ByteKey + PrivateKey *ecdsa.PrivateKey +} + +// HeaderPopulater is an interface for things that may modify the +// JWE header. e.g. ByteWithECPrivateKey +type HeaderPopulater interface { + HeaderPopulate(*Header) +} + +// KeyGenerator generates the raw content encryption keys +type KeyGenerator interface { + KeySize() int + KeyGenerate() (ByteSource, error) +} + +// ContentCipher knows how to encrypt/decrypt the content given a content +// encryption key and other data +type ContentCipher interface { + KeySize() int + encrypt(cek, aad, plaintext []byte) ([]byte, []byte, []byte, error) + decrypt(cek, iv, aad, ciphertext, tag []byte) ([]byte, error) +} + +// GenericContentCrypt encrypts a message by applying all the necessary +// modifications to the keys and the contents +type GenericContentCrypt struct { + alg jwa.ContentEncryptionAlgorithm + keysize int + tagsize int + cipher ContentCipher + cekgen KeyGenerator +} + +// StaticKeyGenerate uses a static byte buffer to provide keys. +type StaticKeyGenerate []byte + +// RandomKeyGenerate generates random keys +type RandomKeyGenerate struct { + keysize int +} + +// EcdhesKeyGenerate generates keys using ECDH-ES algorithm +type EcdhesKeyGenerate struct { + algorithm jwa.KeyEncryptionAlgorithm + keysize int + pubkey *ecdsa.PublicKey +} + +// Serializer converts an encrypted message into a byte buffer +type Serializer interface { + Serialize(*Message) ([]byte, error) +} + +// CompactSerialize serializes the message into JWE compact serialized format +type CompactSerialize struct{} + +// JSONSerialize serializes the message into JWE JSON serialized format. If you +// set `Pretty` to true, `json.MarshalIndent` is used instead of `json.Marshal` +type JSONSerialize struct { + Pretty bool +} + +// AeadFetcher is an interface for things that can fetch AEAD ciphers +type AeadFetcher interface { + AeadFetch([]byte) (cipher.AEAD, error) +} + +// AeadFetchFunc fetches a AEAD cipher from the given key, and is +// represented by a function +type AeadFetchFunc func([]byte) (cipher.AEAD, error) + +// AesContentCipher represents a cipher based on AES +type AesContentCipher struct { + AeadFetcher + NonceGenerator KeyGenerator + keysize int + tagsize int +} + +// RsaContentCipher represents a cipher based on RSA +type RsaContentCipher struct { + pubkey *rsa.PublicKey +} + +// RSAPKCS15KeyDecrypt decrypts keys using RSA PKCS1v15 algorithm +type RSAPKCS15KeyDecrypt struct { + alg jwa.KeyEncryptionAlgorithm + privkey *rsa.PrivateKey + generator KeyGenerator +} + +// RSAPKCSKeyEncrypt encrypts keys using RSA PKCS1v15 algorithm +type RSAPKCSKeyEncrypt struct { + alg jwa.KeyEncryptionAlgorithm + pubkey *rsa.PublicKey + KeyID string +} + +// RSAOAEPKeyEncrypt encrypts keys using RSA OAEP algorithm +type RSAOAEPKeyEncrypt struct { + alg jwa.KeyEncryptionAlgorithm + pubkey *rsa.PublicKey + KeyID string +} + +// RSAOAEPKeyDecrypt decrypts keys using RSA OAEP algorithm +type RSAOAEPKeyDecrypt struct { + alg jwa.KeyEncryptionAlgorithm + privkey *rsa.PrivateKey +} + +// DirectDecrypt does not encryption (Note: Unimplemented) +type DirectDecrypt struct { + Key []byte +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/jwe.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/jwe.go new file mode 100644 index 0000000000..203fabdc03 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/jwe.go @@ -0,0 +1,285 @@ +// Package jwe implements JWE as described in https://tools.ietf.org/html/rfc7516 +package jwe + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rsa" + "encoding/json" + + "github.com/lestrrat-go/jwx/buffer" + "github.com/lestrrat-go/jwx/internal/debug" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/pkg/errors" +) + +// Encrypt takes the plaintext payload and encrypts it in JWE compact format. +func Encrypt(payload []byte, keyalg jwa.KeyEncryptionAlgorithm, key interface{}, contentalg jwa.ContentEncryptionAlgorithm, compressalg jwa.CompressionAlgorithm) ([]byte, error) { + contentcrypt, err := NewAesCrypt(contentalg) + if err != nil { + return nil, errors.Wrap(err, `failed to create AES encrypter`) + } + + var keyenc KeyEncrypter + var keysize int + switch keyalg { + case jwa.RSA1_5: + pubkey, ok := key.(*rsa.PublicKey) + if !ok { + return nil, errors.New("invalid key: *rsa.PublicKey required") + } + keyenc, err = NewRSAPKCSKeyEncrypt(keyalg, pubkey) + if err != nil { + return nil, errors.Wrap(err, "failed to create RSA PKCS encrypter") + } + keysize = contentcrypt.KeySize() / 2 + case jwa.RSA_OAEP, jwa.RSA_OAEP_256: + pubkey, ok := key.(*rsa.PublicKey) + if !ok { + return nil, errors.New("invalid key: *rsa.PublicKey required") + } + keyenc, err = NewRSAOAEPKeyEncrypt(keyalg, pubkey) + if err != nil { + return nil, errors.Wrap(err, "failed to create RSA OAEP encrypter") + } + keysize = contentcrypt.KeySize() / 2 + case jwa.A128KW, jwa.A192KW, jwa.A256KW: + sharedkey, ok := key.([]byte) + if !ok { + return nil, errors.New("invalid key: []byte required") + } + keyenc, err = NewKeyWrapEncrypt(keyalg, sharedkey) + if err != nil { + return nil, errors.Wrap(err, "failed to create key wrap encrypter") + } + keysize = contentcrypt.KeySize() + switch aesKeySize := keysize / 2; aesKeySize { + case 16, 24, 32: + default: + return nil, errors.Errorf("unsupported keysize %d (from content encryption algorithm %s). consider using content encryption that uses 32, 48, or 64 byte keys", keysize, contentalg) + } + case jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: + pubkey, ok := key.(*ecdsa.PublicKey) + if !ok { + return nil, errors.New("invalid key: *ecdsa.PublicKey required") + } + keyenc, err = NewEcdhesKeyWrapEncrypt(keyalg, pubkey) + if err != nil { + return nil, errors.Wrap(err, "failed to create ECDHS key wrap encrypter") + } + keysize = contentcrypt.KeySize() / 2 + case jwa.ECDH_ES: + fallthrough + case jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW: + fallthrough + case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: + fallthrough + default: + if debug.Enabled { + debug.Printf("Encrypt: unknown key encryption algorithm: %s", keyalg) + } + return nil, errors.Wrap(ErrUnsupportedAlgorithm, "failed to create encrypter") + } + + if debug.Enabled { + debug.Printf("Encrypt: keysize = %d", keysize) + } + enc := NewMultiEncrypt(contentcrypt, NewRandomKeyGenerate(keysize), keyenc) + msg, err := enc.Encrypt(payload) + if err != nil { + if debug.Enabled { + debug.Printf("Encrypt: failed to encrypt: %s", err) + } + return nil, errors.Wrap(err, "failed to encrypt payload") + } + + return CompactSerialize{}.Serialize(msg) +} + +// Decrypt takes the key encryption algorithm and the corresponding +// key to decrypt the JWE message, and returns the decrypted payload. +// The JWE message can be either compact or full JSON format. +func Decrypt(buf []byte, alg jwa.KeyEncryptionAlgorithm, key interface{}) ([]byte, error) { + msg, err := Parse(buf) + if err != nil { + return nil, errors.Wrap(err, "failed to parse buffer for Decrypt") + } + + return msg.Decrypt(alg, key) +} + +// Parse parses the JWE message into a Message object. The JWE message +// can be either compact or full JSON format. +func Parse(buf []byte) (*Message, error) { + buf = bytes.TrimSpace(buf) + if len(buf) == 0 { + return nil, errors.New("empty buffer") + } + + if buf[0] == '{' { + return parseJSON(buf) + } + return parseCompact(buf) +} + +// ParseString is the same as Parse, but takes a string. +func ParseString(s string) (*Message, error) { + return Parse([]byte(s)) +} + +func parseJSON(buf []byte) (*Message, error) { + m := struct { + *Message + *Recipient + }{} + + if err := json.Unmarshal(buf, &m); err != nil { + return nil, errors.Wrap(err, "failed to parse JSON") + } + + // if the "signature" field exist, treat it as a flattened + if m.Recipient != nil { + if len(m.Message.Recipients) != 0 { + return nil, errors.New("invalid message: mixed flattened/full json serialization") + } + + m.Message.Recipients = []Recipient{*m.Recipient} + } + + return m.Message, nil +} + +func parseCompact(buf []byte) (*Message, error) { + if debug.Enabled { + debug.Printf("Parse(Compact): buf = '%s'", buf) + } + parts := bytes.Split(buf, []byte{'.'}) + if len(parts) != 5 { + return nil, ErrInvalidCompactPartsCount + } + + hdrbuf := buffer.Buffer{} + if err := hdrbuf.Base64Decode(parts[0]); err != nil { + return nil, errors.Wrap(err, `failed to parse first part of compact form`) + } + if debug.Enabled { + debug.Printf("hdrbuf = %s", hdrbuf) + } + + hdr := NewHeader() + if err := json.Unmarshal(hdrbuf, hdr); err != nil { + return nil, errors.Wrap(err, "failed to parse header JSON") + } + + // We need the protected header to contain the content encryption + // algorithm. XXX probably other headers need to go there too + protected := NewEncodedHeader() + protected.ContentEncryption = hdr.ContentEncryption + hdr.ContentEncryption = "" + + enckeybuf := buffer.Buffer{} + if err := enckeybuf.Base64Decode(parts[1]); err != nil { + return nil, errors.Wrap(err, "failed to base64 decode encryption key") + } + + ivbuf := buffer.Buffer{} + if err := ivbuf.Base64Decode(parts[2]); err != nil { + return nil, errors.Wrap(err, "failed to base64 decode iv") + } + + ctbuf := buffer.Buffer{} + if err := ctbuf.Base64Decode(parts[3]); err != nil { + return nil, errors.Wrap(err, "failed to base64 decode content") + } + + tagbuf := buffer.Buffer{} + if err := tagbuf.Base64Decode(parts[4]); err != nil { + return nil, errors.Wrap(err, "failed to base64 decode tag") + } + + m := NewMessage() + m.AuthenticatedData.SetBytes(hdrbuf.Bytes()) + m.ProtectedHeader = protected + m.Tag = tagbuf + m.CipherText = ctbuf + m.InitializationVector = ivbuf + m.Recipients = []Recipient{ + { + Header: hdr, + EncryptedKey: enckeybuf, + }, + } + return m, nil +} + +// BuildKeyDecrypter creates a new KeyDecrypter instance from the given +// parameters. It is used by the Message.Decrypt method to create +// key decrypter(s) from the given message. `keysize` is only used by +// some decrypters. Pass the value from ContentCipher.KeySize(). +func BuildKeyDecrypter(alg jwa.KeyEncryptionAlgorithm, h *Header, key interface{}, keysize int) (KeyDecrypter, error) { + switch alg { + case jwa.RSA1_5: + privkey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("*rsa.PrivateKey is required as the key to build this key decrypter") + } + return NewRSAPKCS15KeyDecrypt(alg, privkey, keysize/2), nil + case jwa.RSA_OAEP, jwa.RSA_OAEP_256: + privkey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("*rsa.PrivateKey is required as the key to build this key decrypter") + } + return NewRSAOAEPKeyDecrypt(alg, privkey) + case jwa.A128KW, jwa.A192KW, jwa.A256KW: + sharedkey, ok := key.([]byte) + if !ok { + return nil, errors.New("[]byte is required as the key to build this key decrypter") + } + return NewKeyWrapEncrypt(alg, sharedkey) + case jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: + epkif, err := h.Get("epk") + if err != nil { + return nil, errors.Wrap(err, "failed to get 'epk' field") + } + if epkif == nil { + return nil, errors.New("'epk' header is required as the key to build this key decrypter") + } + + epk, ok := epkif.(*jwk.ECDSAPublicKey) + if !ok { + return nil, errors.New("'epk' header is required as the key to build this key decrypter") + } + + pubkey, err := epk.Materialize() + if err != nil { + return nil, errors.Wrap(err, "failed to get public key") + } + + privkey, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, errors.New("*ecdsa.PrivateKey is required as the key to build this key decrypter") + } + apuif, err := h.Get("apu") + if err != nil { + return nil, errors.New("'apu' key is required for this key decrypter") + } + apu, ok := apuif.(buffer.Buffer) + if !ok { + return nil, errors.New("'apu' key is required for this key decrypter") + } + + apvif, err := h.Get("apv") + if err != nil { + return nil, errors.New("'apv' key is required for this key decrypter") + } + apv, ok := apvif.(buffer.Buffer) + if !ok { + return nil, errors.New("'apv' key is required for this key decrypter") + } + + return NewEcdhesKeyWrapDecrypt(alg, pubkey.(*ecdsa.PublicKey), apu.Bytes(), apv.Bytes(), privkey), nil + } + + return nil, NewErrUnsupportedAlgorithm(string(alg), "key decryption") +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/jwe_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/jwe_test.go new file mode 100644 index 0000000000..46c36e40a4 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/jwe_test.go @@ -0,0 +1,327 @@ +package jwe_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "encoding/json" + "net/url" + "testing" + + "github.com/lestrrat-go/jwx/internal/rsautil" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwe" + "github.com/stretchr/testify/assert" +) + +const ( + examplePayload = `The true sign of intelligence is not knowledge but imagination.` +) + +var rsaPrivKey *rsa.PrivateKey + +func init() { + var jwkstr = []byte(` + {"kty":"RSA", + "n":"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw", + "e":"AQAB", + "d":"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ", + "p":"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lffNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0", + "q":"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBmUDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aXIWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc", + "dp":"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KLhMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE", + "dq":"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCjywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDBUfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis", + "qi":"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY" + }`) + + var err error + rsaPrivKey, err = rsautil.PrivateKeyFromJSON(jwkstr) + if err != nil { + panic(err) + } +} + +func TestSanityCheck_JWEExamplePayload(t *testing.T) { + expected := []byte{ + 84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32, + 111, 102, 32, 105, 110, 116, 101, 108, 108, 105, 103, 101, 110, 99, + 101, 32, 105, 115, 32, 110, 111, 116, 32, 107, 110, 111, 119, 108, + 101, 100, 103, 101, 32, 98, 117, 116, 32, 105, 109, 97, 103, 105, + 110, 97, 116, 105, 111, 110, 46, + } + assert.Equal(t, expected, []byte(examplePayload), "examplePayload OK") +} + +func TestParse_Compact(t *testing.T) { + s := `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ` + + msg, err := jwe.Parse([]byte(s)) + if !assert.NoError(t, err, "Parsing JWE is successful") { + return + } + + if !assert.Len(t, msg.Recipients, 1, "There is exactly 1 recipient") { + return + } +} + +// This test parses the example found in https://tools.ietf.org/html/rfc7516#appendix-A.1, +// and checks if we can roundtrip to the same compact serialization format. +func TestParse_RSAES_OAEP_AES_GCM(t *testing.T) { + const payload = `The true sign of intelligence is not knowledge but imagination.` + const serialized = `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ` + var jwkstr = []byte(` + {"kty":"RSA", + "n":"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw", + "e":"AQAB", + "d":"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ", + "p":"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lffNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0", + "q":"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBmUDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aXIWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc", + "dp":"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KLhMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE", + "dq":"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCjywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDBUfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis", + "qi":"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY" + }`) + privkey, err := rsautil.PrivateKeyFromJSON(jwkstr) + if !assert.NoError(t, err, "PrivateKey created") { + return + } + + msg, err := jwe.ParseString(serialized) + if !assert.NoError(t, err, "parse successful") { + return + } + t.Logf("------ ParseString done") + + plaintext, err := msg.Decrypt(jwa.RSA_OAEP, privkey) + if !assert.NoError(t, err, "Decrypt message succeeded") { + return + } + + if !assert.Equal(t, payload, string(plaintext), "decrypted value does not match") { + return + } + + jsonbuf, err := jwe.CompactSerialize{}.Serialize(msg) + if !assert.NoError(t, err, "Compact serialize succeeded") { + return + } + + if !assert.Equal(t, serialized, string(jsonbuf), "Compact serialize matches") { + jsonbuf, _ = jwe.JSONSerialize{Pretty: true}.Serialize(msg) + t.Logf("%s", jsonbuf) + return + } + + encrypted, err := jwe.Encrypt(plaintext, jwa.RSA_OAEP, &privkey.PublicKey, jwa.A256GCM, jwa.NoCompress) + if !assert.NoError(t, err, "jwe.Encrypt should succeed") { + return + } + + plaintext, err = jwe.Decrypt(encrypted, jwa.RSA_OAEP, privkey) + if !assert.NoError(t, err, "jwe.Decrypt should succeed") { + return + } + + if !assert.Equal(t, payload, string(plaintext), "jwe.Decrypt should produce the same plaintext") { + return + } +} + +// https://tools.ietf.org/html/rfc7516#appendix-A.1. +func TestRoundtrip_RSAES_OAEP_AES_GCM(t *testing.T) { + var plaintext = []byte{ + 84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32, + 111, 102, 32, 105, 110, 116, 101, 108, 108, 105, 103, 101, 110, 99, + 101, 32, 105, 115, 32, 110, 111, 116, 32, 107, 110, 111, 119, 108, + 101, 100, 103, 101, 32, 98, 117, 116, 32, 105, 109, 97, 103, 105, + 110, 97, 116, 105, 111, 110, 46, + } + + max := 100 + if testing.Short() { + max = 1 + } + + for i := 0; i < max; i++ { + encrypted, err := jwe.Encrypt(plaintext, jwa.RSA_OAEP, &rsaPrivKey.PublicKey, jwa.A256GCM, jwa.NoCompress) + if !assert.NoError(t, err, "Encrypt should succeed") { + return + } + + decrypted, err := jwe.Decrypt(encrypted, jwa.RSA_OAEP, rsaPrivKey) + if !assert.NoError(t, err, "Decrypt should succeed") { + return + } + + if !assert.Equal(t, plaintext, decrypted, "Decrypted content should match") { + return + } + } +} + +func TestRoundtrip_RSA1_5_A128CBC_HS256(t *testing.T) { + var plaintext = []byte{ + 76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, + 112, 114, 111, 115, 112, 101, 114, 46, + } + + max := 100 + if testing.Short() { + max = 1 + } + + for i := 0; i < max; i++ { + encrypted, err := jwe.Encrypt(plaintext, jwa.RSA1_5, &rsaPrivKey.PublicKey, jwa.A128CBC_HS256, jwa.NoCompress) + if !assert.NoError(t, err, "Encrypt is successful") { + return + } + + decrypted, err := jwe.Decrypt(encrypted, jwa.RSA1_5, rsaPrivKey) + if !assert.NoError(t, err, "Decrypt successful") { + return + } + + if !assert.Equal(t, plaintext, decrypted, "Decrypted correct plaintext") { + return + } + } +} + +// https://tools.ietf.org/html/rfc7516#appendix-A.3. Note that cek is dynamically +// generated, so the encrypted values will NOT match that of the RFC. +func TestEncode_A128KW_A128CBC_HS256(t *testing.T) { + var plaintext = []byte{ + 76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, + 112, 114, 111, 115, 112, 101, 114, 46, + } + var sharedkey = []byte{ + 25, 172, 32, 130, 225, 114, 26, 181, 138, 106, 254, 192, 95, 133, 74, 82, + } + + max := 100 + if testing.Short() { + max = 1 + } + + for i := 0; i < max; i++ { + encrypted, err := jwe.Encrypt(plaintext, jwa.A128KW, sharedkey, jwa.A128CBC_HS256, jwa.NoCompress) + if !assert.NoError(t, err, "Encrypt is successful") { + return + } + + decrypted, err := jwe.Decrypt(encrypted, jwa.A128KW, sharedkey) + if !assert.NoError(t, err, "Decrypt successful") { + return + } + + if !assert.Equal(t, plaintext, decrypted, "Decrypted correct plaintext") { + return + } + } +} + +func TestEncode_ECDHES(t *testing.T) { + plaintext := []byte("Lorem ipsum") + privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if !assert.NoError(t, err, "ecdsa key generated") { + return + } + encrypted, err := jwe.Encrypt(plaintext, jwa.ECDH_ES_A128KW, &privkey.PublicKey, jwa.A128CBC_HS256, jwa.NoCompress) + if !assert.NoError(t, err, "Encrypt succeeds") { + return + } + + t.Logf("encrypted = %s", encrypted) + + msg, _ := jwe.Parse(encrypted) + jsonbuf, _ := json.MarshalIndent(msg, "", " ") + t.Logf("%s", jsonbuf) + + decrypted, err := jwe.Decrypt(encrypted, jwa.ECDH_ES_A128KW, privkey) + if !assert.NoError(t, err, "Decrypt succeeds") { + return + } + t.Logf("%s", decrypted) +} + +func TestEncode_ECDH_ES_A256KW_A192KW_A128KW(t *testing.T) { + plaintext := []byte("Lorem ipsum") + privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if !assert.NoError(t, err, "ecdsa key generated") { + return + } + + algorithms := []jwa.KeyEncryptionAlgorithm{jwa.ECDH_ES_A256KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A128KW} + + for i := 0; i < len(algorithms); i++ { + encrypted, err := jwe.Encrypt(plaintext, algorithms[i], &privkey.PublicKey, jwa.A256GCM, jwa.NoCompress) + if !assert.NoError(t, err, "Encrypt succeeds") { + return + } + + t.Logf("encrypted = %s", encrypted) + + msg, _ := jwe.Parse(encrypted) + jsonbuf, _ := json.MarshalIndent(msg, "", " ") + t.Logf("%s", jsonbuf) + + decrypted, err := jwe.Decrypt(encrypted, algorithms[i], privkey) + if !assert.NoError(t, err, "Decrypt succeeds") { + return + } + t.Logf("%s", decrypted) + } +} + +func Test_A256KW_A256CBC_HS512(t *testing.T) { + var keysize = 32 + var key = make([]byte, keysize) + for i := 0; i < keysize; i++ { + key[i] = byte(i) + } + _, err := jwe.Encrypt([]byte(examplePayload), jwa.A256KW, key, jwa.A256CBC_HS512, jwa.NoCompress) + if !assert.Error(t, err, "should fail to encrypt payload") { + return + } +} + +func TestHeaders(t *testing.T) { + h := jwe.NewHeader() + + data := map[string]struct { + Value interface{} + Expected interface{} + }{ + "kid": {Value: "kid blah"}, + "enc": {Value: jwa.A128GCM}, + "cty": {Value: "application/json"}, + "typ": {Value: "typ blah"}, + "x5t": {Value: "x5t blah"}, + "x5t#256": {Value: "x5t#256 blah"}, + "crit": {Value: []string{"crit blah"}}, + "jku": { + Value: "http://github.com/lestrrat-go/jwx", + Expected: &url.URL{Scheme: "http", Host: "github.com", Path: "/lestrrat-go/jwx"}, + }, + "x5u": { + Value: "http://github.com/lestrrat-go/jwx", + Expected: &url.URL{Scheme: "http", Host: "github.com", Path: "/lestrrat-go/jwx"}, + }, + } + + for name, testcase := range data { + h.Set(name, testcase.Value) + got, err := h.Get(name) + if !assert.NoError(t, err, "value should exist") { + return + } + + expected := testcase.Expected + if expected == nil { + expected = testcase.Value + } + if !assert.Equal(t, expected, got, "value should match") { + return + } + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/key_encrypt.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/key_encrypt.go new file mode 100644 index 0000000000..7916ce9658 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/key_encrypt.go @@ -0,0 +1,435 @@ +package jwe + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/subtle" + "encoding/binary" + "fmt" + "hash" + + "github.com/lestrrat-go/jwx/internal/concatkdf" + "github.com/lestrrat-go/jwx/internal/debug" + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +// NewKeyWrapEncrypt creates a key-wrap encrypter using AES-CGM. +// Although the name suggests otherwise, this does the decryption as well. +func NewKeyWrapEncrypt(alg jwa.KeyEncryptionAlgorithm, sharedkey []byte) (KeyWrapEncrypt, error) { + return KeyWrapEncrypt{ + alg: alg, + sharedkey: sharedkey, + }, nil +} + +// Algorithm returns the key encryption algorithm being used +func (kw KeyWrapEncrypt) Algorithm() jwa.KeyEncryptionAlgorithm { + return kw.alg +} + +// Kid returns the key ID associated with this encrypter +func (kw KeyWrapEncrypt) Kid() string { + return kw.KeyID +} + +// KeyDecrypt decrypts the encrypted key using AES-CGM key unwrap +func (kw KeyWrapEncrypt) KeyDecrypt(enckey []byte) ([]byte, error) { + block, err := aes.NewCipher(kw.sharedkey) + if err != nil { + return nil, errors.Wrap(err, "failed to create cipher from shared key") + } + + cek, err := keyunwrap(block, enckey) + if err != nil { + return nil, errors.Wrap(err, "failed to unwrap data") + } + return cek, nil +} + +// KeyEncrypt encrypts the given content encryption key +func (kw KeyWrapEncrypt) KeyEncrypt(cek []byte) (ByteSource, error) { + block, err := aes.NewCipher(kw.sharedkey) + if err != nil { + return nil, errors.Wrap(err, "failed to create cipher from shared key") + } + encrypted, err := keywrap(block, cek) + if err != nil { + return nil, errors.Wrap(err, `keywrap: failed to wrap key`) + } + return ByteKey(encrypted), nil +} + +// NewEcdhesKeyWrapEncrypt creates a new key encrypter based on ECDH-ES +func NewEcdhesKeyWrapEncrypt(alg jwa.KeyEncryptionAlgorithm, key *ecdsa.PublicKey) (*EcdhesKeyWrapEncrypt, error) { + generator, err := NewEcdhesKeyGenerate(alg, key) + if err != nil { + return nil, errors.Wrap(err, "failed to create key generator") + } + return &EcdhesKeyWrapEncrypt{ + algorithm: alg, + generator: generator, + }, nil +} + +// Algorithm returns the key encryption algorithm being used +func (kw EcdhesKeyWrapEncrypt) Algorithm() jwa.KeyEncryptionAlgorithm { + return kw.algorithm +} + +// Kid returns the key ID associated with this encrypter +func (kw EcdhesKeyWrapEncrypt) Kid() string { + return kw.KeyID +} + +// KeyEncrypt encrypts the content encryption key using ECDH-ES +func (kw EcdhesKeyWrapEncrypt) KeyEncrypt(cek []byte) (ByteSource, error) { + kg, err := kw.generator.KeyGenerate() + if err != nil { + return nil, errors.Wrap(err, "failed to create key generator") + } + + bwpk, ok := kg.(ByteWithECPrivateKey) + if !ok { + return nil, errors.New("key generator generated invalid key (expected ByteWithECPrivateKey)") + } + + block, err := aes.NewCipher(bwpk.Bytes()) + if err != nil { + return nil, errors.Wrap(err, "failed to generate cipher from generated key") + } + + jek, err := keywrap(block, cek) + if err != nil { + return nil, errors.Wrap(err, "failed to wrap data") + } + + bwpk.ByteKey = ByteKey(jek) + + return bwpk, nil +} + +// NewEcdhesKeyWrapDecrypt creates a new key decrypter using ECDH-ES +func NewEcdhesKeyWrapDecrypt(alg jwa.KeyEncryptionAlgorithm, pubkey *ecdsa.PublicKey, apu, apv []byte, privkey *ecdsa.PrivateKey) *EcdhesKeyWrapDecrypt { + return &EcdhesKeyWrapDecrypt{ + algorithm: alg, + apu: apu, + apv: apv, + privkey: privkey, + pubkey: pubkey, + } +} + +// Algorithm returns the key encryption algorithm being used +func (kw EcdhesKeyWrapDecrypt) Algorithm() jwa.KeyEncryptionAlgorithm { + return kw.algorithm +} + +// KeyDecrypt decrypts the encrypted key using ECDH-ES +func (kw EcdhesKeyWrapDecrypt) KeyDecrypt(enckey []byte) ([]byte, error) { + var keysize uint32 + switch kw.algorithm { + case jwa.ECDH_ES_A128KW: + keysize = 16 + case jwa.ECDH_ES_A192KW: + keysize = 24 + case jwa.ECDH_ES_A256KW: + keysize = 32 + default: + return nil, errors.Wrap(ErrUnsupportedAlgorithm, "invalid ECDH-ES key wrap algorithm") + } + + privkey := kw.privkey + pubkey := kw.pubkey + + pubinfo := make([]byte, 4) + binary.BigEndian.PutUint32(pubinfo, keysize*8) + + z, _ := privkey.PublicKey.Curve.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) + kdf := concatkdf.New(crypto.SHA256, []byte(kw.algorithm.String()), z.Bytes(), kw.apu, kw.apv, pubinfo, []byte{}) + kek := make([]byte, keysize) + kdf.Read(kek) + + block, err := aes.NewCipher(kek) + if err != nil { + return nil, errors.Wrap(err, "failed to create cipher for ECDH-ES key wrap") + } + + return keyunwrap(block, enckey) +} + +// NewRSAOAEPKeyEncrypt creates a new key encrypter using RSA OAEP +func NewRSAOAEPKeyEncrypt(alg jwa.KeyEncryptionAlgorithm, pubkey *rsa.PublicKey) (*RSAOAEPKeyEncrypt, error) { + switch alg { + case jwa.RSA_OAEP, jwa.RSA_OAEP_256: + default: + return nil, errors.Wrap(ErrUnsupportedAlgorithm, "invalid RSA OAEP encrypt algorithm") + } + return &RSAOAEPKeyEncrypt{ + alg: alg, + pubkey: pubkey, + }, nil +} + +// NewRSAPKCSKeyEncrypt creates a new key encrypter using PKCS1v15 +func NewRSAPKCSKeyEncrypt(alg jwa.KeyEncryptionAlgorithm, pubkey *rsa.PublicKey) (*RSAPKCSKeyEncrypt, error) { + switch alg { + case jwa.RSA1_5: + default: + return nil, errors.Wrap(ErrUnsupportedAlgorithm, "invalid RSA PKCS encrypt algorithm") + } + + return &RSAPKCSKeyEncrypt{ + alg: alg, + pubkey: pubkey, + }, nil +} + +// Algorithm returns the key encryption algorithm being used +func (e RSAPKCSKeyEncrypt) Algorithm() jwa.KeyEncryptionAlgorithm { + return e.alg +} + +// Kid returns the key ID associated with this encrypter +func (e RSAPKCSKeyEncrypt) Kid() string { + return e.KeyID +} + +// Algorithm returns the key encryption algorithm being used +func (e RSAOAEPKeyEncrypt) Algorithm() jwa.KeyEncryptionAlgorithm { + return e.alg +} + +// Kid returns the key ID associated with this encrypter +func (e RSAOAEPKeyEncrypt) Kid() string { + return e.KeyID +} + +// KeyEncrypt encrypts the content encryption key using RSA PKCS1v15 +func (e RSAPKCSKeyEncrypt) KeyEncrypt(cek []byte) (ByteSource, error) { + if e.alg != jwa.RSA1_5 { + return nil, errors.Wrap(ErrUnsupportedAlgorithm, "invalid RSA PKCS encrypt algorithm") + } + encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, e.pubkey, cek) + if err != nil { + return nil, errors.Wrap(err, "failed to encrypt using PKCS1v15") + } + return ByteKey(encrypted), nil +} + +// KeyEncrypt encrypts the content encryption key using RSA OAEP +func (e RSAOAEPKeyEncrypt) KeyEncrypt(cek []byte) (ByteSource, error) { + var hash hash.Hash + switch e.alg { + case jwa.RSA_OAEP: + hash = sha1.New() + case jwa.RSA_OAEP_256: + hash = sha256.New() + default: + return nil, errors.New("failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256 required") + } + encrypted, err := rsa.EncryptOAEP(hash, rand.Reader, e.pubkey, cek, []byte{}) + if err != nil { + return nil, errors.Wrap(err, `failed to OAEP encrypt`) + } + return ByteKey(encrypted), nil +} + +// NewRSAPKCS15KeyDecrypt creates a new decrypter using RSA PKCS1v15 +func NewRSAPKCS15KeyDecrypt(alg jwa.KeyEncryptionAlgorithm, privkey *rsa.PrivateKey, keysize int) *RSAPKCS15KeyDecrypt { + generator := NewRandomKeyGenerate(keysize * 2) + return &RSAPKCS15KeyDecrypt{ + alg: alg, + privkey: privkey, + generator: generator, + } +} + +// Algorithm returns the key encryption algorithm being used +func (d RSAPKCS15KeyDecrypt) Algorithm() jwa.KeyEncryptionAlgorithm { + return d.alg +} + +// KeyDecrypt decryptes the encrypted key using RSA PKCS1v1.5 +func (d RSAPKCS15KeyDecrypt) KeyDecrypt(enckey []byte) ([]byte, error) { + if debug.Enabled { + debug.Printf("START PKCS.KeyDecrypt") + } + // Hey, these notes and workarounds were stolen from go-jose + defer func() { + // DecryptPKCS1v15SessionKey sometimes panics on an invalid payload + // because of an index out of bounds error, which we want to ignore. + // This has been fixed in Go 1.3.1 (released 2014/08/13), the recover() + // only exists for preventing crashes with unpatched versions. + // See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k + // See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33 + _ = recover() + }() + + // Perform some input validation. + expectedlen := d.privkey.PublicKey.N.BitLen() / 8 + if expectedlen != len(enckey) { + // Input size is incorrect, the encrypted payload should always match + // the size of the public modulus (e.g. using a 2048 bit key will + // produce 256 bytes of output). Reject this since it's invalid input. + return nil, fmt.Errorf( + "input size for key decrypt is incorrect (expected %d, got %d)", + expectedlen, + len(enckey), + ) + } + + var err error + + bk, err := d.generator.KeyGenerate() + if err != nil { + return nil, errors.New("failed to generate key") + } + cek := bk.Bytes() + + // When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to + // prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing + // the Million Message Attack on Cryptographic Message Syntax". We are + // therefore deliberatly ignoring errors here. + err = rsa.DecryptPKCS1v15SessionKey(rand.Reader, d.privkey, enckey, cek) + if err != nil { + return nil, errors.Wrap(err, "failed to decrypt via PKCS1v15") + } + + return cek, nil +} + +// NewRSAOAEPKeyDecrypt creates a new key decrypter using RSA OAEP +func NewRSAOAEPKeyDecrypt(alg jwa.KeyEncryptionAlgorithm, privkey *rsa.PrivateKey) (*RSAOAEPKeyDecrypt, error) { + switch alg { + case jwa.RSA_OAEP, jwa.RSA_OAEP_256: + default: + return nil, errors.Wrap(ErrUnsupportedAlgorithm, "invalid RSA OAEP decrypt algorithm") + } + + return &RSAOAEPKeyDecrypt{ + alg: alg, + privkey: privkey, + }, nil +} + +// Algorithm returns the key encryption algorithm being used +func (d RSAOAEPKeyDecrypt) Algorithm() jwa.KeyEncryptionAlgorithm { + return d.alg +} + +// KeyDecrypt decryptes the encrypted key using RSA OAEP +func (d RSAOAEPKeyDecrypt) KeyDecrypt(enckey []byte) ([]byte, error) { + if debug.Enabled { + debug.Printf("START OAEP.KeyDecrypt") + } + var hash hash.Hash + switch d.alg { + case jwa.RSA_OAEP: + hash = sha1.New() + case jwa.RSA_OAEP_256: + hash = sha256.New() + default: + return nil, errors.New("failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256 required") + } + return rsa.DecryptOAEP(hash, rand.Reader, d.privkey, enckey, []byte{}) +} + +// Decrypt for DirectDecrypt does not do anything other than +// return a copy of the embedded key +func (d DirectDecrypt) Decrypt() ([]byte, error) { + cek := make([]byte, len(d.Key)) + copy(cek, d.Key) + return cek, nil +} + +var keywrapDefaultIV = []byte{0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6} + +const keywrapChunkLen = 8 + +func keywrap(kek cipher.Block, cek []byte) ([]byte, error) { + if len(cek)%8 != 0 { + return nil, ErrInvalidBlockSize + } + + n := len(cek) / keywrapChunkLen + r := make([][]byte, n) + + for i := 0; i < n; i++ { + r[i] = make([]byte, keywrapChunkLen) + copy(r[i], cek[i*keywrapChunkLen:]) + } + + buffer := make([]byte, keywrapChunkLen*2) + tBytes := make([]byte, keywrapChunkLen) + copy(buffer, keywrapDefaultIV) + + for t := 0; t < 6*n; t++ { + copy(buffer[keywrapChunkLen:], r[t%n]) + + kek.Encrypt(buffer, buffer) + + binary.BigEndian.PutUint64(tBytes, uint64(t+1)) + + for i := 0; i < keywrapChunkLen; i++ { + buffer[i] = buffer[i] ^ tBytes[i] + } + copy(r[t%n], buffer[keywrapChunkLen:]) + } + + out := make([]byte, (n+1)*keywrapChunkLen) + copy(out, buffer[:keywrapChunkLen]) + for i := range r { + copy(out[(i+1)*8:], r[i]) + } + + return out, nil +} + +func keyunwrap(block cipher.Block, ciphertxt []byte) ([]byte, error) { + if len(ciphertxt)%keywrapChunkLen != 0 { + return nil, ErrInvalidBlockSize + } + + n := (len(ciphertxt) / keywrapChunkLen) - 1 + r := make([][]byte, n) + + for i := range r { + r[i] = make([]byte, keywrapChunkLen) + copy(r[i], ciphertxt[(i+1)*keywrapChunkLen:]) + } + + buffer := make([]byte, keywrapChunkLen*2) + tBytes := make([]byte, keywrapChunkLen) + copy(buffer[:keywrapChunkLen], ciphertxt[:keywrapChunkLen]) + + for t := 6*n - 1; t >= 0; t-- { + binary.BigEndian.PutUint64(tBytes, uint64(t+1)) + + for i := 0; i < keywrapChunkLen; i++ { + buffer[i] = buffer[i] ^ tBytes[i] + } + copy(buffer[keywrapChunkLen:], r[t%n]) + + block.Decrypt(buffer, buffer) + + copy(r[t%n], buffer[keywrapChunkLen:]) + } + + if subtle.ConstantTimeCompare(buffer[:keywrapChunkLen], keywrapDefaultIV) == 0 { + return nil, errors.New("keywrap: failed to unwrap key") + } + + out := make([]byte, n*keywrapChunkLen) + for i := range r { + copy(out[i*keywrapChunkLen:], r[i]) + } + + return out, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/key_generate.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/key_generate.go new file mode 100644 index 0000000000..8c63fac938 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/key_generate.go @@ -0,0 +1,109 @@ +package jwe + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "encoding/binary" + "io" + + "github.com/lestrrat-go/jwx/internal/concatkdf" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/pkg/errors" +) + +// Bytes returns the byte from this ByteKey +func (k ByteKey) Bytes() []byte { + return []byte(k) +} + +// KeySize returns the size of the key +func (g StaticKeyGenerate) KeySize() int { + return len(g) +} + +// KeyGenerate returns the key +func (g StaticKeyGenerate) KeyGenerate() (ByteSource, error) { + buf := make([]byte, g.KeySize()) + copy(buf, g) + return ByteKey(buf), nil +} + +// NewRandomKeyGenerate creates a new KeyGenerator that returns +// random bytes +func NewRandomKeyGenerate(n int) RandomKeyGenerate { + return RandomKeyGenerate{keysize: n} +} + +// KeySize returns the key size +func (g RandomKeyGenerate) KeySize() int { + return g.keysize +} + +// KeyGenerate generates a random new key +func (g RandomKeyGenerate) KeyGenerate() (ByteSource, error) { + buf := make([]byte, g.keysize) + if _, err := io.ReadFull(rand.Reader, buf); err != nil { + return nil, errors.Wrap(err, "failed to read from rand.Reader") + } + return ByteKey(buf), nil +} + +// NewEcdhesKeyGenerate creates a new key generator using ECDH-ES +func NewEcdhesKeyGenerate(alg jwa.KeyEncryptionAlgorithm, pubkey *ecdsa.PublicKey) (*EcdhesKeyGenerate, error) { + var keysize int + switch alg { + case jwa.ECDH_ES: + return nil, errors.New("unimplemented") + case jwa.ECDH_ES_A128KW: + keysize = 16 + case jwa.ECDH_ES_A192KW: + keysize = 24 + case jwa.ECDH_ES_A256KW: + keysize = 32 + default: + return nil, errors.Wrap(ErrUnsupportedAlgorithm, "invalid ECDH-ES key generation algorithm") + } + + return &EcdhesKeyGenerate{ + algorithm: alg, + keysize: keysize, + pubkey: pubkey, + }, nil +} + +// KeySize returns the key size associated with this generator +func (g EcdhesKeyGenerate) KeySize() int { + return g.keysize +} + +// KeyGenerate generates new keys using ECDH-ES +func (g EcdhesKeyGenerate) KeyGenerate() (ByteSource, error) { + priv, err := ecdsa.GenerateKey(g.pubkey.Curve, rand.Reader) + if err != nil { + return nil, errors.Wrap(err, "failed to generate key for ECDH-ES") + } + + pubinfo := make([]byte, 4) + binary.BigEndian.PutUint32(pubinfo, uint32(g.keysize)*8) + + z, _ := priv.PublicKey.Curve.ScalarMult(g.pubkey.X, g.pubkey.Y, priv.D.Bytes()) + kdf := concatkdf.New(crypto.SHA256, []byte(g.algorithm.String()), z.Bytes(), []byte{}, []byte{}, pubinfo, []byte{}) + kek := make([]byte, g.keysize) + kdf.Read(kek) + + return ByteWithECPrivateKey{ + PrivateKey: priv, + ByteKey: ByteKey(kek), + }, nil +} + +// HeaderPopulate populates the header with the required EC-DSA public key +// information ('epk' key) +func (k ByteWithECPrivateKey) HeaderPopulate(h *Header) { + key, err := jwk.New(&k.PrivateKey.PublicKey) + if err == nil { + h.Set("epk", key) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/keywrap_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/keywrap_test.go new file mode 100644 index 0000000000..e522bf7dc6 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/keywrap_test.go @@ -0,0 +1,75 @@ +package jwe + +import ( + "crypto/aes" + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" +) + +func mustHexDecode(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +type vector struct { + Kek string + Data string + Expected string +} + +func TestRFC3394_Wrap(t *testing.T) { + vectors := []vector{ + vector{ + Kek: "000102030405060708090A0B0C0D0E0F", + Data: "00112233445566778899AABBCCDDEEFF", + Expected: "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5", + }, + vector{ + Kek: "000102030405060708090A0B0C0D0E0F1011121314151617", + Data: "00112233445566778899AABBCCDDEEFF", + Expected: "96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D", + }, + vector{ + Kek: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + Data: "00112233445566778899AABBCCDDEEFF0001020304050607", + Expected: "A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1", + }, + } + + for _, v := range vectors { + t.Logf("kek = %s", v.Kek) + t.Logf("data = %s", v.Data) + t.Logf("expected = %s", v.Expected) + + kek := mustHexDecode(v.Kek) + data := mustHexDecode(v.Data) + expected := mustHexDecode(v.Expected) + + block, err := aes.NewCipher(kek) + if !assert.NoError(t, err, "NewCipher is successful") { + return + } + out, err := keywrap(block, data) + if !assert.NoError(t, err, "Wrap is successful") { + return + } + + if !assert.Equal(t, expected, out, "Wrap generates expected output") { + return + } + + unwrapped, err := keyunwrap(block, out) + if !assert.NoError(t, err, "Unwrap is successful") { + return + } + + if !assert.Equal(t, data, unwrapped, "Unwrapped data matches") { + return + } + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/lowlevel_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/lowlevel_test.go new file mode 100644 index 0000000000..bd7832815a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/lowlevel_test.go @@ -0,0 +1,122 @@ +package jwe + +import ( + "testing" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/stretchr/testify/assert" +) + +// This test uses Appendix 3 to verify some low level tools for +// KeyWrap and CBC HMAC encryption. +// This test uses a static cek so that we can validate the results +// against the contents in the above Appendix +func TestLowLevelParts_A128KW_A128CBCHS256(t *testing.T) { + var plaintext = []byte{ + 76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, + 112, 114, 111, 115, 112, 101, 114, 46, + } + var cek = []byte{ + 4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, + 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, + 44, 207, + } + var iv = []byte{ + 3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, + 101, + } + var sharedkey = []byte{ + 25, 172, 32, 130, 225, 114, 26, 181, 138, 106, 254, 192, 95, 133, 74, 82, + } + var encsharedkey = []byte{ + 232, 160, 123, 211, 183, 76, 245, 132, 200, 128, 123, 75, 190, 216, + 22, 67, 201, 138, 193, 186, 9, 91, 122, 31, 246, 90, 28, 139, 57, 3, + 76, 124, 193, 11, 98, 37, 173, 61, 104, 57, + } + var aad = []byte{ + 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 66, 77, 84, 73, 52, + 83, 49, 99, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, 74, 66, + 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, 50, 73, + 110, 48, + } + var ciphertext = []byte{ + 40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, + 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, + 112, 56, 102, + } + var authtag = []byte{ + 83, 73, 191, 98, 104, 205, 211, 128, 201, 189, 199, 133, 32, 38, + 194, 85, + } + + const compactExpected = `eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.U0m_YmjN04DJvceFICbCVQ` + + k, err := NewKeyWrapEncrypt(jwa.A128KW, sharedkey) + if !assert.NoError(t, err, "Create key wrap") { + return + } + + enckey, err := k.KeyEncrypt(cek) + if !assert.NoError(t, err, "Failed to encrypt key") { + return + } + if !assert.Equal(t, encsharedkey, enckey.Bytes(), "encrypted keys match") { + return + } + + cipher, err := NewAesContentCipher(jwa.A128CBC_HS256) + if !assert.NoError(t, err, "NewAesContentCipher is successful") { + return + } + cipher.NonceGenerator = StaticKeyGenerate(iv) + + iv, encrypted, tag, err := cipher.encrypt(cek, plaintext, aad) + if !assert.NoError(t, err, "encrypt() successful") { + return + } + + if !assert.Equal(t, ciphertext, encrypted, "Generated cipher text does not match") { + return + } + + if !assert.Equal(t, tag, authtag, "Generated tag text does not match") { + return + } + + data, err := cipher.decrypt(cek, iv, encrypted, tag, aad) + if !assert.NoError(t, err, "decrypt successful") { + return + } + + if !assert.Equal(t, plaintext, data, "decrypt works") { + return + } + + r := NewRecipient() + r.Header.Set("alg", jwa.A128KW) + r.EncryptedKey = enckey.Bytes() + + protected := NewEncodedHeader() + protected.Set("enc", jwa.A128CBC_HS256) + + msg := NewMessage() + msg.ProtectedHeader = protected + msg.AuthenticatedData = aad + msg.CipherText = ciphertext + msg.InitializationVector = iv + msg.Tag = tag + msg.Recipients = []Recipient{*r} + + serialized, err := CompactSerialize{}.Serialize(msg) + if !assert.NoError(t, err, "compact serialization is successful") { + return + } + + if !assert.Equal(t, compactExpected, string(serialized), "compact serialization matches") { + serialized, err = JSONSerialize{Pretty: true}.Serialize(msg) + if !assert.NoError(t, err, "JSON serialization is successful") { + return + } + t.Logf("%s", serialized) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/message.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/message.go new file mode 100644 index 0000000000..10fda981e6 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/message.go @@ -0,0 +1,559 @@ +package jwe + +import ( + "bytes" + "compress/flate" + "encoding/json" + "net/url" + + "github.com/lestrrat-go/jwx/buffer" + "github.com/lestrrat-go/jwx/internal/debug" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/pkg/errors" +) + +// NewRecipient creates a Recipient object +func NewRecipient() *Recipient { + return &Recipient{ + Header: NewHeader(), + } +} + +// NewHeader creates a new Header object +func NewHeader() *Header { + return &Header{ + EssentialHeader: &EssentialHeader{}, + PrivateParams: map[string]interface{}{}, + } +} + +// NewEncodedHeader creates a new encoded Header object +func NewEncodedHeader() *EncodedHeader { + return &EncodedHeader{ + Header: NewHeader(), + } +} + +// Get returns the header key +func (h *Header) Get(key string) (interface{}, error) { + switch key { + case "alg": + return h.Algorithm, nil + case "apu": + return h.AgreementPartyUInfo, nil + case "apv": + return h.AgreementPartyVInfo, nil + case "enc": + return h.ContentEncryption, nil + case "epk": + return h.EphemeralPublicKey, nil + case "cty": + return h.ContentType, nil + case "kid": + return h.KeyID, nil + case "typ": + return h.Type, nil + case "x5t": + return h.X509CertThumbprint, nil + case "x5t#256": + return h.X509CertThumbprintS256, nil + case "x5c": + return h.X509CertChain, nil + case "crit": + return h.Critical, nil + case "jku": + return h.JwkSetURL, nil + case "x5u": + return h.X509Url, nil + default: + v, ok := h.PrivateParams[key] + if !ok { + return nil, errors.New("invalid header name") + } + return v, nil + } +} + +// Set sets the value of the given key to the given value. If it's +// one of the known keys, it will be set in EssentialHeader field. +// Otherwise, it is set in PrivateParams field. +func (h *Header) Set(key string, value interface{}) error { + switch key { + case "alg": + var v jwa.KeyEncryptionAlgorithm + s, ok := value.(string) + if ok { + v = jwa.KeyEncryptionAlgorithm(s) + } else { + v, ok = value.(jwa.KeyEncryptionAlgorithm) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'alg'") + } + } + h.Algorithm = v + case "apu": + var v buffer.Buffer + switch value.(type) { + case buffer.Buffer: + v = value.(buffer.Buffer) + case []byte: + v = buffer.Buffer(value.([]byte)) + case string: + v = buffer.Buffer(value.(string)) + default: + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'apu'") + } + h.AgreementPartyUInfo = v + case "apv": + var v buffer.Buffer + switch value.(type) { + case buffer.Buffer: + v = value.(buffer.Buffer) + case []byte: + v = buffer.Buffer(value.([]byte)) + case string: + v = buffer.Buffer(value.(string)) + default: + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'apv'") + } + h.AgreementPartyVInfo = v + case "enc": + var v jwa.ContentEncryptionAlgorithm + s, ok := value.(string) + if ok { + v = jwa.ContentEncryptionAlgorithm(s) + } else { + v, ok = value.(jwa.ContentEncryptionAlgorithm) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'enc'") + } + } + h.ContentEncryption = v + case "cty": + v, ok := value.(string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'cty'") + } + h.ContentType = v + case "epk": + v, ok := value.(*jwk.ECDSAPublicKey) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'epk'") + } + h.EphemeralPublicKey = v + case "kid": + v, ok := value.(string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'kid'") + } + h.KeyID = v + case "typ": + v, ok := value.(string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'typ'") + } + h.Type = v + case "x5t": + v, ok := value.(string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'x5t'") + } + h.X509CertThumbprint = v + case "x5t#256": + v, ok := value.(string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'x5t#256'") + } + h.X509CertThumbprintS256 = v + case "x5c": + v, ok := value.([]string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'x5c'") + } + h.X509CertChain = v + case "crit": + v, ok := value.([]string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'crit'") + } + h.Critical = v + case "jku": + v, ok := value.(string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'jku'") + } + u, err := url.Parse(v) + if err != nil { + return errors.Wrap(errors.Wrap(err, "failed to parse new value for 'jku' header"), "invalid header value") + } + h.JwkSetURL = u + case "x5u": + v, ok := value.(string) + if !ok { + return errors.Wrap(ErrInvalidHeaderValue, "invalid header value for 'x5u'") + } + u, err := url.Parse(v) + if err != nil { + return errors.Wrap(errors.Wrap(err, "failed to parse new value for 'x5u' header"), "invalid header value") + } + h.X509Url = u + default: + h.PrivateParams[key] = value + } + return nil +} + +// Merge merges the current header with another. +func (h *Header) Merge(h2 *Header) (*Header, error) { + if h2 == nil { + return nil, errors.New("merge target is nil") + } + + h3 := NewHeader() + if err := h3.Copy(h); err != nil { + return nil, errors.Wrap(err, "failed to copy header values") + } + + h3.EssentialHeader.Merge(h2.EssentialHeader) + + for k, v := range h2.PrivateParams { + h3.PrivateParams[k] = v + } + + return h3, nil +} + +// Merge merges the current header with another. +func (h *EssentialHeader) Merge(h2 *EssentialHeader) { + if h2.AgreementPartyUInfo.Len() != 0 { + h.AgreementPartyUInfo = h2.AgreementPartyUInfo + } + + if h2.AgreementPartyVInfo.Len() != 0 { + h.AgreementPartyVInfo = h2.AgreementPartyVInfo + } + + if h2.Algorithm != "" { + h.Algorithm = h2.Algorithm + } + + if h2.ContentEncryption != "" { + h.ContentEncryption = h2.ContentEncryption + } + + if h2.ContentType != "" { + h.ContentType = h2.ContentType + } + + if h2.Compression != "" { + h.Compression = h2.Compression + } + + if h2.Critical != nil { + h.Critical = h2.Critical + } + + if h2.EphemeralPublicKey != nil { + h.EphemeralPublicKey = h2.EphemeralPublicKey + } + + if h2.Jwk != nil { + h.Jwk = h2.Jwk + } + + if h2.JwkSetURL != nil { + h.JwkSetURL = h2.JwkSetURL + } + + if h2.KeyID != "" { + h.KeyID = h2.KeyID + } + + if h2.Type != "" { + h.Type = h2.Type + } + + if h2.X509Url != nil { + h.X509Url = h2.X509Url + } + + if h2.X509CertChain != nil { + h.X509CertChain = h2.X509CertChain + } + + if h2.X509CertThumbprint != "" { + h.X509CertThumbprint = h2.X509CertThumbprint + } + + if h2.X509CertThumbprintS256 != "" { + h.X509CertThumbprintS256 = h2.X509CertThumbprintS256 + } +} + +// Copy copies the other heder over this one +func (h *Header) Copy(h2 *Header) error { + if h == nil { + return errors.New("copy destination is nil") + } + if h2 == nil { + return errors.New("copy target is nil") + } + + h.EssentialHeader.Copy(h2.EssentialHeader) + + for k, v := range h2.PrivateParams { + h.PrivateParams[k] = v + } + + return nil +} + +// Copy copies the other heder over this one +func (h *EssentialHeader) Copy(h2 *EssentialHeader) { + h.AgreementPartyUInfo = h2.AgreementPartyUInfo + h.AgreementPartyVInfo = h2.AgreementPartyVInfo + h.Algorithm = h2.Algorithm + h.ContentEncryption = h2.ContentEncryption + h.ContentType = h2.ContentType + h.Compression = h2.Compression + h.Critical = h2.Critical + h.EphemeralPublicKey = h2.EphemeralPublicKey + h.Jwk = h2.Jwk + h.JwkSetURL = h2.JwkSetURL + h.KeyID = h2.KeyID + h.Type = h2.Type + h.X509Url = h2.X509Url + h.X509CertChain = h2.X509CertChain + h.X509CertThumbprint = h2.X509CertThumbprint + h.X509CertThumbprintS256 = h2.X509CertThumbprintS256 +} + +func mergeMarshal(e interface{}, p map[string]interface{}) ([]byte, error) { + buf, err := json.Marshal(e) + if err != nil { + return nil, errors.Wrap(err, `failed to marshal e`) + } + + if len(p) == 0 { + return buf, nil + } + + ext, err := json.Marshal(p) + if err != nil { + return nil, errors.Wrap(err, `failed to marshal p`) + } + + if len(buf) < 2 { + return nil, errors.New(`invalid json`) + } + + if buf[0] != '{' || buf[len(buf)-1] != '}' { + return nil, errors.New("invalid JSON") + } + buf[len(buf)-1] = ',' + buf = append(buf, ext[1:]...) + return buf, nil +} + +// MarshalJSON generates the JSON representation of this header +func (h Header) MarshalJSON() ([]byte, error) { + return mergeMarshal(h.EssentialHeader, h.PrivateParams) +} + +// UnmarshalJSON parses the JSON buffer into a Header +func (h *Header) UnmarshalJSON(data []byte) error { + if h.EssentialHeader == nil { + h.EssentialHeader = &EssentialHeader{} + } + if h.PrivateParams == nil { + h.PrivateParams = map[string]interface{}{} + } + + if err := json.Unmarshal(data, h.EssentialHeader); err != nil { + return errors.Wrap(err, "failed to parse JSON (essential) headers") + } + + m := map[string]interface{}{} + if err := json.Unmarshal(data, &m); err != nil { + return errors.Wrap(err, "failed to parse JSON headers") + } + for _, n := range []string{"alg", "apu", "apv", "enc", "cty", "zip", "crit", "epk", "jwk", "jku", "kid", "typ", "x5u", "x5c", "x5t", "x5t#S256"} { + delete(m, n) + } + + for name, value := range m { + if err := h.Set(name, value); err != nil { + return errors.Wrap(err, "failed to set header field '"+name+"'") + } + } + return nil +} + +// Base64Encode creates the base64 encoded version of the JSON +// representation of this header +func (e EncodedHeader) Base64Encode() ([]byte, error) { + buf, err := json.Marshal(e.Header) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal encoded header into JSON") + } + + buf, err = buffer.Buffer(buf).Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to base64 encode encoded header") + } + + return buf, nil +} + +// MarshalJSON generates the JSON representation of this header +func (e EncodedHeader) MarshalJSON() ([]byte, error) { + buf, err := e.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to base64 encode encoded header") + } + return json.Marshal(string(buf)) +} + +// UnmarshalJSON parses the JSON buffer into a Header +func (e *EncodedHeader) UnmarshalJSON(buf []byte) error { + b := buffer.Buffer{} + // base646 json string -> json object representation of header + if err := json.Unmarshal(buf, &b); err != nil { + return errors.Wrap(err, "failed to unmarshal buffer") + } + + if err := json.Unmarshal(b.Bytes(), &e.Header); err != nil { + return errors.Wrap(err, "failed to unmarshal buffer") + } + + return nil +} + +// NewMessage creates a new message +func NewMessage() *Message { + return &Message{ + ProtectedHeader: NewEncodedHeader(), + UnprotectedHeader: NewHeader(), + } +} + +// Decrypt decrypts the message using the specified algorithm and key +func (m *Message) Decrypt(alg jwa.KeyEncryptionAlgorithm, key interface{}) ([]byte, error) { + var err error + + if len(m.Recipients) == 0 { + return nil, errors.New("no recipients, can not proceed with decrypt") + } + + enc := m.ProtectedHeader.ContentEncryption + + h := NewHeader() + if err := h.Copy(m.ProtectedHeader.Header); err != nil { + return nil, errors.Wrap(err, `failed to copy protected headers`) + } + h, err = h.Merge(m.UnprotectedHeader) + if err != nil { + if debug.Enabled { + debug.Printf("failed to merge unprotected header") + } + return nil, errors.Wrap(err, "failed to merge headers for message decryption") + } + + aad, err := m.AuthenticatedData.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to base64 encode authenticated data for message decryption") + } + ciphertext := m.CipherText.Bytes() + iv := m.InitializationVector.Bytes() + tag := m.Tag.Bytes() + + cipher, err := buildContentCipher(enc) + if err != nil { + return nil, errors.Wrap(err, "unsupported content cipher algorithm '"+enc.String()+"'") + } + keysize := cipher.KeySize() + + var plaintext []byte + for _, recipient := range m.Recipients { + if debug.Enabled { + debug.Printf("Attempting to check if we can decode for recipient (alg = %s)", recipient.Header.Algorithm) + } + if recipient.Header.Algorithm != alg { + continue + } + + h2 := NewHeader() + if err := h2.Copy(h); err != nil { + if debug.Enabled { + debug.Printf("failed to copy header: %s", err) + } + continue + } + + h2, err := h2.Merge(recipient.Header) + if err != nil { + if debug.Enabled { + debug.Printf("Failed to merge! %s", err) + } + continue + } + + k, err := BuildKeyDecrypter(h2.Algorithm, h2, key, keysize) + if err != nil { + if debug.Enabled { + debug.Printf("failed to create key decrypter: %s", err) + } + continue + } + + cek, err := k.KeyDecrypt(recipient.EncryptedKey.Bytes()) + if err != nil { + if debug.Enabled { + debug.Printf("failed to decrypt key: %s", err) + } + continue + } + + plaintext, err = cipher.decrypt(cek, iv, ciphertext, tag, aad) + if err == nil { + break + } + if debug.Enabled { + debug.Printf("DecryptMessage: failed to decrypt using %s: %s", h2.Algorithm, err) + } + // Keep looping because there might be another key with the same algo + } + + if plaintext == nil { + return nil, errors.New("failed to find matching recipient to decrypt key") + } + + if h.Compression == jwa.Deflate { + output := bytes.Buffer{} + w, _ := flate.NewWriter(&output, 1) + in := plaintext + for len(in) > 0 { + n, err := w.Write(in) + if err != nil { + return nil, errors.Wrap(err, `failed to write to compression writer`) + } + in = in[n:] + } + if err := w.Close(); err != nil { + return nil, errors.Wrap(err, "failed to close compression writer") + } + plaintext = output.Bytes() + } + + return plaintext, nil +} + +func buildContentCipher(alg jwa.ContentEncryptionAlgorithm) (ContentCipher, error) { + switch alg { + case jwa.A128GCM, jwa.A192GCM, jwa.A256GCM, jwa.A128CBC_HS256, jwa.A192CBC_HS384, jwa.A256CBC_HS512: + return NewAesContentCipher(alg) + } + + return nil, ErrUnsupportedAlgorithm +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/out b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/out new file mode 100644 index 0000000000..2453b774de --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/out @@ -0,0 +1,27 @@ +=== RUN TestEncode_A128KW_A128CBC_HS256 +|DEBUG| AES Crypt: alg = A128CBC-HS256 +|DEBUG| AES Crypt: cipher.keysize = 32 +2017/04/07 07:32:00 contentcrypt.KeySize = 64 +|DEBUG| Encrypt: keysize = 64 +|DEBUG| Encrypt: generated cek len = 64 +|DEBUG| Encrypt: encrypted_key = ce6b0d820526b80d4f8167b94aaea7a25a43ea1a7194c881babfb2aa15bf496b9536a7677d1499ed0e152240722c22e82bfb8b687bff8f24f8e1e66541869148a5bc4b76f01f077e (72) +|DEBUG| ContentCrypt.Encrypt: cek = 8e070931dc0fb523fffc886cb70dcf435d63a40c662851eb74b2566d197d9a90f4bf5246ba7bd684718cbc3abb586e8b0f4c8d68b54d2ee00a93c476a589b0b4 (64) +|DEBUG| ContentCrypt.Encrypt: ciphertext = 4c697665206c6f6e6720616e642070726f737065722e (22) +|DEBUG| ContentCrypt.Encrypt: aad = 65794a68624763694f694a424d544934533163694c434a6c626d4d694f694a424d54493451304a444c5568544d6a5532496e30 (51) +|DEBUG| CbcAeadFetch: fetching key (64) +|DEBUG| New: cek (key) = 8e070931dc0fb523fffc886cb70dcf435d63a40c662851eb74b2566d197d9a90f4bf5246ba7bd684718cbc3abb586e8b0f4c8d68b54d2ee00a93c476a589b0b4 (64) +|DEBUG| New: ikey = 8e070931dc0fb523fffc886cb70dcf43 (16) +|DEBUG| New: ekey = 5d63a40c662851eb74b2566d197d9a90f4bf5246ba7bd684718cbc3abb586e8b0f4c8d68b54d2ee00a93c476a589b0b4 (48) +|DEBUG| CbcAeadFetch: failed to create aead fetcher [142 7 9 49 220 15 181 35 255 252 136 108 183 13 207 67 93 99 164 12 102 40 81 235 116 178 86 109 25 125 154 144 244 191 82 70 186 123 214 132 113 140 188 58 187 88 110 139 15 76 141 104 181 77 46 224 10 147 196 118 165 137 176 180] (64): crypto/aes: invalid key size 48 +|DEBUG| AeadFetch failed: cipher: failed to create AES cipher for CBC: crypto/aes: invalid key size 48 +|DEBUG| cipher.encrypt failed +|DEBUG| Failed to encrypt: failed to crypt content: failed to fetch AEAD for AesContentCipher.encrypt: cipher: failed to create AES cipher for CBC: crypto/aes: invalid key size 48 +|DEBUG| Encrypt: failed to encrypt: failed to crypt content: failed to fetch AEAD for AesContentCipher.encrypt: cipher: failed to create AES cipher for CBC: crypto/aes: invalid key size 48 +--- FAIL: TestEncode_A128KW_A128CBC_HS256 (0.00s) + assertions.go:219: Error Trace: jwe_test.go:320 + Error: Received unexpected error "failed to crypt content: failed to fetch AEAD for AesContentCipher.encrypt: cipher: failed to create AES cipher for CBC: crypto/aes: invalid key size 48" + Messages: Encrypt is successful + +FAIL +exit status 1 +FAIL github.com/lestrrat-go/jwx/jwe 0.013s diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/out.256 b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/out.256 new file mode 100644 index 0000000000..2d751284b3 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/out.256 @@ -0,0 +1,28 @@ +=== RUN Test_A256KW_A256CBC_HS512 +|DEBUG| AES Crypt: alg = A256CBC-HS512 +|DEBUG| AES Crypt: cipher.keysize = 64 +2017/04/07 07:32:55 contentcrypt.KeySize = 128 +|DEBUG| Encrypt: keysize = 128 +|DEBUG| Encrypt: generated cek len = 128 +|DEBUG| Encrypt: encrypted_key = fe1409b4a2580c25b431ccb29de26e19457d8ba5e1724cd55601ea5ee66836159f299c2b561e501893e3d06da5a9012dff4a2b46ffb1ccdbc859da1dac776d50fea0e23354b3065a8b941989965c9ac584b40e5a9e61ed573fd7d29ae6b95cc9e024b8a57f6218ecb8428734a866f23217fe2fc240d4ab487e4eaca8339e92331436edabbd2f5486 (136) +|DEBUG| ContentCrypt.Encrypt: cek = b70ae35958ec04c8256fd927ef8f63541ac1d77eac41c39fe173ad92c166879e678d91445fe858be19d2731d2cdd498b0e481ff78204b675b7016208798277167f040064591d5251dc5c691b3431bb2d2deacbca60630637f2f2c73502d451eff4086a8e1a3b2d2068f6806896df306b55b2ef63d8b22dfcf5d1aa6336774465 (128) +|DEBUG| ContentCrypt.Encrypt: ciphertext = 5468652074727565207369676e206f6620696e74656c6c6967656e6365206973206e6f74206b6e6f776c656467652062757420696d6167696e6174696f6e2e (63) +|DEBUG| ContentCrypt.Encrypt: aad = 65794a68624763694f694a424d6a5532533163694c434a6c626d4d694f694a424d6a553251304a444c5568544e544579496e30 (51) +|DEBUG| CbcAeadFetch: fetching key (128) +|DEBUG| New: keysize = 64 +|DEBUG| New: cek (key) = b70ae35958ec04c8256fd927ef8f63541ac1d77eac41c39fe173ad92c166879e678d91445fe858be19d2731d2cdd498b0e481ff78204b675b7016208798277167f040064591d5251dc5c691b3431bb2d2deacbca60630637f2f2c73502d451eff4086a8e1a3b2d2068f6806896df306b55b2ef63d8b22dfcf5d1aa6336774465 (128) +|DEBUG| New: ikey = b70ae35958ec04c8256fd927ef8f63541ac1d77eac41c39fe173ad92c166879e678d91445fe858be19d2731d2cdd498b0e481ff78204b675b701620879827716 (64) +|DEBUG| New: ekey = 7f040064591d5251dc5c691b3431bb2d2deacbca60630637f2f2c73502d451eff4086a8e1a3b2d2068f6806896df306b55b2ef63d8b22dfcf5d1aa6336774465 (64) +|DEBUG| CbcAeadFetch: failed to create aead fetcher [183 10 227 89 88 236 4 200 37 111 217 39 239 143 99 84 26 193 215 126 172 65 195 159 225 115 173 146 193 102 135 158 103 141 145 68 95 232 88 190 25 210 115 29 44 221 73 139 14 72 31 247 130 4 182 117 183 1 98 8 121 130 119 22 127 4 0 100 89 29 82 81 220 92 105 27 52 49 187 45 45 234 203 202 96 99 6 55 242 242 199 53 2 212 81 239 244 8 106 142 26 59 45 32 104 246 128 104 150 223 48 107 85 178 239 99 216 178 45 252 245 209 170 99 54 119 68 101] (128): crypto/aes: invalid key size 64 +|DEBUG| AeadFetch failed: cipher: failed to create AES cipher for CBC: crypto/aes: invalid key size 64 +|DEBUG| cipher.encrypt failed +|DEBUG| Failed to encrypt: failed to crypt content: failed to fetch AEAD for AesContentCipher.encrypt: cipher: failed to create AES cipher for CBC: crypto/aes: invalid key size 64 +|DEBUG| Encrypt: failed to encrypt: failed to crypt content: failed to fetch AEAD for AesContentCipher.encrypt: cipher: failed to create AES cipher for CBC: crypto/aes: invalid key size 64 +--- FAIL: Test_A256KW_A256CBC_HS512 (0.00s) + assertions.go:219: Error Trace: jwe_test.go:366 + Error: Received unexpected error "failed to crypt content: failed to fetch AEAD for AesContentCipher.encrypt: cipher: failed to create AES cipher for CBC: crypto/aes: invalid key size 64" + Messages: failed to encrypt payload + +FAIL +exit status 1 +FAIL github.com/lestrrat-go/jwx/jwe 0.012s diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/serializer.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/serializer.go new file mode 100644 index 0000000000..97bffe7f5c --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/serializer.go @@ -0,0 +1,73 @@ +package jwe + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +// Serialize converts the message into a JWE compact serialize format byte buffer +func (s CompactSerialize) Serialize(m *Message) ([]byte, error) { + if len(m.Recipients) != 1 { + return nil, errors.New("wrong number of recipients for compact serialization") + } + + recipient := m.Recipients[0] + + // The protected header must be a merge between the message-wide + // protected header AND the recipient header + hcopy := NewHeader() + // There's something wrong if m.ProtectedHeader.Header is nil, but + // it could happen + if m.ProtectedHeader == nil || m.ProtectedHeader.Header == nil { + return nil, errors.New("invalid protected header") + } + err := hcopy.Copy(m.ProtectedHeader.Header) + if err != nil { + return nil, errors.Wrap(err, "failed to copy protected header") + } + hcopy, err = hcopy.Merge(m.UnprotectedHeader) + if err != nil { + return nil, errors.Wrap(err, "failed to merge unprotected header") + } + hcopy, err = hcopy.Merge(recipient.Header) + if err != nil { + return nil, errors.Wrap(err, "failed to merge recipient header") + } + + protected, err := EncodedHeader{Header: hcopy}.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to encode header") + } + + encryptedKey, err := recipient.EncryptedKey.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to encode encryption key") + } + + iv, err := m.InitializationVector.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to encode iv") + } + + cipher, err := m.CipherText.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to encode cipher text") + } + + tag, err := m.Tag.Base64Encode() + if err != nil { + return nil, errors.Wrap(err, "failed to encode tag") + } + + buf := append(append(append(append(append(append(append(append(protected, '.'), encryptedKey...), '.'), iv...), '.'), cipher...), '.'), tag...) + return buf, nil +} + +// Serialize converts the message into a JWE JSON serialize format byte buffer +func (s JSONSerialize) Serialize(m *Message) ([]byte, error) { + if s.Pretty { + return json.MarshalIndent(m, "", " ") + } + return json.Marshal(m) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/speed_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/speed_test.go new file mode 100644 index 0000000000..9db6f6d8e4 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwe/speed_test.go @@ -0,0 +1,50 @@ +package jwe + +import ( + "bytes" + "testing" +) + +var s = []byte(`eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ`) + +func BenchmarkSplitLib(b *testing.B) { + for i := 0; i < b.N; i++ { + SplitLib(s) + } +} + +func BenchmarkSplitManual(b *testing.B) { + ret := make([][]byte, 5) + for i := 0; i < b.N; i++ { + SplitManual(ret, s) + } +} + +func SplitLib(buf []byte) [][]byte { + return bytes.Split(buf, []byte{'.'}) +} + +func SplitManual(parts [][]byte, buf []byte) { + bufi := 0 + for len(buf) > 0 { + i := bytes.IndexByte(buf, '.') + if i == -1 { + return + } + + parts[bufi] = buf[:i] + bufi++ + if len(buf) > i { + buf = buf[i+1:] + } + if bufi == 4 { + break + } + } + + if i := bytes.IndexByte(buf, '.'); i != -1 { + return + } + + parts[4] = buf +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/certchain.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/certchain.go new file mode 100644 index 0000000000..2aa7dca821 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/certchain.go @@ -0,0 +1,49 @@ +package jwk + +import ( + "crypto/x509" + "encoding/base64" + + "github.com/pkg/errors" +) + +func (c CertificateChain) Get() []*x509.Certificate { + return c.certs +} + +func (c *CertificateChain) Accept(v interface{}) error { + switch x := v.(type) { + case string: + return c.Accept([]string{x}) + case []interface{}: + l := make([]string, len(x)) + for i, e := range x { + if es, ok := e.(string); ok { + l[i] = es + } else { + return errors.Errorf(`invalid list element type: expected string, got %T`, v) + } + } + return c.Accept(l) + case []string: + certs := make([]*x509.Certificate, len(x)) + for i, e := range x { + buf, err := base64.StdEncoding.DecodeString(e) + if err != nil { + return errors.Wrap(err, `failed to base64 decode list element`) + } + cert, err := x509.ParseCertificate(buf) + if err != nil { + return errors.Wrap(err, `failed to parse certificate`) + } + certs[i] = cert + } + + *c = CertificateChain{ + certs: certs, + } + return nil + default: + return errors.Errorf(`invalid value %T`, v) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/doc_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/doc_test.go new file mode 100644 index 0000000000..c66577d904 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/doc_test.go @@ -0,0 +1,31 @@ +package jwk_test + +import ( + "log" + + "github.com/lestrrat-go/jwx/jwk" +) + +func Example() { + set, err := jwk.Fetch("https://foobar.domain/json") + if err != nil { + log.Printf("failed to parse JWK: %s", err) + return + } + + // If you KNOW you have exactly one key, you can just + // use set.Keys[0] + keys := set.LookupKeyID("mykey") + if len(keys) == 0 { + log.Printf("failed to lookup key: %s", err) + return + } + + key, err := keys[0].Materialize() + if err != nil { + log.Printf("failed to generate public key: %s", err) + return + } + // Use key for jws.Verify() or whatever + _ = key +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/ecdsa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/ecdsa.go new file mode 100644 index 0000000000..36c1eea841 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/ecdsa.go @@ -0,0 +1,274 @@ +package jwk + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "encoding/json" + "fmt" + "math/big" + + "github.com/lestrrat-go/jwx/internal/base64" + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +func newECDSAPublicKey(key *ecdsa.PublicKey) (*ECDSAPublicKey, error) { + if key == nil { + return nil, errors.New(`non-nil ecdsa.PublicKey required`) + } + + var hdr StandardHeaders + hdr.Set(KeyTypeKey, jwa.EC) + return &ECDSAPublicKey{ + headers: &hdr, + key: key, + }, nil +} + +func newECDSAPrivateKey(key *ecdsa.PrivateKey) (*ECDSAPrivateKey, error) { + if key == nil { + return nil, errors.New(`non-nil ecdsa.PrivateKey required`) + } + + var hdr StandardHeaders + hdr.Set(KeyTypeKey, jwa.EC) + return &ECDSAPrivateKey{ + headers: &hdr, + key: key, + }, nil +} + +func (k ECDSAPrivateKey) PublicKey() (*ECDSAPublicKey, error) { + return newECDSAPublicKey(&k.key.PublicKey) +} + +// Materialize returns the EC-DSA public key represented by this JWK +func (k ECDSAPublicKey) Materialize() (interface{}, error) { + return k.key, nil +} + +func (k ECDSAPublicKey) Curve() jwa.EllipticCurveAlgorithm { + return jwa.EllipticCurveAlgorithm(k.key.Curve.Params().Name) +} + +func (k ECDSAPrivateKey) Curve() jwa.EllipticCurveAlgorithm { + return jwa.EllipticCurveAlgorithm(k.key.PublicKey.Curve.Params().Name) +} + +func ecdsaThumbprint(hash crypto.Hash, crv, x, y string) []byte { + h := hash.New() + fmt.Fprintf(h, `{"crv":"`) + fmt.Fprintf(h, crv) + fmt.Fprintf(h, `","kty":"EC","x":"`) + fmt.Fprintf(h, x) + fmt.Fprintf(h, `","y":"`) + fmt.Fprintf(h, y) + fmt.Fprintf(h, `"}`) + return h.Sum(nil) +} + +// Thumbprint returns the JWK thumbprint using the indicated +// hashing algorithm, according to RFC 7638 +func (k ECDSAPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) { + return ecdsaThumbprint( + hash, + k.key.Curve.Params().Name, + base64.EncodeToString(k.key.X.Bytes()), + base64.EncodeToString(k.key.Y.Bytes()), + ), nil +} + +// Thumbprint returns the JWK thumbprint using the indicated +// hashing algorithm, according to RFC 7638 +func (k ECDSAPrivateKey) Thumbprint(hash crypto.Hash) ([]byte, error) { + return ecdsaThumbprint( + hash, + k.key.Curve.Params().Name, + base64.EncodeToString(k.key.X.Bytes()), + base64.EncodeToString(k.key.Y.Bytes()), + ), nil +} + +// Materialize returns the EC-DSA private key represented by this JWK +func (k ECDSAPrivateKey) Materialize() (interface{}, error) { + return k.key, nil +} + +func (k ECDSAPublicKey) MarshalJSON() (buf []byte, err error) { + + m := make(map[string]interface{}) + if err := k.PopulateMap(m); err != nil { + return nil, errors.Wrap(err, `failed to populate public key values`) + } + + return json.Marshal(m) +} + +func (k ECDSAPublicKey) PopulateMap(m map[string]interface{}) (err error) { + + if err := k.headers.PopulateMap(m); err != nil { + return errors.Wrap(err, `failed to populate header values`) + } + + const ( + xKey = `x` + yKey = `y` + crvKey = `crv` + ) + m[xKey] = base64.EncodeToString(k.key.X.Bytes()) + m[yKey] = base64.EncodeToString(k.key.Y.Bytes()) + m[crvKey] = k.key.Curve.Params().Name + + return nil +} + +func (k ECDSAPrivateKey) MarshalJSON() (buf []byte, err error) { + + m := make(map[string]interface{}) + if err := k.PopulateMap(m); err != nil { + return nil, errors.Wrap(err, `failed to populate public key values`) + } + + return json.Marshal(m) +} + +func (k ECDSAPrivateKey) PopulateMap(m map[string]interface{}) (err error) { + + if err := k.headers.PopulateMap(m); err != nil { + return errors.Wrap(err, `failed to populate header values`) + } + + pubkey, err := newECDSAPublicKey(&k.key.PublicKey) + if err != nil { + return errors.Wrap(err, `failed to construct public key from private key`) + } + + if err := pubkey.PopulateMap(m); err != nil { + return errors.Wrap(err, `failed to populate public key values`) + } + + m[`d`] = base64.EncodeToString(k.key.D.Bytes()) + + return nil +} + +func (k *ECDSAPublicKey) UnmarshalJSON(data []byte) (err error) { + + m := map[string]interface{}{} + if err := json.Unmarshal(data, &m); err != nil { + return errors.Wrap(err, `failed to unmarshal public key`) + } + + if err := k.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract data from map`) + } + return nil +} + +func (k *ECDSAPublicKey) ExtractMap(m map[string]interface{}) (err error) { + + const ( + xKey = `x` + yKey = `y` + crvKey = `crv` + ) + + crvname, ok := m[crvKey] + if !ok { + return errors.Errorf(`failed to get required key crv`) + } + delete(m, crvKey) + + var crv jwa.EllipticCurveAlgorithm + if err := crv.Accept(crvname); err != nil { + return errors.Wrap(err, `failed to accept value for crv key`) + } + + var curve elliptic.Curve + switch crv { + case jwa.P256: + curve = elliptic.P256() + case jwa.P384: + curve = elliptic.P384() + case jwa.P521: + curve = elliptic.P521() + default: + return errors.Errorf(`invalid curve name %s`, crv) + } + + xbuf, err := getRequiredKey(m, xKey) + if err != nil { + return errors.Wrapf(err, `failed to get required key %s`, xKey) + } + delete(m, xKey) + + ybuf, err := getRequiredKey(m, yKey) + if err != nil { + return errors.Wrapf(err, `failed to get required key %s`, yKey) + } + delete(m, yKey) + + var x, y big.Int + x.SetBytes(xbuf) + y.SetBytes(ybuf) + + var hdrs StandardHeaders + if err := hdrs.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract header values`) + } + + *k = ECDSAPublicKey{ + headers: &hdrs, + key: &ecdsa.PublicKey{ + Curve: curve, + X: &x, + Y: &y, + }, + } + return nil +} + +func (k *ECDSAPrivateKey) UnmarshalJSON(data []byte) (err error) { + + m := map[string]interface{}{} + if err := json.Unmarshal(data, &m); err != nil { + return errors.Wrap(err, `failed to unmarshal public key`) + } + + if err := k.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract data from map`) + } + return nil +} + +func (k *ECDSAPrivateKey) ExtractMap(m map[string]interface{}) (err error) { + + const ( + dKey = `d` + ) + + dbuf, err := getRequiredKey(m, dKey) + if err != nil { + return errors.Wrapf(err, `failed to get required key %s`, dKey) + } + delete(m, dKey) + + var pubkey ECDSAPublicKey + if err := pubkey.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract public key values`) + } + + var d big.Int + d.SetBytes(dbuf) + + *k = ECDSAPrivateKey{ + headers: pubkey.headers, + key: &ecdsa.PrivateKey{ + PublicKey: *(pubkey.key), + D: &d, + }, + } + pubkey.headers = nil + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/ecdsa_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/ecdsa_test.go new file mode 100644 index 0000000000..6ed17391bc --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/ecdsa_test.go @@ -0,0 +1,228 @@ +package jwk_test + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/json" + "reflect" + "testing" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/stretchr/testify/assert" +) + +func TestECDSA(t *testing.T) { + t.Run("Parse Private Key", func(t *testing.T) { + s := `{"keys": + [ + {"kty":"EC", + "crv":"P-256", + "key_ops": ["verify"], + "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE" + } + ] + }` + set, err := jwk.ParseString(s) + if !assert.NoError(t, err, "Parsing private key is successful") { + return + } + + if !assert.Len(t, set.Keys, 1, `should be 1 key`) { + return + } + + rawKey, err := set.Keys[0].Materialize() + if !assert.NoError(t, err, "Materialize should succeed") { + return + } + + if !assert.IsType(t, &ecdsa.PrivateKey{}, rawKey, `should be *ecdsa.PrivateKey`) { + return + } + + rawPrivKey := rawKey.(*ecdsa.PrivateKey) + + pubkey, err := set.Keys[0].(*jwk.ECDSAPrivateKey).PublicKey() + if !assert.NoError(t, err, "Should be able to get ECDSA public key") { + return + } + + rawKey, err = pubkey.Materialize() + if !assert.NoError(t, err, "Materialize should succeed") { + return + } + + if !assert.IsType(t, &ecdsa.PublicKey{}, rawKey, `should be *ecdsa.PublicKey`) { + return + } + + rawPubKey := rawKey.(*ecdsa.PublicKey) + + if !assert.Equal(t, elliptic.P256(), rawPubKey.Curve, "Curve matches") { + return + } + + if !assert.NotEmpty(t, rawPubKey.X, "X exists") { + return + } + + if !assert.NotEmpty(t, rawPubKey.Y, "Y exists") { + return + } + + if !assert.NotEmpty(t, rawPrivKey.D, "D exists") { + return + } + }) + t.Run("Initialization", func(t *testing.T) { + // Generate an ECDSA P-256 test key. + ecPrk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if !assert.NoError(t, err, "Failed to generate EC P-256 key") { + return + } + // Test initialization of a private EC JWK. + prk, err := jwk.New(ecPrk) + if !assert.NoError(t, err, `jwk.New should succeed`) { + return + } + + if !assert.NoError(t, prk.Set(jwk.KeyIDKey, "MyKey"), "Set private key ID success") { + return + } + + if !assert.Equal(t, prk.KeyType(), jwa.EC, "Private key type match") { + return + } + + if !assert.Equal(t, prk.KeyID(), "MyKey", "Private key ID match") { + return + } + + // Test initialization of a public EC JWK. + puk, err := jwk.New(&ecPrk.PublicKey) + if !assert.NoError(t, err, `jwk.New should succeed`) { + return + } + + if !assert.NoError(t, puk.Set(jwk.KeyIDKey, "MyKey"), " Set public key ID success") { + return + } + + if !assert.Equal(t, puk.KeyType(), jwa.EC, "Public key type match") { + return + } + + if !assert.Equal(t, prk.KeyID(), "MyKey", "Public key ID match") { + return + } + }) + t.Run("Marshall Unmarshal Public Key", func(t *testing.T) { + s := `{"keys": + [ + {"kty":"EC", + "crv":"P-256", + "key_ops": ["verify"], + "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE" + } + ] + }` + expectedPublicKeyBytes := []byte{123, 34, 99, 114, 118, 34, 58, 34, 80, 45, 50, 53, 54, 34, 44, 34, 107, 116, 121, 34, 58, 34, 69, 67, 34, 44, 34, 120, 34, 58, 34, 77, 75, 66, 67, 84, 78, 73, 99, 75, 85, 83, 68, 105, 105, 49, 49, 121, 83, 115, 51, 53, 50, 54, 105, 68, 90, 56, 65, 105, 84, 111, 55, 84, 117, 54, 75, 80, 65, 113, 118, 55, 68, 52, 34, 44, 34, 121, 34, 58, 34, 52, 69, 116, 108, 54, 83, 82, 87, 50, 89, 105, 76, 85, 114, 78, 53, 118, 102, 118, 86, 72, 117, 104, 112, 55, 120, 56, 80, 120, 108, 116, 109, 87, 87, 108, 98, 98, 77, 52, 73, 70, 121, 77, 34, 125} + + set, err := jwk.ParseString(s) + if !assert.NoError(t, err, "Parsing private key is successful") { + return + } + + if !assert.Len(t, set.Keys, 1, `should be 1 key`) { + return + } + + eCDSAPrivateKey := set.Keys[0].(*jwk.ECDSAPrivateKey) + + //Coverage for Curve() function + ellipticCurveAlgorithm := eCDSAPrivateKey.Curve() + if ellipticCurveAlgorithm.String() != "P-256" { + t.Fatal("ellipticCurveAlgorithm does not match") + + } + pubKey, err := set.Keys[0].(*jwk.ECDSAPrivateKey).PublicKey() + rawPubKey, err := pubKey.Materialize() + if err != nil { + t.Fatal("Failed to create raw ECDSAPublicKey") + } + eCDSAPublicKey, err := jwk.New(rawPubKey) + if err != nil { + t.Fatal("Failed to create ECDSAPublicKey") + } + + // verify marshal + pubKeyBytes, err := json.Marshal(eCDSAPublicKey) + if err != nil { + t.Fatal("Failed to marshal ECDSAPublicKey") + } + + if bytes.Compare(pubKeyBytes, expectedPublicKeyBytes) != 0 { + t.Fatal("Expected and created ECDSA Public Keys do not match") + } + + // verify unmarshal + eCDSAPublicKey2 := &jwk.ECDSAPublicKey{} + err = json.Unmarshal(expectedPublicKeyBytes, eCDSAPublicKey2) + if err != nil { + t.Fatal("Failed to unmarshal ECDSAPublicKey") + } + pECDSAPublicKey := eCDSAPublicKey.(*jwk.ECDSAPublicKey) + if !reflect.DeepEqual(pECDSAPublicKey, eCDSAPublicKey2) { + t.Fatal("ECDSA Public Keys do not match") + } + }) + t.Run("Marshall Unmarshal Private Key", func(t *testing.T) { + s := `{"keys": + [ + {"kty":"EC", + "crv":"P-256", + "key_ops": ["verify"], + "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE" + } + ] + }` + expectedPrivKey := []byte{123, 34, 99, 114, 118, 34, 58, 34, 80, 45, 50, 53, 54, 34, 44, 34, 100, 34, 58, 34, 56, 55, 48, 77, 66, 54, 103, 102, 117, 84, 74, 52, 72, 116, 85, 110, 85, 118, 89, 77, 121, 74, 112, 114, 53, 101, 85, 90, 78, 80, 52, 66, 107, 52, 51, 98, 86, 100, 106, 51, 101, 65, 69, 34, 44, 34, 107, 101, 121, 95, 111, 112, 115, 34, 58, 91, 34, 118, 101, 114, 105, 102, 121, 34, 93, 44, 34, 107, 116, 121, 34, 58, 34, 69, 67, 34, 44, 34, 120, 34, 58, 34, 77, 75, 66, 67, 84, 78, 73, 99, 75, 85, 83, 68, 105, 105, 49, 49, 121, 83, 115, 51, 53, 50, 54, 105, 68, 90, 56, 65, 105, 84, 111, 55, 84, 117, 54, 75, 80, 65, 113, 118, 55, 68, 52, 34, 44, 34, 121, 34, 58, 34, 52, 69, 116, 108, 54, 83, 82, 87, 50, 89, 105, 76, 85, 114, 78, 53, 118, 102, 118, 86, 72, 117, 104, 112, 55, 120, 56, 80, 120, 108, 116, 109, 87, 87, 108, 98, 98, 77, 52, 73, 70, 121, 77, 34, 125} + + set, err := jwk.ParseString(s) + if err != nil { + t.Fatal("Failed to parse JWK ECDSA") + } + eCDSAPrivateKey := set.Keys[0].(*jwk.ECDSAPrivateKey) + + privKeyBytes, err := json.Marshal(eCDSAPrivateKey) + if err != nil { + t.Fatal("Failed to marshal ECDSAPrivateKey") + } + // verify marshal + + if bytes.Compare(privKeyBytes, expectedPrivKey) != 0 { + t.Fatal("ECDSAPrivate in bytes do not match") + } + + // verify unmarshal + + expECDSAPrivateKey := &jwk.ECDSAPrivateKey{} + err = json.Unmarshal(expectedPrivKey, expECDSAPrivateKey) + if err != nil { + t.Fatal("Failed to unmarshal ECDSAPublicKey") + } + + if !reflect.DeepEqual(expECDSAPrivateKey, eCDSAPrivateKey) { + t.Fatal("ECDSAPrivate Keys do not match") + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/headers_gen.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/headers_gen.go new file mode 100644 index 0000000000..954596ce5d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/headers_gen.go @@ -0,0 +1,371 @@ +// This file is auto-generated. DO NOT EDIT + +package jwk + +import ( + "crypto/x509" + "fmt" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +const ( + AlgorithmKey = "alg" + KeyIDKey = "kid" + KeyTypeKey = "kty" + KeyUsageKey = "use" + KeyOpsKey = "key_ops" + X509CertChainKey = "x5c" + X509CertThumbprintKey = "x5t" + X509CertThumbprintS256Key = "x5t#S256" + X509URLKey = "x5u" +) + +type Headers interface { + Remove(string) + Get(string) (interface{}, bool) + Set(string, interface{}) error + PopulateMap(map[string]interface{}) error + ExtractMap(map[string]interface{}) error + Walk(func(string, interface{}) error) error + Algorithm() string + KeyID() string + KeyType() jwa.KeyType + KeyUsage() string + KeyOps() KeyOperationList + X509CertChain() []*x509.Certificate + X509CertThumbprint() string + X509CertThumbprintS256() string + X509URL() string +} + +type StandardHeaders struct { + algorithm *string // https://tools.ietf.org/html/rfc7517#section-4.4 + keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4 + keyType *jwa.KeyType // https://tools.ietf.org/html/rfc7517#section-4.1 + keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2 + keyops KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3 + x509CertChain *CertificateChain // https://tools.ietf.org/html/rfc7515#section-4.1.6 + x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7 + x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8 + x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5 + privateParams map[string]interface{} +} + +func (h *StandardHeaders) Remove(s string) { + delete(h.privateParams, s) +} + +func (h *StandardHeaders) Algorithm() string { + if v := h.algorithm; v != nil { + return *v + } + return "" +} + +func (h *StandardHeaders) KeyID() string { + if v := h.keyID; v != nil { + return *v + } + return "" +} + +func (h *StandardHeaders) KeyType() jwa.KeyType { + if v := h.keyType; v != nil { + return *v + } + return jwa.InvalidKeyType +} + +func (h *StandardHeaders) KeyUsage() string { + if v := h.keyUsage; v != nil { + return *v + } + return "" +} + +func (h *StandardHeaders) KeyOps() KeyOperationList { + return h.keyops +} + +func (h *StandardHeaders) X509CertChain() []*x509.Certificate { + return h.x509CertChain.Get() +} + +func (h *StandardHeaders) X509CertThumbprint() string { + if v := h.x509CertThumbprint; v != nil { + return *v + } + return "" +} + +func (h *StandardHeaders) X509CertThumbprintS256() string { + if v := h.x509CertThumbprintS256; v != nil { + return *v + } + return "" +} + +func (h *StandardHeaders) X509URL() string { + if v := h.x509URL; v != nil { + return *v + } + return "" +} + +func (h *StandardHeaders) Get(name string) (interface{}, bool) { + switch name { + case AlgorithmKey: + v := h.algorithm + if v == nil { + return nil, false + } + return *v, true + case KeyIDKey: + v := h.keyID + if v == nil { + return nil, false + } + return *v, true + case KeyTypeKey: + v := h.keyType + if v == nil { + return nil, false + } + return *v, true + case KeyUsageKey: + v := h.keyUsage + if v == nil { + return nil, false + } + return *v, true + case KeyOpsKey: + v := h.keyops + if v == nil { + return nil, false + } + return v, true + case X509CertChainKey: + v := h.x509CertChain + if v == nil { + return nil, false + } + return v.Get(), true + case X509CertThumbprintKey: + v := h.x509CertThumbprint + if v == nil { + return nil, false + } + return *v, true + case X509CertThumbprintS256Key: + v := h.x509CertThumbprintS256 + if v == nil { + return nil, false + } + return *v, true + case X509URLKey: + v := h.x509URL + if v == nil { + return nil, false + } + return *v, true + default: + v, ok := h.privateParams[name] + return v, ok + } +} + +func (h *StandardHeaders) Set(name string, value interface{}) error { + switch name { + case AlgorithmKey: + switch v := value.(type) { + case string: + h.algorithm = &v + return nil + case fmt.Stringer: + s := v.String() + h.algorithm = &s + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, AlgorithmKey, value) + case KeyIDKey: + if v, ok := value.(string); ok { + h.keyID = &v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) + case KeyTypeKey: + var acceptor jwa.KeyType + if err := acceptor.Accept(value); err != nil { + return errors.Wrapf(err, `invalid value for %s key`, KeyTypeKey) + } + h.keyType = &acceptor + return nil + case KeyUsageKey: + if v, ok := value.(string); ok { + h.keyUsage = &v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, KeyUsageKey, value) + case KeyOpsKey: + if err := h.keyops.Accept(value); err != nil { + return errors.Wrapf(err, `invalid value for %s key`, KeyOpsKey) + } + return nil + case X509CertChainKey: + var acceptor CertificateChain + if err := acceptor.Accept(value); err != nil { + return errors.Wrapf(err, `invalid value for %s key`, X509CertChainKey) + } + h.x509CertChain = &acceptor + return nil + case X509CertThumbprintKey: + if v, ok := value.(string); ok { + h.x509CertThumbprint = &v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) + case X509CertThumbprintS256Key: + if v, ok := value.(string); ok { + h.x509CertThumbprintS256 = &v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) + case X509URLKey: + if v, ok := value.(string); ok { + h.x509URL = &v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, X509URLKey, value) + default: + if h.privateParams == nil { + h.privateParams = map[string]interface{}{} + } + h.privateParams[name] = value + } + return nil +} + +// PopulateMap populates a map with appropriate values that represent +// the headers as a JSON object. This exists primarily because JWKs are +// represented as flat objects instead of differentiating the different +// parts of the message in separate sub objects. +func (h StandardHeaders) PopulateMap(m map[string]interface{}) error { + for k, v := range h.privateParams { + m[k] = v + } + if v, ok := h.Get(AlgorithmKey); ok { + m[AlgorithmKey] = v + } + if v, ok := h.Get(KeyIDKey); ok { + m[KeyIDKey] = v + } + if v, ok := h.Get(KeyTypeKey); ok { + m[KeyTypeKey] = v + } + if v, ok := h.Get(KeyUsageKey); ok { + m[KeyUsageKey] = v + } + if v, ok := h.Get(KeyOpsKey); ok { + m[KeyOpsKey] = v + } + if v, ok := h.Get(X509CertChainKey); ok { + m[X509CertChainKey] = v + } + if v, ok := h.Get(X509CertThumbprintKey); ok { + m[X509CertThumbprintKey] = v + } + if v, ok := h.Get(X509CertThumbprintS256Key); ok { + m[X509CertThumbprintS256Key] = v + } + if v, ok := h.Get(X509URLKey); ok { + m[X509URLKey] = v + } + + return nil +} + +// ExtractMap populates the appropriate values from a map that represent +// the headers as a JSON object. This exists primarily because JWKs are +// represented as flat objects instead of differentiating the different +// parts of the message in separate sub objects. +func (h *StandardHeaders) ExtractMap(m map[string]interface{}) (err error) { + if v, ok := m[AlgorithmKey]; ok { + if err := h.Set(AlgorithmKey, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, AlgorithmKey) + } + delete(m, AlgorithmKey) + } + if v, ok := m[KeyIDKey]; ok { + if err := h.Set(KeyIDKey, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, KeyIDKey) + } + delete(m, KeyIDKey) + } + if v, ok := m[KeyTypeKey]; ok { + if err := h.Set(KeyTypeKey, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, KeyTypeKey) + } + delete(m, KeyTypeKey) + } + if v, ok := m[KeyUsageKey]; ok { + if err := h.Set(KeyUsageKey, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, KeyUsageKey) + } + delete(m, KeyUsageKey) + } + if v, ok := m[KeyOpsKey]; ok { + if err := h.Set(KeyOpsKey, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, KeyOpsKey) + } + delete(m, KeyOpsKey) + } + if v, ok := m[X509CertChainKey]; ok { + if err := h.Set(X509CertChainKey, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, X509CertChainKey) + } + delete(m, X509CertChainKey) + } + if v, ok := m[X509CertThumbprintKey]; ok { + if err := h.Set(X509CertThumbprintKey, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, X509CertThumbprintKey) + } + delete(m, X509CertThumbprintKey) + } + if v, ok := m[X509CertThumbprintS256Key]; ok { + if err := h.Set(X509CertThumbprintS256Key, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, X509CertThumbprintS256Key) + } + delete(m, X509CertThumbprintS256Key) + } + if v, ok := m[X509URLKey]; ok { + if err := h.Set(X509URLKey, v); err != nil { + return errors.Wrapf(err, `failed to set value for key %s`, X509URLKey) + } + delete(m, X509URLKey) + } + // Fix: A nil map is different from a empty map as far as deep.equal is concerned + if len(m) > 0 { + h.privateParams = m + } + + return nil +} + +func (h StandardHeaders) Walk(f func(string, interface{}) error) error { + for _, key := range []string{AlgorithmKey, KeyIDKey, KeyTypeKey, KeyUsageKey, KeyOpsKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, X509URLKey} { + if v, ok := h.Get(key); ok { + if err := f(key, v); err != nil { + return errors.Wrapf(err, `walk function returned error for %s`, key) + } + } + } + + for k, v := range h.privateParams { + if err := f(k, v); err != nil { + return errors.Wrapf(err, `walk function returned error for %s`, k) + } + } + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/headers_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/headers_test.go new file mode 100644 index 0000000000..a7bdcc639d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/headers_test.go @@ -0,0 +1,161 @@ +package jwk_test + +import ( + "fmt" + "testing" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/stretchr/testify/assert" +) + +func TestHeader(t *testing.T) { + t.Run("Roundtrip", func(t *testing.T) { + values := map[string]interface{}{ + jwk.KeyIDKey: "helloworld01", + jwk.KeyTypeKey: jwa.RSA, + jwk.KeyOpsKey: jwk.KeyOperationList{jwk.KeyOpSign}, + jwk.KeyUsageKey: "sig", + jwk.X509CertThumbprintKey: "thumbprint", + jwk.X509CertThumbprintS256Key: "thumbprint256", + jwk.X509URLKey: "cert1", + } + + var h jwk.StandardHeaders + for k, v := range values { + if !assert.NoError(t, h.Set(k, v), "Set works for '%s'", k) { + return + } + + got, ok := h.Get(k) + if !assert.True(t, ok, "Get works for '%s'", k) { + return + } + + if !assert.Equal(t, v, got, "values match '%s'", k) { + return + } + + if !assert.NoError(t, h.Set(k, v), "Set works for '%s'", k) { + return + } + } + }) + t.Run("RoundtripError", func(t *testing.T) { + + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + values := map[string]interface{}{ + jwk.AlgorithmKey: dummy, + jwk.KeyIDKey: dummy, + jwk.KeyTypeKey: dummy, + jwk.KeyUsageKey: dummy, + jwk.KeyOpsKey: dummy, + jwk.X509CertChainKey: dummy, + jwk.X509CertThumbprintKey: dummy, + jwk.X509CertThumbprintS256Key: dummy, + jwk.X509URLKey: dummy, + } + + var h jwk.StandardHeaders + for k, v := range values { + err := h.Set(k, v) + if err == nil { + t.Fatalf("Setting %s value should have failed", k) + } + } + err := h.Set("Default", dummy) + if err != nil { + t.Fatalf("Setting %s value failed", "default") + } + if h.Algorithm() != "" { + t.Fatalf("Algorithm should be empty string") + } + if h.KeyID() != "" { + t.Fatalf("KeyID should be empty string") + } + if h.KeyType() != "" { + t.Fatalf("KeyType should be empty string") + } + if h.KeyUsage() != "" { + t.Fatalf("KeyUsage should be empty string") + } + if h.KeyOps() != nil { + t.Fatalf("KeyOps should be empty string") + } + }) + t.Run("ExtractMapError", func(t *testing.T) { + + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + values := map[string]interface{}{ + jwk.AlgorithmKey: dummy, + jwk.KeyIDKey: dummy, + jwk.KeyTypeKey: dummy, + jwk.KeyUsageKey: dummy, + jwk.KeyOpsKey: dummy, + jwk.X509CertChainKey: dummy, + jwk.X509CertThumbprintKey: dummy, + jwk.X509CertThumbprintS256Key: dummy, + jwk.X509URLKey: dummy, + } + + var h jwk.StandardHeaders + for k, _ := range values { + err := h.ExtractMap(values) + if err == nil { + t.Fatalf("Extracting %s value should have failed", k) + } + delete(values, k) + } + }) + + t.Run("Algorithm", func(t *testing.T) { + var h jwk.StandardHeaders + for _, value := range []interface{}{jwa.RS256, jwa.RSA1_5} { + if !assert.NoError(t, h.Set("alg", value), "Set for alg should succeed") { + return + } + + got, ok := h.Get("alg") + if !assert.True(t, ok, "Get for alg should succeed") { + return + } + + if !assert.Equal(t, value.(fmt.Stringer).String(), got, "values match") { + return + } + } + }) + t.Run("KeyType", func(t *testing.T) { + var h jwk.StandardHeaders + for _, value := range []interface{}{jwa.RSA, "RSA"} { + if !assert.NoError(t, h.Set(jwk.KeyTypeKey, value), "Set for kty should succeed") { + return + } + + got, ok := h.Get(jwk.KeyTypeKey) + if !assert.True(t, ok, "Get for kty should succeed") { + return + } + + var s string + switch value.(type) { + case jwa.KeyType: + s = value.(jwa.KeyType).String() + case string: + s = value.(string) + } + + if !assert.Equal(t, jwa.KeyType(s), got, "values match") { + return + } + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/interface.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/interface.go new file mode 100644 index 0000000000..d664befe32 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/interface.go @@ -0,0 +1,105 @@ +package jwk + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "errors" +) + +// KeyUsageType is used to denote what this key should be used for +type KeyUsageType string + +const ( + // ForSignature is the value used in the headers to indicate that + // this key should be used for signatures + ForSignature KeyUsageType = "sig" + // ForEncryption is the value used in the headers to indicate that + // this key should be used for encryptiong + ForEncryption KeyUsageType = "enc" +) + +type CertificateChain struct { + certs []*x509.Certificate +} + +// Errors related to JWK +var ( + ErrInvalidHeaderName = errors.New("invalid header name") + ErrInvalidHeaderValue = errors.New("invalid value for header key") + ErrUnsupportedKty = errors.New("unsupported kty") + ErrUnsupportedCurve = errors.New("unsupported curve") +) + +type KeyOperation string +type KeyOperationList []KeyOperation + +const ( + KeyOpSign KeyOperation = "sign" // (compute digital signature or MAC) + KeyOpVerify = "verify" // (verify digital signature or MAC) + KeyOpEncrypt = "encrypt" // (encrypt content) + KeyOpDecrypt = "decrypt" // (decrypt content and validate decryption, if applicable) + KeyOpWrapKey = "wrapKey" // (encrypt key) + KeyOpUnwrapKey = "unwrapKey" // (decrypt key and validate decryption, if applicable) + KeyOpDeriveKey = "deriveKey" // (derive key) + KeyOpDeriveBits = "deriveBits" // (derive bits not to be used as a key) +) + +// Set is a convenience struct to allow generating and parsing +// JWK sets as opposed to single JWKs +type Set struct { + Keys []Key `json:"keys"` +} + +// Key defines the minimal interface for each of the +// key types. Their use and implementation differ significantly +// between each key types, so you should use type assertions +// to perform more specific tasks with each key +type Key interface { + Headers + + // Materialize creates the corresponding key. For example, + // RSA types would create *rsa.PublicKey or *rsa.PrivateKey, + // EC types would create *ecdsa.PublicKey or *ecdsa.PrivateKey, + // and OctetSeq types create a []byte key. + Materialize() (interface{}, error) + + // Thumbprint returns the JWK thumbprint using the indicated + // hashing algorithm, according to RFC 7638 + Thumbprint(crypto.Hash) ([]byte, error) +} + +type headers interface { + Headers +} + +// RSAPublicKey is a type of JWK generated from RSA public keys +type RSAPublicKey struct { + headers + key *rsa.PublicKey +} + +// RSAPrivateKey is a type of JWK generated from RSA private keys +type RSAPrivateKey struct { + headers + key *rsa.PrivateKey +} + +// SymmetricKey is a type of JWK generated from symmetric keys +type SymmetricKey struct { + headers + key []byte +} + +// ECDSAPublicKey is a type of JWK generated from ECDSA public keys +type ECDSAPublicKey struct { + headers + key *ecdsa.PublicKey +} + +// ECDSAPrivateKey is a type of JWK generated from ECDH-ES private keys +type ECDSAPrivateKey struct { + headers + key *ecdsa.PrivateKey +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/internal/cmd/genheader/main.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/internal/cmd/genheader/main.go new file mode 100644 index 0000000000..87e25f1ebd --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/internal/cmd/genheader/main.go @@ -0,0 +1,368 @@ +package main + +import ( + "bytes" + "fmt" + "go/format" + "log" + "os" + "sort" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +func main() { + if err := _main(); err != nil { + log.Printf("%s", err) + os.Exit(1) + } +} + +func _main() error { + return generateHeaders() +} + +type headerField struct { + name string + method string + typ string + returnType string + key string + comment string + hasAccept bool + hasGet bool + noDeref bool + isList bool +} + +func (f headerField) IsList() bool { + return f.isList || strings.HasPrefix(f.typ, "[]") +} + +func (f headerField) IsPointer() bool { + return strings.HasPrefix(f.typ, "*") +} + +func (f headerField) PointerElem() string { + return strings.TrimPrefix(f.typ, "*") +} + +var zerovals = map[string]string{ + "string": `""`, + "jwa.KeyType": "jwa.InvalidKeyType", +} + +func zeroval(s string) string { + if v, ok := zerovals[s]; ok { + return v + } + return "nil" +} + +func generateHeaders() error { + fields := []headerField{ + { + name: `keyType`, + method: `KeyType`, + typ: `*jwa.KeyType`, + key: `kty`, + comment: `https://tools.ietf.org/html/rfc7517#section-4.1`, + hasAccept: true, + }, + { + name: `keyUsage`, + method: `KeyUsage`, + key: `use`, + typ: `*string`, + comment: `https://tools.ietf.org/html/rfc7517#section-4.2`, + }, + { + name: `keyops`, + method: `KeyOps`, + typ: `KeyOperationList`, + key: `key_ops`, + comment: `https://tools.ietf.org/html/rfc7517#section-4.3`, + hasAccept: true, + }, + { + name: `algorithm`, + method: `Algorithm`, + typ: `*string`, + key: `alg`, + comment: `https://tools.ietf.org/html/rfc7517#section-4.4`, + }, + { + name: `keyID`, + method: `KeyID`, + typ: `*string`, + key: `kid`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.4`, + }, + { + name: `x509URL`, + method: `X509URL`, + typ: `*string`, + key: `x5u`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.5`, + }, + { + name: `x509CertChain`, + method: `X509CertChain`, + typ: `*CertificateChain`, + key: `x5c`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.6`, + hasAccept: true, + hasGet: true, + noDeref: true, + returnType: `[]*x509.Certificate`, + }, + { + name: `x509CertThumbprint`, + method: `X509CertThumbprint`, + typ: `*string`, + key: `x5t`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.7`, + }, + { + name: `x509CertThumbprintS256`, + method: `X509CertThumbprintS256`, + typ: `*string`, + key: `x5t#S256`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.8`, + }, + } + + sort.Slice(fields, func(i, j int) bool { + return fields[i].name < fields[j].name + }) + + var buf bytes.Buffer + + fmt.Fprintf(&buf, "\n// This file is auto-generated. DO NOT EDIT") + fmt.Fprintf(&buf, "\n\npackage jwk") + fmt.Fprintf(&buf, "\n\nimport (") + for _, pkg := range []string{"crypto/x509", "fmt"} { + fmt.Fprintf(&buf, "\n%s", strconv.Quote(pkg)) + } + fmt.Fprintf(&buf, "\n\n") + for _, pkg := range []string{"github.com/lestrrat-go/jwx/jwa", "github.com/pkg/errors"} { + fmt.Fprintf(&buf, "\n%s", strconv.Quote(pkg)) + } + fmt.Fprintf(&buf, "\n)") + + fmt.Fprintf(&buf, "\n\nconst (") + for _, f := range fields { + fmt.Fprintf(&buf, "\n%sKey = %s", f.method, strconv.Quote(f.key)) + } + fmt.Fprintf(&buf, "\n)") // end const + + fmt.Fprintf(&buf, "\n\ntype Headers interface {") + fmt.Fprintf(&buf, "\nRemove(string)") + fmt.Fprintf(&buf, "\nGet(string) (interface{}, bool)") + fmt.Fprintf(&buf, "\nSet(string, interface{}) error") + fmt.Fprintf(&buf, "\nPopulateMap(map[string]interface{}) error") + fmt.Fprintf(&buf, "\nExtractMap(map[string]interface{}) error") + fmt.Fprintf(&buf, "\nWalk(func(string, interface{}) error) error") + for _, f := range fields { + fmt.Fprintf(&buf, "\n%s() ", f.method) + if f.returnType != "" { + fmt.Fprintf(&buf, "%s", f.returnType) + } else if f.IsPointer() && f.noDeref { + fmt.Fprintf(&buf, "%s", f.typ) + } else { + fmt.Fprintf(&buf, "%s", f.PointerElem()) + } + } + fmt.Fprintf(&buf, "\n}") // end type Headers interface + fmt.Fprintf(&buf, "\n\ntype StandardHeaders struct {") + for _, f := range fields { + fmt.Fprintf(&buf, "\n%s %s // %s", f.name, f.typ, f.comment) + } + fmt.Fprintf(&buf, "\nprivateParams map[string]interface{}") + fmt.Fprintf(&buf, "\n}") // end type StandardHeaders + + fmt.Fprintf(&buf, "\n\nfunc (h *StandardHeaders) Remove(s string) {") + fmt.Fprintf(&buf, "\ndelete(h.privateParams, s)") + fmt.Fprintf(&buf, "\n}") // func Remove(s string) + + for _, f := range fields { + fmt.Fprintf(&buf, "\n\nfunc (h *StandardHeaders) %s() ", f.method) + if f.returnType != "" { + fmt.Fprintf(&buf, "%s", f.returnType) + } else if f.IsPointer() && f.noDeref { + fmt.Fprintf(&buf, "%s", f.typ) + } else { + fmt.Fprintf(&buf, "%s", f.PointerElem()) + } + fmt.Fprintf(&buf, " {") + + if f.hasGet { + fmt.Fprintf(&buf, "\nreturn h.%s.Get()", f.name) + } else if !f.IsPointer() { + fmt.Fprintf(&buf, "\nreturn h.%s", f.name) + } else { + fmt.Fprintf(&buf, "\nif v := h.%s; v != %s {", f.name, zeroval(f.typ)) + if f.IsPointer() && !f.noDeref { + fmt.Fprintf(&buf, "\nreturn *v") + } else { + fmt.Fprintf(&buf, "\nreturn v") + } + fmt.Fprintf(&buf, "\n}") // if h.%s != %s + fmt.Fprintf(&buf, "\nreturn %s", zeroval(f.PointerElem())) + } + fmt.Fprintf(&buf, "\n}") // func (h *StandardHeaders) %s() %s + } + + fmt.Fprintf(&buf, "\n\nfunc (h *StandardHeaders) Get(name string) (interface{}, bool) {") + fmt.Fprintf(&buf, "\nswitch name {") + for _, f := range fields { + fmt.Fprintf(&buf, "\ncase %sKey:", f.method) + fmt.Fprintf(&buf, "\nv := h.%s", f.name) + if f.IsList() { + fmt.Fprintf(&buf, "\nif len(v) == 0 {") + } else { + fmt.Fprintf(&buf, "\nif v == %s {", zeroval(f.typ)) + } + fmt.Fprintf(&buf, "\nreturn nil, false") + fmt.Fprintf(&buf, "\n}") // end if h.%s == nil + if f.hasGet { + fmt.Fprintf(&buf, "\nreturn v.Get(), true") + } else if f.IsPointer() && !f.noDeref { + fmt.Fprintf(&buf, "\nreturn *v, true") + } else { + fmt.Fprintf(&buf, "\nreturn v, true") + } + } + fmt.Fprintf(&buf, "\ndefault:") + fmt.Fprintf(&buf, "\nv, ok := h.privateParams[name]") + fmt.Fprintf(&buf, "\nreturn v, ok") + fmt.Fprintf(&buf, "\n}") // end switch name + fmt.Fprintf(&buf, "\n}") // func (h *StandardHeaders) Get(name string) (interface{}, bool) + + fmt.Fprintf(&buf, "\n\nfunc (h *StandardHeaders) Set(name string, value interface{}) error {") + fmt.Fprintf(&buf, "\nswitch name {") + for _, f := range fields { + fmt.Fprintf(&buf, "\ncase %sKey:", f.method) + if f.name == "algorithm" { + fmt.Fprintf(&buf, "\nswitch v := value.(type) {") + fmt.Fprintf(&buf, "\ncase string:") + fmt.Fprintf(&buf, "\nh.algorithm = &v") + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\ncase fmt.Stringer:") + fmt.Fprintf(&buf, "\ns := v.String()") + fmt.Fprintf(&buf, "\nh.algorithm = &s") + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\nreturn errors.Errorf(`invalid value for %%s key: %%T`, AlgorithmKey, value)") + } else if f.hasAccept { + if f.IsPointer() { + fmt.Fprintf(&buf, "\nvar acceptor %s", f.PointerElem()) + fmt.Fprintf(&buf, "\nif err := acceptor.Accept(value); err != nil {") + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `invalid value for %%s key`, %sKey)", f.method) + fmt.Fprintf(&buf, "\n}") // end if err := h.%s.Accept(value) + fmt.Fprintf(&buf, "\nh.%s = &acceptor", f.name) + } else { + fmt.Fprintf(&buf, "\nif err := h.%s.Accept(value); err != nil {", f.name) + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `invalid value for %%s key`, %sKey)", f.method) + fmt.Fprintf(&buf, "\n}") // end if err := h.%s.Accept(value) + } + fmt.Fprintf(&buf, "\nreturn nil") + } else { + if f.IsPointer() { + fmt.Fprintf(&buf, "\nif v, ok := value.(%s); ok {", f.PointerElem()) + fmt.Fprintf(&buf, "\nh.%s = &v", f.name) + } else { + fmt.Fprintf(&buf, "\nif v, ok := value.(%s); ok {", f.typ) + fmt.Fprintf(&buf, "\nh.%s = v", f.name) + } + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") // end if v, ok := value.(%s) + fmt.Fprintf(&buf, "\nreturn errors.Errorf(`invalid value for %%s key: %%T`, %sKey, value)", f.method) + } + } + fmt.Fprintf(&buf, "\ndefault:") + fmt.Fprintf(&buf, "\nif h.privateParams == nil {") + fmt.Fprintf(&buf, "\nh.privateParams = map[string]interface{}{}") + fmt.Fprintf(&buf, "\n}") // end if h.privateParams == nil + fmt.Fprintf(&buf, "\nh.privateParams[name] = value") + fmt.Fprintf(&buf, "\n}") // end switch name + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") // end func (h *StandardHeaders) Set(name string, value interface{}) + + fmt.Fprintf(&buf, "\n\n// PopulateMap populates a map with appropriate values that represent") + fmt.Fprintf(&buf, "\n// the headers as a JSON object. This exists primarily because JWKs are") + fmt.Fprintf(&buf, "\n// represented as flat objects instead of differentiating the different") + fmt.Fprintf(&buf, "\n// parts of the message in separate sub objects.") + fmt.Fprintf(&buf, "\nfunc (h StandardHeaders) PopulateMap(m map[string]interface{}) error {") + fmt.Fprintf(&buf, "\nfor k, v := range h.privateParams {") + fmt.Fprintf(&buf, "\nm[k] = v") + fmt.Fprintf(&buf, "\n}") // end for k, v := range h.privateParams + for _, f := range fields { + fmt.Fprintf(&buf, "\nif v, ok := h.Get(%sKey); ok {", f.method) + fmt.Fprintf(&buf, "\nm[%sKey] = v", f.method) + fmt.Fprintf(&buf, "\n}") // end if v, ok := h.Get(%sKey); ok + } + fmt.Fprintf(&buf, "\n\nreturn nil") + fmt.Fprintf(&buf, "\n}") // func (h StandardHeaders) PopulateMap(m map[string]interface{}) + + fmt.Fprintf(&buf, "\n\n// ExtractMap populates the appropriate values from a map that represent") + fmt.Fprintf(&buf, "\n// the headers as a JSON object. This exists primarily because JWKs are") + fmt.Fprintf(&buf, "\n// represented as flat objects instead of differentiating the different") + fmt.Fprintf(&buf, "\n// parts of the message in separate sub objects.") + fmt.Fprintf(&buf, "\nfunc (h *StandardHeaders) ExtractMap(m map[string]interface{}) (err error) {") + for _, f := range fields { + fmt.Fprintf(&buf, "\nif v, ok := m[%sKey]; ok {", f.method) + fmt.Fprintf(&buf, "\nif err := h.Set(%sKey, v); err != nil {", f.method) + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `failed to set value for key %%s`, %sKey)", f.method) + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\ndelete(m, %sKey)", f.method) + fmt.Fprintf(&buf, "\n}") // end if v, ok := m[%sKey] + } + fmt.Fprintf(&buf, "\n// Fix: A nil map is different from a empty map as far as deep.equal is concerned") + fmt.Fprintf(&buf, "\nif len(m) > 0 {") + fmt.Fprintf(&buf, "\nh.privateParams = m") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\n\nreturn nil") + fmt.Fprintf(&buf, "\n}") // end func (h *StandardHeaders) ExtractMap(m map[string]interface{}) error + + fmt.Fprintf(&buf, "\n\nfunc (h StandardHeaders) Walk(f func(string, interface{}) error) error {") + fmt.Fprintf(&buf, "\nfor _, key := range []string{") + for i, field := range fields { + fmt.Fprintf(&buf, "%sKey", field.method) + if i < len(fields)-1 { + fmt.Fprintf(&buf, ", ") + } + } + fmt.Fprintf(&buf, "} {") + fmt.Fprintf(&buf, "\nif v, ok := h.Get(key); ok {") + fmt.Fprintf(&buf, "\nif err := f(key, v); err != nil {") + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `walk function returned error for %%s`, key)") + fmt.Fprintf(&buf, "\n}") // end if err := f(key, v); err != nil + fmt.Fprintf(&buf, "\n}") // end if v, ok := h.Get(key); ok + fmt.Fprintf(&buf, "\n}") // end for _, key := range []string{} + + fmt.Fprintf(&buf, "\n\nfor k, v := range h.privateParams {") + fmt.Fprintf(&buf, "\nif err := f(k, v); err != nil {") + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `walk function returned error for %%s`, k)") + fmt.Fprintf(&buf, "\n}") // end if err := f(key, v); err != nil + fmt.Fprintf(&buf, "\n}") // end for k, v := range h.privateParams + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") // end func (h StandardHeaders) Walk(f func(string, interface{}) error) + + formatted, err := format.Source(buf.Bytes()) + if err != nil { + buf.WriteTo(os.Stdout) + return errors.Wrap(err, `failed to format code`) + } + + f, err := os.Create("headers_gen.go") + if err != nil { + return errors.Wrap(err, `failed to open headers_gen.go`) + } + defer f.Close() + f.Write(formatted) + + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/jwk.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/jwk.go new file mode 100644 index 0000000000..741e683de7 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/jwk.go @@ -0,0 +1,271 @@ +//go:generate go run internal/cmd/genheader/main.go + +// Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517 +package jwk + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rsa" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" + + "github.com/lestrrat-go/jwx/internal/base64" + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +// GetPublicKey returns the public key based on te private key type. +// For rsa key types *rsa.PublicKey is returned; for ecdsa key types *ecdsa.PublicKey; +// for byte slice (raw) keys, the key itself is returned. If the corresponding +// public key cannot be deduced, an error is returned +func GetPublicKey(key interface{}) (interface{}, error) { + if key == nil { + return nil, errors.New(`jwk.New requires a non-nil key`) + } + + switch v := key.(type) { + // Mental note: although Public() is defined in both types, + // you can not coalesce the clauses for rsa.PrivateKey and + // ecdsa.PrivateKey, as then `v` becomes interface{} + // b/c the compiler cannot deduce the exact type. + case *rsa.PrivateKey: + return v.Public(), nil + case *ecdsa.PrivateKey: + return v.Public(), nil + case []byte: + return v, nil + default: + return nil, errors.Errorf(`invalid key type %T`, key) + } +} + +// New creates a jwk.Key from the given key. +func New(key interface{}) (Key, error) { + if key == nil { + return nil, errors.New(`jwk.New requires a non-nil key`) + } + + switch v := key.(type) { + case *rsa.PrivateKey: + return newRSAPrivateKey(v) + case *rsa.PublicKey: + return newRSAPublicKey(v) + case *ecdsa.PrivateKey: + return newECDSAPrivateKey(v) + case *ecdsa.PublicKey: + return newECDSAPublicKey(v) + case []byte: + return newSymmetricKey(v) + default: + return nil, errors.Errorf(`invalid key type %T`, key) + } +} + +// Fetch fetches a JWK resource specified by a URL +func Fetch(urlstring string, options ...Option) (*Set, error) { + u, err := url.Parse(urlstring) + if err != nil { + return nil, errors.Wrap(err, `failed to parse url`) + } + + switch u.Scheme { + case "http", "https": + return FetchHTTP(urlstring, options...) + case "file": + f, err := os.Open(u.Path) + if err != nil { + return nil, errors.Wrap(err, `failed to open jwk file`) + } + defer f.Close() + + buf, err := ioutil.ReadAll(f) + if err != nil { + return nil, errors.Wrap(err, `failed read content from jwk file`) + } + return ParseBytes(buf) + } + return nil, errors.Errorf(`invalid url scheme %s`, u.Scheme) +} + +// FetchHTTP fetches the remote JWK and parses its contents +func FetchHTTP(jwkurl string, options ...Option) (*Set, error) { + var httpcl HTTPClient = http.DefaultClient + for _, option := range options { + switch option.Name() { + case optkeyHTTPClient: + httpcl = option.Value().(HTTPClient) + } + } + + res, err := httpcl.Get(jwkurl) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch remote JWK") + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch remote JWK (status = %d)", res.StatusCode) + } + + buf, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, errors.Wrap(err, "failed to read JWK HTTP response body") + } + + return ParseBytes(buf) +} + +func (set *Set) UnmarshalJSON(data []byte) error { + v, err := ParseBytes(data) + if err != nil { + return errors.Wrap(err, `failed to parse jwk.Set`) + } + *set = *v + return nil +} + +// Parse parses JWK from the incoming io.Reader. +func Parse(in io.Reader) (*Set, error) { + m := make(map[string]interface{}) + if err := json.NewDecoder(in).Decode(&m); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal JWK") + } + + // We must change what the underlying structure that gets decoded + // out of this JSON is based on parameters within the already parsed + // JSON (m). In order to do this, we have to go through the tedious + // task of parsing the contents of this map :/ + if _, ok := m["keys"]; ok { + var set Set + if err := set.ExtractMap(m); err != nil { + return nil, errors.Wrap(err, `failed to extract from map`) + } + return &set, nil + } + + k, err := constructKey(m) + if err != nil { + return nil, errors.Wrap(err, `failed to construct key from keys`) + } + return &Set{Keys: []Key{k}}, nil +} + +// ParseBytes parses JWK from the incoming byte buffer. +func ParseBytes(buf []byte) (*Set, error) { + return Parse(bytes.NewReader(buf)) +} + +// ParseString parses JWK from the incoming string. +func ParseString(s string) (*Set, error) { + return Parse(strings.NewReader(s)) +} + +// LookupKeyID looks for keys matching the given key id. Note that the +// Set *may* contain multiple keys with the same key id +func (s Set) LookupKeyID(kid string) []Key { + var keys []Key + for _, key := range s.Keys { + if key.KeyID() == kid { + keys = append(keys, key) + } + } + return keys +} + +func (s *Set) ExtractMap(m map[string]interface{}) error { + raw, ok := m["keys"] + if !ok { + return errors.New("missing 'keys' parameter") + } + + v, ok := raw.([]interface{}) + if !ok { + return errors.New("invalid 'keys' parameter") + } + + var ks Set + for _, c := range v { + conf, ok := c.(map[string]interface{}) + if !ok { + return errors.New("invalid element in 'keys'") + } + + k, err := constructKey(conf) + if err != nil { + return errors.Wrap(err, `failed to construct key from map`) + } + ks.Keys = append(ks.Keys, k) + } + + *s = ks + return nil +} + +func constructKey(m map[string]interface{}) (Key, error) { + kty, ok := m[KeyTypeKey].(string) + if !ok { + return nil, errors.Errorf(`unsupported kty type %T`, m[KeyTypeKey]) + } + + var key Key + switch jwa.KeyType(kty) { + case jwa.RSA: + if _, ok := m["d"]; ok { + key = &RSAPrivateKey{} + } else { + key = &RSAPublicKey{} + } + case jwa.EC: + if _, ok := m["d"]; ok { + key = &ECDSAPrivateKey{} + } else { + key = &ECDSAPublicKey{} + } + case jwa.OctetSeq: + key = &SymmetricKey{} + default: + return nil, errors.Errorf(`invalid kty %s`, kty) + } + + if err := key.ExtractMap(m); err != nil { + return nil, errors.Wrap(err, `failed to extract key from map`) + } + + return key, nil +} + +func getRequiredKey(m map[string]interface{}, key string) ([]byte, error) { + return getKey(m, key, true) +} + +func getOptionalKey(m map[string]interface{}, key string) ([]byte, error) { + return getKey(m, key, false) +} + +func getKey(m map[string]interface{}, key string, required bool) ([]byte, error) { + v, ok := m[key] + if !ok { + if !required { + return nil, errors.Errorf(`missing parameter '%s'`, key) + } + return nil, errors.Errorf(`missing required parameter '%s'`, key) + } + + vs, ok := v.(string) + if !ok { + return nil, errors.Errorf(`invalid type for parameter '%s': %T`, key, v) + } + + buf, err := base64.DecodeString(vs) + if err != nil { + return nil, errors.Wrapf(err, `failed to base64 decode key %s`, key) + } + return buf, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/jwk_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/jwk_test.go new file mode 100644 index 0000000000..ef249ab396 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/jwk_test.go @@ -0,0 +1,563 @@ +package jwk_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/lestrrat-go/jwx/internal/base64" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + k, err := jwk.New(nil) + if !assert.Nil(t, k, "key should be nil") { + return + } + if !assert.Error(t, err, "nil key should cause an error") { + return + } +} + +func TestParse(t *testing.T) { + verify := func(t *testing.T, src string, expected interface{}) { + t.Run("json.Unmarshal", func(t *testing.T) { + var set jwk.Set + if err := json.Unmarshal([]byte(src), &set); !assert.NoError(t, err, `json.Unmarshal should succeed`) { + return + } + + if !assert.True(t, len(set.Keys) > 0, "set.Keys should be greater than 0") { + return + } + for _, key := range set.Keys { + if !assert.IsType(t, expected, key, "key should be a jwk.RSAPublicKey") { + return + } + } + }) + t.Run("jwk.Parse", func(t *testing.T) { + set, err := jwk.ParseBytes([]byte(src)) + if !assert.NoError(t, err, `jwk.Parse should succeed`) { + return + } + + if !assert.True(t, len(set.Keys) > 0, "set.Keys should be greater than 0") { + return + } + for _, key := range set.Keys { + if !assert.IsType(t, expected, key, "key should be a jwk.RSAPublicKey") { + return + } + + switch key := key.(type) { + case *jwk.RSAPrivateKey, *jwk.ECDSAPrivateKey: + realKey, err := key.(jwk.Key).Materialize() + if !assert.NoError(t, err, "failed to get underlying private key") { + return + } + + if _, err := jwk.GetPublicKey(realKey); !assert.NoError(t, err, `failed to get public key from underlying private key`) { + return + } + } + } + }) + } + + t.Run("RSA Public Key", func(t *testing.T) { + const src = `{ + "e":"AQAB", + "kty":"RSA", + "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" + }` + verify(t, src, &jwk.RSAPublicKey{}) + }) + t.Run("RSA Private Key", func(t *testing.T) { + const src = `{ + "kty":"RSA", + "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e":"AQAB", + "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", + "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", + "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", + "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", + "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", + "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", + "alg":"RS256", + "kid":"2011-04-29" + }` + verify(t, src, &jwk.RSAPrivateKey{}) + }) + t.Run("ECDSA Private Key", func(t *testing.T) { + const src = `{ + "kty" : "EC", + "crv" : "P-256", + "x" : "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", + "y" : "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", + "d" : "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk" + }` + verify(t, src, &jwk.ECDSAPrivateKey{}) + }) + t.Run("Invalid ECDSA Private Key", func(t *testing.T) { + const src = `{ + "kty" : "EC", + "crv" : "P-256", + "y" : "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", + "d" : "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk" + }` + _, err := jwk.ParseString(src) + if !assert.Error(t, err, `jwk.ParseString should fail`) { + return + } + }) +} + +func TestRoundtrip(t *testing.T) { + generateRSA := func(use string, keyID string) (jwk.Key, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, errors.Wrap(err, `failed to generate RSA private key`) + } + + k, err := jwk.New(key) + if err != nil { + return nil, errors.Wrap(err, `failed to generate jwk.RSAPrivateKey`) + } + + k.Set(jwk.KeyUsageKey, use) + k.Set(jwk.KeyIDKey, keyID) + return k, nil + } + + generateECDSA := func(use, keyID string) (jwk.Key, error) { + key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return nil, errors.Wrap(err, `failed to generate ECDSA private key`) + } + + k, err := jwk.New(key) + if err != nil { + return nil, errors.Wrap(err, `failed to generate jwk.ECDSAPrivateKey`) + } + + k.Set(jwk.KeyUsageKey, use) + k.Set(jwk.KeyIDKey, keyID) + return k, nil + } + + generateSymmetric := func(use, keyID string) (jwk.Key, error) { + sharedKey := make([]byte, 64) + rand.Read(sharedKey) + + key, err := jwk.New(sharedKey) + if err != nil { + return nil, errors.Wrap(err, `failed to generate jwk.SymmetricKey`) + } + + key.Set(jwk.KeyUsageKey, use) + key.Set(jwk.KeyIDKey, keyID) + return key, nil + } + + tests := []struct { + use string + keyID string + generate func(string, string) (jwk.Key, error) + }{ + { + use: "enc", + keyID: "enc1", + generate: generateRSA, + }, + { + use: "enc", + keyID: "enc2", + generate: generateRSA, + }, + { + use: "sig", + keyID: "sig1", + generate: generateRSA, + }, + { + use: "sig", + keyID: "sig2", + generate: generateRSA, + }, + { + use: "sig", + keyID: "sig3", + generate: generateSymmetric, + }, + { + use: "enc", + keyID: "enc4", + generate: generateECDSA, + }, + { + use: "enc", + keyID: "enc5", + generate: generateECDSA, + }, + { + use: "sig", + keyID: "sig4", + generate: generateECDSA, + }, + { + use: "sig", + keyID: "sig5", + generate: generateECDSA, + }, + } + + var ks1 jwk.Set + for _, tc := range tests { + key, err := tc.generate(tc.use, tc.keyID) + if !assert.NoError(t, err, `tc.generate should succeed`) { + return + } + ks1.Keys = append(ks1.Keys, key) + + } + + buf, err := json.MarshalIndent(ks1, "", " ") + if !assert.NoError(t, err, "JSON marshal succeeded") { + return + } + + ks2, err := jwk.ParseBytes(buf) + if !assert.NoError(t, err, "JSON unmarshal succeeded") { + t.Logf("%s", buf) + return + } + + for _, tc := range tests { + keys := ks2.LookupKeyID(tc.keyID) + if !assert.Len(t, keys, 1, "Should be 1 key") { + return + } + key1 := keys[0] + + keys = ks1.LookupKeyID(tc.keyID) + if !assert.Len(t, keys, 1, "Should be 1 key") { + return + } + + key2 := keys[0] + + pk1json, _ := json.Marshal(key1) + pk2json, _ := json.Marshal(key2) + if !assert.Equal(t, pk1json, pk2json, "Keys should match (kid = %s)", tc.keyID) { + return + } + } +} + +/* + +func TestJwksSerializationPadding(t *testing.T) { + x := new(big.Int) + y := new(big.Int) + + e := &EssentialHeader{} + e.KeyType = jwa.EC + x.SetString("123520477547912006148785171019615806128401248503564636913311359802381551887648525354374204836279603443398171853465", 10) + y.SetString("13515585925570416130130241699780319456178918334914981404162640338265336278264431930522217750520011829472589865088261", 10) + pubKey := &ecdsa.PublicKey{ + Curve: elliptic.P384(), + X: x, + Y: y, + } + jwkPubKey := NewEcdsaPublicKey(pubKey) + jwkPubKey.EssentialHeader = e + jwkJSON, err := json.Marshal(jwkPubKey) + if !assert.NoError(t, err, "JWK Marshalled") { + return + } + + _, err = Parse(jwkJSON) + if !assert.NoError(t, err, "JWK Parsed") { + return + } + +} + +func TestRSAPrivateKey(t *testing.T) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if !assert.NoError(t, err, "RSA key generated") { + return + } + + k1, err := NewRSAPrivateKey(key) + if !assert.NoError(t, err, "JWK RSA key generated") { + return + } + + jsonbuf, err := json.MarshalIndent(k1, "", " ") + if !assert.NoError(t, err, "Marshal to JSON succeeded") { + return + } + + t.Logf("%s", jsonbuf) + + k2 := &RSAPrivateKey{} + if !assert.NoError(t, json.Unmarshal(jsonbuf, k2), "Unmarshal from JSON succeeded") { + return + } + + if !assert.Equal(t, k1, k2, "keys match") { + return + } + + k3, err := Parse(jsonbuf) + if !assert.NoError(t, err, "Parse should succeed") { + return + } + + if !assert.Equal(t, k1, k3.Keys[0], "keys match") { + return + } +} +*/ + +func TestAppendix(t *testing.T) { + t.Run("A1", func(t *testing.T) { + var jwksrc = []byte(`{"keys": + [ + {"kty":"EC", + "crv":"P-256", + "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "use":"enc", + "kid":"1"}, + + {"kty":"RSA", + "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e":"AQAB", + "alg":"RS256", + "kid":"2011-04-29"} + ] + }`) + + set, err := jwk.ParseBytes(jwksrc) + if !assert.NoError(t, err, "Parse should succeed") { + return + } + + if !assert.Len(t, set.Keys, 2, "There should be 2 keys") { + return + } + + { + key, ok := set.Keys[0].(*jwk.ECDSAPublicKey) + if !assert.True(t, ok, "set.Keys[0] should be a EcdsaPublicKey") { + return + } + + if !assert.Equal(t, jwa.P256, key.Curve(), "curve is P-256") { + return + } + } + }) + + t.Run("A3", func(t *testing.T) { + const ( + key1 = `GawgguFyGrWKav7AX4VKUg` + key2 = `AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow` + ) + + buf1, err := base64.DecodeString(key1) + if !assert.NoError(t, err, "failed to decode key1") { + return + } + + buf2, err := base64.DecodeString(key2) + if !assert.NoError(t, err, "failed to decode key2") { + return + } + + var jwksrc = []byte(`{"keys": + [ + {"kty":"oct", + "alg":"A128KW", + "k":"` + key1 + `"}, + + {"kty":"oct", + "k":"` + key2 + `", + "kid":"HMAC key used in JWS spec Appendix A.1 example"} + ] + }`) + set, err := jwk.ParseBytes(jwksrc) + if !assert.NoError(t, err, "Parse should succeed") { + return + } + + tests := []struct { + headers map[string]interface{} + key []byte + }{ + { + headers: map[string]interface{}{ + jwk.KeyTypeKey: jwa.OctetSeq, + jwk.AlgorithmKey: jwa.A128KW.String(), + }, + key: buf1, + }, + { + headers: map[string]interface{}{ + jwk.KeyTypeKey: jwa.OctetSeq, + jwk.KeyIDKey: "HMAC key used in JWS spec Appendix A.1 example", + }, + key: buf2, + }, + } + + for i, data := range tests { + key, ok := set.Keys[i].(*jwk.SymmetricKey) + if !assert.True(t, ok, "set.Keys[%d] should be a SymmetricKey", i) { + return + } + + ckey, err := key.Materialize() + if !assert.NoError(t, err, "materialized key") { + return + } + + if !assert.Equal(t, data.key, ckey, `key byte sequence should match`) { + return + } + + for k, expected := range data.headers { + t.Run(k, func(t *testing.T) { + if v, ok := key.Get(k); assert.True(t, ok, "getting %s from jwk.Key should succeed", k) { + if !assert.Equal(t, expected, v, "value for %s should match", k) { + return + } + } + }) + } + } + }) + t.Run("B", func(t *testing.T) { + var jwksrc = []byte(`{"keys": + [ + {"kty":"RSA", + "use":"sig", + "kid":"1b94c", + "n":"vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08PLbK_PdiVGKPrqzmDIsLI7sA25VEnHU1uCLNwBuUiCO11_-7dYbsr4iJmG0Qu2j8DsVyT1azpJC_NG84Ty5KKthuCaPod7iI7w0LK9orSMhBEwwZDCxTWq4aYWAchc8t-emd9qOvWtVMDC2BXksRngh6X5bUYLy6AyHKvj-nUy1wgzjYQDwHMTplCoLtU-o-8SNnZ1tmRoGE9uJkBLdh5gFENabWnU5m1ZqZPdwS-qo-meMvVfJb6jJVWRpl2SUtCnYG2C32qvbWbjZ_jBPD5eunqsIo1vQ", + "e":"AQAB", + "x5c": [ + "MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=", + "MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==", + "MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd" + ] + }]}`) + + set, err := jwk.ParseBytes(jwksrc) + if !assert.NoError(t, err, "Parse should succeed") { + return + } + if !assert.Len(t, set.Keys, 1, "There should be 1 key") { + return + } + + { + key, ok := set.Keys[0].(*jwk.RSAPublicKey) + if !assert.True(t, ok, "set.Keys[0] should be a jwk.RSAPublicKey") { + return + } + if !assert.Len(t, key.X509CertChain(), 3, "key.X509CertChain should be 3 cert") { + return + } + } + }) +} + +func TestFetch(t *testing.T) { + const jwksrc = `{"keys": + [ + {"kty":"EC", + "crv":"P-256", + "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "use":"enc", + "kid":"1"}, + + {"kty":"RSA", + "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e":"AQAB", + "alg":"RS256", + "kid":"2011-04-29"} + ] + }` + + verify := func(t *testing.T, set *jwk.Set) { + key, ok := set.Keys[0].(*jwk.ECDSAPublicKey) + if !assert.True(t, ok, "set.Keys[0] should be a EcdsaPublicKey") { + return + } + + if !assert.Equal(t, jwa.P256, key.Curve(), "curve is P-256") { + return + } + } + t.Run("HTTP", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/": + w.WriteHeader(http.StatusOK) + io.WriteString(w, jwksrc) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + defer srv.Close() + + cl := srv.Client() + + set, err := jwk.Fetch(srv.URL, jwk.WithHTTPClient(cl)) + if !assert.NoError(t, err, `failed to fetch jwk`) { + return + } + verify(t, set) + }) + t.Run("Local File", func(t *testing.T) { + f, err := ioutil.TempFile("", "jwk-fetch-test") + if !assert.NoError(t, err, `failed to generate temporary file`) { + return + } + defer f.Close() + defer os.Remove(f.Name()) + + io.WriteString(f, jwksrc) + f.Sync() + + set, err := jwk.Fetch("file://" + f.Name()) + if !assert.NoError(t, err, `failed to fetch jwk`) { + return + } + verify(t, set) + }) + t.Run("Invalid Scheme", func(t *testing.T) { + set, err := jwk.Fetch("gopher://foo/bar") + if !assert.Nil(t, set, `set should be nil`) { + return + } + if !assert.Error(t, err, `invalid sche,e should be an error`) { + return + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/key_ops.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/key_ops.go new file mode 100644 index 0000000000..ed3316d3b0 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/key_ops.go @@ -0,0 +1,45 @@ +package jwk + +import "github.com/pkg/errors" + +func (ops *KeyOperationList) Get() KeyOperationList { + if ops == nil { + return nil + } + return *ops +} + +func (ops *KeyOperationList) Accept(v interface{}) error { + switch x := v.(type) { + case string: + return ops.Accept([]string{x}) + case []interface{}: + l := make([]string, len(x)) + for i, e := range x { + if es, ok := e.(string); ok { + l[i] = es + } else { + return errors.Errorf(`invalid list element type: expected string, got %T`, v) + } + } + return ops.Accept(l) + case []string: + list := make([]KeyOperation, len(x)) + for i, e := range x { + switch e := KeyOperation(e); e { + case KeyOpSign, KeyOpVerify, KeyOpEncrypt, KeyOpDecrypt, KeyOpWrapKey, KeyOpUnwrapKey, KeyOpDeriveKey, KeyOpDeriveBits: + list[i] = e + default: + return errors.Errorf(`invalid keyoperation %v`, e) + } + } + + *ops = list + return nil + case KeyOperationList: + *ops = x + return nil + default: + return errors.Errorf(`invalid value %T`, v) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/option.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/option.go new file mode 100644 index 0000000000..dd55019a90 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/option.go @@ -0,0 +1,21 @@ +package jwk + +import ( + "net/http" + + "github.com/lestrrat-go/jwx/internal/option" +) + +type Option = option.Interface + +const ( + optkeyHTTPClient = `http-client` +) + +type HTTPClient interface { + Get(string) (*http.Response, error) +} + +func WithHTTPClient(cl HTTPClient) Option { + return option.New(optkeyHTTPClient, cl) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/rsa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/rsa.go new file mode 100644 index 0000000000..d8f92adc07 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/rsa.go @@ -0,0 +1,311 @@ +package jwk + +import ( + "bytes" + "crypto" + "crypto/rsa" + "encoding/json" + "math/big" + + "github.com/lestrrat-go/jwx/internal/base64" + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +func newRSAPublicKey(key *rsa.PublicKey) (*RSAPublicKey, error) { + if key == nil { + return nil, errors.New(`non-nil rsa.PublicKey required`) + } + + var hdr StandardHeaders + hdr.Set(KeyTypeKey, jwa.RSA) + return &RSAPublicKey{ + headers: &hdr, + key: key, + }, nil +} + +func newRSAPrivateKey(key *rsa.PrivateKey) (*RSAPrivateKey, error) { + if key == nil { + return nil, errors.New(`non-nil rsa.PrivateKey required`) + } + + if len(key.Primes) < 2 { + return nil, errors.New("two primes required for RSA private key") + } + + var hdr StandardHeaders + hdr.Set(KeyTypeKey, jwa.RSA) + return &RSAPrivateKey{ + headers: &hdr, + key: key, + }, nil +} + +func (k RSAPrivateKey) PublicKey() (*RSAPublicKey, error) { + return newRSAPublicKey(&k.key.PublicKey) +} + +func (k *RSAPublicKey) Materialize() (interface{}, error) { + if k.key == nil { + return nil, errors.New(`key has no rsa.PublicKey associated with it`) + } + return k.key, nil +} + +func (k *RSAPrivateKey) Materialize() (interface{}, error) { + if k.key == nil { + return nil, errors.New(`key has no rsa.PrivateKey associated with it`) + } + return k.key, nil +} + +func (k RSAPublicKey) MarshalJSON() (buf []byte, err error) { + + m := map[string]interface{}{} + if err := k.PopulateMap(m); err != nil { + return nil, errors.Wrap(err, `failed to populate public key values`) + } + + return json.Marshal(m) +} + +func (k RSAPublicKey) PopulateMap(m map[string]interface{}) (err error) { + + if err := k.headers.PopulateMap(m); err != nil { + return errors.Wrap(err, `failed to populate header values`) + } + + m[`n`] = base64.EncodeToString(k.key.N.Bytes()) + m[`e`] = base64.EncodeUint64ToString(uint64(k.key.E)) + + return nil +} + +func (k *RSAPublicKey) UnmarshalJSON(data []byte) (err error) { + + m := map[string]interface{}{} + if err := json.Unmarshal(data, &m); err != nil { + return errors.Wrap(err, `failed to unmarshal public key`) + } + + if err := k.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract data from map`) + } + return nil +} + +func (k *RSAPublicKey) ExtractMap(m map[string]interface{}) (err error) { + + const ( + eKey = `e` + nKey = `n` + ) + + nbuf, err := getRequiredKey(m, nKey) + if err != nil { + return errors.Wrapf(err, `failed to get required key %s`, nKey) + } + delete(m, nKey) + + ebuf, err := getRequiredKey(m, eKey) + if err != nil { + return errors.Wrapf(err, `failed to get required key %s`, eKey) + } + delete(m, eKey) + + var n, e big.Int + n.SetBytes(nbuf) + e.SetBytes(ebuf) + + var hdrs StandardHeaders + if err := hdrs.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract header values`) + } + + *k = RSAPublicKey{ + headers: &hdrs, + key: &rsa.PublicKey{E: int(e.Int64()), N: &n}, + } + return nil +} + +func (k RSAPrivateKey) MarshalJSON() (buf []byte, err error) { + + m := make(map[string]interface{}) + if err := k.PopulateMap(m); err != nil { + return nil, errors.Wrap(err, `failed to populate private key values`) + } + + return json.Marshal(m) +} + +func (k RSAPrivateKey) PopulateMap(m map[string]interface{}) (err error) { + + const ( + dKey = `d` + pKey = `p` + qKey = `q` + dpKey = `dp` + dqKey = `dq` + qiKey = `qi` + ) + + if err := k.headers.PopulateMap(m); err != nil { + return errors.Wrap(err, `failed to populate header values`) + } + + pubkey, _ := newRSAPublicKey(&k.key.PublicKey) + if err := pubkey.PopulateMap(m); err != nil { + return errors.Wrap(err, `failed to populate public key values`) + } + + if err := k.headers.PopulateMap(m); err != nil { + return errors.Wrap(err, `failed to populate header values`) + } + m[dKey] = base64.EncodeToString(k.key.D.Bytes()) + m[pKey] = base64.EncodeToString(k.key.Primes[0].Bytes()) + m[qKey] = base64.EncodeToString(k.key.Primes[1].Bytes()) + if v := k.key.Precomputed.Dp; v != nil { + m[dpKey] = base64.EncodeToString(v.Bytes()) + } + if v := k.key.Precomputed.Dq; v != nil { + m[dqKey] = base64.EncodeToString(v.Bytes()) + } + if v := k.key.Precomputed.Qinv; v != nil { + m[qiKey] = base64.EncodeToString(v.Bytes()) + } + return nil +} + +func (k *RSAPrivateKey) UnmarshalJSON(data []byte) (err error) { + + m := map[string]interface{}{} + if err := json.Unmarshal(data, &m); err != nil { + return errors.Wrap(err, `failed to unmarshal public key`) + } + + var key RSAPrivateKey + if err := key.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract data from map`) + } + *k = key + + return nil +} + +func (k *RSAPrivateKey) ExtractMap(m map[string]interface{}) (err error) { + + const ( + dKey = `d` + pKey = `p` + qKey = `q` + dpKey = `dp` + dqKey = `dq` + qiKey = `qi` + ) + + dbuf, err := getRequiredKey(m, dKey) + if err != nil { + return errors.Wrap(err, `failed to get required key`) + } + delete(m, dKey) + + pbuf, err := getRequiredKey(m, pKey) + if err != nil { + return errors.Wrap(err, `failed to get required key`) + } + delete(m, pKey) + + qbuf, err := getRequiredKey(m, qKey) + if err != nil { + return errors.Wrap(err, `failed to get required key`) + } + delete(m, qKey) + + var d, q, p big.Int + d.SetBytes(dbuf) + q.SetBytes(qbuf) + p.SetBytes(pbuf) + + var dp, dq, qi *big.Int + + dpbuf, err := getOptionalKey(m, dpKey) + if err == nil { + delete(m, dpKey) + + dp = &big.Int{} + dp.SetBytes(dpbuf) + } + + dqbuf, err := getOptionalKey(m, dqKey) + if err == nil { + delete(m, dqKey) + + dq = &big.Int{} + dq.SetBytes(dqbuf) + } + + qibuf, err := getOptionalKey(m, qiKey) + if err == nil { + delete(m, qiKey) + + qi = &big.Int{} + qi.SetBytes(qibuf) + } + + var pubkey RSAPublicKey + if err := pubkey.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract fields for public key`) + } + + materialized, err := pubkey.Materialize() + if err != nil { + return errors.Wrap(err, `failed to materialize RSA public key`) + } + rsaPubkey := materialized.(*rsa.PublicKey) + + var key rsa.PrivateKey + key.PublicKey = *rsaPubkey + key.D = &d + key.Primes = []*big.Int{&p, &q} + + if dp != nil { + key.Precomputed.Dp = dp + } + if dq != nil { + key.Precomputed.Dq = dq + } + if qi != nil { + key.Precomputed.Qinv = qi + } + + *k = RSAPrivateKey{ + headers: pubkey.headers, + key: &key, + } + return nil +} + +// Thumbprint returns the JWK thumbprint using the indicated +// hashing algorithm, according to RFC 7638 +func (k RSAPrivateKey) Thumbprint(hash crypto.Hash) ([]byte, error) { + return rsaThumbprint(hash, &k.key.PublicKey) +} + +func (k RSAPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) { + return rsaThumbprint(hash, k.key) +} + +func rsaThumbprint(hash crypto.Hash, key *rsa.PublicKey) ([]byte, error) { + var buf bytes.Buffer + buf.WriteString(`{"e":"`) + buf.WriteString(base64.EncodeUint64ToString(uint64(key.E))) + buf.WriteString(`","kty":"RSA","n":"`) + buf.WriteString(base64.EncodeToString(key.N.Bytes())) + buf.WriteString(`"}`) + + h := hash.New() + buf.WriteTo(h) + return h.Sum(nil), nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/rsa_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/rsa_test.go new file mode 100644 index 0000000000..d3d1cb1dc5 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/rsa_test.go @@ -0,0 +1,184 @@ +package jwk_test + +import ( + "crypto" + "crypto/rsa" + "encoding/json" + "strings" + "testing" + + "github.com/lestrrat-go/jwx/jwk" + "github.com/stretchr/testify/assert" +) + +func TestRSA(t *testing.T) { + verify := func(t *testing.T, key jwk.Key) { + t.Helper() + + rsaKey, err := key.Materialize() + if !assert.NoError(t, err, `Materialize() should succeed`) { + return + } + + newKey, err := jwk.New(rsaKey) + if !assert.NoError(t, err, `jwk.New should succeed`) { + return + } + + key.Walk(func(k string, v interface{}) error { + return newKey.Set(k, v) + }) + + jsonbuf1, err := json.Marshal(key) + if !assert.NoError(t, err, `json.Marshal should succeed`) { + return + } + + jsonbuf2, err := json.Marshal(newKey) + if !assert.NoError(t, err, `json.Marshal should succeed`) { + return + } + + if !assert.Equal(t, jsonbuf1, jsonbuf2, `generated JSON buffers should match`) { + t.Logf("%s", jsonbuf1) + t.Logf("%s", jsonbuf2) + return + } + } + t.Run("Public Key", func(t *testing.T) { + const src = `{ + "e":"AQAB", + "kty":"RSA", + "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" + }` + + var key jwk.RSAPublicKey + if !assert.NoError(t, json.Unmarshal([]byte(src), &key), `json.Unmarshal should succeed`) { + return + } + verify(t, &key) + }) + t.Run("Private Key", func(t *testing.T) { + const src = `{ + "kty":"RSA", + "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e":"AQAB", + "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", + "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", + "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", + "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", + "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", + "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", + "alg":"RS256", + "kid":"2011-04-29" + }` + var key jwk.RSAPrivateKey + if !assert.NoError(t, json.Unmarshal([]byte(src), &key), `json.Unmarshal should succeed`) { + return + } + verify(t, &key) + }) + t.Run("Private Key", func(t *testing.T) { + s := `{"keys": + [ + {"kty":"RSA", + "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e":"AQAB", + "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", + "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", + "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", + "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", + "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", + "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", + "alg":"RS256", + "kid":"2011-04-29"} + ] + }` + set, err := jwk.ParseString(s) + if !assert.NoError(t, err, "Parsing private key is successful") { + return + } + + rsakey, ok := set.Keys[0].(*jwk.RSAPrivateKey) + if !assert.True(t, ok, "Type assertion for RSAPrivateKey is successful") { + return + } + + var privkey *rsa.PrivateKey + var pubkey *rsa.PublicKey + + { + pkey, err := rsakey.PublicKey() + if !assert.NoError(t, err, "rsakey.PublickKey is successful") { + return + } + + mkey, err := pkey.Materialize() + if !assert.NoError(t, err, "RSAPublickKey.Materialize is successful") { + return + } + var ok bool + pubkey, ok = mkey.(*rsa.PublicKey) + if !assert.True(t, ok, "Materialized key is a *rsa.PublicKey") { + return + } + } + + if !assert.NotEmpty(t, pubkey.N, "N exists") { + return + } + + if !assert.NotEmpty(t, pubkey.E, "E exists") { + return + } + + { + mkey, err := rsakey.Materialize() + if !assert.NoError(t, err, "RSAPrivateKey.Materialize is successful") { + return + } + var ok bool + privkey, ok = mkey.(*rsa.PrivateKey) + if !assert.True(t, ok, "Materialized key is a *rsa.PrivateKey") { + return + } + } + + if !assert.NotEmpty(t, privkey.Precomputed.Dp, "Dp exists") { + return + } + + if !assert.NotEmpty(t, privkey.Precomputed.Dq, "Dq exists") { + return + } + + if !assert.NotEmpty(t, privkey.Precomputed.Qinv, "Qinv exists") { + return + } + }) + t.Run("Thumbprint", func(t *testing.T) { + expected := []byte{55, 54, 203, 177, 120, 124, 184, 48, 156, 119, 238, + 140, 55, 5, 197, 225, 111, 251, 158, 133, 151, 21, 144, 31, 30, 76, 89, + 177, 17, 130, 245, 123, + } + const src = `{ + "kty":"RSA", + "e": "AQAB", + "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" + }` + + var key jwk.RSAPublicKey + if err := json.NewDecoder(strings.NewReader(src)).Decode(&key); !assert.NoError(t, err, `json.Unmarshal should succeed`) { + return + } + + tp, err := key.Thumbprint(crypto.SHA256) + if !assert.NoError(t, err, "Thumbprint should succeed") { + return + } + + if !assert.Equal(t, expected, tp, "Thumbprint should match") { + return + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/symmetric.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/symmetric.go new file mode 100644 index 0000000000..59e5132e94 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwk/symmetric.go @@ -0,0 +1,87 @@ +package jwk + +import ( + "crypto" + "encoding/json" + "fmt" + "github.com/lestrrat-go/jwx/internal/base64" + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +func newSymmetricKey(key []byte) (*SymmetricKey, error) { + if len(key) == 0 { + return nil, errors.New(`non-empty []byte key required`) + } + + var hdr StandardHeaders + hdr.Set(KeyTypeKey, jwa.OctetSeq) + return &SymmetricKey{ + headers: &hdr, + key: key, + }, nil +} + +// Materialize returns the octets for this symmetric key. +// Since this is a symmetric key, this just calls Octets +func (s SymmetricKey) Materialize() (interface{}, error) { + return s.Octets(), nil +} + +// Octets returns the octets in the key +func (s SymmetricKey) Octets() []byte { + return s.key +} + +// Thumbprint returns the JWK thumbprint using the indicated +// hashing algorithm, according to RFC 7638 +func (s SymmetricKey) Thumbprint(hash crypto.Hash) ([]byte, error) { + h := hash.New() + fmt.Fprintf(h, `{"k":"`) + fmt.Fprintf(h, base64.EncodeToString(s.key)) + fmt.Fprintf(h, `","kty":"oct"}`) + return h.Sum(nil), nil +} + +func (s *SymmetricKey) ExtractMap(m map[string]interface{}) (err error) { + + const kKey = `k` + + kbuf, err := getRequiredKey(m, kKey) + if err != nil { + return errors.Wrapf(err, `failed to get required key '%s'`, kKey) + } + delete(m, kKey) + + var hdrs StandardHeaders + if err := hdrs.ExtractMap(m); err != nil { + return errors.Wrap(err, `failed to extract header values`) + } + + *s = SymmetricKey{ + headers: &hdrs, + key: kbuf, + } + return nil +} + +func (s SymmetricKey) MarshalJSON() (buf []byte, err error) { + + m := make(map[string]interface{}) + if err := s.PopulateMap(m); err != nil { + return nil, errors.Wrap(err, `failed to populate symmetric key values`) + } + + return json.Marshal(m) +} + +func (s SymmetricKey) PopulateMap(m map[string]interface{}) (err error) { + + if err := s.headers.PopulateMap(m); err != nil { + return errors.Wrap(err, `failed to populate header values`) + } + + const kKey = `k` + m[kKey] = base64.EncodeToString(s.key) + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/doc_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/doc_test.go new file mode 100644 index 0000000000..65de2e3c2a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/doc_test.go @@ -0,0 +1,63 @@ +package jws_test + +import ( + "crypto/rand" + "crypto/rsa" + "log" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jws" +) + +func ExampleSign_JWSCompact() { + privkey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Printf("failed to create private key: %s", err) + return + } + + buf, err := jws.Sign([]byte("Lorem ipsum"), jwa.RS256, privkey) + if err != nil { + log.Printf("failed to sign payload: %s", err) + return + } + + log.Printf("%s", buf) + + verified, err := jws.Verify(buf, jwa.RS256, &privkey.PublicKey) + if err != nil { + log.Printf("failed to verify JWS message: %s", err) + return + } + log.Printf("message verified!") + + // Do something with `verified` .... + _ = verified +} + +func ExampleSign_JWSJSON() { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Printf("failed to create private key: %s", err) + return + } + + payload := "Lorem ipsum" + + //TODO fix formatter + buf, err := jws.Sign([]byte(payload), jwa.RS256, key) + if err != nil { + log.Printf("failed to sign payload: %s", err) + return + } + + verified, err := jws.Verify(buf, jwa.RS256, &key.PublicKey) + if err != nil { + log.Printf("failed to verify JWS message: %s", err) + return + } + log.Printf("message verified!") + + // Do something with `verified` .... + _ = verified +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/headers.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/headers.go new file mode 100644 index 0000000000..37b54365f6 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/headers.go @@ -0,0 +1,198 @@ +// This file is auto-generated. DO NOT EDIT +package jws + +import ( + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/pkg/errors" +) + +const ( + AlgorithmKey = "alg" + ContentTypeKey = "cty" + CriticalKey = "crit" + JWKKey = "jwk" + JWKSetURLKey = "jku" + KeyIDKey = "kid" + TypeKey = "typ" + X509CertChainKey = "x5c" + X509CertThumbprintKey = "x5t" + X509CertThumbprintS256Key = "x5t#S256" + X509URLKey = "x5u" +) + +type Headers interface { + Get(string) (interface{}, bool) + Set(string, interface{}) error + Algorithm() jwa.SignatureAlgorithm +} + +type StandardHeaders struct { + JWSalgorithm jwa.SignatureAlgorithm `json:"alg,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.1 + JWScontentType string `json:"cty,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.10 + JWScritical []string `json:"crit,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.11 + JWSjwk *jwk.Set `json:"jwk,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.3 + JWSjwkSetURL string `json:"jku,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.2 + JWSkeyID string `json:"kid,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.4 + JWStyp string `json:"typ,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.9 + JWSx509CertChain []string `json:"x5c,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.6 + JWSx509CertThumbprint string `json:"x5t,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.7 + JWSx509CertThumbprintS256 string `json:"x5t#S256,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.8 + JWSx509URL string `json:"x5u,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.5 + privateParams map[string]interface{} +} + +func (h *StandardHeaders) Algorithm() jwa.SignatureAlgorithm { + return h.JWSalgorithm +} + +func (h *StandardHeaders) Get(name string) (interface{}, bool) { + switch name { + case AlgorithmKey: + v := h.JWSalgorithm + if v == "" { + return nil, false + } + return v, true + case ContentTypeKey: + v := h.JWScontentType + if v == "" { + return nil, false + } + return v, true + case CriticalKey: + v := h.JWScritical + if len(v) == 0 { + return nil, false + } + return v, true + case JWKKey: + v := h.JWSjwk + if v == nil { + return nil, false + } + return v, true + case JWKSetURLKey: + v := h.JWSjwkSetURL + if v == "" { + return nil, false + } + return v, true + case KeyIDKey: + v := h.JWSkeyID + if v == "" { + return nil, false + } + return v, true + case TypeKey: + v := h.JWStyp + if v == "" { + return nil, false + } + return v, true + case X509CertChainKey: + v := h.JWSx509CertChain + if len(v) == 0 { + return nil, false + } + return v, true + case X509CertThumbprintKey: + v := h.JWSx509CertThumbprint + if v == "" { + return nil, false + } + return v, true + case X509CertThumbprintS256Key: + v := h.JWSx509CertThumbprintS256 + if v == "" { + return nil, false + } + return v, true + case X509URLKey: + v := h.JWSx509URL + if v == "" { + return nil, false + } + return v, true + default: + v, ok := h.privateParams[name] + return v, ok + } +} + +func (h *StandardHeaders) Set(name string, value interface{}) error { + switch name { + case AlgorithmKey: + if err := h.JWSalgorithm.Accept(value); err != nil { + return errors.Wrapf(err, `invalid value for %s key`, AlgorithmKey) + } + return nil + case ContentTypeKey: + if v, ok := value.(string); ok { + h.JWScontentType = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, ContentTypeKey, value) + case CriticalKey: + if v, ok := value.([]string); ok { + h.JWScritical = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, CriticalKey, value) + case JWKKey: + v, ok := value.(*jwk.Set) + if ok { + h.JWSjwk = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, JWKKey, value) + case JWKSetURLKey: + if v, ok := value.(string); ok { + h.JWSjwkSetURL = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, JWKSetURLKey, value) + case KeyIDKey: + if v, ok := value.(string); ok { + h.JWSkeyID = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, KeyIDKey, value) + case TypeKey: + if v, ok := value.(string); ok { + h.JWStyp = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, TypeKey, value) + case X509CertChainKey: + if v, ok := value.([]string); ok { + h.JWSx509CertChain = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value) + case X509CertThumbprintKey: + if v, ok := value.(string); ok { + h.JWSx509CertThumbprint = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value) + case X509CertThumbprintS256Key: + if v, ok := value.(string); ok { + h.JWSx509CertThumbprintS256 = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value) + case X509URLKey: + if v, ok := value.(string); ok { + h.JWSx509URL = v + return nil + } + return errors.Errorf(`invalid value for %s key: %T`, X509URLKey, value) + default: + if h.privateParams == nil { + h.privateParams = map[string]interface{}{} + } + h.privateParams[name] = value + } + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/headers_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/headers_test.go new file mode 100644 index 0000000000..34e068e994 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/headers_test.go @@ -0,0 +1,128 @@ +package jws_test + +import ( + "encoding/json" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jws" + "reflect" + "testing" +) + +func TestHeader(t *testing.T) { + publicKey := `{"kty":"RSA", + "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + "e":"AQAB", + "alg":"RS256", + "kid":"2011-04-29"}` + jwkPublicKeySet, err := jwk.ParseString(publicKey) + if err != nil { + t.Fatal("Failed to parse RSA public key") + } + certChain := []string{ + "MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWVU+4=", + "MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==", + "MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd", + } + values := map[string]interface{}{ + jws.AlgorithmKey: jwa.ES256, + jws.ContentTypeKey: "example", + jws.CriticalKey: []string{"exp"}, + jws.JWKKey: jwkPublicKeySet, + jws.JWKSetURLKey: "https://www.jwk.com/key.json", + jws.TypeKey: "JWT", + jws.KeyIDKey: "e9bc097a-ce51-4036-9562-d2ade882db0d", + jws.X509CertChainKey: certChain, + jws.X509CertThumbprintKey: "QzY0NjREMjkyQTI4RTU2RkE4MUJBRDExNzY1MUY1N0I4QjFCODlBOQ", + jws.X509URLKey: "https://www.x509.com/key.pem", + } + t.Run("Roundtrip", func(t *testing.T) { + + var h jws.StandardHeaders + for k, v := range values { + err := h.Set(k, v) + if err != nil { + t.Fatalf("Set failed for %s", k) + } + got, ok := h.Get(k) + if !ok { + t.Fatalf("Set failed for %s", k) + } + //fmt.Println(reflect.TypeOf(got).String()) + //fmt.Println(reflect.TypeOf(v).String()) + if !reflect.DeepEqual(v, got) { + t.Fatalf("Values do not match: (%v, %v)", v, got) + } + } + }) + t.Run("JSON Marshal Unmarshal", func(t *testing.T) { + + var h jws.StandardHeaders + for k, v := range values { + err := h.Set(k, v) + if err != nil { + t.Fatalf("Set failed for %s", k) + } + got, ok := h.Get(k) + if !ok { + t.Fatalf("Set failed for %s", k) + } + if !reflect.DeepEqual(v, got) { + t.Fatalf("Values do not match: (%v, %v)", v, got) + } + } + hByte, err := json.Marshal(h) + if err != nil { + t.Fatal("Failed to JSON marshal") + } + var hNew jws.StandardHeaders + err = json.Unmarshal(hByte, &hNew) + if err != nil { + t.Fatal("Failed to JSON marshal") + } + }) + t.Run("RoundtripError", func(t *testing.T) { + + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + + values := map[string]interface{}{ + jws.AlgorithmKey: dummy, + jws.ContentTypeKey: dummy, + jws.CriticalKey: dummy, + jws.JWKKey: dummy, + jws.JWKSetURLKey: dummy, + jws.KeyIDKey: dummy, + jws.TypeKey: dummy, + jws.X509CertChainKey: dummy, + jws.X509CertThumbprintKey: dummy, + jws.X509CertThumbprintS256Key: dummy, + jws.X509URLKey: dummy, + } + + var h jws.StandardHeaders + for k, v := range values { + err := h.Set(k, v) + if err == nil { + t.Fatalf("Setting %s value should have failed", k) + } + } + err := h.Set("default", dummy) // private params + if err != nil { + t.Fatalf("Setting %s value failed", "default") + } + for k, _ := range values { + _, ok := h.Get(k) + if ok { + t.Fatalf("Getting %s value should have failed", k) + } + } + _, ok := h.Get("default") + if !ok { + t.Fatal("Failed to get default value") + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/interface.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/interface.go new file mode 100644 index 0000000000..60eb1c7b01 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/interface.go @@ -0,0 +1,91 @@ +package jws + +import ( + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" +) + +type EncodedSignature struct { + Protected string `json:"protected,omitempty"` + Headers Headers `json:"header,omitempty"` + Signature string `json:"signature,omitempty"` +} + +type EncodedSignatureUnmarshalProxy struct { + Protected string `json:"protected,omitempty"` + Headers *StandardHeaders `json:"header,omitempty"` + Signature string `json:"signature,omitempty"` +} + +type EncodedMessage struct { + Payload string `json:"payload"` + Signatures []*EncodedSignature `json:"signatures,omitempty"` +} + +type EncodedMessageUnmarshalProxy struct { + Payload string `json:"payload"` + Signatures []*EncodedSignatureUnmarshalProxy `json:"signatures,omitempty"` +} + +type FullEncodedMessage struct { + *EncodedSignature // embedded to pick up flattened JSON message + *EncodedMessage +} + +type FullEncodedMessageUnmarshalProxy struct { + *EncodedSignatureUnmarshalProxy // embedded to pick up flattened JSON message + *EncodedMessageUnmarshalProxy +} + +// PayloadSigner generates signature for the given payload +type PayloadSigner interface { + Sign([]byte) ([]byte, error) + Algorithm() jwa.SignatureAlgorithm + ProtectedHeader() Headers + PublicHeader() Headers +} + +// Message represents a full JWS encoded message. Flattened serialization +// is not supported as a struct, but rather it's represented as a +// Message struct with only one `signature` element. +// +// Do not expect to use the Message object to verify or construct a +// signed payloads with. You should only use this when you want to actually +// want to programmatically view the contents for the full JWS payload. +// +// To sign and verify, use the appropriate `Sign()` nad `Verify()` functions +type Message struct { + payload []byte `json:"payload"` + signatures []*Signature `json:"signatures,omitempty"` +} + +type Signature struct { + headers Headers `json:"header,omitempty"` // Unprotected Headers + protected Headers `json:"protected,omitempty"` // Protected Headers + signature []byte `json:"signature,omitempty"` // Signature +} + +// JWKAcceptor decides which keys can be accepted +// by functions that iterate over a JWK key set. +type JWKAcceptor interface { + Accept(jwk.Key) bool +} + +// JWKAcceptFunc is an implementation of JWKAcceptor +// using a plain function +type JWKAcceptFunc func(jwk.Key) bool + +// Accept executes the provided function to determine if the +// given key can be used +func (f JWKAcceptFunc) Accept(key jwk.Key) bool { + return f(key) +} + +// DefaultJWKAcceptor is the default acceptor that is used +// in functions like VerifyWithJWKSet +var DefaultJWKAcceptor = JWKAcceptFunc(func(key jwk.Key) bool { + if u := key.KeyUsage(); u != "" && u != "enc" && u != "sig" { + return false + } + return true +}) diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/internal/cmd/genheader/main.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/internal/cmd/genheader/main.go new file mode 100644 index 0000000000..68f4e50c5e --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/internal/cmd/genheader/main.go @@ -0,0 +1,268 @@ +package main + +import ( + "bytes" + "fmt" + "go/format" + "log" + "os" + "sort" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +func main() { + if err := _main(); err != nil { + log.Printf("%s", err) + os.Exit(1) + } +} + +func _main() error { + return generateHeaders() +} + +type headerField struct { + name string + method string + typ string + key string + comment string + hasAccept bool + noDeref bool + jsonTag string +} + +func (f headerField) IsPointer() bool { + return strings.HasPrefix(f.typ, "*") +} + +func (f headerField) PointerElem() string { + return strings.TrimPrefix(f.typ, "*") +} + +var zerovals = map[string]string{ + "string": `""`, + "jwa.SignatureAlgorithm": `""`, + "[]string": "0", +} + +func zeroval(s string) string { + if v, ok := zerovals[s]; ok { + return v + } + return "nil" +} + +func generateHeaders() error { + fields := []headerField{ + { + name: `JWSalgorithm`, + method: `Algorithm`, + typ: `jwa.SignatureAlgorithm`, + key: `alg`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.1`, + hasAccept: true, + jsonTag: "`" + `json:"alg,omitempty"` + "`", + }, + { + name: `JWScontentType`, + method: `ContentType`, + typ: `string`, + key: `cty`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.10`, + jsonTag: "`" + `json:"cty,omitempty"` + "`", + }, + { + name: `JWScritical`, + method: `Critical`, + typ: `[]string`, + key: `crit`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.11`, + jsonTag: "`" + `json:"crit,omitempty"` + "`", + }, + { + name: `JWSjwk`, + method: `JWK`, + typ: `*jwk.Set`, + key: `jwk`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.3`, + jsonTag: "`" + `json:"jwk,omitempty"` + "`", + }, + { + name: `JWSjwkSetURL`, + method: `JWKSetURL`, + typ: `string`, + key: `jku`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.2`, + jsonTag: "`" + `json:"jku,omitempty"` + "`", + }, + { + name: `JWSkeyID`, + method: `KeyID`, + typ: `string`, + key: `kid`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.4`, + jsonTag: "`" + `json:"kid,omitempty"` + "`", + }, + { + name: `JWStyp`, + method: `Type`, + typ: `string`, + key: `typ`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.9`, + jsonTag: "`" + `json:"typ,omitempty"` + "`", + }, + { + name: `JWSx509CertChain`, + method: `X509CertChain`, + typ: `[]string`, + key: `x5c`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.6`, + jsonTag: "`" + `json:"x5c,omitempty"` + "`", + }, + { + name: `JWSx509CertThumbprint`, + method: `X509CertThumbprint`, + typ: `string`, + key: `x5t`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.7`, + jsonTag: "`" + `json:"x5t,omitempty"` + "`", + }, + { + name: `JWSx509CertThumbprintS256`, + method: `X509CertThumbprintS256`, + typ: `string`, + key: `x5t#S256`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.8`, + jsonTag: "`" + `json:"x5t#S256,omitempty"` + "`", + }, + { + name: `JWSx509URL`, + method: `X509URL`, + typ: `string`, + key: `x5u`, + comment: `https://tools.ietf.org/html/rfc7515#section-4.1.5`, + jsonTag: "`" + `json:"x5u,omitempty"` + "`", + }, + } + + sort.Slice(fields, func(i, j int) bool { + return fields[i].name < fields[j].name + }) + + var buf bytes.Buffer + + fmt.Fprintf(&buf, "\n// This file is auto-generated. DO NOT EDIT") + fmt.Fprintf(&buf, "\npackage jws") + fmt.Fprintf(&buf, "\n\nimport (") + for _, pkg := range []string{"github.com/lestrrat-go/jwx/jwa", "github.com/lestrrat-go/jwx/jwk", "github.com/pkg/errors"} { + fmt.Fprintf(&buf, "\n%s", strconv.Quote(pkg)) + } + fmt.Fprintf(&buf, "\n)") + + fmt.Fprintf(&buf, "\n\nconst (") + for _, f := range fields { + fmt.Fprintf(&buf, "\n%sKey = %s", f.method, strconv.Quote(f.key)) + } + fmt.Fprintf(&buf, "\n)") // end const + + fmt.Fprintf(&buf, "\n\ntype Headers interface {") + fmt.Fprintf(&buf, "\nGet(string) (interface{}, bool)") + fmt.Fprintf(&buf, "\nSet(string, interface{}) error") + fmt.Fprintf(&buf, "\nAlgorithm() jwa.SignatureAlgorithm") + + /* for _, f := range fields { + fmt.Fprintf(&buf, "\n%s() %s", f.method, f.PointerElem()) + }*/ + fmt.Fprintf(&buf, "\n}") // end type Headers interface + fmt.Fprintf(&buf, "\n\ntype StandardHeaders struct {") + for _, f := range fields { + fmt.Fprintf(&buf, "\n%s %s %s // %s", f.name, f.typ, f.jsonTag, f.comment) + } + fmt.Fprintf(&buf, "\nprivateParams map[string]interface{}") + fmt.Fprintf(&buf, "\n}") // end type StandardHeaders + + fmt.Fprintf(&buf, "\n\nfunc (h *StandardHeaders) Algorithm() jwa.SignatureAlgorithm {") + fmt.Fprintf(&buf, "\nreturn h.JWSalgorithm") + fmt.Fprintf(&buf, "\n}") // func (h *StandardHeaders) %s() %s + + fmt.Fprintf(&buf, "\n\nfunc (h *StandardHeaders) Get(name string) (interface{}, bool) {") + fmt.Fprintf(&buf, "\nswitch name {") + for _, f := range fields { + fmt.Fprintf(&buf, "\ncase %sKey:", f.method) + fmt.Fprintf(&buf, "\nv := h.%s", f.name) + + if f.typ == "[]string" { + fmt.Fprintf(&buf, "\nif len(v) == %s {", zeroval(f.typ)) + } else { + fmt.Fprintf(&buf, "\nif v == %s {", zeroval(f.typ)) + } + fmt.Fprintf(&buf, "\nreturn nil, false") + fmt.Fprintf(&buf, "\n}") // end if h.%s == nil + fmt.Fprintf(&buf, "\nreturn v, true") + + } + fmt.Fprintf(&buf, "\ndefault:") + fmt.Fprintf(&buf, "\nv, ok := h.privateParams[name]") + fmt.Fprintf(&buf, "\nreturn v, ok") + fmt.Fprintf(&buf, "\n}") // end switch name + fmt.Fprintf(&buf, "\n}") // func (h *StandardHeaders) Get(name string) (interface{}, bool) + + fmt.Fprintf(&buf, "\n\nfunc (h *StandardHeaders) Set(name string, value interface{}) error {") + fmt.Fprintf(&buf, "\nswitch name {") + for _, f := range fields { + fmt.Fprintf(&buf, "\ncase %sKey:", f.method) + if f.hasAccept { + if f.IsPointer() { + fmt.Fprintf(&buf, "\nvar acceptor %s", f.PointerElem()) + fmt.Fprintf(&buf, "\nif err := acceptor.Accept(value); err != nil {") + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `invalid value for %%s key`, %sKey)", f.method) + fmt.Fprintf(&buf, "\n}") // end if err := h.%s.Accept(value) + fmt.Fprintf(&buf, "\nh.%s = &acceptor", f.name) + } else { + fmt.Fprintf(&buf, "\nif err := h.%s.Accept(value); err != nil {", f.name) + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `invalid value for %%s key`, %sKey)", f.method) + fmt.Fprintf(&buf, "\n}") // end if err := h.%s.Accept(value) + } + fmt.Fprintf(&buf, "\nreturn nil") + } else { + if f.name == "JWSjwk" { + fmt.Fprintf(&buf, "\nv, ok := value.(%s)", f.typ) + fmt.Fprintf(&buf, "\nif ok {") + fmt.Fprintf(&buf, "\nh.%s = v", f.name) + } else { + fmt.Fprintf(&buf, "\nif v, ok := value.(%s); ok {", f.typ) + fmt.Fprintf(&buf, "\nh.%s = v", f.name) + } + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") // end if v, ok := value.(%s) + fmt.Fprintf(&buf, "\nreturn errors.Errorf(`invalid value for %%s key: %%T`, %sKey, value)", f.method) + } + } + fmt.Fprintf(&buf, "\ndefault:") + fmt.Fprintf(&buf, "\nif h.privateParams == nil {") + fmt.Fprintf(&buf, "\nh.privateParams = map[string]interface{}{}") + fmt.Fprintf(&buf, "\n}") // end if h.privateParams == nil + fmt.Fprintf(&buf, "\nh.privateParams[name] = value") + fmt.Fprintf(&buf, "\n}") // end switch name + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") // end func (h *StandardHeaders) Set(name string, value interface{}) + + formatted, err := format.Source(buf.Bytes()) + if err != nil { + buf.WriteTo(os.Stdout) + return errors.Wrap(err, `failed to format code`) + } + + f, err := os.Create("headers.go") + if err != nil { + return errors.Wrap(err, `failed to open headers.go`) + } + defer f.Close() + f.Write(formatted) + + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/jws.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/jws.go new file mode 100644 index 0000000000..a8ca7ec737 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/jws.go @@ -0,0 +1,567 @@ +//go:generate go run internal/cmd/genheader/main.go + +// Package jws implements the digital signature on JSON based data +// structures as described in https://tools.ietf.org/html/rfc7515 +// +// If you do not care about the details, the only things that you +// would need to use are the following functions: +// +// jws.Sign(payload, algorithm, key) +// jws.Verify(encodedjws, algorithm, key) +// +// To sign, simply use `jws.Sign`. `payload` is a []byte buffer that +// contains whatever data you want to sign. `alg` is one of the +// jwa.SignatureAlgorithm constants from package jwa. For RSA and +// ECDSA family of algorithms, you will need to prepare a private key. +// For HMAC family, you just need a []byte value. The `jws.Sign` +// function will return the encoded JWS message on success. +// +// To verify, use `jws.Verify`. It will parse the `encodedjws` buffer +// and verify the result using `algorithm` and `key`. Upon successful +// verification, the original payload is returned, so you can work on it. +package jws + +import ( + "bufio" + "bytes" + "encoding/base64" + "encoding/json" + "io" + "strings" + "unicode" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jws/sign" + "github.com/lestrrat-go/jwx/jws/verify" + "github.com/pkg/errors" +) + +// Sign is a short way to generate a JWS in compact serialization +// for a given payload. If you need more control over the signature +// generation process, you should manually create signers and tweak +// the message. +/* +func Sign(payload []byte, alg jwa.SignatureAlgorithm, key interface{}, options ...Option) ([]byte, error) { + signer, err := sign.New(alg) + if err != nil { + return nil, errors.Wrap(err, "failed to create signer") + } + + msg, err := SignMulti(payload, WithSigner(signer, key)) + if err != nil { + return nil, errors.Wrap(err, "failed to sign payload") + } + return msg, nil +} +*/ + +type payloadSigner struct { + signer sign.Signer + key interface{} + protected Headers + public Headers +} + +func (s *payloadSigner) Sign(payload []byte) ([]byte, error) { + return s.signer.Sign(payload, s.key) +} + +func (s *payloadSigner) Algorithm() jwa.SignatureAlgorithm { + return s.signer.Algorithm() +} + +func (s *payloadSigner) ProtectedHeader() Headers { + return s.protected +} + +func (s *payloadSigner) PublicHeader() Headers { + return s.public +} + +// Sign generates a signature for the given payload, and serializes +// it in compact serialization format. In this format you may NOT use +// multiple signers. +// +// If you would like to pass custom headers, use the WithHeaders option. +func Sign(payload []byte, alg jwa.SignatureAlgorithm, key interface{}, options ...Option) ([]byte, error) { + var hdrs Headers = &StandardHeaders{} + for _, o := range options { + switch o.Name() { + case optkeyHeaders: + hdrs = o.Value().(Headers) + } + } + + signer, err := sign.New(alg) + if err != nil { + return nil, errors.Wrap(err, `failed to create signer`) + } + + hdrs.Set(AlgorithmKey, signer.Algorithm()) + + hdrbuf, err := json.Marshal(hdrs) + if err != nil { + return nil, errors.Wrap(err, `failed to marshal headers`) + } + + var buf bytes.Buffer + enc := base64.NewEncoder(base64.RawURLEncoding, &buf) + if _, err := enc.Write(hdrbuf); err != nil { + return nil, errors.Wrap(err, `failed to write headers as base64`) + } + if err := enc.Close(); err != nil { + return nil, errors.Wrap(err, `failed to finalize writing headers as base64`) + } + + buf.WriteByte('.') + enc = base64.NewEncoder(base64.RawURLEncoding, &buf) + if _, err := enc.Write(payload); err != nil { + return nil, errors.Wrap(err, `failed to write payload as base64`) + } + if err := enc.Close(); err != nil { + return nil, errors.Wrap(err, `failed to finalize writing payload as base64`) + } + + signature, err := signer.Sign(buf.Bytes(), key) + if err != nil { + return nil, errors.Wrap(err, `failed to sign payload`) + } + + buf.WriteByte('.') + enc = base64.NewEncoder(base64.RawURLEncoding, &buf) + if _, err := enc.Write(signature); err != nil { + return nil, errors.Wrap(err, `failed to write signature as base64`) + } + if err := enc.Close(); err != nil { + return nil, errors.Wrap(err, `failed to finalize writing signature as base64`) + } + + return buf.Bytes(), nil +} + +// SignLiteral generates a signature for the given payload and headers, and serializes +// it in compact serialization format. In this format you may NOT use +// multiple signers. +// +func SignLiteral(payload []byte, alg jwa.SignatureAlgorithm, key interface{}, headers []byte) ([]byte, error) { + + signer, err := sign.New(alg) + if err != nil { + return nil, errors.Wrap(err, `failed to create signer`) + } + + var buf bytes.Buffer + enc := base64.NewEncoder(base64.RawURLEncoding, &buf) + if _, err := enc.Write(headers); err != nil { + return nil, errors.Wrap(err, `failed to write headers as base64`) + } + if err := enc.Close(); err != nil { + return nil, errors.Wrap(err, `failed to finalize writing headers as base64`) + } + + buf.WriteByte('.') + enc = base64.NewEncoder(base64.RawURLEncoding, &buf) + if _, err := enc.Write(payload); err != nil { + return nil, errors.Wrap(err, `failed to write payload as base64`) + } + if err := enc.Close(); err != nil { + return nil, errors.Wrap(err, `failed to finalize writing payload as base64`) + } + + signature, err := signer.Sign(buf.Bytes(), key) + if err != nil { + return nil, errors.Wrap(err, `failed to sign payload`) + } + + buf.WriteByte('.') + enc = base64.NewEncoder(base64.RawURLEncoding, &buf) + if _, err := enc.Write(signature); err != nil { + return nil, errors.Wrap(err, `failed to write signature as base64`) + } + if err := enc.Close(); err != nil { + return nil, errors.Wrap(err, `failed to finalize writing signature as base64`) + } + + return buf.Bytes(), nil +} + +// SignMulti accepts multiple signers via the options parameter, +// and creates a JWS in JSON serialization format that contains +// signatures from applying aforementioned signers. +func SignMulti(payload []byte, options ...Option) ([]byte, error) { + var signers []PayloadSigner + for _, o := range options { + switch o.Name() { + case optkeyPayloadSigner: + signers = append(signers, o.Value().(PayloadSigner)) + } + } + + if len(signers) == 0 { + return nil, errors.New(`no signers provided`) + } + + var result EncodedMessage + + result.Payload = base64.RawURLEncoding.EncodeToString(payload) + + for _, signer := range signers { + protected := signer.ProtectedHeader() + if protected == nil { + protected = &StandardHeaders{} + } + + protected.Set(AlgorithmKey, signer.Algorithm()) + + hdrbuf, err := json.Marshal(protected) + if err != nil { + return nil, errors.Wrap(err, `failed to marshal headers`) + } + encodedHeader := base64.RawURLEncoding.EncodeToString(hdrbuf) + var buf bytes.Buffer + buf.WriteString(encodedHeader) + buf.WriteByte('.') + buf.WriteString(result.Payload) + signature, err := signer.Sign(buf.Bytes()) + if err != nil { + return nil, errors.Wrap(err, `failed to sign payload`) + } + + result.Signatures = append(result.Signatures, &EncodedSignature{ + Headers: signer.PublicHeader(), + Protected: encodedHeader, + Signature: base64.RawURLEncoding.EncodeToString(signature), + }) + } + + return json.Marshal(result) +} + +// Verify checks if the given JWS message is verifiable using `alg` and `key`. +// If the verification is successful, `err` is nil, and the content of the +// payload that was signed is returned. If you need more fine-grained +// control of the verification process, manually call `Parse`, generate a +// verifier, and call `Verify` on the parsed JWS message object. +func Verify(buf []byte, alg jwa.SignatureAlgorithm, key interface{}) (ret []byte, err error) { + + verifier, err := verify.New(alg) + if err != nil { + return nil, errors.Wrap(err, "failed to create verifier") + } + + buf = bytes.TrimSpace(buf) + if len(buf) == 0 { + return nil, errors.New(`attempt to verify empty buffer`) + } + + if buf[0] == '{' { + + var v FullEncodedMessage + if err := json.Unmarshal(buf, &v); err != nil { + return nil, errors.Wrap(err, `failed to unmarshal JWS message`) + } + + // There's something wrong if the Message part is not initialized + if v.EncodedMessage == nil { + return nil, errors.New(`invalid JWS message format`) + } + + // if we're using the flattened serialization format, then m.Signature + // will be non-nil + msg := v.EncodedMessage + if v.EncodedSignature != nil { + msg.Signatures[0] = v.EncodedSignature + } + + var buf bytes.Buffer + for _, sig := range msg.Signatures { + buf.Reset() + buf.WriteString(sig.Protected) + buf.WriteByte('.') + buf.WriteString(msg.Payload) + decodedSignature, err := base64.RawURLEncoding.DecodeString(sig.Signature) + if err != nil { + continue + } + + if err := verifier.Verify(buf.Bytes(), decodedSignature, key); err == nil { + // verified! + decodedPayload, err := base64.RawURLEncoding.DecodeString(msg.Payload) + if err != nil { + return nil, errors.Wrap(err, `message verified, failed to decode payload`) + } + return decodedPayload, nil + } + } + return nil, errors.New(`could not verify with any of the signatures`) + } + + protected, payload, signature, err := SplitCompact(bytes.NewReader(buf)) + if err != nil { + return nil, errors.Wrap(err, `failed extract from compact serialization format`) + } + + var verifyBuf bytes.Buffer + verifyBuf.Write(protected) + verifyBuf.WriteByte('.') + verifyBuf.Write(payload) + + decodedSignature := make([]byte, base64.RawURLEncoding.DecodedLen(len(signature))) + if _, err := base64.RawURLEncoding.Decode(decodedSignature, signature); err != nil { + return nil, errors.Wrap(err, `failed to decode signature`) + } + if err := verifier.Verify(verifyBuf.Bytes(), decodedSignature, key); err != nil { + return nil, errors.Wrap(err, `failed to verify message`) + } + + decodedPayload := make([]byte, base64.RawURLEncoding.DecodedLen(len(payload))) + if _, err := base64.RawURLEncoding.Decode(decodedPayload, payload); err != nil { + return nil, errors.Wrap(err, `message verified, failed to decode payload`) + } + return decodedPayload, nil +} + +// VerifyWithJKU verifies the JWS message using a remote JWK +// file represented in the url. +func VerifyWithJKU(buf []byte, jwkurl string) ([]byte, error) { + key, err := jwk.FetchHTTP(jwkurl) + if err != nil { + return nil, errors.Wrap(err, `failed to fetch jwk via HTTP`) + } + + return VerifyWithJWKSet(buf, key, nil) +} + +// VerifyWithJWK verifies the JWS message using the specified JWK +func VerifyWithJWK(buf []byte, key jwk.Key) (payload []byte, err error) { + + keyval, err := key.Materialize() + if err != nil { + return nil, errors.Wrap(err, `failed to materialize jwk.Key`) + } + + payload, err = Verify(buf, jwa.SignatureAlgorithm(key.Algorithm()), keyval) + if err != nil { + return nil, errors.Wrap(err, "failed to verify message") + } + return payload, nil +} + +// VerifyWithJWKSet verifies the JWS message using JWK key set. +// By default it will only pick up keys that have the "use" key +// set to either "sig" or "enc", but you can override it by +// providing a keyaccept function. +func VerifyWithJWKSet(buf []byte, keyset *jwk.Set, keyaccept JWKAcceptFunc) (payload []byte, err error) { + + if keyaccept == nil { + keyaccept = DefaultJWKAcceptor + } + + for _, key := range keyset.Keys { + if !keyaccept(key) { + continue + } + + payload, err := VerifyWithJWK(buf, key) + if err == nil { + return payload, nil + } + } + + return nil, errors.New("failed to verify with any of the keys") +} + +// Parse parses contents from the given source and creates a jws.Message +// struct. The input can be in either compact or full JSON serialization. +func Parse(src io.Reader) (m *Message, err error) { + + rdr := bufio.NewReader(src) + var first rune + for { + r, _, err := rdr.ReadRune() + if err != nil { + return nil, errors.Wrap(err, `failed to read rune`) + } + if !unicode.IsSpace(r) { + first = r + rdr.UnreadRune() + break + } + } + + var parser func(io.Reader) (*Message, error) + if first == '{' { + parser = parseJSON + } else { + parser = parseCompact + } + + m, err = parser(rdr) + if err != nil { + return nil, errors.Wrap(err, `failed to parse jws message`) + } + + return m, nil +} + +// ParseString is the same as Parse, but take in a string +func ParseString(s string) (*Message, error) { + return Parse(strings.NewReader(s)) +} + +func parseJSON(src io.Reader) (result *Message, err error) { + + var wrapper FullEncodedMessageUnmarshalProxy + + if err := json.NewDecoder(src).Decode(&wrapper); err != nil { + return nil, errors.Wrap(err, `failed to unmarshal jws message`) + } + + if wrapper.EncodedMessageUnmarshalProxy == nil { + return nil, errors.New(`invalid payload (probably empty)`) + } + + // if the "signature" field exist, treat it as a flattened + if wrapper.EncodedSignatureUnmarshalProxy != nil { + if len(wrapper.Signatures) != 0 { + return nil, errors.New("invalid message: mixed flattened/full json serialization") + } + + wrapper.Signatures = append(wrapper.Signatures, wrapper.EncodedSignatureUnmarshalProxy) + } + + var plain Message + plain.payload, err = base64.RawURLEncoding.DecodeString(wrapper.Payload) + if err != nil { + return nil, errors.Wrap(err, `failed to decode payload`) + } + + for i, sig := range wrapper.Signatures { + var plainSig Signature + + plainSig.headers = sig.Headers + + if l := len(sig.Protected); l > 0 { + plainSig.protected = new(StandardHeaders) + hdrbuf, err := base64.RawURLEncoding.DecodeString(sig.Protected) + if err != nil { + return nil, errors.Wrapf(err, `failed to base64 decode protected header for signature #%d`, i+1) + } + if err := json.Unmarshal(hdrbuf, &plainSig.protected); err != nil { + return nil, errors.Wrapf(err, `failed to unmarshal protected header for signature #%d`, i+1) + } + } + + plainSig.signature, err = base64.RawURLEncoding.DecodeString(sig.Signature) + if err != nil { + return nil, errors.Wrapf(err, `failed to decode signature #%d`, i) + } + + plain.signatures = append(plain.signatures, &plainSig) + } + + return &plain, nil +} + +// SplitCompact splits a JWT and returns its three parts +// separately: protected headers, payload and signature. +func SplitCompact(rdr io.Reader) ([]byte, []byte, []byte, error) { + var protected []byte + var payload []byte + var signature []byte + var periods int = 0 + var state int = 0 + + buf := make([]byte, 4096) + var sofar []byte + + for { + // read next bytes + n, err := rdr.Read(buf) + // return on unexpected read error + if err != nil && err != io.EOF { + return nil, nil, nil, err + } + + // append to current buffer + sofar = append(sofar, buf[:n]...) + // loop to capture multiple '.' in current buffer + for loop := true; loop; { + var i = bytes.IndexByte(sofar, '.') + if i == -1 && err != io.EOF { + // no '.' found -> exit and read next bytes (outer loop) + loop = false + continue + } else if i == -1 && err == io.EOF { + // no '.' found -> process rest and exit + i = len(sofar) + loop = false + } else { + // '.' found + periods++ + } + + // Reaching this point means we have found a '.' or EOF and process the rest of the buffer + switch state { + case 0: + protected = sofar[:i] + state++ + case 1: + payload = sofar[:i] + state++ + case 2: + signature = sofar[:i] + } + // Shorten current buffer + if len(sofar) > i { + sofar = sofar[i+1:] + } + } + // Exit on EOF + if err == io.EOF { + break + } + } + if periods != 2 { + return nil, nil, nil, errors.New(`invalid number of segments`) + } + + return protected, payload, signature, nil +} + +// parseCompact parses a JWS value serialized via compact serialization. +func parseCompact(rdr io.Reader) (m *Message, err error) { + + protected, payload, signature, err := SplitCompact(rdr) + if err != nil { + return nil, errors.Wrap(err, `invalid compact serialization format`) + } + + decodedHeader := make([]byte, base64.RawURLEncoding.DecodedLen(len(protected))) + if _, err := base64.RawURLEncoding.Decode(decodedHeader, protected); err != nil { + return nil, errors.Wrap(err, `failed to decode headers`) + } + var hdr StandardHeaders + if err := json.Unmarshal(decodedHeader, &hdr); err != nil { + return nil, errors.Wrap(err, `failed to parse JOSE headers`) + } + + decodedPayload := make([]byte, base64.RawURLEncoding.DecodedLen(len(payload))) + if _, err = base64.RawURLEncoding.Decode(decodedPayload, payload); err != nil { + return nil, errors.Wrap(err, `failed to decode payload`) + } + + decodedSignature := make([]byte, base64.RawURLEncoding.DecodedLen(len(signature))) + if _, err := base64.RawURLEncoding.Decode(decodedSignature, signature); err != nil { + return nil, errors.Wrap(err, `failed to decode signature`) + } + + var msg Message + msg.payload = decodedPayload + msg.signatures = append(msg.signatures, &Signature{ + protected: &hdr, + signature: decodedSignature, + }) + return &msg, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/jws_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/jws_test.go new file mode 100644 index 0000000000..aee2381039 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/jws_test.go @@ -0,0 +1,925 @@ +package jws_test + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/sha512" + "encoding/base64" + "encoding/json" + "math/big" + "strings" + "testing" + + "github.com/lestrrat-go/jwx/buffer" + "github.com/lestrrat-go/jwx/internal/ecdsautil" + "github.com/lestrrat-go/jwx/internal/rsautil" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jws" + "github.com/lestrrat-go/jwx/jws/sign" + "github.com/lestrrat-go/jwx/jws/verify" + "github.com/stretchr/testify/assert" +) + +const examplePayload = `{"iss":"joe",` + "\r\n" + ` "exp":1300819380,` + "\r\n" + ` "http://example.com/is_root":true}` +const exampleCompactSerialization = `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk` + +func TestParse(t *testing.T) { + t.Run("Empty bytes.Buffer", func(t *testing.T) { + _, err := jws.Parse(&bytes.Buffer{}) + if !assert.Error(t, err, "Parsing an empty buffer should result in an error") { + return + } + }) + t.Run("Compact missing parts", func(t *testing.T) { + incoming := strings.Join( + (strings.Split( + exampleCompactSerialization, + ".", + ))[:2], + ".", + ) + _, err := jws.ParseString(incoming) + if !assert.Error(t, err, "Parsing compact serialization with less than 3 parts should be an error") { + return + } + }) + t.Run("Compact bad header", func(t *testing.T) { + parts := strings.Split(exampleCompactSerialization, ".") + parts[0] = "%badvalue%" + incoming := strings.Join(parts, ".") + + _, err := jws.ParseString(incoming) + if !assert.Error(t, err, "Parsing compact serialization with bad header should be an error") { + return + } + }) + t.Run("Compact bad payload", func(t *testing.T) { + parts := strings.Split(exampleCompactSerialization, ".") + parts[1] = "%badvalue%" + incoming := strings.Join(parts, ".") + + _, err := jws.ParseString(incoming) + if !assert.Error(t, err, "Parsing compact serialization with bad payload should be an error") { + return + } + }) + t.Run("Compact bad signature", func(t *testing.T) { + parts := strings.Split(exampleCompactSerialization, ".") + parts[2] = "%badvalue%" + incoming := strings.Join(parts, ".") + + t.Logf("incoming = '%s'", incoming) + _, err := jws.ParseString(incoming) + if !assert.Error(t, err, "Parsing compact serialization with bad signature should be an error") { + return + } + }) +} + +func TestRoundtrip(t *testing.T) { + payload := []byte("Lorem ipsum") + sharedkey := []byte("Avracadabra") + + hmacAlgorithms := []jwa.SignatureAlgorithm{jwa.HS256, jwa.HS384, jwa.HS512} + for _, alg := range hmacAlgorithms { + t.Run("HMAC "+alg.String(), func(t *testing.T) { + signed, err := jws.Sign(payload, alg, sharedkey) + if !assert.NoError(t, err, "Sign succeeds") { + return + } + + verified, err := jws.Verify(signed, alg, sharedkey) + if !assert.NoError(t, err, "Verify succeeded") { + return + } + + if !assert.Equal(t, payload, verified, "verified payload matches") { + return + } + }) + } + t.Run("HMAC SignMulti", func(t *testing.T) { + var signed []byte + t.Run("Sign", func(t *testing.T) { + var options []jws.Option + for _, alg := range hmacAlgorithms { + signer, err := sign.New(alg) + if !assert.NoError(t, err, `sign.New should succeed`) { + return + } + options = append(options, jws.WithSigner(signer, sharedkey, nil, nil)) + } + var err error + signed, err = jws.SignMulti(payload, options...) + if !assert.NoError(t, err, `jws.SignMulti should succeed`) { + return + } + }) + for _, alg := range hmacAlgorithms { + t.Run("Verify "+alg.String(), func(t *testing.T) { + verified, err := jws.Verify(signed, alg, sharedkey) + if !assert.NoError(t, err, "Verify succeeded") { + return + } + + if !assert.Equal(t, payload, verified, "verified payload matches") { + return + } + }) + } + }) +} + +func TestVerifyWithJWKSet(t *testing.T) { + payload := []byte("Hello, World!") + key, err := rsa.GenerateKey(rand.Reader, 2048) + if !assert.NoError(t, err, "RSA key generated") { + return + } + + jwkKey, err := jwk.New(&key.PublicKey) + if !assert.NoError(t, err, "JWK public key generated") { + return + } + err = jwkKey.Set(jwk.AlgorithmKey, jwa.RS256) + if !assert.NoError(t, err, "Algorithm set successfully") { + return + } + + buf, err := jws.Sign(payload, jwa.RS256, key) + if !assert.NoError(t, err, "Signature generated successfully") { + return + } + + verified, err := jws.VerifyWithJWKSet(buf, &jwk.Set{Keys: []jwk.Key{jwkKey}}, nil) + if !assert.NoError(t, err, "Verify is successful") { + return + } + + verified, err = jws.VerifyWithJWK(buf, jwkKey) + if !assert.NoError(t, err, "Verify is successful") { + return + } + + if !assert.Equal(t, payload, verified, "Verified payload is the same") { + return + } +} + +func TestRoundtrip_RSACompact(t *testing.T) { + payload := []byte("Hello, World!") + for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512} { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if !assert.NoError(t, err, "RSA key generated") { + return + } + + buf, err := jws.Sign(payload, alg, key) + if !assert.NoError(t, err, "(%s) Signature generated successfully", alg) { + return + } + + parsers := map[string]func([]byte) (*jws.Message, error){ + "Parse(io.Reader)": func(b []byte) (*jws.Message, error) { return jws.Parse(bytes.NewReader(b)) }, + "Parse(string)": func(b []byte) (*jws.Message, error) { return jws.ParseString(string(b)) }, + } + for name, f := range parsers { + m, err := f(buf) + if !assert.NoError(t, err, "(%s) %s is successful", alg, name) { + return + } + + if !assert.Equal(t, payload, m.Payload(), "(%s) %s: Payload is decoded", alg, name) { + return + } + } + + verified, err := jws.Verify(buf, alg, &key.PublicKey) + if !assert.NoError(t, err, "(%s) Verify is successful", alg) { + return + } + + if !assert.Equal(t, payload, verified, "(%s) Verified payload is the same", alg) { + return + } + } +} + +func TestEncode(t *testing.T) { + // HS256Compact tests that https://tools.ietf.org/html/rfc7515#appendix-A.1 works + t.Run("HS256Compact", func(t *testing.T) { + const hdr = `{"typ":"JWT",` + "\r\n" + ` "alg":"HS256"}` + const hmacKey = `AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow` + const expected = `eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk` + + hmacKeyDecoded := buffer.Buffer{} + err := hmacKeyDecoded.Base64Decode([]byte(hmacKey)) + if !assert.NoError(t, err, "HMAC base64 decoded successful") { + return + } + + hdrbuf, err := buffer.Buffer(hdr).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + payload, err := buffer.Buffer(examplePayload).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + + signingInput := bytes.Join( + [][]byte{ + hdrbuf, + payload, + }, + []byte{'.'}, + ) + + sign, err := sign.New(jwa.HS256) + if !assert.NoError(t, err, "HMAC signer created successfully") { + return + } + + signature, err := sign.Sign(signingInput, hmacKeyDecoded.Bytes()) + if !assert.NoError(t, err, "PayloadSign is successful") { + return + } + sigbuf, err := buffer.Buffer(signature).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + + encoded := bytes.Join( + [][]byte{ + signingInput, + sigbuf, + }, + []byte{'.'}, + ) + if !assert.Equal(t, expected, string(encoded), "generated compact serialization should match") { + return + } + + msg, err := jws.Parse(bytes.NewReader(encoded)) + if !assert.NoError(t, err, "Parsing compact encoded serialization succeeds") { + return + } + + signatures := msg.Signatures() + if !assert.Len(t, signatures, 1, `there should be exactly one signature`) { + return + } + + algorithm := signatures[0].ProtectedHeaders().Algorithm() + if algorithm != jwa.HS256 { + t.Fatal("Algorithm in header does not match") + } + + v, err := verify.New(jwa.HS256) + if !assert.NoError(t, err, "HmacVerify created") { + return + } + + if !assert.NoError(t, v.Verify(signingInput, signature, hmacKeyDecoded.Bytes()), "Verify succeeds") { + return + } + }) + t.Run("HS256CompactLiteral", func(t *testing.T) { + const hdr = `{"typ":"JWT",` + "\r\n" + ` "alg":"HS256"}` + const jwksrc = `{ +"kty":"oct", +"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" +}` + + hdrBytes := []byte(hdr) + + hdrBuf, err := buffer.Buffer(hdr).Base64Encode() + if err != nil { + t.Fatal("Failed to base64 encode protected header") + } + standardHeaders := &jws.StandardHeaders{} + err = json.Unmarshal(hdrBytes, standardHeaders) + if err != nil { + t.Fatal("Failed to parse protected header") + } + alg := standardHeaders.Algorithm() + + payload, err := buffer.Buffer(examplePayload).Base64Encode() + if err != nil { + t.Fatal("Failed to base64 encode payload") + } + + keys, _ := jwk.ParseString(jwksrc) + key, err := keys.Keys[0].Materialize() + if err != nil { + t.Fatal("Failed to parse key") + } + var jwsCompact []byte + jwsCompact, err = jws.SignLiteral([]byte(examplePayload), alg, key, hdrBytes) + if err != nil { + t.Fatal("Failed to sign message") + } + + msg, err := jws.Parse(bytes.NewReader(jwsCompact)) + if !assert.NoError(t, err, "Parsing compact encoded serialization succeeds") { + return + } + + signatures := msg.Signatures() + if !assert.Len(t, signatures, 1, `there should be exactly one signature`) { + return + } + + algorithm := signatures[0].ProtectedHeaders().Algorithm() + if algorithm != alg { + t.Fatal("Algorithm in header does not match") + } + + v, err := verify.New(alg) + if !assert.NoError(t, err, "HmacVerify created") { + return + } + + signingInput := bytes.Join( + [][]byte{ + hdrBuf, + payload, + }, + []byte{'.'}, + ) + + if !assert.NoError(t, v.Verify(signingInput, signatures[0].Signature(), key), "Verify succeeds") { + return + } + }) + t.Run("ES512Compact", func(t *testing.T) { + // ES256Compact tests that https://tools.ietf.org/html/rfc7515#appendix-A.3 works + hdr := []byte{123, 34, 97, 108, 103, 34, 58, 34, 69, 83, 53, 49, 50, 34, 125} + const jwksrc = `{ +"kty":"EC", +"crv":"P-521", +"x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk", +"y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2", +"d":"AY5pb7A0UFiB3RELSD64fTLOSV_jazdF7fLYyuTw8lOfRhWg6Y6rUrPAxerEzgdRhajnu0ferB0d53vM9mE15j2C" +}` + + // "Payload" + jwsPayload := []byte{80, 97, 121, 108, 111, 97, 100} + + standardHeaders := &jws.StandardHeaders{} + err := json.Unmarshal(hdr, standardHeaders) + if err != nil { + t.Fatal("Failed to parse header") + } + alg := standardHeaders.Algorithm() + + keys, err := jwk.ParseString(jwksrc) + if err != nil { + t.Fatal("Failed to parse JWK") + } + key, err := keys.Keys[0].Materialize() + if err != nil { + t.Fatal("Failed to create private key") + } + var jwsCompact []byte + jwsCompact, err = jws.Sign(jwsPayload, alg, key) + if err != nil { + t.Fatal("Failed to sign message") + } + + // Verify with standard ecdsa library + _, _, jwsSignature, err := jws.SplitCompact(bytes.NewReader(jwsCompact)) + if err != nil { + t.Fatal("Failed to split compact JWT") + } + decodedJwsSignature := make([]byte, base64.RawURLEncoding.DecodedLen(len(jwsSignature))) + decodedLen, err := base64.RawURLEncoding.Decode(decodedJwsSignature, jwsSignature) + if err != nil { + t.Fatal("Failed to sign message") + } + r, s := &big.Int{}, &big.Int{} + n := decodedLen / 2 + r.SetBytes(decodedJwsSignature[:n]) + s.SetBytes(decodedJwsSignature[n:]) + signingHdr, err := buffer.Buffer(hdr).Base64Encode() + if err != nil { + t.Fatal("Failed to base64 encode headers") + } + signingPayload, err := buffer.Buffer(jwsPayload).Base64Encode() + if err != nil { + t.Fatal("Failed to base64 encode payload") + } + jwsSigningInput := bytes.Join( + [][]byte{ + signingHdr, + signingPayload, + }, + []byte{'.'}, + ) + hashed512 := sha512.Sum512(jwsSigningInput) + ecdsaPrivateKey := key.(*ecdsa.PrivateKey) + verified := ecdsa.Verify(&ecdsaPrivateKey.PublicKey, hashed512[:], r, s) + if !verified { + t.Fatal("Failed to verify message") + } + + // Verify with API library + + publicKey, err := jwk.GetPublicKey(key) + if err != nil { + t.Fatal("Failed to get public from private key") + } + verifiedPayload, err := jws.Verify(jwsCompact, alg, publicKey) + if err != nil || string(verifiedPayload) != string(jwsPayload) { + t.Fatal("Failed to verify message") + } + }) + t.Run("RS256Compact", func(t *testing.T) { + // RS256Compact tests that https://tools.ietf.org/html/rfc7515#appendix-A.2 works + const hdr = `{"alg":"RS256"}` + const expected = `eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw` + const jwksrc = `{ + "kty":"RSA", + "n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ", + "e":"AQAB", + "d":"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97IjlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYTCBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLhBOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ", + "p":"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdiYrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPGBY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc", + "q":"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxaewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc", + "dp":"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3QCLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0", + "dq":"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-kyNlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU", + "qi":"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2oy26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLUW0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U" + }` + + privkey, err := rsautil.PrivateKeyFromJSON([]byte(jwksrc)) + if !assert.NoError(t, err, "parsing jwk should be successful") { + return + } + + sign, err := sign.New(jwa.RS256) + if !assert.NoError(t, err, "RsaSign created successfully") { + return + } + + hdrbuf, err := buffer.Buffer(hdr).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + payload, err := buffer.Buffer(examplePayload).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + + signingInput := bytes.Join( + [][]byte{ + hdrbuf, + payload, + }, + []byte{'.'}, + ) + signature, err := sign.Sign(signingInput, privkey) + if !assert.NoError(t, err, "PayloadSign is successful") { + return + } + sigbuf, err := buffer.Buffer(signature).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + + encoded := bytes.Join( + [][]byte{ + signingInput, + sigbuf, + }, + []byte{'.'}, + ) + + if !assert.Equal(t, expected, string(encoded), "generated compact serialization should match") { + return + } + + msg, err := jws.Parse(bytes.NewReader(encoded)) + if !assert.NoError(t, err, "Parsing compact encoded serialization succeeds") { + return + } + + signatures := msg.Signatures() + if !assert.Len(t, signatures, 1, `there should be exactly one signature`) { + return + } + + algorithm := signatures[0].ProtectedHeaders().Algorithm() + if algorithm != jwa.RS256 { + t.Fatal("Algorithm in header does not match") + } + + v, err := verify.New(jwa.RS256) + if !assert.NoError(t, err, "Verify created") { + return + } + + if !assert.NoError(t, v.Verify(signingInput, signature, &privkey.PublicKey), "Verify succeeds") { + return + } + }) + t.Run("ES256Compact", func(t *testing.T) { + // ES256Compact tests that https://tools.ietf.org/html/rfc7515#appendix-A.3 works + const hdr = `{"alg":"ES256"}` + const jwksrc = `{ + "kty":"EC", + "crv":"P-256", + "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", + "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", + "d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI" + }` + + privkey, err := ecdsautil.PrivateKeyFromJSON([]byte(jwksrc)) + if !assert.NoError(t, err, "parsing jwk should be successful") { + return + } + + signer, err := sign.New(jwa.ES256) + if !assert.NoError(t, err, "RsaSign created successfully") { + return + } + + hdrbuf, err := buffer.Buffer(hdr).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + payload, err := buffer.Buffer(examplePayload).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + + signingInput := bytes.Join( + [][]byte{ + hdrbuf, + payload, + }, + []byte{'.'}, + ) + signature, err := signer.Sign(signingInput, privkey) + if !assert.NoError(t, err, "PayloadSign is successful") { + return + } + sigbuf, err := buffer.Buffer(signature).Base64Encode() + if !assert.NoError(t, err, "base64 encode successful") { + return + } + + encoded := bytes.Join( + [][]byte{ + signingInput, + sigbuf, + }, + []byte{'.'}, + ) + + // The signature contains random factor, so unfortunately we can't match + // the output against a fixed expected outcome. We'll wave doing an + // exact match, and just try to verify using the signature + + msg, err := jws.Parse(bytes.NewReader(encoded)) + if !assert.NoError(t, err, "Parsing compact encoded serialization succeeds") { + return + } + + signatures := msg.Signatures() + if !assert.Len(t, signatures, 1, `there should be exactly one signature`) { + return + } + + algorithm := signatures[0].ProtectedHeaders().Algorithm() + if algorithm != jwa.ES256 { + t.Fatal("Algorithm in header does not match") + } + + v, err := verify.New(jwa.ES256) + if !assert.NoError(t, err, "EcdsaVerify created") { + return + } + if !assert.NoError(t, v.Verify(signingInput, signature, &privkey.PublicKey), "Verify succeeds") { + return + } + }) + t.Run("UnsecuredCompact", func(t *testing.T) { + s := `eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.` + + m, err := jws.Parse(strings.NewReader(s)) + if !assert.NoError(t, err, "Parsing compact serialization") { + return + } + + { + v := map[string]interface{}{} + if !assert.NoError(t, json.Unmarshal(m.Payload(), &v), "Unmarshal payload") { + return + } + if !assert.Equal(t, v["iss"], "joe", "iss matches") { + return + } + if !assert.Equal(t, int(v["exp"].(float64)), 1300819380, "exp matches") { + return + } + if !assert.Equal(t, v["http://example.com/is_root"], true, "'http://example.com/is_root' matches") { + return + } + } + + if !assert.Len(t, m.Signatures(), 1, "There should be 1 signature") { + return + } + + signatures := m.Signatures() + algorithm := signatures[0].ProtectedHeaders().Algorithm() + if algorithm != jwa.NoSignature { + t.Fatal("Algorithm in header does not match") + } + + if !assert.Empty(t, signatures[0].Signature(), "Signature should be empty") { + return + } + }) + t.Run("CompleteJSON", func(t *testing.T) { + s := `{ + "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", + "signatures":[ + { + "header": {"kid":"2010-12-29"}, + "protected":"eyJhbGciOiJSUzI1NiJ9", + "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" + }, + { + "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}, + "protected":"eyJhbGciOiJFUzI1NiJ9", + "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" + } + ] + }` + + m, err := jws.Parse(strings.NewReader(s)) + if !assert.NoError(t, err, "Unmarshal complete json serialization") { + return + } + + if !assert.Len(t, m.Signatures(), 2, "There should be 2 signatures") { + return + } + + var sigs []*jws.Signature + sigs = m.LookupSignature("2010-12-29") + if !assert.Len(t, sigs, 1, "There should be 1 signature with kid = '2010-12-29'") { + return + } + + jsonbuf, err := json.Marshal(m) + if !assert.NoError(t, err, "Marshal JSON is successful") { + return + } + + b := &bytes.Buffer{} + json.Compact(b, jsonbuf) + + if !assert.Equal(t, b.Bytes(), jsonbuf, "generated json matches") { + return + } + }) + t.Run("Protected Header lookup", func(t *testing.T) { + s := `{ + "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", + "signatures":[ + { + "header": {"cty":"example"}, + "protected":"eyJhbGciOiJFUzI1NiIsImtpZCI6ImU5YmMwOTdhLWNlNTEtNDAzNi05NTYyLWQyYWRlODgyZGIwZCJ9", + "signature": "JcLb1udPAV72TayGv6eawZKlIQQ3K1NzB0fU7wwYoFypGxEczdCQU-V9jp4WwY2ueJKYeE4fF6jigB0PdSKR0Q" + } + ] + }` + + // Protected Header is {"alg":"ES256","kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"} + // This protected header combination forces the parser/unmarshal to go trough the code path to populate and look for protected header fields. + // The signature is valid. + + m, err := jws.Parse(strings.NewReader(s)) + if !assert.NoError(t, err, "Unmarshal complete json serialization") { + return + } + if len(m.Signatures()) != 1 { + t.Fatal("There should be 1 signature") + } + + var sigs []*jws.Signature + sigs = m.LookupSignature("e9bc097a-ce51-4036-9562-d2ade882db0d") + if !assert.Len(t, sigs, 1, "There should be 1 signature with kid = '2010-12-29'") { + return + } + }) + t.Run("FlattenedJSON", func(t *testing.T) { + s := `{ + "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", + "protected":"eyJhbGciOiJFUzI1NiJ9", + "header": { + "kid":"e9bc097a-ce51-4036-9562-d2ade882db0d" + }, + "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" + }` + + m, err := jws.Parse(strings.NewReader(s)) + if !assert.NoError(t, err, "Parsing flattened json serialization") { + return + } + + if !assert.Len(t, m.Signatures(), 1, "There should be 1 signature") { + return + } + + jsonbuf, _ := json.MarshalIndent(m, "", " ") + t.Logf("%s", jsonbuf) + }) + t.Run("SplitCompact short", func(t *testing.T) { + // Create string with X.Y.Z + numX := 100 + numY := 100 + numZ := 100 + var largeString = "" + for i := 0; i < numX; i++ { + largeString += "X" + } + largeString += "." + for i := 0; i < numY; i++ { + largeString += "Y" + } + largeString += "." + for i := 0; i < numZ; i++ { + largeString += "Z" + } + x, y, z, err := jws.SplitCompact(strings.NewReader(largeString)) + if !assert.NoError(t, err, "SplitCompactShort string split") { + return + } + if !assert.Len(t, x, numX, "Length of header") { + return + } + if !assert.Len(t, y, numY, "Length of payload") { + return + } + if !assert.Len(t, z, numZ, "Length of signature") { + return + } + }) + t.Run("SplitCompact long", func(t *testing.T) { + // Create string with X.Y.Z + numX := 8000 + numY := 8000 + numZ := 8000 + var largeString = "" + for i := 0; i < numX; i++ { + largeString += "X" + } + largeString += "." + for i := 0; i < numY; i++ { + largeString += "Y" + } + largeString += "." + for i := 0; i < numZ; i++ { + largeString += "Z" + } + x, y, z, err := jws.SplitCompact(strings.NewReader(largeString)) + if !assert.NoError(t, err, "SplitCompactShort string split") { + return + } + if !assert.Len(t, x, numX, "Length of header") { + return + } + if !assert.Len(t, y, numY, "Length of payload") { + return + } + if !assert.Len(t, z, numZ, "Length of signature") { + return + } + }) +} + +/* +func TestSign_HeaderValues(t *testing.T) { + const jwksrc = `{ + "kty":"EC", + "crv":"P-256", + "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", + "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", + "d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI" + }` + + privkey, err := ecdsautil.PrivateKeyFromJSON([]byte(jwksrc)) + if !assert.NoError(t, err, "parsing jwk should be successful") { + return + } + + payload := []byte("Hello, World!") + + hdr := jws.NewHeader() + hdr.KeyID = "helloworld01" + encoded, err := jws.Sign(payload, jwa.ES256, privkey, jws.WithPublicHeaders(hdr)) + if !assert.NoError(t, err, "Sign should succeed") { + return + } + + // Although we set KeyID to the public header, in compact serialization + // there's no difference + msg, err := jws.Parse(bytes.NewReader(encoded)) + if !assert.NoError(t, err, `parse should succeed`) { + return + } + + if !assert.Equal(t, hdr.KeyID, msg.Signatures[0].ProtectedHeader.KeyID, "KeyID should match") { + return + } + + verified, err := jws.Verify(encoded, jwa.ES256, &privkey.PublicKey) + if !assert.NoError(t, err, "Verify should succeed") { + return + } + if !assert.Equal(t, verified, payload, "Payload should match") { + return + } +} +*/ + +func TestPublicHeaders(t *testing.T) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if !assert.NoError(t, err, "GenerateKey should succeed") { + return + } + + signer, err := sign.New(jwa.RS256) + if !assert.NoError(t, err, "rsasign.NewSigner should succeed") { + return + } + _ = signer // TODO + + pubkey := key.PublicKey + pubjwk, err := jwk.New(&pubkey) + if !assert.NoError(t, err, "NewRsaPublicKey should succeed") { + return + } + _ = pubjwk // TODO + + /* + if !assert.NoError(t, signer.UnprotectedHeaders().Set("jwk", pubjwk), "Set('jwk') should succeed") { + return + } + */ +} + +func TestDecode_ES384Compact_NoSigTrim(t *testing.T) { + incoming := "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6IjE5MzFmZTQ0YmFhMWNhZTkyZWUzNzYzOTQ0MDU1OGMwODdlMTRlNjk5ZWU5NjVhM2Q1OGU1MmU2NGY4MDE0NWIifQ.eyJpc3MiOiJicmt0LWNsaS0xLjAuN3ByZTEiLCJpYXQiOjE0ODQ2OTU1MjAsImp0aSI6IjgxYjczY2Y3In0.DdFi0KmPHSv4PfIMGcWGMSRLmZsfRPQ3muLFW6Ly2HpiLFFQWZ0VEanyrFV263wjlp3udfedgw_vrBLz3XC8CkbvCo_xeHMzaTr_yfhjoheSj8gWRLwB-22rOnUX_M0A" + t.Logf("incoming = '%s'", incoming) + const jwksrc = `{ + "kty":"EC", + "crv":"P-384", + "x":"YHVZ4gc1RDoqxKm4NzaN_Y1r7R7h3RM3JMteC478apSKUiLVb4UNytqWaLoE6ygH", + "y":"CRKSqP-aYTIsqJfg_wZEEYUayUR5JhZaS2m4NLk2t1DfXZgfApAJ2lBO0vWKnUMp" + }` + + pubkey, err := ecdsautil.PublicKeyFromJSON([]byte(jwksrc)) + if !assert.NoError(t, err, "parsing jwk should be successful") { + return + } + v, err := verify.New(jwa.ES384) + if !assert.NoError(t, err, "EcdsaVerify created") { + return + } + + protected, payload, signature, err := jws.SplitCompact(strings.NewReader(incoming)) + if !assert.NoError(t, err, `jws.SplitCompact should succeed`) { + return + } + + var buf bytes.Buffer + buf.Write(protected) + buf.WriteByte('.') + buf.Write(payload) + + decodedSignature := make([]byte, base64.RawURLEncoding.DecodedLen(len(signature))) + if _, err := base64.RawURLEncoding.Decode(decodedSignature, signature); !assert.NoError(t, err, `decoding signature should succeed`) { + return + } + + if !assert.NoError(t, v.Verify(buf.Bytes(), decodedSignature, pubkey), "Verify succeeds") { + return + } +} + +func TestGHIssue126(t *testing.T) { + _, err := jws.Verify([]byte("{}"), jwa.ES384, nil) + if !assert.Error(t, err, "Verify should fail") { + return + } + + if !assert.Equal(t, err.Error(), `invalid JWS message format`) { + return + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/message.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/message.go new file mode 100644 index 0000000000..fca60855b2 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/message.go @@ -0,0 +1,45 @@ +package jws + +func (s Signature) PublicHeaders() Headers { + return s.headers +} + +func (s Signature) ProtectedHeaders() Headers { + return s.protected +} + +func (s Signature) Signature() []byte { + return s.signature +} + +func (m Message) Payload() []byte { + return m.payload +} + +func (m Message) Signatures() []*Signature { + return m.signatures +} + +// LookupSignature looks up a particular signature entry using +// the `kid` value +func (m Message) LookupSignature(kid string) []*Signature { + var sigs []*Signature + for _, sig := range m.signatures { + if hdr := sig.PublicHeaders(); hdr != nil { + hdrKeyId, ok := hdr.Get(KeyIDKey) + if ok && hdrKeyId == kid { + sigs = append(sigs, sig) + continue + } + } + + if hdr := sig.ProtectedHeaders(); hdr != nil { + hdrKeyId, ok := hdr.Get(KeyIDKey) + if ok && hdrKeyId == kid { + sigs = append(sigs, sig) + continue + } + } + } + return sigs +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/option.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/option.go new file mode 100644 index 0000000000..5a0d49147d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/option.go @@ -0,0 +1,26 @@ +package jws + +import ( + "github.com/lestrrat-go/jwx/internal/option" + "github.com/lestrrat-go/jwx/jws/sign" +) + +type Option = option.Interface + +const ( + optkeyPayloadSigner = `payload-signer` + optkeyHeaders = `headers` +) + +func WithSigner(signer sign.Signer, key interface{}, public, protected Headers) Option { + return option.New(optkeyPayloadSigner, &payloadSigner{ + signer: signer, + key: key, + protected: protected, + public: public, + }) +} + +func WithHeaders(h Headers) Option { + return option.New(optkeyHeaders, h) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/ecdsa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/ecdsa.go new file mode 100644 index 0000000000..a4aca4a2d8 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/ecdsa.go @@ -0,0 +1,81 @@ +package sign + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +var ecdsaSignFuncs = map[jwa.SignatureAlgorithm]ecdsaSignFunc{} + +func init() { + algs := map[jwa.SignatureAlgorithm]crypto.Hash{ + jwa.ES256: crypto.SHA256, + jwa.ES384: crypto.SHA384, + jwa.ES512: crypto.SHA512, + } + + for alg, h := range algs { + ecdsaSignFuncs[alg] = makeECDSASignFunc(h) + } +} + +func makeECDSASignFunc(hash crypto.Hash) ecdsaSignFunc { + return ecdsaSignFunc(func(payload []byte, key *ecdsa.PrivateKey) ([]byte, error) { + curveBits := key.Curve.Params().BitSize + keyBytes := curveBits / 8 + // Curve bits do not need to be a multiple of 8. + if curveBits%8 > 0 { + keyBytes += 1 + } + h := hash.New() + h.Write(payload) + r, s, err := ecdsa.Sign(rand.Reader, key, h.Sum(nil)) + if err != nil { + return nil, errors.Wrap(err, "failed to sign payload using ecdsa") + } + + rBytes := r.Bytes() + rBytesPadded := make([]byte, keyBytes) + copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) + + sBytes := s.Bytes() + sBytesPadded := make([]byte, keyBytes) + copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) + + out := append(rBytesPadded, sBytesPadded...) + return out, nil + }) +} + +func newECDSA(alg jwa.SignatureAlgorithm) (*ECDSASigner, error) { + signfn, ok := ecdsaSignFuncs[alg] + if !ok { + return nil, errors.Errorf(`unsupported algorithm while trying to create ECDSA signer: %s`, alg) + } + + return &ECDSASigner{ + alg: alg, + sign: signfn, + }, nil +} + +func (s ECDSASigner) Algorithm() jwa.SignatureAlgorithm { + return s.alg +} + +func (s ECDSASigner) Sign(payload []byte, key interface{}) ([]byte, error) { + if key == nil { + return nil, errors.New(`missing private key while signing payload`) + } + + ecdsakey, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, errors.Errorf(`invalid key type %T. *ecdsa.PrivateKey is required`, key) + } + + return s.sign(payload, ecdsakey) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/ecdsa_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/ecdsa_test.go new file mode 100644 index 0000000000..325b10f425 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/ecdsa_test.go @@ -0,0 +1,34 @@ +package sign + +import ( + "github.com/lestrrat-go/jwx/jwa" + "testing" +) + +func TestECDSASign(t *testing.T) { + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + t.Run("ECDSA Creation Error", func(t *testing.T) { + _, err := newECDSA(jwa.HS256) + if err == nil { + t.Fatal("ECDSA Object creation should fail") + } + }) + t.Run("ECDSA Sign Error", func(t *testing.T) { + signer, err := newECDSA(jwa.ES512) + if err != nil { + t.Fatalf("Signer creation failure: %v", jwa.ES512) + } + _, err = signer.Sign([]byte("payload"), dummy) + if err == nil { + t.Fatal("HMAC Object creation should fail") + } + _, err = signer.Sign([]byte("payload"), []byte("")) + if err == nil { + t.Fatal("HMAC Object creation should fail") + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/hmac.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/hmac.go new file mode 100644 index 0000000000..0921f77cd4 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/hmac.go @@ -0,0 +1,63 @@ +package sign + +import ( + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "hash" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +var HMACSignFuncs = map[jwa.SignatureAlgorithm]hmacSignFunc{} + +func init() { + algs := map[jwa.SignatureAlgorithm]func() hash.Hash{ + jwa.HS256: sha256.New, + jwa.HS384: sha512.New384, + jwa.HS512: sha512.New, + } + + for alg, h := range algs { + HMACSignFuncs[alg] = makeHMACSignFunc(h) + + } +} + +func newHMAC(alg jwa.SignatureAlgorithm) (*HMACSigner, error) { + signer, ok := HMACSignFuncs[alg] + if !ok { + return nil, errors.Errorf(`unsupported algorithm while trying to create HMAC signer: %s`, alg) + } + + return &HMACSigner{ + alg: alg, + sign: signer, + }, nil +} + +func makeHMACSignFunc(hfunc func() hash.Hash) hmacSignFunc { + return hmacSignFunc(func(payload []byte, key []byte) ([]byte, error) { + h := hmac.New(hfunc, key) + h.Write(payload) + return h.Sum(nil), nil + }) +} + +func (s HMACSigner) Algorithm() jwa.SignatureAlgorithm { + return s.alg +} + +func (s HMACSigner) Sign(payload []byte, key interface{}) ([]byte, error) { + hmackey, ok := key.([]byte) + if !ok { + return nil, errors.Errorf(`invalid key type %T. []byte is required`, key) + } + + if len(hmackey) == 0 { + return nil, errors.New(`missing key while signing payload`) + } + + return s.sign(payload, hmackey) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/hmac_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/hmac_test.go new file mode 100644 index 0000000000..b9d7ebf117 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/hmac_test.go @@ -0,0 +1,34 @@ +package sign + +import ( + "github.com/lestrrat-go/jwx/jwa" + "testing" +) + +func TestHMACSign(t *testing.T) { + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + t.Run("HMAC Creation Error", func(t *testing.T) { + _, err := newHMAC(jwa.ES256) + if err == nil { + t.Fatal("HMAC Object creation should fail") + } + }) + t.Run("HMAC Sign Error", func(t *testing.T) { + signer, err := newHMAC(jwa.HS512) + if err != nil { + t.Fatalf("Signer creation failure: %v", jwa.HS512) + } + _, err = signer.Sign([]byte("payload"), dummy) + if err == nil { + t.Fatal("HMAC Object creation should fail") + } + _, err = signer.Sign([]byte("payload"), []byte("")) + if err == nil { + t.Fatal("HMAC Object creation should fail") + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/interface.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/interface.go new file mode 100644 index 0000000000..f5371a6287 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/interface.go @@ -0,0 +1,44 @@ +package sign + +import ( + "crypto/ecdsa" + "crypto/rsa" + + "github.com/lestrrat-go/jwx/jwa" +) + +type Signer interface { + // Sign creates a signature for the given `payload`. + // `key` is the key used for signing the payload, and is usually + // the private key type associated with the signature method. For example, + // for `jwa.RSXXX` and `jwa.PSXXX` types, you need to pass the + // `*"crypto/rsa".PrivateKey` type. + // Check the documentation for each signer for details + Sign(payload []byte, key interface{}) ([]byte, error) + + Algorithm() jwa.SignatureAlgorithm +} + +type rsaSignFunc func([]byte, *rsa.PrivateKey) ([]byte, error) + +// RSASigner uses crypto/rsa to sign the payloads. +type RSASigner struct { + alg jwa.SignatureAlgorithm + sign rsaSignFunc +} + +type ecdsaSignFunc func([]byte, *ecdsa.PrivateKey) ([]byte, error) + +// ECDSASigner uses crypto/ecdsa to sign the payloads. +type ECDSASigner struct { + alg jwa.SignatureAlgorithm + sign ecdsaSignFunc +} + +type hmacSignFunc func([]byte, []byte) ([]byte, error) + +// HMACSigner uses crypto/hmac to sign the payloads. +type HMACSigner struct { + alg jwa.SignatureAlgorithm + sign hmacSignFunc +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/rsa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/rsa.go new file mode 100644 index 0000000000..1e29390501 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/rsa.go @@ -0,0 +1,95 @@ +package sign + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +var rsaSignFuncs = map[jwa.SignatureAlgorithm]rsaSignFunc{} + +func init() { + algs := map[jwa.SignatureAlgorithm]struct { + Hash crypto.Hash + SignFunc func(crypto.Hash) rsaSignFunc + }{ + jwa.RS256: { + Hash: crypto.SHA256, + SignFunc: makeSignPKCS1v15, + }, + jwa.RS384: { + Hash: crypto.SHA384, + SignFunc: makeSignPKCS1v15, + }, + jwa.RS512: { + Hash: crypto.SHA512, + SignFunc: makeSignPKCS1v15, + }, + jwa.PS256: { + Hash: crypto.SHA256, + SignFunc: makeSignPSS, + }, + jwa.PS384: { + Hash: crypto.SHA384, + SignFunc: makeSignPSS, + }, + jwa.PS512: { + Hash: crypto.SHA512, + SignFunc: makeSignPSS, + }, + } + + for alg, item := range algs { + rsaSignFuncs[alg] = item.SignFunc(item.Hash) + } +} + +func makeSignPKCS1v15(hash crypto.Hash) rsaSignFunc { + return rsaSignFunc(func(payload []byte, key *rsa.PrivateKey) ([]byte, error) { + h := hash.New() + h.Write(payload) + return rsa.SignPKCS1v15(rand.Reader, key, hash, h.Sum(nil)) + }) +} + +func makeSignPSS(hash crypto.Hash) rsaSignFunc { + return rsaSignFunc(func(payload []byte, key *rsa.PrivateKey) ([]byte, error) { + h := hash.New() + h.Write(payload) + return rsa.SignPSS(rand.Reader, key, hash, h.Sum(nil), &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + }) + }) +} + +func newRSA(alg jwa.SignatureAlgorithm) (*RSASigner, error) { + signfn, ok := rsaSignFuncs[alg] + if !ok { + return nil, errors.Errorf(`unsupported algorithm while trying to create RSA signer: %s`, alg) + } + return &RSASigner{ + alg: alg, + sign: signfn, + }, nil +} + +func (s RSASigner) Algorithm() jwa.SignatureAlgorithm { + return s.alg +} + +// Sign creates a signature using crypto/rsa. key must be a non-nil instance of +// `*"crypto/rsa".PrivateKey`. +func (s RSASigner) Sign(payload []byte, key interface{}) ([]byte, error) { + if key == nil { + return nil, errors.New(`missing private key while signing payload`) + } + rsakey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, errors.Errorf(`invalid key type %T. *rsa.PrivateKey is required`, key) + } + + return s.sign(payload, rsakey) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/sign.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/sign.go new file mode 100644 index 0000000000..1b4474b558 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/sign/sign.go @@ -0,0 +1,20 @@ +package sign + +import ( + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +// New creates a signer that signs payloads using the given signature algorithm. +func New(alg jwa.SignatureAlgorithm) (Signer, error) { + switch alg { + case jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512: + return newRSA(alg) + case jwa.ES256, jwa.ES384, jwa.ES512: + return newECDSA(alg) + case jwa.HS256, jwa.HS384, jwa.HS512: + return newHMAC(alg) + default: + return nil, errors.Errorf(`unsupported signature algorithm %s`, alg) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/signer.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/signer.go new file mode 100644 index 0000000000..1351a3b326 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/signer.go @@ -0,0 +1 @@ +package jws diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/signer_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/signer_test.go new file mode 100644 index 0000000000..fa4c01b2dc --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/signer_test.go @@ -0,0 +1,100 @@ +package jws_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "strings" + "testing" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jws" + "github.com/lestrrat-go/jwx/jws/sign" + "github.com/lestrrat-go/jwx/jws/verify" + "github.com/stretchr/testify/assert" +) + +func TestSign(t *testing.T) { + t.Run("Bad algorithm", func(t *testing.T) { + _, err := jws.Sign([]byte(nil), jwa.SignatureAlgorithm("FooBar"), nil) + if !assert.Error(t, err, "Unknown algorithm should return error") { + return + } + }) + t.Run("No private key", func(t *testing.T) { + _, err := jws.Sign([]byte{'a', 'b', 'c'}, jwa.RS256, nil) + if !assert.Error(t, err, "Sign with no private key should return error") { + return + } + }) + t.Run("RSA verify with no public key", func(t *testing.T) { + _, err := jws.Verify([]byte(nil), jwa.RS256, nil) + if !assert.Error(t, err, "Verify with no private key should return error") { + return + } + }) + t.Run("RSA roundtrip", func(t *testing.T) { + rsakey, err := rsa.GenerateKey(rand.Reader, 2048) + if !assert.NoError(t, err, "RSA key generated") { + return + } + + signer, err := sign.New(jwa.RS256) + if !assert.NoError(t, err, `creating a signer should succeed`) { + return + } + + payload := []byte("Hello, world") + + signed, err := signer.Sign(payload, rsakey) + if !assert.NoError(t, err, "Payload signed") { + return + } + + verifier, err := verify.New(jwa.RS256) + if !assert.NoError(t, err, "creating a verifier should succeed") { + return + } + + if !assert.NoError(t, verifier.Verify(payload, signed, &rsakey.PublicKey), "Payload verified") { + return + } + }) +} +func TestSignMulti(t *testing.T) { + rsakey, err := rsa.GenerateKey(rand.Reader, 2048) + if !assert.NoError(t, err, "RSA key generated") { + return + } + + dsakey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if !assert.NoError(t, err, "ECDSA key generated") { + return + } + + s1, err := sign.New(jwa.RS256) + if !assert.NoError(t, err, "RSA Signer created") { + return + } + var s1hdr jws.StandardHeaders + s1hdr.Set(jws.KeyIDKey, "2010-12-29") + + s2, err := sign.New(jwa.ES256) + if !assert.NoError(t, err, "DSA Signer created") { + return + } + var s2hdr jws.StandardHeaders + s2hdr.Set(jws.KeyIDKey, "e9bc097a-ce51-4036-9562-d2ade882db0d") + + v := strings.Join([]string{`{"iss":"joe",`, ` "exp":1300819380,`, ` "http://example.com/is_root":true}`}, "\r\n") + m, err := jws.SignMulti([]byte(v), + jws.WithSigner(s1, rsakey, &s1hdr, nil), + jws.WithSigner(s2, dsakey, &s2hdr, nil), + ) + if !assert.NoError(t, err, "jws.SignMulti should succeed") { + return + } + + t.Logf("%s", m) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/ecdsa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/ecdsa.go new file mode 100644 index 0000000000..879e1e346f --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/ecdsa.go @@ -0,0 +1,65 @@ +package verify + +import ( + "crypto" + "crypto/ecdsa" + "math/big" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +var ecdsaVerifyFuncs = map[jwa.SignatureAlgorithm]ecdsaVerifyFunc{} + +func init() { + algs := map[jwa.SignatureAlgorithm]crypto.Hash{ + jwa.ES256: crypto.SHA256, + jwa.ES384: crypto.SHA384, + jwa.ES512: crypto.SHA512, + } + + for alg, h := range algs { + ecdsaVerifyFuncs[alg] = makeECDSAVerifyFunc(h) + } +} + +func makeECDSAVerifyFunc(hash crypto.Hash) ecdsaVerifyFunc { + return ecdsaVerifyFunc(func(payload []byte, signature []byte, key *ecdsa.PublicKey) error { + + r, s := &big.Int{}, &big.Int{} + n := len(signature) / 2 + r.SetBytes(signature[:n]) + s.SetBytes(signature[n:]) + + h := hash.New() + h.Write(payload) + + if !ecdsa.Verify(key, h.Sum(nil), r, s) { + return errors.New(`failed to verify signature using ecdsa`) + } + return nil + }) +} + +func newECDSA(alg jwa.SignatureAlgorithm) (*ECDSAVerifier, error) { + verifyfn, ok := ecdsaVerifyFuncs[alg] + if !ok { + return nil, errors.Errorf(`unsupported algorithm while trying to create ECDSA verifier: %s`, alg) + } + + return &ECDSAVerifier{ + verify: verifyfn, + }, nil +} + +func (v ECDSAVerifier) Verify(payload []byte, signature []byte, key interface{}) error { + if key == nil { + return errors.New(`missing public key while verifying payload`) + } + ecdsakey, ok := key.(*ecdsa.PublicKey) + if !ok { + return errors.Errorf(`invalid key type %T. *ecdsa.PublicKey is required`, key) + } + + return v.verify(payload, signature, ecdsakey) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/ecdsa_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/ecdsa_test.go new file mode 100644 index 0000000000..465ed0211a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/ecdsa_test.go @@ -0,0 +1,34 @@ +package verify + +import ( + "github.com/lestrrat-go/jwx/jwa" + "testing" +) + +func TestECDSAVerify(t *testing.T) { + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + t.Run("ECDSA Verifier Creation Error", func(t *testing.T) { + _, err := newECDSA(jwa.HS256) + if err == nil { + t.Fatal("ECDSA Verifier Object creation should fail") + } + }) + t.Run("ECDSA Verifier Sign Error", func(t *testing.T) { + pVerifier, err := newECDSA(jwa.ES512) + if err != nil { + t.Fatalf("Signer creation failure: %v", jwa.ES512) + } + err = pVerifier.Verify([]byte("payload"), []byte("signature"), dummy) + if err == nil { + t.Fatal("ECDSA Verification should fail") + } + err = pVerifier.Verify([]byte("payload"), []byte("signature"), nil) + if err == nil { + t.Fatal("ECDSA Verification should fail") + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/hmac.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/hmac.go new file mode 100644 index 0000000000..268ce25921 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/hmac.go @@ -0,0 +1,33 @@ +package verify + +import ( + "crypto/hmac" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jws/sign" + "github.com/pkg/errors" +) + +func newHMAC(alg jwa.SignatureAlgorithm) (*HMACVerifier, error) { + _, ok := sign.HMACSignFuncs[alg] + if !ok { + return nil, errors.Errorf(`unsupported algorithm while trying to create HMAC signer: %s`, alg) + } + s, err := sign.New(alg) + if err != nil { + return nil, errors.Wrap(err, `failed to generate HMAC signer`) + } + return &HMACVerifier{signer: s}, nil +} + +func (v HMACVerifier) Verify(payload, signature []byte, key interface{}) (err error) { + + expected, err := v.signer.Sign(payload, key) + if err != nil { + return errors.Wrap(err, `failed to generated signature`) + } + + if !hmac.Equal(signature, expected) { + return errors.New(`failed to match hmac signature`) + } + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/hmac_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/hmac_test.go new file mode 100644 index 0000000000..bd676a458f --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/hmac_test.go @@ -0,0 +1,31 @@ +package verify + +import ( + "github.com/lestrrat-go/jwx/jwa" + "testing" +) + +func TestHMACVerify(t *testing.T) { + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + t.Run("HMAC Verifier Creation Error", func(t *testing.T) { + _, err := newHMAC(jwa.ES256) + if err == nil { + t.Fatal("HMAC Verifier Object creation should fail") + } + }) + t.Run("HMAC Verifier Sign Error", func(t *testing.T) { + pVerifier, err := newHMAC(jwa.HS512) + if err != nil { + t.Fatalf("Signer creation failure: %v", jwa.HS512) + } + err = pVerifier.Verify([]byte("payload"), []byte("signature"), dummy) + if err == nil { + t.Fatal("HMAC Verification should fail") + } + + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/interface.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/interface.go new file mode 100644 index 0000000000..a17b63e725 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/interface.go @@ -0,0 +1,35 @@ +package verify + +import ( + "crypto/ecdsa" + "crypto/rsa" + + "github.com/lestrrat-go/jwx/jws/sign" +) + +type Verifier interface { + // Verify checks whether the payload and signature are valid for + // the given key. + // `key` is the key used for verifying the payload, and is usually + // the public key associated with the signature method. For example, + // for `jwa.RSXXX` and `jwa.PSXXX` types, you need to pass the + // `*"crypto/rsa".PublicKey` type. + // Check the documentation for each verifier for details + Verify(payload []byte, signature []byte, key interface{}) error +} + +type rsaVerifyFunc func([]byte, []byte, *rsa.PublicKey) error + +type RSAVerifier struct { + verify rsaVerifyFunc +} + +type ecdsaVerifyFunc func([]byte, []byte, *ecdsa.PublicKey) error + +type ECDSAVerifier struct { + verify ecdsaVerifyFunc +} + +type HMACVerifier struct { + signer sign.Signer +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/rsa.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/rsa.go new file mode 100644 index 0000000000..4bbf861b05 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/rsa.go @@ -0,0 +1,86 @@ +package verify + +import ( + "crypto" + "crypto/rsa" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +var rsaVerifyFuncs = map[jwa.SignatureAlgorithm]rsaVerifyFunc{} + +func init() { + algs := map[jwa.SignatureAlgorithm]struct { + Hash crypto.Hash + VerifyFunc func(crypto.Hash) rsaVerifyFunc + }{ + jwa.RS256: { + Hash: crypto.SHA256, + VerifyFunc: makeVerifyPKCS1v15, + }, + jwa.RS384: { + Hash: crypto.SHA384, + VerifyFunc: makeVerifyPKCS1v15, + }, + jwa.RS512: { + Hash: crypto.SHA512, + VerifyFunc: makeVerifyPKCS1v15, + }, + jwa.PS256: { + Hash: crypto.SHA256, + VerifyFunc: makeVerifyPSS, + }, + jwa.PS384: { + Hash: crypto.SHA384, + VerifyFunc: makeVerifyPSS, + }, + jwa.PS512: { + Hash: crypto.SHA512, + VerifyFunc: makeVerifyPSS, + }, + } + + for alg, item := range algs { + rsaVerifyFuncs[alg] = item.VerifyFunc(item.Hash) + } +} + +func makeVerifyPKCS1v15(hash crypto.Hash) rsaVerifyFunc { + return rsaVerifyFunc(func(payload, signature []byte, key *rsa.PublicKey) error { + h := hash.New() + h.Write(payload) + return rsa.VerifyPKCS1v15(key, hash, h.Sum(nil), signature) + }) +} + +func makeVerifyPSS(hash crypto.Hash) rsaVerifyFunc { + return rsaVerifyFunc(func(payload, signature []byte, key *rsa.PublicKey) error { + h := hash.New() + h.Write(payload) + return rsa.VerifyPSS(key, hash, h.Sum(nil), signature, nil) + }) +} + +func newRSA(alg jwa.SignatureAlgorithm) (*RSAVerifier, error) { + verifyfn, ok := rsaVerifyFuncs[alg] + if !ok { + return nil, errors.Errorf(`unsupported algorithm while trying to create RSA verifier: %s`, alg) + } + + return &RSAVerifier{ + verify: verifyfn, + }, nil +} + +func (v RSAVerifier) Verify(payload, signature []byte, key interface{}) error { + if key == nil { + return errors.New(`missing public key while verifying payload`) + } + rsakey, ok := key.(*rsa.PublicKey) + if !ok { + return errors.Errorf(`invalid key type %T. *rsa.PublicKey is required`, key) + } + + return v.verify(payload, signature, rsakey) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/rsa_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/rsa_test.go new file mode 100644 index 0000000000..1126aa27dc --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/rsa_test.go @@ -0,0 +1,34 @@ +package verify + +import ( + "github.com/lestrrat-go/jwx/jwa" + "testing" +) + +func TestRSAVerify(t *testing.T) { + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + t.Run("RSA Verifier Creation Error", func(t *testing.T) { + _, err := newRSA(jwa.HS256) + if err == nil { + t.Fatal("ECDSA Verifier Object creation should fail") + } + }) + t.Run("RSA Verifier Sign Error", func(t *testing.T) { + pVerifier, err := newRSA(jwa.PS512) + if err != nil { + t.Fatalf("Signer creation failure: %v", jwa.ES512) + } + err = pVerifier.Verify([]byte("payload"), []byte("signature"), dummy) + if err == nil { + t.Fatal("RSA Verification should fail") + } + err = pVerifier.Verify([]byte("payload"), []byte("signature"), nil) + if err == nil { + t.Fatal("RSA Verification should fail") + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/verify.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/verify.go new file mode 100644 index 0000000000..368e7b454d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jws/verify/verify.go @@ -0,0 +1,21 @@ +package verify + +import ( + "github.com/lestrrat-go/jwx/jwa" + "github.com/pkg/errors" +) + +// New creates a new JWS verifier using the specified algorithm +// and the public key +func New(alg jwa.SignatureAlgorithm) (Verifier, error) { + switch alg { + case jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512: + return newRSA(alg) + case jwa.ES256, jwa.ES384, jwa.ES512: + return newECDSA(alg) + case jwa.HS256, jwa.HS384, jwa.HS512: + return newHMAC(alg) + default: + return nil, errors.Errorf(`unsupported signature algorithm: %s`, alg) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/README.md b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/README.md new file mode 100644 index 0000000000..962bd21b7a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/README.md @@ -0,0 +1,78 @@ +# jwt + +JWT tokens + +# SYNOPSIS + +```go +package jwt_test + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "encoding/json" + "fmt" + "time" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwt" +) + +func ExampleSignAndParse() { + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + fmt.Printf("failed to generate private key: %s\n", err) + return + } + + var payload []byte + { // Create signed payload + token := jwt.New() + token.Set(`foo`, `bar`) + payload, err = token.Sign(jwa.RS256, privKey) + if err != nil { + fmt.Printf("failed to generate signed payload: %s\n", err) + return + } + } + + { // Parse signed payload + // Use jwt.ParseVerify if you want to make absolutely sure that you + // are going to verify the signatures every time + token, err := jwt.Parse(bytes.NewReader(payload), jwt.WithVerify(jwa.RS256, &privKey.PublicKey)) + if err != nil { + fmt.Printf("failed to parse JWT token: %s\n", err) + return + } + buf, err := json.MarshalIndent(token, "", " ") + if err != nil { + fmt.Printf("failed to generate JSON: %s\n", err) + return + } + fmt.Printf("%s\n", buf) + } +} + +func ExampleToken() { + t := jwt.New() + t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`) + t.Set(jwt.AudienceKey, `Golang Users`) + t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0)) + t.Set(`privateClaimKey`, `Hello, World!`) + + buf, err := json.MarshalIndent(t, "", " ") + if err != nil { + fmt.Printf("failed to generate JSON: %s\n", err) + return + } + + fmt.Printf("%s\n", buf) + fmt.Printf("aud -> '%s'\n", t.Audience()) + fmt.Printf("iat -> '%s'\n", t.IssuedAt().Format(time.RFC3339)) + if v, ok := t.Get(`privateClaimKey`); ok { + fmt.Printf("privateClaimKey -> '%s'\n", v) + } + fmt.Printf("sub -> '%s'\n", t.Subject()) +} +``` \ No newline at end of file diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/example_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/example_test.go new file mode 100644 index 0000000000..b52748db79 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/example_test.go @@ -0,0 +1,88 @@ +package jwt_test + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "encoding/json" + "fmt" + "time" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwt" +) + +func ExampleSignAndParse() { + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + fmt.Printf("failed to generate private key: %s\n", err) + return + } + + var payload []byte + { // Create signed payload + token := jwt.New() + token.Set(`foo`, `bar`) + payload, err = token.Sign(jwa.RS256, privKey) + if err != nil { + fmt.Printf("failed to generate signed payload: %s\n", err) + return + } + } + + { // Parse signed payload + // Use jwt.ParseVerify if you want to make absolutely sure that you + // are going to verify the signatures every time + token, err := jwt.Parse(bytes.NewReader(payload), jwt.WithVerify(jwa.RS256, &privKey.PublicKey)) + if err != nil { + fmt.Printf("failed to parse JWT token: %s\n", err) + return + } + buf, err := json.MarshalIndent(token, "", " ") + if err != nil { + fmt.Printf("failed to generate JSON: %s\n", err) + return + } + fmt.Printf("%s\n", buf) + } + // OUTPUT: + // { + // "foo": "bar" + // } +} + +func ExampleToken() { + t := jwt.New() + t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`) + t.Set(jwt.AudienceKey, `Golang Users`) + t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0)) + t.Set(`privateClaimKey`, `Hello, World!`) + + buf, err := json.MarshalIndent(t, "", " ") + if err != nil { + fmt.Printf("failed to generate JSON: %s\n", err) + return + } + + fmt.Printf("%s\n", buf) + fmt.Printf("aud -> '%s'\n", t.Audience()) + fmt.Printf("iat -> '%s'\n", t.IssuedAt().Format(time.RFC3339)) + if v, ok := t.Get(`privateClaimKey`); ok { + fmt.Printf("privateClaimKey -> '%s'\n", v) + } + fmt.Printf("sub -> '%s'\n", t.Subject()) + + // OUTPUT: + // { + // "aud": [ + // "Golang Users" + // ], + // "iat": 233431200, + // "sub": "https://github.com/lestrrat-go/jwx/jwt", + // "privateClaimKey": "Hello, World!" + // } + // aud -> '[Golang Users]' + // iat -> '1977-05-25T18:00:00Z' + // privateClaimKey -> 'Hello, World!' + // sub -> 'https://github.com/lestrrat-go/jwx/jwt' +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/interface.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/interface.go new file mode 100644 index 0000000000..730224eeaa --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/interface.go @@ -0,0 +1,3 @@ +package jwt + +type StringList []string diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/cmd/gentoken/main.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/cmd/gentoken/main.go new file mode 100644 index 0000000000..73c9a7b33d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/cmd/gentoken/main.go @@ -0,0 +1,370 @@ +package main + +import ( + "bytes" + "fmt" + "go/format" + "log" + "os" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +func main() { + if err := _main(); err != nil { + log.Printf("%s", err) + os.Exit(1) + } +} + +func _main() error { + if err := generateToken(); err != nil { + return errors.Wrap(err, `failed to generate token file`) + } + return nil +} + +type tokenField struct { + Name string + JSONKey string + Type string + Comment string + isList bool + hasAccept bool + noDeref bool + elemType string +} + +func (t tokenField) UpperName() string { + return strings.Title(t.Name) +} + +func (t tokenField) IsList() bool { + return t.isList || strings.HasPrefix(t.Type, `[]`) +} + +func (t tokenField) ListElem() string { + if t.elemType != "" { + return t.elemType + } + return strings.TrimPrefix(t.Type, `[]`) +} + +func (t tokenField) IsPointer() bool { + return strings.HasPrefix(t.Type, `*`) +} + +func (t tokenField) PointerElem() string { + return strings.TrimPrefix(t.Type, `*`) +} + +var zerovals = map[string]string{ + `string`: `""`, +} + +func zeroval(s string) string { + if v, ok := zerovals[s]; ok { + return v + } + return `nil` +} + +func generateToken() error { + var buf bytes.Buffer + + var fields = []tokenField{ + { + Name: "audience", + JSONKey: "aud", + Type: "StringList", + Comment: `https://tools.ietf.org/html/rfc7519#section-4.1.3`, + isList: true, + hasAccept: true, + elemType: `string`, + }, + { + Name: "expiration", + JSONKey: "exp", + Type: "*types.NumericDate", + Comment: `https://tools.ietf.org/html/rfc7519#section-4.1.4`, + hasAccept: true, + noDeref: true, + }, + { + Name: "issuedAt", + JSONKey: "iat", + Type: "*types.NumericDate", + Comment: `https://tools.ietf.org/html/rfc7519#section-4.1.6`, + hasAccept: true, + noDeref: true, + }, + { + Name: "issuer", + JSONKey: "iss", + Type: "*string", + Comment: `https://tools.ietf.org/html/rfc7519#section-4.1.1`, + }, + { + Name: "jwtID", + JSONKey: "jti", + Type: "*string", + Comment: `https://tools.ietf.org/html/rfc7519#section-4.1.7`, + }, + { + Name: "notBefore", + JSONKey: "nbf", + Type: "*types.NumericDate", + Comment: `https://tools.ietf.org/html/rfc7519#section-4.1.5`, + hasAccept: true, + noDeref: true, + }, + { + Name: "subject", + JSONKey: "sub", + Type: "*string", + Comment: `https://tools.ietf.org/html/rfc7519#section-4.1.2`, + }, + } + + fmt.Fprintf(&buf, "\n// This file is auto-generated. DO NOT EDIT") + fmt.Fprintf(&buf, "\npackage jwt") + fmt.Fprintf(&buf, "\n\nimport (") + for _, pkg := range []string{"bytes", "encoding/json", "time", "github.com/pkg/errors", "github.com/lestrrat-go/jwx/jwt/internal/types"} { + fmt.Fprintf(&buf, "\n%s", strconv.Quote(pkg)) + } + fmt.Fprintf(&buf, "\n)") // end of import + + fmt.Fprintf(&buf, "\n\n// Key names for standard claims") + fmt.Fprintf(&buf, "\nconst (") + for _, field := range fields { + fmt.Fprintf(&buf, "\n%sKey = %s", field.UpperName(), strconv.Quote(field.JSONKey)) + } + fmt.Fprintf(&buf, "\n)") // end const + + fmt.Fprintf(&buf, "\n\n// Token represents a JWT token. The object has convenience accessors") + fmt.Fprintf(&buf, "\n// to %d standard claims including ", len(fields)) + for i, field := range fields { + fmt.Fprintf(&buf, "%s", strconv.Quote(field.JSONKey)) + switch { + case i < len(fields)-2: + fmt.Fprintf(&buf, ", ") + case i == len(fields)-2: + fmt.Fprintf(&buf, " and ") + } + } + fmt.Fprintf(&buf, "\n// which are type-aware (to an extent). Other claims may be accessed via the `Get`/`Set`") + fmt.Fprintf(&buf, "\n// methods but their types are not taken into consideration at all. If you have non-standard") + fmt.Fprintf(&buf, "\n// claims that you must frequently access, consider wrapping the token in a wrapper") + fmt.Fprintf(&buf, "\n// by embedding the jwt.Token type in it") + fmt.Fprintf(&buf, "\ntype Token struct {") + for _, field := range fields { + fmt.Fprintf(&buf, "\n%s %s `json:\"%s,omitempty\"` // %s", field.Name, field.Type, field.JSONKey, field.Comment) + } + fmt.Fprintf(&buf, "\nprivateClaims map[string]interface{} `json:\"-\"`") + fmt.Fprintf(&buf, "\n}") // end type Token + + fmt.Fprintf(&buf, "\n\nfunc (t *Token) Get(s string) (interface{}, bool) {") + fmt.Fprintf(&buf, "\nswitch s {") + for _, field := range fields { + fmt.Fprintf(&buf, "\ncase %sKey:", field.UpperName()) + switch { + case field.IsList(): + fmt.Fprintf(&buf, "\nif len(t.%s) == 0 {", field.Name) + fmt.Fprintf(&buf, "\nreturn nil, false") + fmt.Fprintf(&buf, "\n}") // end if len(t.%s) == 0 + fmt.Fprintf(&buf, "\nreturn ") + // some types such as `aud` need explicit conversion + var pre, post string + if field.Type == "StringList" { + pre = "[]string(" + post = ")" + } + fmt.Fprintf(&buf, "%st.%s%s, true", pre, field.Name, post) + case field.IsPointer(): + fmt.Fprintf(&buf, "\nif t.%s == nil {", field.Name) + fmt.Fprintf(&buf, "\nreturn nil, false") + fmt.Fprintf(&buf, "\n} else {") + if field.noDeref { + if field.Type == "*types.NumericDate" { + fmt.Fprintf(&buf, "\nreturn t.%s.Get(), true", field.Name) + } else { + fmt.Fprintf(&buf, "\nreturn t.%s, true", field.Name) + } + } else { + fmt.Fprintf(&buf, "\nreturn *(t.%s), true", field.Name) + } + fmt.Fprintf(&buf, "\n}") // end if t.%s != nil + } + } + fmt.Fprintf(&buf, "\n}") // end switch + fmt.Fprintf(&buf, "\nif v, ok := t.privateClaims[s]; ok {") + fmt.Fprintf(&buf, "\nreturn v, true") + fmt.Fprintf(&buf, "\n}") // end if v, ok := t.privateClaims[s] + fmt.Fprintf(&buf, "\nreturn nil, false") + fmt.Fprintf(&buf, "\n}") // end of Get + + fmt.Fprintf(&buf, "\n\nfunc (t *Token) Set(name string, v interface{}) error {") + fmt.Fprintf(&buf, "\nswitch name {") + for _, field := range fields { + fmt.Fprintf(&buf, "\ncase %sKey:", field.UpperName()) + switch { + case field.hasAccept: + if field.IsPointer() { + fmt.Fprintf(&buf, "\nvar x %s", field.PointerElem()) + } else { + fmt.Fprintf(&buf, "\nvar x %s", field.Type) + } + fmt.Fprintf(&buf, "\nif err := x.Accept(v); err != nil {") + fmt.Fprintf(&buf, "\nreturn errors.Wrap(err, `invalid value for '%s' key`)", field.Name) + fmt.Fprintf(&buf, "\n}") + if field.IsPointer() { + fmt.Fprintf(&buf, "\nt.%s = &x", field.Name) + } else { + fmt.Fprintf(&buf, "\nt.%s = x", field.Name) + } + case field.IsPointer(): + fmt.Fprintf(&buf, "\nx, ok := v.(%s)", field.PointerElem()) + fmt.Fprintf(&buf, "\nif !ok {") + fmt.Fprintf(&buf, "\nreturn errors.Errorf(`invalid type for '%s' key: %%T`, v)", field.Name) + fmt.Fprintf(&buf, "\n}") // end if !ok + fmt.Fprintf(&buf, "\nt.%s = &x", field.Name) + case field.IsList(): + fmt.Fprintf(&buf, "\nswitch x := v.(type) {") + fmt.Fprintf(&buf, "\ncase %s:", field.ListElem()) + fmt.Fprintf(&buf, "\nt.%s = []string{x}", field.Name) + fmt.Fprintf(&buf, "\ncase %s:", field.Type) + fmt.Fprintf(&buf, "\nt.%s = x", field.Name) + fmt.Fprintf(&buf, "\ndefault:") + fmt.Fprintf(&buf, "\nreturn errors.Errorf(`invalid type for '%s' key: %%T`, v)", field.Name) + fmt.Fprintf(&buf, "\n}") // end of switch x := v.(type) {") + } + } + fmt.Fprintf(&buf, "\ndefault:") + fmt.Fprintf(&buf, "\nif t.privateClaims == nil {") + fmt.Fprintf(&buf, "\nt.privateClaims = make(map[string]interface{})") + fmt.Fprintf(&buf, "\n}") // end if h.privateParams == nil + fmt.Fprintf(&buf, "\nt.privateClaims[name] = v") + fmt.Fprintf(&buf, "\n}") // end switch name + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") // end func (h *StandardHeaders) Set(name string, value interface{}) + + for _, field := range fields { + switch { + case field.IsList(): + fmt.Fprintf(&buf, "\n\nfunc (t Token) %s() %s {", field.UpperName(), field.Type) + fmt.Fprintf(&buf, "\nif v, ok := t.Get(%sKey); ok {", field.UpperName()) + fmt.Fprintf(&buf, "\nreturn v.([]string)") + fmt.Fprintf(&buf, "\n}") // end if v, ok := t.Get(%sKey) + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") // end func (t Token) %s() %s + case field.Type == "*types.NumericDate": + fmt.Fprintf(&buf, "\n\nfunc (t Token) %s() time.Time {", field.UpperName()) + fmt.Fprintf(&buf, "\nif v, ok := t.Get(%sKey); ok {", field.UpperName()) + fmt.Fprintf(&buf, "\nreturn v.(time.Time)") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\nreturn time.Time{}") + fmt.Fprintf(&buf, "\n}") // end func (t Token) %s() + case field.IsPointer(): + fmt.Fprintf(&buf, "\n\n// %s is a convenience function to retrieve the corresponding value store in the token", field.UpperName()) + fmt.Fprintf(&buf, "\n// if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead") + fmt.Fprintf(&buf, "\n\nfunc (t Token) %s() %s {", field.UpperName(), field.PointerElem()) + fmt.Fprintf(&buf, "\nif v, ok := t.Get(%sKey); ok {", field.UpperName()) + fmt.Fprintf(&buf, "\nreturn v.(%s)", field.PointerElem()) + fmt.Fprintf(&buf, "\n}") // end if v, ok := t.Get(%sKey) + fmt.Fprintf(&buf, "\nreturn %s", zeroval(field.PointerElem())) + fmt.Fprintf(&buf, "\n}") // end func (t Token) %s() %s + } + } + + // JSON related stuff + fmt.Fprintf(&buf, "\n\n// this is almost identical to json.Encoder.Encode(), but we use Marshal") + fmt.Fprintf(&buf, "\n// to avoid having to remove the trailing newline for each successive") + fmt.Fprintf(&buf, "\n// call to Encode()") + fmt.Fprintf(&buf, "\nfunc writeJSON(buf *bytes.Buffer, v interface{}, keyName string) error {") + fmt.Fprintf(&buf, "\nif enc, err := json.Marshal(v); err != nil {") + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `failed to encode '%%s'`, keyName)") + fmt.Fprintf(&buf, "\n} else {") + fmt.Fprintf(&buf, "\nbuf.Write(enc)") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") + + fmt.Fprintf(&buf, "\n\n// MarshalJSON serializes the token in JSON format. This exists to") + fmt.Fprintf(&buf, "\n// allow flattening of private claims.") + fmt.Fprintf(&buf, "\nfunc (t Token) MarshalJSON() ([]byte, error) {") + fmt.Fprintf(&buf, "\nvar buf bytes.Buffer") + fmt.Fprintf(&buf, "\nbuf.WriteRune('{')") + + for i, field := range fields { + if strings.HasPrefix(field.Type, "*") { + fmt.Fprintf(&buf, "\nif t.%s != nil {", field.Name) + } else { + fmt.Fprintf(&buf, "\nif len(t.%s) > 0 {", field.Name) + } + if i > 0 { + fmt.Fprintf(&buf, "\nif buf.Len() > 1 {") + fmt.Fprintf(&buf, "\nbuf.WriteRune(',')") + fmt.Fprintf(&buf, "\n}") + } + fmt.Fprintf(&buf, "\nbuf.WriteRune('\"')") + fmt.Fprintf(&buf, "\nbuf.WriteString(%sKey)", field.UpperName()) + fmt.Fprintf(&buf, "\nbuf.WriteString(`\":`)") + fmt.Fprintf(&buf, "\nif err := writeJSON(&buf, t.%s, %sKey); err != nil {", field.Name, field.UpperName()) + fmt.Fprintf(&buf, "\nreturn nil, err") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\n}") + } + + fmt.Fprintf(&buf, "\nif len(t.privateClaims) == 0 {") + fmt.Fprintf(&buf, "\nbuf.WriteRune('}')") + fmt.Fprintf(&buf, "\nreturn buf.Bytes(), nil") + fmt.Fprintf(&buf, "\n}") + + fmt.Fprintf(&buf, "\n// If private claims exist, they need to flattened and included in the token") + fmt.Fprintf(&buf, "\npcjson, err := json.Marshal(t.privateClaims)") + fmt.Fprintf(&buf, "\nif err != nil {") + fmt.Fprintf(&buf, "\nreturn nil, errors.Wrap(err, `failed to marshal private claims`)") + fmt.Fprintf(&buf, "\n}") + + fmt.Fprintf(&buf, "\n// remove '{' from the private claims") + fmt.Fprintf(&buf, "\npcjson = pcjson[1:]") + fmt.Fprintf(&buf, "\nif buf.Len() > 1 {") + fmt.Fprintf(&buf, "\nbuf.WriteRune(',')") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\nbuf.Write(pcjson)") + fmt.Fprintf(&buf, "\nreturn buf.Bytes(), nil") + fmt.Fprintf(&buf, "\n}") + + fmt.Fprintf(&buf, "\n\n// UnmarshalJSON deserializes data from a JSON data buffer into a Token") + fmt.Fprintf(&buf, "\nfunc (t *Token) UnmarshalJSON(data []byte) error {") + fmt.Fprintf(&buf, "\nvar m map[string]interface{}") + fmt.Fprintf(&buf, "\nif err := json.Unmarshal(data, &m); err != nil {") + fmt.Fprintf(&buf, "\nreturn errors.Wrap(err, `failed to unmarshal token`)") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\nfor name, value := range m {") + fmt.Fprintf(&buf, "\nif err := t.Set(name, value); err != nil {") + fmt.Fprintf(&buf, "\nreturn errors.Wrapf(err, `failed to set value for %%s`, name)") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\n}") + fmt.Fprintf(&buf, "\nreturn nil") + fmt.Fprintf(&buf, "\n}") + + formatted, err := format.Source(buf.Bytes()) + if err != nil { + log.Printf("%s", buf.Bytes()) + log.Printf("%s", err) + return errors.Wrap(err, `failed to format source`) + } + + filename := "token_gen.go" + f, err := os.Create(filename) + if err != nil { + return errors.Wrapf(err, `failed to open file %s for writing`, filename) + } + defer f.Close() + + f.Write(formatted) + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/types/date.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/types/date.go new file mode 100644 index 0000000000..2f0052103e --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/types/date.go @@ -0,0 +1,83 @@ +package types + +import ( + "encoding/json" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// NumericDate represents the date format used in the 'nbf' claim +type NumericDate struct { + time.Time +} + +func (n *NumericDate) Get() time.Time { + if n == nil { + return (time.Time{}).UTC() + } + return n.Time +} + +func numericToTime(v interface{}, t *time.Time) bool { + var n int64 + switch x := v.(type) { + case int64: + n = x + case int32: + n = int64(x) + case int16: + n = int64(x) + case int8: + n = int64(x) + case int: + n = int64(x) + case float32: + n = int64(x) + case float64: + n = int64(x) + default: + return false + } + + *t = time.Unix(n, 0) + return true +} + +func (n *NumericDate) Accept(v interface{}) error { + var t time.Time + + switch x := v.(type) { + case string: + i, err := strconv.ParseInt(string(x[:]), 10, 64) + if err != nil { + return errors.Errorf(`invalid epoch value`) + } + t = time.Unix(i, 0) + + case json.Number: + intval, err := x.Int64() + if err != nil { + return errors.Wrap(err, `failed to convert json value to int64`) + } + t = time.Unix(intval, 0) + case time.Time: + t = x + default: + if !numericToTime(v, &t) { + return errors.Errorf(`invalid type %T`, v) + } + } + n.Time = t.UTC() + return nil +} + +// MarshalJSON translates from internal representation to JSON NumericDate +// See https://tools.ietf.org/html/rfc7519#page-6 +func (n *NumericDate) MarshalJSON() ([]byte, error) { + if n.IsZero() { + return json.Marshal(nil) + } + return json.Marshal(n.Unix()) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/types/date_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/types/date_test.go new file mode 100644 index 0000000000..4c1b84e333 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/internal/types/date_test.go @@ -0,0 +1,37 @@ +package types_test + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + "time" + + "github.com/lestrrat-go/jwx/jwt" +) + +func TestDate(t *testing.T) { + t.Run("Accept values", func(t *testing.T) { + // NumericDate allows assignment from various different Go types, + // so that it's easier for the devs, and conversion to/from JSON + // use of "127" is just to allow use of int8's + now := time.Unix(127, 0).UTC() + for _, ut := range []interface{}{int64(127), int32(127), int16(127), int8(127), float32(127), float64(127), json.Number("127")} { + t.Run(fmt.Sprintf("%T", ut), func(t *testing.T) { + var t1 jwt.Token + err := t1.Set(jwt.IssuedAtKey, ut) + if err != nil { + t.Fatalf("Failed to set IssuedAt value: %v", ut) + } + v, ok := t1.Get(jwt.IssuedAtKey) + if !ok { + t.Fatal("Failed to retrieve IssuedAt value") + } + realized := v.(time.Time) + if !reflect.DeepEqual(now, realized) { + t.Fatalf("Token time mistmatch. Expected:Realized (%v:%v)", now, realized) + } + }) + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/jwt.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/jwt.go new file mode 100644 index 0000000000..fd804fba65 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/jwt.go @@ -0,0 +1,106 @@ +//go:generate go run internal/cmd/gentoken/main.go + +// Package jwt implements JSON Web Tokens as described in https://tools.ietf.org/html/rfc7519 +package jwt + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "strings" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jws" + "github.com/pkg/errors" +) + +// ParseString calls Parse with the given string +func ParseString(s string, options ...Option) (*Token, error) { + return Parse(strings.NewReader(s), options...) +} + +// ParseString calls Parse with the given byte sequence +func ParseBytes(s []byte, options ...Option) (*Token, error) { + return Parse(bytes.NewReader(s), options...) +} + +// Parse parses the JWT token payload and creates a new `jwt.Token` object. +// The token must be encoded in either JSON format or compact format. +// +// If the token is signed and you want to verify the payload, you must +// pass the jwt.WithVerify(alg, key) option. If you do not specify these +// parameters, no verification will be performed. +func Parse(src io.Reader, options ...Option) (*Token, error) { + var params VerifyParameters + for _, o := range options { + switch o.Name() { + case optkeyVerify: + params = o.Value().(VerifyParameters) + } + } + + if params != nil { + return ParseVerify(src, params.Algorithm(), params.Key()) + } + + m, err := jws.Parse(src) + if err != nil { + return nil, errors.Wrap(err, `invalid jws message`) + } + + token := New() + if err := json.Unmarshal(m.Payload(), token); err != nil { + return nil, errors.Wrap(err, `failed to parse token`) + } + return token, nil +} + +// ParseVerify is a function that is similar to Parse(), but does not +// allow for parsing without signature verification parameters. +func ParseVerify(src io.Reader, alg jwa.SignatureAlgorithm, key interface{}) (*Token, error) { + data, err := ioutil.ReadAll(src) + if err != nil { + return nil, errors.Wrap(err, `failed to read token from source`) + } + + v, err := jws.Verify(data, alg, key) + if err != nil { + return nil, errors.Wrap(err, `failed to verify jws signature`) + } + + var token Token + if err := json.Unmarshal(v, &token); err != nil { + return nil, errors.Wrap(err, `failed to parse token`) + } + return &token, nil +} + +// New creates a new empty JWT token +func New() *Token { + return &Token{} +} + +// Sign is a convenience function to create a signed JWT token serialized in +// compact form. `key` must match the key type required by the given +// signature method `method` +func (t *Token) Sign(method jwa.SignatureAlgorithm, key interface{}) ([]byte, error) { + buf, err := json.Marshal(t) + if err != nil { + return nil, errors.Wrap(err, `failed to marshal token`) + } + + var hdr jws.StandardHeaders + if hdr.Set(`alg`, method.String()) != nil { + return nil, errors.Wrap(err, `failed to sign payload`) + } + if hdr.Set(`typ`, `JWT`) != nil { + return nil, errors.Wrap(err, `failed to sign payload`) + } + sign, err := jws.Sign(buf, method, key, jws.WithHeaders(&hdr)) + if err != nil { + return nil, errors.Wrap(err, `failed to sign payload`) + } + + return sign, nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/jwt_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/jwt_test.go new file mode 100644 index 0000000000..b10b114cb0 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/jwt_test.go @@ -0,0 +1,253 @@ +package jwt_test + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "encoding/json" + "strings" + "testing" + "time" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jws" + "github.com/lestrrat-go/jwx/jwt" + "github.com/stretchr/testify/assert" +) + +func TestJWTParse(t *testing.T) { + + alg := jwa.RS256 + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal("Failed to generate RSA key") + } + t1 := jwt.New() + signed, err := t1.Sign(alg, key) + if err != nil { + t.Fatal("Failed to sign JWT") + } + + t.Run("Parse (no signature verification)", func(t *testing.T) { + t2, err := jwt.Parse(bytes.NewReader(signed)) + if !assert.NoError(t, err, `jwt.Parse should succeed`) { + return + } + if !assert.Equal(t, t1, t2, `t1 == t2`) { + return + } + }) + t.Run("ParseString (no signature verification)", func(t *testing.T) { + t2, err := jwt.ParseString(string(signed)) + if !assert.NoError(t, err, `jwt.ParseString should succeed`) { + return + } + if !assert.Equal(t, t1, t2, `t1 == t2`) { + return + } + }) + t.Run("ParseBytes (no signature verification)", func(t *testing.T) { + t2, err := jwt.ParseBytes(signed) + if !assert.NoError(t, err, `jwt.ParseBytes should succeed`) { + return + } + if !assert.Equal(t, t1, t2, `t1 == t2`) { + return + } + }) + t.Run("Parse (correct signature key)", func(t *testing.T) { + t2, err := jwt.Parse(bytes.NewReader(signed), jwt.WithVerify(alg, &key.PublicKey)) + if !assert.NoError(t, err, `jwt.Parse should succeed`) { + return + } + if !assert.Equal(t, t1, t2, `t1 == t2`) { + return + } + }) + t.Run("parse (wrong signature algorithm)", func(t *testing.T) { + _, err := jwt.Parse(bytes.NewReader(signed), jwt.WithVerify(jwa.RS512, &key.PublicKey)) + if !assert.Error(t, err, `jwt.Parse should fail`) { + return + } + }) + t.Run("parse (wrong signature key)", func(t *testing.T) { + pubkey := key.PublicKey + pubkey.E = 0 // bogus value + _, err := jwt.Parse(bytes.NewReader(signed), jwt.WithVerify(alg, &pubkey)) + if !assert.Error(t, err, `jwt.Parse should fail`) { + return + } + }) +} + +func TestJWTParseVerify(t *testing.T) { + alg := jwa.RS256 + key, err := rsa.GenerateKey(rand.Reader, 2048) + if !assert.NoError(t, err, "RSA key generated") { + return + } + + t1 := jwt.New() + signed, err := t1.Sign(alg, key) + + t.Run("parse (no signature verification)", func(t *testing.T) { + _, err := jwt.ParseVerify(bytes.NewReader(signed), "", nil) + if !assert.Error(t, err, `jwt.ParseVerify should fail`) { + return + } + }) + t.Run("parse (correct signature key)", func(t *testing.T) { + t2, err := jwt.ParseVerify(bytes.NewReader(signed), alg, &key.PublicKey) + if !assert.NoError(t, err, `jwt.ParseVerify should succeed`) { + return + } + if !assert.Equal(t, t1, t2, `t1 == t2`) { + return + } + }) + t.Run("parse (wrong signature algorithm)", func(t *testing.T) { + _, err := jwt.ParseVerify(bytes.NewReader(signed), jwa.RS512, &key.PublicKey) + if !assert.Error(t, err, `jwt.ParseVerify should fail`) { + return + } + }) + t.Run("parse (wrong signature key)", func(t *testing.T) { + pubkey := key.PublicKey + pubkey.E = 0 // bogus value + _, err := jwt.ParseVerify(bytes.NewReader(signed), alg, &pubkey) + if !assert.Error(t, err, `jwt.ParseVerify should fail`) { + return + } + }) +} + +func TestVerifyClaims(t *testing.T) { + // GitHub issue #37: tokens are invalid in the second they are created (because Now() is not after IssuedAt()) + t.Run(jwt.IssuedAtKey+"+skew", func(t *testing.T) { + token := jwt.New() + now := time.Now().UTC() + token.Set(jwt.IssuedAtKey, now) + + const DefaultSkew = 0 + + args := []jwt.Option{ + jwt.WithClock(jwt.ClockFunc(func() time.Time { return now })), + jwt.WithAcceptableSkew(DefaultSkew), + } + + if !assert.NoError(t, token.Verify(args...), "token.Verify should validate tokens in the same second they are created") { + if now.Equal(token.IssuedAt()) { + t.Errorf("iat claim failed: iat == now") + } + return + } + }) +} + +const aLongLongTimeAgo = 233431200 +const aLongLongTimeAgoString = "233431200" + +func TestUnmarshal(t *testing.T) { + testcases := []struct { + Title string + Source string + Expected func() *jwt.Token + ExpectedJSON string + }{ + { + Title: "single aud", + Source: `{"aud":"foo"}`, + Expected: func() *jwt.Token { + t := jwt.New() + t.Set("aud", "foo") + return t + }, + ExpectedJSON: `{"aud":["foo"]}`, + }, + { + Title: "multiple aud's", + Source: `{"aud":["foo","bar"]}`, + Expected: func() *jwt.Token { + t := jwt.New() + t.Set("aud", []string{"foo", "bar"}) + return t + }, + ExpectedJSON: `{"aud":["foo","bar"]}`, + }, + { + Title: "issuedAt", + Source: `{"` + jwt.IssuedAtKey + `":` + aLongLongTimeAgoString + `}`, + Expected: func() *jwt.Token { + t := jwt.New() + t.Set(jwt.IssuedAtKey, aLongLongTimeAgo) + return t + }, + ExpectedJSON: `{"` + jwt.IssuedAtKey + `":` + aLongLongTimeAgoString + `}`, + }, + } + + for _, tc := range testcases { + t.Run(tc.Title, func(t *testing.T) { + var token jwt.Token + if !assert.NoError(t, json.Unmarshal([]byte(tc.Source), &token), `json.Unmarshal should succeed`) { + return + } + if !assert.Equal(t, tc.Expected(), &token, `token should match expected value`) { + return + } + + var buf bytes.Buffer + if !assert.NoError(t, json.NewEncoder(&buf).Encode(token), `json.Marshal should succeed`) { + return + } + if !assert.Equal(t, tc.ExpectedJSON, strings.TrimSpace(buf.String()), `json should match`) { + return + } + }) + } +} + +func TestGH52(t *testing.T) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if !assert.NoError(t, err) { + return + } + + pub := &priv.PublicKey + if !assert.NoError(t, err) { + return + } + for i := 0; i < 1000; i++ { + tok := jwt.New() + + s, err := tok.Sign(jwa.ES256, priv) + if !assert.NoError(t, err) { + return + } + + if _, err = jws.Verify([]byte(s), jwa.ES256, pub); !assert.NoError(t, err, `test should pass (run %d)`, i) { + return + } + } +} + +func TestUnmarshalJSON(t *testing.T) { + + t.Run("Unmarshal audience with multiple values", func(t *testing.T) { + var t1 jwt.Token + if !assert.NoError(t, json.Unmarshal([]byte(`{"aud":["foo", "bar", "baz"]}`), &t1), `jwt.Parse should succeed`) { + return + } + aud, ok := t1.Get(jwt.AudienceKey) + if !assert.True(t, ok, `jwt.Get(jwt.AudienceKey) should succeed`) { + t.Logf("%#v", t1) + return + } + + if !assert.Equal(t, aud.([]string), []string{"foo", "bar", "baz"}, "audience should match. got %v", aud) { + return + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/options.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/options.go new file mode 100644 index 0000000000..9f495bbe3a --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/options.go @@ -0,0 +1,37 @@ +package jwt + +import ( + "github.com/lestrrat-go/jwx/internal/option" + "github.com/lestrrat-go/jwx/jwa" +) + +type Option = option.Interface + +const ( + optkeyVerify = `verify` +) + +type VerifyParameters interface { + Algorithm() jwa.SignatureAlgorithm + Key() interface{} +} + +type verifyParams struct { + alg jwa.SignatureAlgorithm + key interface{} +} + +func (p *verifyParams) Algorithm() jwa.SignatureAlgorithm { + return p.alg +} + +func (p *verifyParams) Key() interface{} { + return p.key +} + +func WithVerify(alg jwa.SignatureAlgorithm, key interface{}) Option { + return option.New(optkeyVerify, &verifyParams{ + alg: alg, + key: key, + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/string.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/string.go new file mode 100644 index 0000000000..8bf1a0ea62 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/string.go @@ -0,0 +1,37 @@ +package jwt + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +func (l *StringList) Accept(v interface{}) error { + switch x := v.(type) { + case string: + *l = StringList([]string{x}) + case []string: + *l = StringList(x) + case []interface{}: + list := make(StringList, len(x)) + for i, e := range x { + if s, ok := e.(string); ok { + list[i] = s + continue + } + return errors.Errorf(`invalid list element type %T`, e) + } + *l = list + default: + return errors.Errorf(`invalid type: %T`, v) + } + return nil +} + +func (l *StringList) UnmarshalJSON(data []byte) error { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return errors.Wrap(err, `failed to unmarshal data`) + } + return l.Accept(v) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/string_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/string_test.go new file mode 100644 index 0000000000..8458e6ee1c --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/string_test.go @@ -0,0 +1,18 @@ +package jwt_test + +import ( + "github.com/lestrrat-go/jwx/jwt" + "testing" +) + +func TestStringList_Accept(t *testing.T) { + + var x jwt.StringList + interfaceList := make([]interface{}, 0) + interfaceList = append(interfaceList, "first") + interfaceList = append(interfaceList, "second") + err := x.Accept(interfaceList) + if err != nil { + t.Fatal("Failed to convert []interface{} into StringList: %", err.Error()) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/token_gen.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/token_gen.go new file mode 100644 index 0000000000..5e2f5ac82d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/token_gen.go @@ -0,0 +1,322 @@ +// This file is auto-generated. DO NOT EDIT +package jwt + +import ( + "bytes" + "encoding/json" + "github.com/lestrrat-go/jwx/jwt/internal/types" + "github.com/pkg/errors" + "time" +) + +// Key names for standard claims +const ( + AudienceKey = "aud" + ExpirationKey = "exp" + IssuedAtKey = "iat" + IssuerKey = "iss" + JwtIDKey = "jti" + NotBeforeKey = "nbf" + SubjectKey = "sub" +) + +// Token represents a JWT token. The object has convenience accessors +// to 7 standard claims including "aud", "exp", "iat", "iss", "jti", "nbf" and "sub" +// which are type-aware (to an extent). Other claims may be accessed via the `Get`/`Set` +// methods but their types are not taken into consideration at all. If you have non-standard +// claims that you must frequently access, consider wrapping the token in a wrapper +// by embedding the jwt.Token type in it +type Token struct { + audience StringList `json:"aud,omitempty"` // https://tools.ietf.org/html/rfc7519#section-4.1.3 + expiration *types.NumericDate `json:"exp,omitempty"` // https://tools.ietf.org/html/rfc7519#section-4.1.4 + issuedAt *types.NumericDate `json:"iat,omitempty"` // https://tools.ietf.org/html/rfc7519#section-4.1.6 + issuer *string `json:"iss,omitempty"` // https://tools.ietf.org/html/rfc7519#section-4.1.1 + jwtID *string `json:"jti,omitempty"` // https://tools.ietf.org/html/rfc7519#section-4.1.7 + notBefore *types.NumericDate `json:"nbf,omitempty"` // https://tools.ietf.org/html/rfc7519#section-4.1.5 + subject *string `json:"sub,omitempty"` // https://tools.ietf.org/html/rfc7519#section-4.1.2 + privateClaims map[string]interface{} `json:"-"` +} + +func (t *Token) Get(s string) (interface{}, bool) { + switch s { + case AudienceKey: + if len(t.audience) == 0 { + return nil, false + } + return []string(t.audience), true + case ExpirationKey: + if t.expiration == nil { + return nil, false + } else { + return t.expiration.Get(), true + } + case IssuedAtKey: + if t.issuedAt == nil { + return nil, false + } else { + return t.issuedAt.Get(), true + } + case IssuerKey: + if t.issuer == nil { + return nil, false + } else { + return *(t.issuer), true + } + case JwtIDKey: + if t.jwtID == nil { + return nil, false + } else { + return *(t.jwtID), true + } + case NotBeforeKey: + if t.notBefore == nil { + return nil, false + } else { + return t.notBefore.Get(), true + } + case SubjectKey: + if t.subject == nil { + return nil, false + } else { + return *(t.subject), true + } + } + if v, ok := t.privateClaims[s]; ok { + return v, true + } + return nil, false +} + +func (t *Token) Set(name string, v interface{}) error { + switch name { + case AudienceKey: + var x StringList + if err := x.Accept(v); err != nil { + return errors.Wrap(err, `invalid value for 'audience' key`) + } + t.audience = x + case ExpirationKey: + var x types.NumericDate + if err := x.Accept(v); err != nil { + return errors.Wrap(err, `invalid value for 'expiration' key`) + } + t.expiration = &x + case IssuedAtKey: + var x types.NumericDate + if err := x.Accept(v); err != nil { + return errors.Wrap(err, `invalid value for 'issuedAt' key`) + } + t.issuedAt = &x + case IssuerKey: + x, ok := v.(string) + if !ok { + return errors.Errorf(`invalid type for 'issuer' key: %T`, v) + } + t.issuer = &x + case JwtIDKey: + x, ok := v.(string) + if !ok { + return errors.Errorf(`invalid type for 'jwtID' key: %T`, v) + } + t.jwtID = &x + case NotBeforeKey: + var x types.NumericDate + if err := x.Accept(v); err != nil { + return errors.Wrap(err, `invalid value for 'notBefore' key`) + } + t.notBefore = &x + case SubjectKey: + x, ok := v.(string) + if !ok { + return errors.Errorf(`invalid type for 'subject' key: %T`, v) + } + t.subject = &x + default: + if t.privateClaims == nil { + t.privateClaims = make(map[string]interface{}) + } + t.privateClaims[name] = v + } + return nil +} + +func (t Token) Audience() StringList { + if v, ok := t.Get(AudienceKey); ok { + return v.([]string) + } + return nil +} + +func (t Token) Expiration() time.Time { + if v, ok := t.Get(ExpirationKey); ok { + return v.(time.Time) + } + return time.Time{} +} + +func (t Token) IssuedAt() time.Time { + if v, ok := t.Get(IssuedAtKey); ok { + return v.(time.Time) + } + return time.Time{} +} + +// Issuer is a convenience function to retrieve the corresponding value store in the token +// if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead + +func (t Token) Issuer() string { + if v, ok := t.Get(IssuerKey); ok { + return v.(string) + } + return "" +} + +// JwtID is a convenience function to retrieve the corresponding value store in the token +// if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead + +func (t Token) JwtID() string { + if v, ok := t.Get(JwtIDKey); ok { + return v.(string) + } + return "" +} + +func (t Token) NotBefore() time.Time { + if v, ok := t.Get(NotBeforeKey); ok { + return v.(time.Time) + } + return time.Time{} +} + +// Subject is a convenience function to retrieve the corresponding value store in the token +// if there is a problem retrieving the value, the zero value is returned. If you need to differentiate between existing/non-existing values, use `Get` instead + +func (t Token) Subject() string { + if v, ok := t.Get(SubjectKey); ok { + return v.(string) + } + return "" +} + +// this is almost identical to json.Encoder.Encode(), but we use Marshal +// to avoid having to remove the trailing newline for each successive +// call to Encode() +func writeJSON(buf *bytes.Buffer, v interface{}, keyName string) error { + if enc, err := json.Marshal(v); err != nil { + return errors.Wrapf(err, `failed to encode '%s'`, keyName) + } else { + buf.Write(enc) + } + return nil +} + +// MarshalJSON serializes the token in JSON format. This exists to +// allow flattening of private claims. +func (t Token) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + buf.WriteRune('{') + if len(t.audience) > 0 { + buf.WriteRune('"') + buf.WriteString(AudienceKey) + buf.WriteString(`":`) + if err := writeJSON(&buf, t.audience, AudienceKey); err != nil { + return nil, err + } + } + if t.expiration != nil { + if buf.Len() > 1 { + buf.WriteRune(',') + } + buf.WriteRune('"') + buf.WriteString(ExpirationKey) + buf.WriteString(`":`) + if err := writeJSON(&buf, t.expiration, ExpirationKey); err != nil { + return nil, err + } + } + if t.issuedAt != nil { + if buf.Len() > 1 { + buf.WriteRune(',') + } + buf.WriteRune('"') + buf.WriteString(IssuedAtKey) + buf.WriteString(`":`) + if err := writeJSON(&buf, t.issuedAt, IssuedAtKey); err != nil { + return nil, err + } + } + if t.issuer != nil { + if buf.Len() > 1 { + buf.WriteRune(',') + } + buf.WriteRune('"') + buf.WriteString(IssuerKey) + buf.WriteString(`":`) + if err := writeJSON(&buf, t.issuer, IssuerKey); err != nil { + return nil, err + } + } + if t.jwtID != nil { + if buf.Len() > 1 { + buf.WriteRune(',') + } + buf.WriteRune('"') + buf.WriteString(JwtIDKey) + buf.WriteString(`":`) + if err := writeJSON(&buf, t.jwtID, JwtIDKey); err != nil { + return nil, err + } + } + if t.notBefore != nil { + if buf.Len() > 1 { + buf.WriteRune(',') + } + buf.WriteRune('"') + buf.WriteString(NotBeforeKey) + buf.WriteString(`":`) + if err := writeJSON(&buf, t.notBefore, NotBeforeKey); err != nil { + return nil, err + } + } + if t.subject != nil { + if buf.Len() > 1 { + buf.WriteRune(',') + } + buf.WriteRune('"') + buf.WriteString(SubjectKey) + buf.WriteString(`":`) + if err := writeJSON(&buf, t.subject, SubjectKey); err != nil { + return nil, err + } + } + if len(t.privateClaims) == 0 { + buf.WriteRune('}') + return buf.Bytes(), nil + } + // If private claims exist, they need to flattened and included in the token + pcjson, err := json.Marshal(t.privateClaims) + if err != nil { + return nil, errors.Wrap(err, `failed to marshal private claims`) + } + // remove '{' from the private claims + pcjson = pcjson[1:] + if buf.Len() > 1 { + buf.WriteRune(',') + } + buf.Write(pcjson) + return buf.Bytes(), nil +} + +// UnmarshalJSON deserializes data from a JSON data buffer into a Token +func (t *Token) UnmarshalJSON(data []byte) error { + var m map[string]interface{} + if err := json.Unmarshal(data, &m); err != nil { + return errors.Wrap(err, `failed to unmarshal token`) + } + for name, value := range m { + if err := t.Set(name, value); err != nil { + return errors.Wrapf(err, `failed to set value for %s`, name) + } + } + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/token_gen_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/token_gen_test.go new file mode 100644 index 0000000000..4bedc1bcd4 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/token_gen_test.go @@ -0,0 +1,157 @@ +package jwt_test + +import ( + "encoding/json" + "reflect" + "testing" + "time" + + "github.com/lestrrat-go/jwx/jwt" + "github.com/stretchr/testify/assert" +) + +func TestHeader(t *testing.T) { + + const ( + tokenTime = 233431200 + ) + expectedTokenTime := time.Unix(tokenTime, 0).UTC() + + values := map[string]interface{}{ + jwt.AudienceKey: []string{"developers", "secops", "tac"}, + jwt.ExpirationKey: expectedTokenTime, + jwt.IssuedAtKey: expectedTokenTime, + jwt.IssuerKey: "http://www.example.com", + jwt.JwtIDKey: "e9bc097a-ce51-4036-9562-d2ade882db0d", + jwt.NotBeforeKey: expectedTokenTime, + jwt.SubjectKey: "unit test", + } + + t.Run("Roundtrip", func(t *testing.T) { + + var h jwt.Token + for k, v := range values { + err := h.Set(k, v) + if err != nil { + t.Fatalf("Set failed for %s", k) + } + got, ok := h.Get(k) + if !ok { + t.Fatalf("Set failed for %s", k) + } + if !reflect.DeepEqual(v, got) { + t.Fatalf("Values do not match: (%v, %v)", v, got) + } + } + }) + + t.Run("RoundtripError", func(t *testing.T) { + + type dummyStruct struct { + dummy1 int + dummy2 float64 + } + dummy := &dummyStruct{1, 3.4} + + values := map[string]interface{}{ + jwt.AudienceKey: dummy, + jwt.ExpirationKey: dummy, + jwt.IssuedAtKey: dummy, + jwt.IssuerKey: dummy, + jwt.JwtIDKey: dummy, + jwt.NotBeforeKey: dummy, + jwt.SubjectKey: dummy, + } + + var h jwt.Token + for k, v := range values { + err := h.Set(k, v) + if err == nil { + t.Fatalf("Setting %s value should have failed", k) + } + } + err := h.Set("default", dummy) // private params + if err != nil { + t.Fatalf("Setting %s value failed", "default") + } + for k, _ := range values { + _, ok := h.Get(k) + if ok { + t.Fatalf("Getting %s value should have failed", k) + } + } + _, ok := h.Get("default") + if !ok { + t.Fatal("Failed to get default value") + } + }) + + t.Run("GetError", func(t *testing.T) { + + var h jwt.Token + issuer := h.Issuer() + if issuer != "" { + t.Fatalf("Get Issuer should return empty string") + } + jwtId := h.JwtID() + if jwtId != "" { + t.Fatalf("Get JWT Id should return empty string") + } + }) +} + +func TestTokenMarshal(t *testing.T) { + t1 := jwt.New() + err := t1.Set(jwt.JwtIDKey, "AbCdEfG") + if err != nil { + t.Fatalf("Failed to set JWT ID: %s", err.Error()) + } + err = t1.Set(jwt.SubjectKey, "foobar@example.com") + if err != nil { + t.Fatalf("Failed to set Subject: %s", err.Error()) + } + + // Silly fix to remove monotonic element from time.Time obtained + // from time.Now(). Without this, the equality comparison goes + // ga-ga for golang tip (1.9) + now := time.Unix(time.Now().Unix(), 0) + err = t1.Set(jwt.IssuedAtKey, now.Unix()) + if err != nil { + t.Fatalf("Failed to set IssuedAt: %s", err.Error()) + } + err = t1.Set(jwt.NotBeforeKey, now.Add(5*time.Second)) + if err != nil { + t.Fatalf("Failed to set NotBefore: %s", err.Error()) + } + err = t1.Set(jwt.ExpirationKey, now.Add(10*time.Second).Unix()) + if err != nil { + t.Fatalf("Failed to set Expiration: %s", err.Error()) + } + err = t1.Set(jwt.AudienceKey, []string{"devops", "secops", "tac"}) + if err != nil { + t.Fatalf("Failed to set audience: %s", err.Error()) + } + err = t1.Set("custom", "MyValue") + if err != nil { + t.Fatalf(`Failed to set private claim "custom": %s`, err.Error()) + } + jsonbuf1, err := json.MarshalIndent(t1, "", " ") + if err != nil { + t.Fatalf("JSON Marshal failed: %s", err.Error()) + } + + t2 := jwt.New() + err = json.Unmarshal(jsonbuf1, t2) + if err != nil { + t.Fatalf("JSON Unmarshal error: %s", err.Error()) + } + + if !assert.Equal(t, t1, t2, "tokens should match") { + return + } + + _, err = json.MarshalIndent(t2, "", " ") + if err != nil { + t.Fatalf("JSON marshal error: %s", err.Error()) + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/verify.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/verify.go new file mode 100644 index 0000000000..03dc8a446e --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/verify.go @@ -0,0 +1,157 @@ +package jwt + +import ( + "errors" + "time" + + "github.com/lestrrat-go/jwx/internal/option" +) + +const ( + optkeyAcceptableSkew = "acceptableSkew" + optkeyClock = "clock" + optkeyIssuer = "issuer" + optkeySubject = "subject" + optkeyAudience = "audience" + optkeyJwtid = "jwtid" +) + +type Clock interface { + Now() time.Time +} +type ClockFunc func() time.Time + +func (f ClockFunc) Now() time.Time { + return f() +} + +// WithClock specifies the `Clock` to be used when verifying +// claims exp and nbf. +func WithClock(c Clock) Option { + return option.New(optkeyClock, c) +} + +// WithAcceptableSkew specifies the duration in which exp and nbf +// claims may differ by. This value should be positive +func WithAcceptableSkew(dur time.Duration) Option { + return option.New(optkeyAcceptableSkew, dur) +} + +// WithIssuer specifies that expected issuer value. If not specified, +// the value of issuer is not verified at all. +func WithIssuer(s string) Option { + return option.New(optkeyIssuer, s) +} + +// WithSubject specifies that expected subject value. If not specified, +// the value of subject is not verified at all. +func WithSubject(s string) Option { + return option.New(optkeySubject, s) +} + +// WithJwtID specifies that expected jti value. If not specified, +// the value of jti is not verified at all. +func WithJwtID(s string) Option { + return option.New(optkeyJwtid, s) +} + +// WithAudience specifies that expected audience value. +// Verify will return true if one of the values in the `aud` element +// matches this value. If not specified, the value of issuer is not +// verified at all. +func WithAudience(s string) Option { + return option.New(optkeyAudience, s) +} + +// Verify makes sure that the essential claims stand. +// +// See the various `WithXXX` functions for optional parameters +// that can control the behavior of this method. +func (t *Token) Verify(options ...Option) error { + var issuer string + var subject string + var audience string + var jwtid string + var clock Clock = ClockFunc(time.Now) + var skew time.Duration + for _, o := range options { + switch o.Name() { + case optkeyClock: + clock = o.Value().(Clock) + case optkeyAcceptableSkew: + skew = o.Value().(time.Duration) + case optkeyIssuer: + issuer = o.Value().(string) + case optkeySubject: + subject = o.Value().(string) + case optkeyAudience: + audience = o.Value().(string) + case optkeyJwtid: + jwtid = o.Value().(string) + } + } + + // check for iss + if len(issuer) > 0 { + if v := t.Issuer(); v != "" && v != issuer { + return errors.New(`iss not satisfied`) + } + } + + // check for jti + if len(jwtid) > 0 { + if v := t.JwtID(); v != "" && v != jwtid { + return errors.New(`jti not satisfied`) + } + } + + // check for sub + if len(subject) > 0 { + if v := t.Subject(); v != "" && v != subject { + return errors.New(`sub not satisfied`) + } + } + + // check for aud + if len(audience) > 0 { + var found bool + for _, v := range t.Audience() { + if v == audience { + found = true + break + } + } + if !found { + return errors.New(`aud not satisfied`) + } + } + + // check for exp + if tv := t.Expiration(); !tv.IsZero() { + now := clock.Now().Truncate(time.Second) + ttv := tv.Truncate(time.Second) + if !now.Before(ttv.Add(skew)) { + return errors.New(`exp not satisfied`) + } + } + + // check for iat + if tv := t.IssuedAt(); !tv.IsZero() { + now := clock.Now().Truncate(time.Second) + ttv := tv.Truncate(time.Second) + if now.Before(ttv.Add(-1 * skew)) { + return errors.New(`iat not satisfied`) + } + } + + // check for nbf + if tv := t.NotBefore(); !tv.IsZero() { + now := clock.Now().Truncate(time.Second) + ttv := tv.Truncate(time.Second) + // now cannot be before t, so we check for now > t - skew + if !now.After(ttv.Add(-1 * skew)) { + return errors.New(`nbf not satisfied`) + } + } + return nil +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/verify_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/verify_test.go new file mode 100644 index 0000000000..b143db5ab7 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwt/verify_test.go @@ -0,0 +1,128 @@ +package jwt_test + +import ( + "testing" + "time" + + "github.com/lestrrat-go/jwx/jwt" + "github.com/stretchr/testify/assert" +) + +func TestGHIssue10(t *testing.T) { + t.Run(jwt.IssuerKey, func(t *testing.T) { + t1 := jwt.New() + t1.Set(jwt.IssuerKey, "github.com/lestrrat-go/jwx") + + // This should succeed, because WithIssuer is not provided in the + // optional parameters + if !assert.NoError(t, t1.Verify(), "t1.Verify should succeed") { + return + } + + // This should succeed, because WithIssuer is provided with same value + if !assert.NoError(t, t1.Verify(jwt.WithIssuer(t1.Issuer())), "t1.Verify should succeed") { + return + } + + if !assert.Error(t, t1.Verify(jwt.WithIssuer("poop")), "t1.Verify should fail") { + return + } + }) + t.Run(jwt.AudienceKey, func(t *testing.T) { + t1 := jwt.New() + err := t1.Set(jwt.AudienceKey, []string{"foo", "bar", "baz"}) + if err != nil { + t.Fatalf("Failed to set audience claim: %s", err.Error()) + } + + // This should succeed, because WithAudience is not provided in the + // optional parameters + err = t1.Verify() + if err != nil { + t.Fatalf("Error varifying claim: %s", err.Error()) + } + + // This should succeed, because WithAudience is provided, and its + // value matches one of the audience values + if !assert.NoError(t, t1.Verify(jwt.WithAudience("baz")), "token.Verify should succeed") { + return + } + + if !assert.Error(t, t1.Verify(jwt.WithAudience("poop")), "token.Verify should fail") { + return + } + }) + t.Run(jwt.SubjectKey, func(t *testing.T) { + t1 := jwt.New() + t1.Set(jwt.SubjectKey, "github.com/lestrrat-go/jwx") + + // This should succeed, because WithSubject is not provided in the + // optional parameters + if !assert.NoError(t, t1.Verify(), "token.Verify should succeed") { + return + } + + // This should succeed, because WithSubject is provided with same value + if !assert.NoError(t, t1.Verify(jwt.WithSubject(t1.Subject())), "token.Verify should succeed") { + return + } + + if !assert.Error(t, t1.Verify(jwt.WithSubject("poop")), "token.Verify should fail") { + return + } + }) + t.Run(jwt.NotBeforeKey, func(t *testing.T) { + t1 := jwt.New() + + // NotBefore is set to future date + tm := time.Now().Add(72 * time.Hour) + t1.Set(jwt.NotBeforeKey, tm) + + // This should fail, because nbf is the future + if !assert.Error(t, t1.Verify(), "token.Verify should fail") { + return + } + + // This should succeed, because we have given reaaaaaaly big skew + // that is well enough to get us accepted + if !assert.NoError(t, t1.Verify(jwt.WithAcceptableSkew(73*time.Hour)), "token.Verify should succeed") { + return + } + + // This should succeed, because we have given a time + // that is well enough into the future + if !assert.NoError(t, t1.Verify(jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Hour) }))), "token.Verify should succeed") { + return + } + }) + t.Run(jwt.ExpirationKey, func(t *testing.T) { + t1 := jwt.New() + + // issuedat = 1 Hr before current time + tm := time.Now() + t1.Set(jwt.IssuedAtKey, tm.Add(-1*time.Hour)) + + // valid for 2 minutes only from IssuedAt + t1.Set(jwt.ExpirationKey, tm.Add(-58*time.Minute)) + + // This should fail, because exp is set in the past + if !assert.Error(t, t1.Verify(), "token.Verify should fail") { + return + } + + // This should succeed, because we have given big skew + // that is well enough to get us accepted + if !assert.NoError(t, t1.Verify(jwt.WithAcceptableSkew(time.Hour)), "token.Verify should succeed (1)") { + return + } + + // This should succeed, because we have given a time + // that is well enough into the past + clock := jwt.ClockFunc(func() time.Time { + return tm.Add(-59 * time.Minute) + }) + if !assert.NoError(t, t1.Verify(jwt.WithClock(clock)), "token.Verify should succeed (2)") { + return + } + }) +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwx.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwx.go new file mode 100644 index 0000000000..d5ca50951b --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwx.go @@ -0,0 +1,26 @@ +// Package jwx contains tools that deal with the various JWx (JOSE) +// technologies such as JWT, JWS, JWE, etc in Go. +// +// JWS (https://tools.ietf.org/html/rfc7515) +// JWE (https://tools.ietf.org/html/rfc7516) +// JWK (https://tools.ietf.org/html/rfc7517) +// JWA (https://tools.ietf.org/html/rfc7518) +// JWT (https://tools.ietf.org/html/rfc7519) +// +// The primary focus of this library tool set is to implement the extremely +// flexible OAuth2 / OpenID Connect protocols. There are many other libraries +// out there that deal with all or parts of these JWx technologies: +// +// https://github.com/dgrijalva/jwt-go +// https://github.com/square/go-jose +// https://github.com/coreos/oidc +// https://golang.org/x/oauth2 +// +// This library exists because there was a need for a toolset that encompasses +// the whole set of JWx technologies in a highly customizable manner, in one package. +// +// You can find more high level documentation at Github (https://github.com/lestrrat-go/jwx) +package jwx + +// Version describes the version of this library. +const Version = "0.0.1" diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwx_example_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwx_example_test.go new file mode 100644 index 0000000000..db294d00f6 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/jwx_example_test.go @@ -0,0 +1,116 @@ +package jwx_test + +import ( + "crypto/rand" + "crypto/rsa" + "encoding/json" + "fmt" + "log" + "time" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwe" + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jws" + "github.com/lestrrat-go/jwx/jwt" +) + +func ExampleJWT() { + const aLongLongTimeAgo = 233431200 + + t := jwt.New() + t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`) + t.Set(jwt.AudienceKey, `Golang Users`) + t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0)) + t.Set(`privateClaimKey`, `Hello, World!`) + + buf, err := json.MarshalIndent(t, "", " ") + if err != nil { + fmt.Printf("failed to generate JSON: %s\n", err) + return + } + + fmt.Printf("%s\n", buf) + fmt.Printf("aud -> '%s'\n", t.Audience()) + fmt.Printf("iat -> '%s'\n", t.IssuedAt().Format(time.RFC3339)) + if v, ok := t.Get(`privateClaimKey`); ok { + fmt.Printf("privateClaimKey -> '%s'\n", v) + } + fmt.Printf("sub -> '%s'\n", t.Subject()) +} + +func ExampleJWK() { + set, err := jwk.FetchHTTP("https://foobar.domain/jwk.json") + if err != nil { + log.Printf("failed to parse JWK: %s", err) + return + } + + // If you KNOW you have exactly one key, you can just + // use set.Keys[0] + keys := set.LookupKeyID("mykey") + if len(keys) == 0 { + log.Printf("failed to lookup key: %s", err) + return + } + + key, err := keys[0].Materialize() + if err != nil { + log.Printf("failed to create public key: %s", err) + return + } + + // Use key for jws.Verify() or whatever + _ = key +} + +func ExampleJWS() { + privkey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Printf("failed to generate private key: %s", err) + return + } + + buf, err := jws.Sign([]byte("Lorem ipsum"), jwa.RS256, privkey) + if err != nil { + log.Printf("failed to created JWS message: %s", err) + return + } + + // When you received a JWS message, you can verify the signature + // and grab the payload sent in the message in one go: + verified, err := jws.Verify(buf, jwa.RS256, &privkey.PublicKey) + if err != nil { + log.Printf("failed to verify message: %s", err) + return + } + + log.Printf("signed message verified! -> %s", verified) +} + +func ExampleJWE() { + privkey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Printf("failed to generate private key: %s", err) + return + } + + payload := []byte("Lorem Ipsum") + + encrypted, err := jwe.Encrypt(payload, jwa.RSA1_5, &privkey.PublicKey, jwa.A128CBC_HS256, jwa.NoCompress) + if err != nil { + log.Printf("failed to encrypt payload: %s", err) + return + } + + decrypted, err := jwe.Decrypt(encrypted, jwa.RSA1_5, privkey) + if err != nil { + log.Printf("failed to decrypt: %s", err) + return + } + + if string(decrypted) != "Lorem Ipsum" { + log.Printf("WHAT?!") + return + } +} diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/scripts/check-diff.sh b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/scripts/check-diff.sh new file mode 100755 index 0000000000..0359eda8d2 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/vendor/github.com/lestrrat-go/jwx/scripts/check-diff.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +UNTRACKED=$(git ls-files --others --exclude-standard) +DIFF=$(git diff) + +st=0 +if [ ! -z "$DIFF" ]; then + echo "==== START OF DIFF FOUND ===" + echo "" + echo "$DIFF" + echo "" + echo "Above diff was found." + echo "" + echo "==== END OF DIFF FOUND ===" + echo "" + st=1 +fi + +if [ ! -z "$UNTRACKED" ]; then + echo "==== START OF UNTRACKED FILES FOUND ===" + echo "" + echo "$UNTRACKED" + echo "" + echo "Above untracked files were found." + echo "" + echo "==== END OF UNTRACKED FILES FOUND ===" + echo "" + st=1 +fi + +exit $st \ No newline at end of file diff --git a/traffic_portal/app/src/app.js b/traffic_portal/app/src/app.js index 9b01293b70..8d890b267f 100644 --- a/traffic_portal/app/src/app.js +++ b/traffic_portal/app/src/app.js @@ -46,6 +46,7 @@ var trafficPortal = angular.module('trafficPortal', [ // public modules require('./modules/public').name, require('./modules/public/login').name, + require('./modules/public/sso').name, // private modules require('./modules/private').name, @@ -473,7 +474,7 @@ trafficPortal.factory('authInterceptor', function ($rootScope, $q, $window, $loc if (rejection.status === 401) { $rootScope.$broadcast('trafficPortal::exit'); userModel.resetUser(); - if (url == '/login' || $location.search().redirect) { + if (url === '/login' || url ==='/sso' || $location.search().redirect) { messageModel.setMessages(alerts, false); } else { $timeout(function () { diff --git a/traffic_portal/app/src/common/api/AuthService.js b/traffic_portal/app/src/common/api/AuthService.js index 7543cdacbf..d9343ca76e 100644 --- a/traffic_portal/app/src/common/api/AuthService.js +++ b/traffic_portal/app/src/common/api/AuthService.js @@ -17,7 +17,7 @@ * under the License. */ -var AuthService = function($rootScope, $http, $state, $location, userModel, messageModel, ENV) { +var AuthService = function($rootScope, $http, $state, $location, userModel, messageModel, ENV, locationUtils) { this.login = function(username, password) { userModel.resetUser(); @@ -51,6 +51,30 @@ var AuthService = function($rootScope, $http, $state, $location, userModel, mess ); }; + this.oauthLogin = function(authCodeTokenUrl, code, clientId, redirectUri) { + return $http.post(ENV.api['root'] + 'user/login/oauth', { authCodeTokenUrl: authCodeTokenUrl, code: code, clientId: clientId, redirectUri: redirectUri}) + .then( + function(result) { + $rootScope.$broadcast('authService::login'); + var redirect = localStorage.getItem('redirectParam'); + localStorage.clear(); + if (redirect === undefined || redirect === '') { + redirect = decodeURIComponent($location.search().redirect); + } + if (redirect !== undefined) { + $location.search('redirect', null); // remove the redirect query param + $location.url(redirect); + } else { + $location.url('/'); + } + }, + function(fault) { + messageModel.setMessages(fault.data.alerts, true); + locationUtils.navigateToPath('/login'); + } + ); + }; + this.logout = function() { userModel.resetUser(); return $http.post(ENV.api['root'] + 'user/logout').then( @@ -72,5 +96,5 @@ var AuthService = function($rootScope, $http, $state, $location, userModel, mess }; -AuthService.$inject = ['$rootScope', '$http', '$state', '$location', 'userModel', 'messageModel', 'ENV']; +AuthService.$inject = ['$rootScope', '$http', '$state', '$location', 'userModel', 'messageModel', 'ENV', 'locationUtils']; module.exports = AuthService; diff --git a/traffic_portal/app/src/modules/public/login/LoginController.js b/traffic_portal/app/src/modules/public/login/LoginController.js index 374177c03e..9936cc8c7b 100644 --- a/traffic_portal/app/src/modules/public/login/LoginController.js +++ b/traffic_portal/app/src/modules/public/login/LoginController.js @@ -17,7 +17,9 @@ * under the License. */ -var LoginController = function($scope, $log, $uibModal, authService, userService) { +var LoginController = function($scope, $log, $uibModal, $location, authService, userService, propertiesModel) { + + $scope.oAuthEnabled = propertiesModel.properties.oAuth.enabled; $scope.credentials = { username: '', @@ -48,9 +50,28 @@ var LoginController = function($scope, $log, $uibModal, authService, userService }); }; + $scope.loginOauth = function() { + const redirectUriParamKey = propertiesModel.properties.oAuth.redirectUriParameterOverride !== '' ? propertiesModel.properties.oAuth.redirectUriParameterOverride : 'redirect_uri'; + const redirectParam = $location.search()['redirect'] !== undefined ? $location.search()['redirect'] : ''; + + // Builds redirect_uri parameter value to be sent with request to OAuth provider. This will redirect to the /sso page with any previous redirect information + var redirectUriParam = new URL(window.location.href.replace(window.location.hash, '') + 'sso'); + + // Builds the URL to redirect to the OAuth provider including the redirect_uri (or override), client_id, and response_type fields + var continueURL = new URL(propertiesModel.properties.oAuth.oAuthUrl); + continueURL.searchParams.append(redirectUriParamKey, redirectUriParam); + continueURL.searchParams.append('client_id', propertiesModel.properties.oAuth.clientId); + continueURL.searchParams.append('response_type', 'code'); + + localStorage.setItem('redirectUri', redirectUriParam.toString()); + localStorage.setItem('redirectParam', redirectParam); + + window.location.href = continueURL.href; + }; + var init = function() {}; init(); }; -LoginController.$inject = ['$scope', '$log', '$uibModal', 'authService', 'userService']; +LoginController.$inject = ['$scope', '$log', '$uibModal', '$location', 'authService', 'userService', 'propertiesModel']; module.exports = LoginController; diff --git a/traffic_portal/app/src/modules/public/login/login.tpl.html b/traffic_portal/app/src/modules/public/login/login.tpl.html index 880f7abcd1..6ff40761e4 100644 --- a/traffic_portal/app/src/modules/public/login/login.tpl.html +++ b/traffic_portal/app/src/modules/public/login/login.tpl.html @@ -21,12 +21,13 @@
-

Login

+

Login

+

Login With Single Sign On


-
+
@@ -47,6 +48,13 @@

Login

+
+
+
+ +
+
+
diff --git a/traffic_portal/app/src/modules/public/sso/SsoController.js b/traffic_portal/app/src/modules/public/sso/SsoController.js new file mode 100644 index 0000000000..01bc339ee9 --- /dev/null +++ b/traffic_portal/app/src/modules/public/sso/SsoController.js @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +var SsoController = function($scope, $location, authService, propertiesModel) { + + var init = function () { + const authCodeTokenUrl = propertiesModel.properties.oAuth.oAuthCodeTokenUrl; + const clientId = propertiesModel.properties.oAuth.clientId; + const redirectUri = localStorage.getItem('redirectUri'); + + var code = ''; + if ($location.hash()) { + code = $location.hash().replace('code=', ''); + } else { + code = $location.search()['code']; + } + authService.oauthLogin(authCodeTokenUrl, code, clientId, redirectUri); + }; + init(); + +}; + +SsoController.$inject = ['$scope', '$location', 'authService', 'propertiesModel']; +module.exports = SsoController; diff --git a/traffic_portal/app/src/modules/public/sso/index.js b/traffic_portal/app/src/modules/public/sso/index.js new file mode 100644 index 0000000000..8d8ed2f482 --- /dev/null +++ b/traffic_portal/app/src/modules/public/sso/index.js @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +module.exports = angular.module('trafficPortal.public.sso', []) + .controller('SsoController', require('./SsoController')) + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + .state('trafficPortal.public.sso', { + url: 'sso', + views: { + publicContent: { + templateUrl: '', + controller: 'SsoController', + } + } + }) + ; + $urlRouterProvider.otherwise('/'); + }); diff --git a/traffic_portal/app/src/traffic_portal_properties.json b/traffic_portal/app/src/traffic_portal_properties.json index 16663405cb..069fefbbda 100644 --- a/traffic_portal/app/src/traffic_portal_properties.json +++ b/traffic_portal/app/src/traffic_portal_properties.json @@ -201,6 +201,15 @@ "url": "http://trafficcontrol.apache.org/" } ] + }, + "oAuth": { + "_comment": "Opt-in OAuth properties for SSO login. See http://traffic-control-cdn.readthedocs.io/en/release-4.0.0/admin/quick_howto/oauth_login.html for more details. redirectUriParameterOverride defaults to redirect_uri if left blank.", + "enabled": false, + "oAuthUrl": "https://oauthProvider.example.com/auth", + "oAuthTokenQueryParam": "example_token_key", + "redirectUriParameterOverride": "example_redirect_url_key", + "clientId": "exampleClient", + "oAuthCodeTokenUrl": "https://oauthProvider.example.com/auth/token" } } } diff --git a/traffic_portal/server.js b/traffic_portal/server.js index 6494da548e..5d4b187ecc 100644 --- a/traffic_portal/server.js +++ b/traffic_portal/server.js @@ -73,7 +73,8 @@ app.all ("/*", function (req, res, next) { app.use(modRewrite([ '^/api/(.*?)\\?(.*)$ ' + config.api.base_url + '$1?$2 [P]', - '^/api/(.*)$ ' + config.api.base_url + '$1 [P]' + '^/api/(.*)$ ' + config.api.base_url + '$1 [P]', + '^/sso\\?(.*)$ ' + '#!/sso?$1 [R]' ])); app.use(express.static(config.files.static));