diff --git a/server/accounts.go b/server/accounts.go index 0f029076..bd365a46 100644 --- a/server/accounts.go +++ b/server/accounts.go @@ -519,10 +519,10 @@ func (a *Account) checkActivation(acc *Account, claim *jwt.Import, expTimer bool if act.Expires <= tn { return false } - if expTimer && len(act.Exports) > 0 { + if expTimer { expiresAt := time.Duration(act.Expires - tn) time.AfterFunc(expiresAt*time.Second, func() { - acc.activationExpired(string(act.Exports[0].Subject)) + acc.activationExpired(string(act.ImportSubject)) }) } } diff --git a/server/auth.go b/server/auth.go index 963986b9..1ee7b417 100644 --- a/server/auth.go +++ b/server/auth.go @@ -265,7 +265,6 @@ func (s *Server) isClientAuthorized(c *client) bool { // So we have a valid user jwt here. juc, err = jwt.DecodeUserClaims(c.opts.JWT) if err != nil { - // Should we debug log here? s.mu.Unlock() c.Debugf("User JWT not valid: %v", err) return false @@ -322,7 +321,7 @@ func (s *Server) isClientAuthorized(c *client) bool { c.Debugf("Signature not valid base64") return false } - pub, err := nkeys.FromPublicKey([]byte(juc.Subject)) + pub, err := nkeys.FromPublicKey(juc.Subject) if err != nil { c.Debugf("User nkey not valid: %v", err) return false @@ -347,7 +346,7 @@ func (s *Server) isClientAuthorized(c *client) bool { if err != nil { return false } - pub, err := nkeys.FromPublicKey([]byte(c.opts.Nkey)) + pub, err := nkeys.FromPublicKey(c.opts.Nkey) if err != nil { return false } diff --git a/server/jwt_test.go b/server/jwt_test.go index 5c40b528..cfd017db 100644 --- a/server/jwt_test.go +++ b/server/jwt_test.go @@ -681,8 +681,8 @@ func TestJWTAccountBasicImportExport(t *testing.T) { serviceImport = &jwt.Import{Account: string(fooPub), Subject: "req.echo", Type: jwt.Service} activation := jwt.NewActivationClaims(string(barPub)) - activation.Exports = jwt.Exports{} - activation.Exports.Add(&jwt.Export{Subject: "req.echo", Type: jwt.Service}) + activation.ImportSubject = "req.echo" + activation.ImportType = jwt.Service actJWT, err := activation.Encode(fooKP) if err != nil { t.Fatalf("Error generating activation token: %v", err) @@ -705,8 +705,8 @@ func TestJWTAccountBasicImportExport(t *testing.T) { serviceImport = &jwt.Import{Account: string(fooPub), Subject: "req.add", Type: jwt.Service} activation = jwt.NewActivationClaims(string(barPub)) - activation.Exports = jwt.Exports{} - activation.Exports.Add(&jwt.Export{Subject: "req.add", Type: jwt.Service}) + activation.ImportSubject = "req.add" + activation.ImportType = jwt.Service actJWT, err = activation.Encode(fooKP) if err != nil { t.Fatalf("Error generating activation token: %v", err) @@ -751,8 +751,8 @@ func TestJWTAccountBasicImportExport(t *testing.T) { streamImport = &jwt.Import{Account: string(fooPub), Subject: "private", To: "import.private", Type: jwt.Stream} activation = jwt.NewActivationClaims(string(barPub)) - activation.Exports = jwt.Exports{} - activation.Exports.Add(&jwt.Export{Subject: "private", Type: jwt.Stream}) + activation.ImportSubject = "private" + activation.ImportType = jwt.Stream actJWT, err = activation.Encode(fooKP) if err != nil { t.Fatalf("Error generating activation token: %v", err) @@ -943,8 +943,8 @@ func TestJWTAccountImportActivationExpires(t *testing.T) { streamImport := &jwt.Import{Account: string(fooPub), Subject: "foo", To: "import.", Type: jwt.Stream} activation := jwt.NewActivationClaims(string(barPub)) - activation.Exports = jwt.Exports{} - activation.Exports.Add(&jwt.Export{Subject: "foo", Type: jwt.Stream}) + activation.ImportSubject = "foo" + activation.ImportType = jwt.Stream activation.IssuedAt = time.Now().Add(-10 * time.Second).Unix() activation.Expires = time.Now().Add(time.Second).Unix() actJWT, err := activation.Encode(fooKP) diff --git a/server/opts.go b/server/opts.go index be247702..a84d77ce 100644 --- a/server/opts.go +++ b/server/opts.go @@ -444,7 +444,7 @@ func (o *Options) ProcessConfigFile(configFile string) error { } // Do a quick sanity check on keys for _, key := range o.TrustedNkeys { - if !nkeys.IsValidPublicOperatorKey([]byte(key)) { + if !nkeys.IsValidPublicOperatorKey(key) { err := &configErr{tk, fmt.Sprintf("trust key %q required to be a valid public operator nkey", key)} errors = append(errors, err) } @@ -719,7 +719,7 @@ func parseAccounts(v interface{}, opts *Options, errors *[]error, warnings *[]er switch strings.ToLower(k) { case "nkey": nk, ok := mv.(string) - if !ok || !nkeys.IsValidPublicAccountKey([]byte(nk)) { + if !ok || !nkeys.IsValidPublicAccountKey(nk) { err := &configErr{tk, fmt.Sprintf("Not a valid public nkey for an account: %q", mv)} *errors = append(*errors, err) continue @@ -1276,7 +1276,7 @@ func parseUsers(mv interface{}, opts *Options, errors *[]error, warnings *[]erro return nil, nil, &configErr{tk, fmt.Sprintf("User entry requires a user and a password")} } else if nkey.Nkey != "" { // Make sure the nkey a proper public nkey for a user.. - if !nkeys.IsValidPublicUserKey([]byte(nkey.Nkey)) { + if !nkeys.IsValidPublicUserKey(nkey.Nkey) { return nil, nil, &configErr{tk, fmt.Sprintf("Not a valid public nkey for a user")} } // If we have user or password defined here that is an error. diff --git a/server/opts_test.go b/server/opts_test.go index 435d965a..4a6432a8 100644 --- a/server/opts_test.go +++ b/server/opts_test.go @@ -808,7 +808,6 @@ func TestNkeyUsersWithPermsConfig(t *testing.T) { } nk := opts.Nkeys[0] if nk.Permissions == nil { - fmt.Printf("nk is %+v\n", nk) t.Fatal("Expected to have permissions") } if nk.Permissions.Publish == nil { diff --git a/server/parser.go b/server/parser.go index 3ba7e0af..ee33daaa 100644 --- a/server/parser.go +++ b/server/parser.go @@ -109,8 +109,10 @@ func (c *client) parse(buf []byte) error { var b byte mcl := MAX_CONTROL_LINE_SIZE - if c.srv != nil && c.srv.getOpts() != nil { - mcl = c.srv.getOpts().MaxControlLine + if c.srv != nil { + if opts := c.srv.getOpts(); opts != nil { + mcl = opts.MaxControlLine + } } // Snapshot this, and reset when we receive a diff --git a/server/server.go b/server/server.go index 02fb8e14..5f4de599 100644 --- a/server/server.go +++ b/server/server.go @@ -297,7 +297,7 @@ func (s *Server) processTrustedNkeys() bool { return false } else if s.opts.TrustedNkeys != nil { for _, key := range s.opts.TrustedNkeys { - if !nkeys.IsValidPublicOperatorKey([]byte(key)) { + if !nkeys.IsValidPublicOperatorKey(key) { return false } s.trustedNkeys = s.opts.TrustedNkeys @@ -315,7 +315,7 @@ func checkTrustedNkeyString(keys string) []string { } // Walk all the keys and make sure they are valid. for _, key := range tks { - if !nkeys.IsValidPublicOperatorKey([]byte(key)) { + if !nkeys.IsValidPublicOperatorKey(key) { return nil } } diff --git a/server/sublist_test.go b/server/sublist_test.go index 9829287d..83e613a8 100644 --- a/server/sublist_test.go +++ b/server/sublist_test.go @@ -1197,7 +1197,6 @@ func cacheContentionTest(b *testing.B, numMatchers, numAdders, numRemovers int) index := prand.Intn(lh) sub := subs[index] mu.RUnlock() - //fmt.Printf("Removing %d with subject %q\n", index, sub.subject) s.Remove(sub) } } diff --git a/test/test.go b/test/test.go index 932a018d..03ee4351 100644 --- a/test/test.go +++ b/test/test.go @@ -40,7 +40,7 @@ var DefaultTestOptions = server.Options{ Port: 4222, NoLog: true, NoSigs: true, - MaxControlLine: 256, + MaxControlLine: 2048, } // RunDefaultServer starts a new Go routine based server using the default options diff --git a/vendor/github.com/nats-io/jwt/LICENSE b/vendor/github.com/nats-io/jwt/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/nats-io/jwt/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/nats-io/jwt/account_claims.go b/vendor/github.com/nats-io/jwt/account_claims.go new file mode 100644 index 00000000..7d851db5 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/account_claims.go @@ -0,0 +1,136 @@ +package jwt + +import ( + "github.com/nats-io/nkeys" + "github.com/pkg/errors" +) + +// OperatorLimits are used to limit access by an account +type OperatorLimits struct { + Subs int64 `json:"subs,omitempty"` // Max number of subscriptions + Conn int64 `json:"conn,omitempty"` // Max number of active connections + Imports int64 `json:"imports,omitempty"` // Max number of imports + Exports int64 `json:"exports,omitempty"` // Max number of exports + Data int64 `json:"data,omitempty"` // Max number of bytes + Payload int64 `json:"payload,omitempty"` // Max message payload +} + +// IsEmpty returns true if all of the limits are 0 +func (o *OperatorLimits) IsEmpty() bool { + return *o == OperatorLimits{} +} + +// Validate checks that the operator limits contain valid values +func (o *OperatorLimits) Validate(vr *ValidationResults) { + // negative values mean unlimited, so all numbers are valid +} + +// Account holds account specific claims data +type Account struct { + Imports Imports `json:"imports,omitempty"` + Exports Exports `json:"exports,omitempty"` + Identities []Identity `json:"identity,omitempty"` + Limits OperatorLimits `json:"limits,omitempty"` +} + +// Validate checks if the account is valid, based on the wrapper +func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) { + a.Imports.Validate(acct.Subject, vr) + a.Exports.Validate(vr) + a.Limits.Validate(vr) + + for _, i := range a.Identities { + i.Validate(vr) + } + + if !a.Limits.IsEmpty() && a.Limits.Imports >= 0 && int64(len(a.Imports)) > a.Limits.Imports { + vr.AddError("the account contains more imports than allowed by the operator limits") + } + + if !a.Limits.IsEmpty() && a.Limits.Exports >= 0 && int64(len(a.Exports)) > a.Limits.Exports { + vr.AddError("the account contains more exports than allowed by the operator limits") + } +} + +// AccountClaims defines the body of an account JWT +type AccountClaims struct { + ClaimsData + Account `json:"nats,omitempty"` +} + +// NewAccountClaims creates a new account JWT +func NewAccountClaims(subject string) *AccountClaims { + if subject == "" { + return nil + } + c := &AccountClaims{} + c.Subject = subject + return c +} + +// Encode converts account claims into a JWT string +func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicAccountKey(a.Subject) { + return "", errors.New("expected subject to be account public key") + } + + pubKey, err := pair.PublicKey() + if err != nil { + return "", err + } + + if nkeys.IsValidPublicAccountKey(pubKey) { + if len(a.Identities) > 0 { + return "", errors.New("self-signed account JWTs can't contain identity proofs") + } + if !a.Limits.IsEmpty() { + return "", errors.New("self-signed account JWTs can't contain operator limits") + } + } + + a.ClaimsData.Type = AccountClaim + return a.ClaimsData.encode(pair, a) +} + +// DecodeAccountClaims decodes account claims from a JWT string +func DecodeAccountClaims(token string) (*AccountClaims, error) { + v := AccountClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (a *AccountClaims) String() string { + return a.ClaimsData.String(a) +} + +// Payload pulls the accounts specific payload out of the claims +func (a *AccountClaims) Payload() interface{} { + return &a.Account +} + +// Validate checks the accounts contents +func (a *AccountClaims) Validate(vr *ValidationResults) { + a.ClaimsData.Validate(vr) + a.Account.Validate(a, vr) + + if nkeys.IsValidPublicAccountKey(a.ClaimsData.Issuer) { + if len(a.Identities) > 0 { + vr.AddError("self-signed account JWTs can't contain identity proofs") + } + if !a.Limits.IsEmpty() { + vr.AddError("self-signed account JWTs can't contain operator limits") + } + } +} + +// ExpectedPrefixes defines the types that can encode an account jwt, account and operator +func (a *AccountClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator} +} + +// Claims returns the accounts claims data +func (a *AccountClaims) Claims() *ClaimsData { + return &a.ClaimsData +} diff --git a/vendor/github.com/nats-io/jwt/activation_claims.go b/vendor/github.com/nats-io/jwt/activation_claims.go new file mode 100644 index 00000000..7d5d3ae9 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/activation_claims.go @@ -0,0 +1,145 @@ +package jwt + +import ( + "crypto/sha256" + "encoding/base32" + "errors" + "fmt" + "strings" + + "github.com/nats-io/nkeys" +) + +// Activation defines the custom parts of an activation claim +type Activation struct { + ImportSubject Subject `json:"subject,omitempty"` + ImportType ExportType `json:"type,omitempty"` + Limits +} + +// IsService returns true if an Activation is for a service +func (a *Activation) IsService() bool { + return a.ImportType == Service +} + +// IsStream returns true if an Activation is for a stream +func (a *Activation) IsStream() bool { + return a.ImportType == Stream +} + +// Validate checks the exports and limits in an activation JWT +func (a *Activation) Validate(vr *ValidationResults) { + if !a.IsService() && !a.IsStream() { + vr.AddError("invalid export type: %q", a.ImportType) + } + + if a.IsService() { + if a.ImportSubject.HasWildCards() { + vr.AddError("services cannot have wildcard subject: %q", a.ImportSubject) + } + } + + a.ImportSubject.Validate(vr) + a.Limits.Validate(vr) +} + +// ActivationClaims holds the data specific to an activation JWT +type ActivationClaims struct { + ClaimsData + Activation `json:"nats,omitempty"` +} + +// NewActivationClaims creates a new activation claim with the provided sub +func NewActivationClaims(subject string) *ActivationClaims { + if subject == "" { + return nil + } + ac := &ActivationClaims{} + ac.Subject = subject + return ac +} + +// Encode turns an activation claim into a JWT strimg +func (a *ActivationClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicAccountKey(a.ClaimsData.Subject) { + return "", errors.New("expected subject to be an account") + } + a.ClaimsData.Type = ActivationClaim + return a.ClaimsData.encode(pair, a) +} + +// DecodeActivationClaims tries to create an activation claim from a JWT string +func DecodeActivationClaims(token string) (*ActivationClaims, error) { + v := ActivationClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +// Payload returns the activation specific part of the JWT +func (a *ActivationClaims) Payload() interface{} { + return a.Activation +} + +// Validate checks the claims +func (a *ActivationClaims) Validate(vr *ValidationResults) { + a.ClaimsData.Validate(vr) + a.Activation.Validate(vr) +} + +// ExpectedPrefixes defines the types that can sign an activation jwt, account and oeprator +func (a *ActivationClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator} +} + +// Claims returns the generic part of the JWT +func (a *ActivationClaims) Claims() *ClaimsData { + return &a.ClaimsData +} + +func (a *ActivationClaims) String() string { + return a.ClaimsData.String(a) +} + +// HashID returns a hash of the claims that can be used to identify it. +// The hash is calculated by creating a string with +// issuerPubKey.subjectPubKey. and constructing the sha-256 hash and base32 encoding that. +// is the exported subject, minus any wildcards, so foo.* becomes foo. +// the one special case is that if the export start with "*" or is ">" the "_" +func (a *ActivationClaims) HashID() (string, error) { + + if a.Issuer == "" || a.Subject == "" || a.ImportSubject == "" { + return "", fmt.Errorf("not enough data in the activaion claims to create a hash") + } + + subject := cleanSubject(string(a.ImportSubject)) + base := fmt.Sprintf("%s.%s.%s", a.Issuer, a.Subject, subject) + h := sha256.New() + h.Write([]byte(base)) + sha := h.Sum(nil) + hash := base32.StdEncoding.EncodeToString(sha) + + return hash, nil +} + +func cleanSubject(subject string) string { + split := strings.Split(subject, ".") + cleaned := "" + + for i, tok := range split { + if tok == "*" || tok == ">" { + if i == 0 { + cleaned = "_" + break + } + + cleaned = strings.Join(split[:i], ".") + break + } + } + if cleaned == "" { + cleaned = subject + } + return cleaned +} diff --git a/vendor/github.com/nats-io/jwt/claims.go b/vendor/github.com/nats-io/jwt/claims.go new file mode 100644 index 00000000..ae779ab8 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/claims.go @@ -0,0 +1,285 @@ +package jwt + +import ( + "crypto/sha512" + "encoding/base32" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/nats-io/nkeys" +) + +// ClaimType is used to indicate the type of JWT being stored in a Claim +type ClaimType string + +const ( + // AccountClaim is the type of an Account JWT + AccountClaim = "account" + //ActivationClaim is the type of an activation JWT + ActivationClaim = "activation" + //UserClaim is the type of an user JWT + UserClaim = "user" + //ServerClaim is the type of an server JWT + ServerClaim = "server" + //ClusterClaim is the type of an cluster JWT + ClusterClaim = "cluster" + //OperatorClaim is the type of an operator JWT + OperatorClaim = "operator" + //RevocationClaim is the type of an revocation JWT + RevocationClaim = "revocation" +) + +// Claims is a JWT claims +type Claims interface { + Claims() *ClaimsData + Encode(kp nkeys.KeyPair) (string, error) + ExpectedPrefixes() []nkeys.PrefixByte + Payload() interface{} + String() string + Validate(vr *ValidationResults) + Verify(payload string, sig []byte) bool +} + +// ClaimsData is the base struct for all claims +type ClaimsData struct { + Audience string `json:"aud,omitempty"` + Expires int64 `json:"exp,omitempty"` + ID string `json:"jti,omitempty"` + IssuedAt int64 `json:"iat,omitempty"` + Issuer string `json:"iss,omitempty"` + Name string `json:"name,omitempty"` + NotBefore int64 `json:"nbf,omitempty"` + Subject string `json:"sub,omitempty"` + Tags TagList `json:"tags,omitempty"` + Type ClaimType `json:"type,omitempty"` +} + +// Prefix holds the prefix byte for an NKey +type Prefix struct { + nkeys.PrefixByte +} + +func serialize(v interface{}) (string, error) { + j, err := json.Marshal(v) + if err != nil { + return "", err + } + return base64.RawStdEncoding.EncodeToString(j), nil +} + +func (c *ClaimsData) doEncode(header *Header, kp nkeys.KeyPair, claim Claims) (string, error) { + if header == nil { + return "", errors.New("header is required") + } + + if kp == nil { + return "", errors.New("keypair is required") + } + + if c.Subject == "" { + return "", errors.New("subject is not set") + } + + h, err := serialize(header) + if err != nil { + return "", err + } + + issuerBytes, err := kp.PublicKey() + if err != nil { + return "", err + } + + prefixes := claim.ExpectedPrefixes() + if prefixes != nil { + ok := false + for _, p := range prefixes { + switch p { + case nkeys.PrefixByteAccount: + if nkeys.IsValidPublicAccountKey(issuerBytes) { + ok = true + } + case nkeys.PrefixByteOperator: + if nkeys.IsValidPublicOperatorKey(issuerBytes) { + ok = true + } + case nkeys.PrefixByteServer: + if nkeys.IsValidPublicServerKey(issuerBytes) { + ok = true + } + case nkeys.PrefixByteCluster: + if nkeys.IsValidPublicClusterKey(issuerBytes) { + ok = true + } + case nkeys.PrefixByteUser: + if nkeys.IsValidPublicUserKey(issuerBytes) { + ok = true + } + } + } + if !ok { + return "", fmt.Errorf("unable to validate expected prefixes - %v", prefixes) + } + } + + c.Issuer = string(issuerBytes) + c.IssuedAt = time.Now().UTC().Unix() + + c.ID, err = c.hash() + if err != nil { + return "", err + } + + payload, err := serialize(claim) + if err != nil { + return "", err + } + + sig, err := kp.Sign([]byte(payload)) + if err != nil { + return "", err + } + eSig := base64.RawStdEncoding.EncodeToString(sig) + return fmt.Sprintf("%s.%s.%s", h, payload, eSig), nil +} + +func (c *ClaimsData) hash() (string, error) { + j, err := json.Marshal(c) + if err != nil { + return "", err + } + h := sha512.New512_256() + h.Write(j) + return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(h.Sum(nil)), nil +} + +// encode encodes a claim into a JWT token. The claim is signed with the +// provided nkey's private key +func (c *ClaimsData) encode(kp nkeys.KeyPair, payload Claims) (string, error) { + return c.doEncode(&Header{TokenTypeJwt, AlgorithmNkey}, kp, payload) +} + +// Returns a JSON representation of the claim +func (c *ClaimsData) String(claim interface{}) string { + j, err := json.MarshalIndent(claim, "", " ") + if err != nil { + return "" + } + return string(j) +} + +func parseClaims(s string, target Claims) error { + h, err := base64.RawStdEncoding.DecodeString(s) + if err != nil { + return err + } + if err := json.Unmarshal(h, &target); err != nil { + return err + } + + return nil +} + +// Verify verifies that the encoded payload was signed by the +// provided public key. Verify is called automatically with +// the claims portion of the token and the public key in the claim. +// Client code need to insure that the public key in the +// claim is trusted. +func (c *ClaimsData) Verify(payload string, sig []byte) bool { + // decode the public key + kp, err := nkeys.FromPublicKey(c.Issuer) + if err != nil { + return false + } + if err := kp.Verify([]byte(payload), sig); err != nil { + return false + } + return true +} + +// Validate checks a claim to make sure it is valid. Validity checks +// include expiration and not before constraints. +func (c *ClaimsData) Validate(vr *ValidationResults) { + now := time.Now().UTC().Unix() + if c.Expires > 0 && now > c.Expires { + vr.AddTimeCheck("claim is expired") + } + + if c.NotBefore > 0 && c.NotBefore > now { + vr.AddTimeCheck("claim is not yet valid") + } +} + +// IsSelfSigned returns true if the claims issuer is the subject +func (c *ClaimsData) IsSelfSigned() bool { + return c.Issuer == c.Subject +} + +// Decode takes a JWT string decodes it and validates it +// and return the embedded Claims. If the token header +// doesn't match the expected algorithm, or the claim is +// not valid or verification fails an error is returned +func Decode(token string, target Claims) error { + // must have 3 chunks + chunks := strings.Split(token, ".") + if len(chunks) != 3 { + return errors.New("expected 3 chunks") + } + + _, err := parseHeaders(chunks[0]) + if err != nil { + return err + } + + if err := parseClaims(chunks[1], target); err != nil { + return err + } + + sig, err := base64.RawStdEncoding.DecodeString(chunks[2]) + if err != nil { + return err + } + + if !target.Verify(chunks[1], sig) { + return errors.New("claim failed signature verification") + } + + prefixes := target.ExpectedPrefixes() + if prefixes != nil { + ok := false + issuer := target.Claims().Issuer + for _, p := range prefixes { + switch p { + case nkeys.PrefixByteAccount: + if nkeys.IsValidPublicAccountKey(issuer) { + ok = true + } + case nkeys.PrefixByteOperator: + if nkeys.IsValidPublicOperatorKey(issuer) { + ok = true + } + case nkeys.PrefixByteServer: + if nkeys.IsValidPublicServerKey(issuer) { + ok = true + } + case nkeys.PrefixByteCluster: + if nkeys.IsValidPublicClusterKey(issuer) { + ok = true + } + case nkeys.PrefixByteUser: + if nkeys.IsValidPublicUserKey(issuer) { + ok = true + } + } + } + if !ok { + return fmt.Errorf("unable to validate expected prefixes - %v", prefixes) + } + } + + return nil +} diff --git a/vendor/github.com/nats-io/jwt/cluster_claims.go b/vendor/github.com/nats-io/jwt/cluster_claims.go new file mode 100644 index 00000000..6b3d4e0a --- /dev/null +++ b/vendor/github.com/nats-io/jwt/cluster_claims.go @@ -0,0 +1,79 @@ +package jwt + +import ( + "errors" + + "github.com/nats-io/nkeys" +) + +// Cluster stores the cluster specific elements of a cluster JWT +type Cluster struct { + Trust []string `json:"identity,omitempty"` + Accounts []string `json:"accts,omitempty"` + AccountURL string `json:"accturl,omitempty"` + OperatorURL string `json:"opurl,omitempty"` +} + +// Validate checks the cluster and permissions for a cluster JWT +func (c *Cluster) Validate(vr *ValidationResults) { + // fixme validate cluster data +} + +// ClusterClaims defines the data in a cluster JWT +type ClusterClaims struct { + ClaimsData + Cluster `json:"nats,omitempty"` +} + +// NewClusterClaims creates a new cluster JWT with the specified subject/public key +func NewClusterClaims(subject string) *ClusterClaims { + if subject == "" { + return nil + } + c := &ClusterClaims{} + c.Subject = subject + return c +} + +// Encode tries to turn the cluster claims into a JWT string +func (c *ClusterClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicClusterKey(c.Subject) { + return "", errors.New("expected subject to be a cluster public key") + } + c.ClaimsData.Type = ClusterClaim + return c.ClaimsData.encode(pair, c) +} + +// DecodeClusterClaims tries to parse cluster claims from a JWT string +func DecodeClusterClaims(token string) (*ClusterClaims, error) { + v := ClusterClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (c *ClusterClaims) String() string { + return c.ClaimsData.String(c) +} + +// Payload returns the cluster specific data +func (c *ClusterClaims) Payload() interface{} { + return &c.Cluster +} + +// Validate checks the generic and cluster data in the cluster claims +func (c *ClusterClaims) Validate(vr *ValidationResults) { + c.ClaimsData.Validate(vr) + c.Cluster.Validate(vr) +} + +// ExpectedPrefixes defines the types that can encode a cluster JWT, operator or cluster +func (c *ClusterClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator, nkeys.PrefixByteCluster} +} + +// Claims returns the generic data +func (c *ClusterClaims) Claims() *ClaimsData { + return &c.ClaimsData +} diff --git a/vendor/github.com/nats-io/jwt/exports.go b/vendor/github.com/nats-io/jwt/exports.go new file mode 100644 index 00000000..b27f1808 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/exports.go @@ -0,0 +1,92 @@ +package jwt + +import ( + "fmt" +) + +// Export represents a single export +type Export struct { + Name string `json:"name,omitempty"` + Subject Subject `json:"subject,omitempty"` + Type ExportType `json:"type,omitempty"` + TokenReq bool `json:"token_req,omitempty"` +} + +// IsService returns true if an export is for a service +func (e *Export) IsService() bool { + return e.Type == Service +} + +// IsStream returns true if an export is for a stream +func (e *Export) IsStream() bool { + return e.Type == Stream +} + +// Validate appends validation issues to the passed in results list +func (e *Export) Validate(vr *ValidationResults) { + if !e.IsService() && !e.IsStream() { + vr.AddError("invalid export type: %q", e.Type) + } + + if e.IsService() { + if e.Subject.HasWildCards() { + vr.AddWarning("services cannot have wildcard subject: %q", e.Subject) + } + } + + e.Subject.Validate(vr) +} + +// Exports is an array of exports +type Exports []*Export + +// Add appends exports to the list +func (e *Exports) Add(i ...*Export) { + *e = append(*e, i...) +} + +// Validate calls validate on all of the exports +func (e *Exports) Validate(vr *ValidationResults) error { + var subjects []Subject + for _, v := range *e { + subjects = append(subjects, v.Subject) + v.Validate(vr) + } + // collect all the subjects, and validate that no subject is a subset + m := make(map[string]string) + for i, ns := range subjects { + for j, s := range subjects { + if i == j { + continue + } + if ns.IsContainedIn(s) { + str := string(s) + _, ok := m[str] + if !ok { + m[str] = string(ns) + } + } + } + } + + if len(m) != 0 { + for k, v := range m { + var vi ValidationIssue + vi.Blocking = true + vi.Description = fmt.Sprintf("export subject %q already exports %q", k, v) + vr.Add(&vi) + } + } + + return nil +} + +// HasExportContainingSubject checks if the export list has an export with the provided subject +func (e *Exports) HasExportContainingSubject(subject Subject) bool { + for _, s := range *e { + if subject.IsContainedIn(s.Subject) { + return true + } + } + return false +} diff --git a/vendor/github.com/nats-io/jwt/genericlaims.go b/vendor/github.com/nats-io/jwt/genericlaims.go new file mode 100644 index 00000000..a7bba655 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/genericlaims.go @@ -0,0 +1,58 @@ +package jwt + +import "github.com/nats-io/nkeys" + +// GenericClaims can be used to read a JWT as a map for any non-generic fields +type GenericClaims struct { + ClaimsData + Data map[string]interface{} `json:"nats,omitempty"` +} + +// NewGenericClaims creates a map-based Claims +func NewGenericClaims(subject string) *GenericClaims { + if subject == "" { + return nil + } + c := GenericClaims{} + c.Subject = subject + c.Data = make(map[string]interface{}) + return &c +} + +// DecodeGeneric takes a JWT string and decodes it into a ClaimsData and map +func DecodeGeneric(token string) (*GenericClaims, error) { + v := GenericClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +// Claims returns the standard part of the generic claim +func (gc *GenericClaims) Claims() *ClaimsData { + return &gc.ClaimsData +} + +// Payload returns the custom part of the claiims data +func (gc *GenericClaims) Payload() interface{} { + return &gc.Data +} + +// Encode takes a generic claims and creates a JWT string +func (gc *GenericClaims) Encode(pair nkeys.KeyPair) (string, error) { + return gc.ClaimsData.encode(pair, gc) +} + +// Validate checks the generic part of the claims data +func (gc *GenericClaims) Validate(vr *ValidationResults) { + gc.ClaimsData.Validate(vr) +} + +func (gc *GenericClaims) String() string { + return gc.ClaimsData.String(gc) +} + +// ExpectedPrefixes returns the types allowed to encode a generic JWT, which is nil for all +func (gc *GenericClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return nil +} diff --git a/vendor/github.com/nats-io/jwt/header.go b/vendor/github.com/nats-io/jwt/header.go new file mode 100644 index 00000000..8e9222c5 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/header.go @@ -0,0 +1,57 @@ +package jwt + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strings" +) + +const ( + // Version + Version = "0.0.1" + + // TokenTypeJwt is the JWT token type supported JWT tokens + // encoded and decoded by this library + TokenTypeJwt = "jwt" + + // AlgorithmNkey is the algorithm supported by JWT tokens + // encoded and decoded by this library + AlgorithmNkey = "ed25519" +) + +// Header is a JWT Jose Header +type Header struct { + Type string `json:"typ"` + Algorithm string `json:"alg"` +} + +// Parses a header JWT token +func parseHeaders(s string) (*Header, error) { + h, err := base64.RawStdEncoding.DecodeString(s) + if err != nil { + return nil, err + } + header := Header{} + if err := json.Unmarshal(h, &header); err != nil { + return nil, err + } + + if err := header.Valid(); err != nil { + return nil, err + } + return &header, nil +} + +// Valid validates the Header. It returns nil if the Header is +// a JWT header, and the algorithm used is the NKEY algorithm. +func (h *Header) Valid() error { + if TokenTypeJwt != strings.ToLower(h.Type) { + return fmt.Errorf("not supported type %q", h.Type) + } + + if AlgorithmNkey != strings.ToLower(h.Algorithm) { + return fmt.Errorf("unexpected %q algorithm", h.Algorithm) + } + return nil +} diff --git a/vendor/github.com/nats-io/jwt/imports.go b/vendor/github.com/nats-io/jwt/imports.go new file mode 100644 index 00000000..2aaced9c --- /dev/null +++ b/vendor/github.com/nats-io/jwt/imports.go @@ -0,0 +1,107 @@ +package jwt + +import ( + "io/ioutil" + "net/http" + "net/url" + "time" +) + +// Import describes a mapping from another account into this one +type Import struct { + Name string `json:"name,omitempty"` + Subject Subject `json:"subject,omitempty"` + Account string `json:"account,omitempty"` + Token string `json:"token,omitempty"` + To Subject `json:"to,omitempty"` + Type ExportType `json:"type,omitempty"` +} + +// IsService returns true if the import is of type service +func (i *Import) IsService() bool { + return i.Type == Service +} + +// IsStream returns true if the import is of type stream +func (i *Import) IsStream() bool { + return i.Type == Stream +} + +// Validate checks if an import is valid for the wrapping account +func (i *Import) Validate(actPubKey string, vr *ValidationResults) { + if !i.IsService() && !i.IsStream() { + vr.AddError("invalid import type: %q", i.Type) + } + + if i.Account == "" { + vr.AddWarning("account to import from is not specified") + } + + i.Subject.Validate(vr) + + if i.IsService() { + if i.Subject.HasWildCards() { + vr.AddWarning("services cannot have wildcard subject: %q", i.Subject) + } + } + + var act *ActivationClaims + + if i.Token != "" { + // Check to see if its an embedded JWT or a URL. + if url, err := url.Parse(i.Token); err == nil && url.Scheme != "" { + c := &http.Client{Timeout: 5 * time.Second} + resp, err := c.Get(url.String()) + if err != nil { + vr.AddWarning("import %s contains an unreachable token URL %q", i.Subject, i.Token) + } + + if resp != nil { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + vr.AddWarning("import %s contains an unreadable token URL %q", i.Subject, i.Token) + } else { + act, err = DecodeActivationClaims(string(body)) + if err != nil { + vr.AddWarning("import %s contains a url %q with an invalid activation token", i.Subject, i.Token) + } + } + } + } else { + var err error + act, err = DecodeActivationClaims(i.Token) + if err != nil { + vr.AddWarning("import %q contains an invalid activation token", i.Subject) + } + } + } + + if act != nil { + if act.Issuer != i.Account { + vr.AddWarning("activation token doesn't match account for import %q", i.Subject) + } + + if act.ClaimsData.Subject != actPubKey { + vr.AddWarning("activation token doesn't match account it is being included in, %q", i.Subject) + } + } else { + vr.AddWarning("no activation provided for import %s", i.Subject) + } + +} + +// Imports is a list of import structs +type Imports []*Import + +// Validate checks if an import is valid for the wrapping account +func (i *Imports) Validate(acctPubKey string, vr *ValidationResults) { + for _, v := range *i { + v.Validate(acctPubKey, vr) + } +} + +// Add is a simple way to add imports +func (i *Imports) Add(a ...*Import) { + *i = append(*i, a...) +} diff --git a/vendor/github.com/nats-io/jwt/operator_claims.go b/vendor/github.com/nats-io/jwt/operator_claims.go new file mode 100644 index 00000000..f03269ba --- /dev/null +++ b/vendor/github.com/nats-io/jwt/operator_claims.go @@ -0,0 +1,122 @@ +package jwt + +import ( + "errors" + + "github.com/nats-io/nkeys" +) + +// Operator specific claims +type Operator struct { + Identities []Identity `json:"identity,omitempty"` + SigningKeys []string `json:"signing_keys,omitempty"` +} + +// Validate checks the validity of the operators contents +func (o *Operator) Validate(vr *ValidationResults) { + for _, i := range o.Identities { + i.Validate(vr) + } + + if o.SigningKeys == nil { + return + } + + for _, k := range o.SigningKeys { + if !nkeys.IsValidPublicOperatorKey(k) { + vr.AddError("%s is not an operator public key", k) + } + } +} + +// OperatorClaims define the data for an operator JWT +type OperatorClaims struct { + ClaimsData + Operator `json:"nats,omitempty"` +} + +// NewOperatorClaims creates a new operator claim with the specified subject, which should be an operator public key +func NewOperatorClaims(subject string) *OperatorClaims { + if subject == "" { + return nil + } + c := &OperatorClaims{} + c.Subject = subject + return c +} + +// DidSign checks the claims against the operator's public key and its signing keys +func (s *OperatorClaims) DidSign(op Claims) bool { + if op == nil { + return false + } + + issuer := op.Claims().Issuer + + if issuer == s.Subject { + return true + } + + for _, k := range s.SigningKeys { + if k == issuer { + return true + } + } + + return false +} + +// AddSigningKey creates the signing keys array if necessary +// appends the new key, NO Validation is performed +func (s *OperatorClaims) AddSigningKey(pk string) { + + if s.SigningKeys == nil { + s.SigningKeys = []string{pk} + return + } + + s.SigningKeys = append(s.SigningKeys, pk) +} + +// Encode the claims into a JWT string +func (s *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicOperatorKey(s.Subject) { + return "", errors.New("expected subject to be an operator public key") + } + s.ClaimsData.Type = OperatorClaim + return s.ClaimsData.encode(pair, s) +} + +// DecodeOperatorClaims tries to create an operator claims from a JWt string +func DecodeOperatorClaims(token string) (*OperatorClaims, error) { + v := OperatorClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (s *OperatorClaims) String() string { + return s.ClaimsData.String(s) +} + +// Payload returns the operator specific data for an operator JWT +func (s *OperatorClaims) Payload() interface{} { + return &s.Operator +} + +// Validate the contents of the claims +func (s *OperatorClaims) Validate(vr *ValidationResults) { + s.ClaimsData.Validate(vr) + s.Operator.Validate(vr) +} + +// ExpectedPrefixes defines the nkey types that can sign operator claims, operator +func (s *OperatorClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator} +} + +// Claims returns the generic claims data +func (s *OperatorClaims) Claims() *ClaimsData { + return &s.ClaimsData +} diff --git a/vendor/github.com/nats-io/jwt/revocation_claims.go b/vendor/github.com/nats-io/jwt/revocation_claims.go new file mode 100644 index 00000000..2f86b294 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/revocation_claims.go @@ -0,0 +1,90 @@ +package jwt + +import ( + "github.com/nats-io/nkeys" +) + +// Revocation defines the custom parts of a revocation JWt +type Revocation struct { + JWT string `json:"jwt,omitempty"` + Reason string `json:"reason,omitempty"` +} + +// Validate checks the JWT and reason for a revocation +func (u *Revocation) Validate(vr *ValidationResults) { + if u.JWT == "" { + vr.AddError("revocation token has no JWT to revoke") + } + + _, err := DecodeGeneric(u.JWT) + + if err != nil { + vr.AddError("revocation token has an invalid JWT") + } +} + +// RevocationClaims defines a revocation tokens data +type RevocationClaims struct { + ClaimsData + Revocation `json:"nats,omitempty"` +} + +// NewRevocationClaims creates a new revocation JWT for the specified subject/public key +func NewRevocationClaims(subject string) *RevocationClaims { + if subject == "" { + return nil + } + c := &RevocationClaims{} + c.Subject = subject + return c +} + +// Encode translates the claims to a JWT string +func (rc *RevocationClaims) Encode(pair nkeys.KeyPair) (string, error) { + rc.ClaimsData.Type = RevocationClaim + return rc.ClaimsData.encode(pair, rc) +} + +// DecodeRevocationClaims tries to parse a JWT string as a RevocationClaims +func DecodeRevocationClaims(token string) (*RevocationClaims, error) { + v := RevocationClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (rc *RevocationClaims) String() string { + return rc.ClaimsData.String(rc) +} + +// Payload returns the revocation specific part of the claims +func (rc *RevocationClaims) Payload() interface{} { + return &rc.Revocation +} + +// Validate checks the generic and revocation parts of the claims +func (rc *RevocationClaims) Validate(vr *ValidationResults) { + rc.ClaimsData.Validate(vr) + rc.Revocation.Validate(vr) + + theJWT, err := DecodeGeneric(rc.Revocation.JWT) + if err != nil { + vr.AddError("revocation contains an invalid JWT") + return // can't do the remaining checks + } + + if theJWT.Issuer != rc.Issuer { + vr.AddError("Revocation issuer doesn't match JWT to revoke") + } +} + +// ExpectedPrefixes defines who can sign a revocation token, account or operator +func (rc *RevocationClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator, nkeys.PrefixByteAccount} +} + +// Claims returns the generic part of the claims +func (rc *RevocationClaims) Claims() *ClaimsData { + return &rc.ClaimsData +} diff --git a/vendor/github.com/nats-io/jwt/server_claims.go b/vendor/github.com/nats-io/jwt/server_claims.go new file mode 100644 index 00000000..a62f830f --- /dev/null +++ b/vendor/github.com/nats-io/jwt/server_claims.go @@ -0,0 +1,79 @@ +package jwt + +import ( + "errors" + + "github.com/nats-io/nkeys" +) + +// Server defines the custom part of a server jwt +type Server struct { + Permissions + Cluster string `json:"cluster,omitempty"` +} + +// Validate checks the cluster and permissions for a server JWT +func (s *Server) Validate(vr *ValidationResults) { + if s.Cluster == "" { + vr.AddError("servers can't contain an empty cluster") + } +} + +// ServerClaims defines the data in a server JWT +type ServerClaims struct { + ClaimsData + Server `json:"nats,omitempty"` +} + +// NewServerClaims creates a new server JWT with the specified subject/public key +func NewServerClaims(subject string) *ServerClaims { + if subject == "" { + return nil + } + c := &ServerClaims{} + c.Subject = subject + return c +} + +// Encode tries to turn the server claims into a JWT string +func (s *ServerClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicServerKey(s.Subject) { + return "", errors.New("expected subject to be a server public key") + } + s.ClaimsData.Type = ServerClaim + return s.ClaimsData.encode(pair, s) +} + +// DecodeServerClaims tries to parse server claims from a JWT string +func DecodeServerClaims(token string) (*ServerClaims, error) { + v := ServerClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (s *ServerClaims) String() string { + return s.ClaimsData.String(s) +} + +// Payload returns the server specific data +func (s *ServerClaims) Payload() interface{} { + return &s.Server +} + +// Validate checks the generic and server data in the server claims +func (s *ServerClaims) Validate(vr *ValidationResults) { + s.ClaimsData.Validate(vr) + s.Server.Validate(vr) +} + +// ExpectedPrefixes defines the types that can encode a server JWT, operator or cluster +func (s *ServerClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator, nkeys.PrefixByteCluster} +} + +// Claims returns the generic data +func (s *ServerClaims) Claims() *ClaimsData { + return &s.ClaimsData +} diff --git a/vendor/github.com/nats-io/jwt/types.go b/vendor/github.com/nats-io/jwt/types.go new file mode 100644 index 00000000..4383f5cc --- /dev/null +++ b/vendor/github.com/nats-io/jwt/types.go @@ -0,0 +1,298 @@ +package jwt + +import ( + "encoding/json" + "fmt" + "net" + "strings" + "time" +) + +// ExportType defines the type of import/export. +type ExportType int + +const ( + // Unknown is used if we don't know the type + Unknown ExportType = iota + // Stream defines the type field value for a stream "stream" + Stream + // Service defines the type field value for a service "service" + Service +) + +func (t ExportType) String() string { + switch t { + case Stream: + return "stream" + case Service: + return "service" + } + return "unknown" +} + +// MarshalJSON marshals the enum as a quoted json string +func (t *ExportType) MarshalJSON() ([]byte, error) { + switch *t { + case Stream: + return []byte("\"stream\""), nil + case Service: + return []byte("\"service\""), nil + } + return nil, fmt.Errorf("unknown export type") +} + +// UnmarshalJSON unmashals a quoted json string to the enum value +func (t *ExportType) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + switch j { + case "stream": + *t = Stream + return nil + case "service": + *t = Service + return nil + } + return fmt.Errorf("unknown export type") +} + +// Subject is a string that represents a NATS subject +type Subject string + +// Validate checks that a subject string is valid, ie not empty and without spaces +func (s Subject) Validate(vr *ValidationResults) { + v := string(s) + if v == "" { + vr.AddError("subject cannot be empty") + } + if strings.Contains(v, " ") { + vr.AddError("subject %q cannot have spaces", v) + } +} + +// HasWildCards is used to check if a subject contains a > or * +func (s Subject) HasWildCards() bool { + v := string(s) + return strings.HasSuffix(v, ".>") || + strings.Contains(v, ".*.") || + strings.HasSuffix(v, ".*") || + strings.HasPrefix(v, "*.") || + v == "*" || + v == ">" +} + +// IsContainedIn does a simple test to see if the subject is contained in another subject +func (s Subject) IsContainedIn(other Subject) bool { + otherArray := strings.Split(string(other), ".") + myArray := strings.Split(string(s), ".") + + if len(myArray) > len(otherArray) && otherArray[len(otherArray)-1] != ">" { + return false + } + + if len(myArray) < len(otherArray) { + return false + } + + for ind, tok := range otherArray { + myTok := myArray[ind] + + if ind == len(otherArray)-1 && tok == ">" { + return true + } + + if tok != myTok && tok != "*" { + return false + } + } + + return true +} + +// NamedSubject is the combination of a subject and a name for it +type NamedSubject struct { + Name string `json:"name,omitempty"` + Subject Subject `json:"subject,omitempty"` +} + +// Validate checks the subject +func (ns *NamedSubject) Validate(vr *ValidationResults) { + ns.Subject.Validate(vr) +} + +// TimeRange is used to represent a start and end time +type TimeRange struct { + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +// Validate checks the values in a time range struct +func (tr *TimeRange) Validate(vr *ValidationResults) { + format := "15:04:05" + + if tr.Start == "" { + vr.AddError("time ranges start must contain a start") + } else { + _, err := time.Parse(format, tr.Start) + if err != nil { + vr.AddError("start in time range is invalid %q", tr.Start) + } + } + + if tr.End == "" { + vr.AddError("time ranges end must contain an end") + } else { + _, err := time.Parse(format, tr.End) + if err != nil { + vr.AddError("end in time range is invalid %q", tr.End) + } + } +} + +// Limits are used to control acccess for users and importing accounts +type Limits struct { + Max int64 `json:"max,omitempty"` + Payload int64 `json:"payload,omitempty"` + Src string `json:"src,omitempty"` + Times []TimeRange `json:"times,omitempty"` +} + +// Validate checks the values in a limit struct +func (l *Limits) Validate(vr *ValidationResults) { + if l.Max < 0 { + vr.AddError("limits cannot contain a negative maximum, %d", l.Max) + } + if l.Payload < 0 { + vr.AddError("limits cannot contain a negative payload, %d", l.Payload) + } + + if l.Src != "" { + ip := net.ParseIP(l.Src) + + if ip == nil { + vr.AddError("invalid src %q in limits", l.Src) + } + } + + if l.Times != nil && len(l.Times) > 0 { + for _, t := range l.Times { + t.Validate(vr) + } + } +} + +// Permission defines allow/deny subjects +type Permission struct { + Allow StringList `json:"allow,omitempty"` + Deny StringList `json:"deny,omitempty"` +} + +// Validate the allow, deny elements of a permission +func (p *Permission) Validate(vr *ValidationResults) { + for _, subj := range p.Allow { + Subject(subj).Validate(vr) + } + for _, subj := range p.Deny { + Subject(subj).Validate(vr) + } +} + +// Permissions are used to restrict subject access, either on a user or for everyone on a server by default +type Permissions struct { + Pub Permission `json:"pub,omitempty"` + Sub Permission `json:"sub,omitempty"` +} + +// Validate the pub and sub fields in the permissions list +func (p *Permissions) Validate(vr *ValidationResults) { + p.Pub.Validate(vr) + p.Sub.Validate(vr) +} + +// StringList is a wrapper for an array of strings +type StringList []string + +// Contains returns true if the list contains the string +func (u *StringList) Contains(p string) bool { + for _, t := range *u { + if t == p { + return true + } + } + return false +} + +// Add appends 1 or more strings to a list +func (u *StringList) Add(p ...string) { + for _, v := range p { + if !u.Contains(v) && v != "" { + *u = append(*u, v) + } + } +} + +// Remove removes 1 or more strings from a list +func (u *StringList) Remove(p ...string) { + for _, v := range p { + for i, t := range *u { + if t == v { + a := *u + *u = append(a[:i], a[i+1:]...) + break + } + } + } +} + +// TagList is a unique array of lower case strings +// All tag list methods lower case the strings in the arguments +type TagList []string + +// Contains returns true if the list contains the tags +func (u *TagList) Contains(p string) bool { + p = strings.ToLower(p) + for _, t := range *u { + if t == p { + return true + } + } + return false +} + +// Add appends 1 or more tags to a list +func (u *TagList) Add(p ...string) { + for _, v := range p { + v = strings.ToLower(v) + if !u.Contains(v) && v != "" { + *u = append(*u, v) + } + } +} + +// Remove removes 1 or more tags from a list +func (u *TagList) Remove(p ...string) { + for _, v := range p { + v = strings.ToLower(v) + for i, t := range *u { + if t == v { + a := *u + *u = append(a[:i], a[i+1:]...) + break + } + } + } +} + +// Identity is used to associate an account or operator with a real entity +type Identity struct { + ID string `json:"id,omitempty"` + Proof string `json:"proof,omitempty"` +} + +// Validate checks the values in an Identity +func (u *Identity) Validate(vr *ValidationResults) { + //Fixme identity validation +} diff --git a/vendor/github.com/nats-io/jwt/user_claims.go b/vendor/github.com/nats-io/jwt/user_claims.go new file mode 100644 index 00000000..34e63035 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/user_claims.go @@ -0,0 +1,78 @@ +package jwt + +import ( + "errors" + + "github.com/nats-io/nkeys" +) + +// User defines the user specific data in a user JWT +type User struct { + Permissions + Limits +} + +// Validate checks the permissions and limits in a User jwt +func (u *User) Validate(vr *ValidationResults) { + u.Permissions.Validate(vr) + u.Limits.Validate(vr) +} + +// UserClaims defines a user JWT +type UserClaims struct { + ClaimsData + User `json:"nats,omitempty"` +} + +// NewUserClaims creates a user JWT with the specific subject/public key +func NewUserClaims(subject string) *UserClaims { + if subject == "" { + return nil + } + c := &UserClaims{} + c.Subject = subject + return c +} + +// Encode tries to turn the user claims into a JWT string +func (u *UserClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicUserKey(u.Subject) { + return "", errors.New("expected subject to be user public key") + } + u.ClaimsData.Type = UserClaim + return u.ClaimsData.encode(pair, u) +} + +// DecodeUserClaims tries to parse a user claims from a JWT string +func DecodeUserClaims(token string) (*UserClaims, error) { + v := UserClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +// Validate checks the generic and specific parts of the user jwt +func (u *UserClaims) Validate(vr *ValidationResults) { + u.ClaimsData.Validate(vr) + u.User.Validate(vr) +} + +// ExpectedPrefixes defines the types that can encode a user JWT, account +func (u *UserClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteAccount} +} + +// Claims returns the generic data from a user jwt +func (u *UserClaims) Claims() *ClaimsData { + return &u.ClaimsData +} + +// Payload returns the user specific data from a user JWT +func (u *UserClaims) Payload() interface{} { + return &u.User +} + +func (u *UserClaims) String() string { + return u.ClaimsData.String(u) +} diff --git a/vendor/github.com/nats-io/jwt/validation.go b/vendor/github.com/nats-io/jwt/validation.go new file mode 100644 index 00000000..9ea77418 --- /dev/null +++ b/vendor/github.com/nats-io/jwt/validation.go @@ -0,0 +1,78 @@ +package jwt + +import "fmt" + +// ValidationIssue represents an issue during JWT validation, it may or may not be a blocking error +type ValidationIssue struct { + Description string + Blocking bool + TimeCheck bool +} + +func (ve *ValidationIssue) Error() string { + return ve.Description +} + +// ValidationResults is a list of ValidationIssue pointers +type ValidationResults struct { + Issues []*ValidationIssue +} + +// CreateValidationResults creates an empty list of validation issues +func CreateValidationResults() *ValidationResults { + issues := []*ValidationIssue{} + return &ValidationResults{ + Issues: issues, + } +} + +//Add appends an issue to the list +func (v *ValidationResults) Add(vi *ValidationIssue) { + v.Issues = append(v.Issues, vi) +} + +// AddError creates a new validation error and adds it to the list +func (v *ValidationResults) AddError(format string, args ...interface{}) { + v.Add(&ValidationIssue{ + Description: fmt.Sprintf(format, args...), + Blocking: true, + TimeCheck: false, + }) +} + +// AddTimeCheck creates a new validation issue related to a time check and adds it to the list +func (v *ValidationResults) AddTimeCheck(format string, args ...interface{}) { + v.Add(&ValidationIssue{ + Description: fmt.Sprintf(format, args...), + Blocking: false, + TimeCheck: true, + }) +} + +// AddWarning creates a new validation warning and adds it to the list +func (v *ValidationResults) AddWarning(format string, args ...interface{}) { + v.Add(&ValidationIssue{ + Description: fmt.Sprintf(format, args...), + Blocking: false, + TimeCheck: false, + }) +} + +// IsBlocking returns true if the list contains a blocking error +func (v *ValidationResults) IsBlocking(includeTimeChecks bool) bool { + for _, i := range v.Issues { + if i.Blocking { + return true + } + + if includeTimeChecks && i.TimeCheck { + return true + } + } + return false +} + +// IsEmpty returns true if the list is empty +func (v *ValidationResults) IsEmpty() bool { + return len(v.Issues) == 0 +} diff --git a/vendor/github.com/nats-io/nkeys/keypair.go b/vendor/github.com/nats-io/nkeys/keypair.go index 7dfcb14f..acd86743 100644 --- a/vendor/github.com/nats-io/nkeys/keypair.go +++ b/vendor/github.com/nats-io/nkeys/keypair.go @@ -26,8 +26,8 @@ type kp struct { seed []byte } -// createPair will create a KeyPair based on the rand entropy and a type/prefix byte. rand can be nil. -func createPair(prefix PrefixByte) (KeyPair, error) { +// CreatePair will create a KeyPair based on the rand entropy and a type/prefix byte. rand can be nil. +func CreatePair(prefix PrefixByte) (KeyPair, error) { var rawSeed [32]byte _, err := io.ReadFull(rand.Reader, rawSeed[:]) @@ -70,16 +70,20 @@ func (pair *kp) Seed() ([]byte, error) { // PublicKey will return the encoded public key associated with the KeyPair. // All KeyPairs have a public key. -func (pair *kp) PublicKey() ([]byte, error) { +func (pair *kp) PublicKey() (string, error) { public, raw, err := DecodeSeed(pair.seed) if err != nil { - return nil, err + return "", err } pub, _, err := ed25519.GenerateKey(bytes.NewReader(raw)) if err != nil { - return nil, err + return "", err } - return Encode(public, pub) + pk, err := Encode(public, pub) + if err != nil { + return "", err + } + return string(pk), nil } // PrivateKey will return the encoded private key for KeyPair. diff --git a/vendor/github.com/nats-io/nkeys/main.go b/vendor/github.com/nats-io/nkeys/main.go index ad2f22eb..a85b8b46 100644 --- a/vendor/github.com/nats-io/nkeys/main.go +++ b/vendor/github.com/nats-io/nkeys/main.go @@ -19,6 +19,9 @@ import ( "errors" ) +// Version +const Version = "0.0.1" + // Errors var ( ErrInvalidPrefixByte = errors.New("nkeys: invalid prefix byte") @@ -35,7 +38,7 @@ var ( // KeyPair provides the central interface to nkeys. type KeyPair interface { Seed() ([]byte, error) - PublicKey() ([]byte, error) + PublicKey() (string, error) PrivateKey() ([]byte, error) Sign(input []byte) ([]byte, error) Verify(input []byte, sig []byte) error @@ -44,32 +47,32 @@ type KeyPair interface { // CreateUser will create a User typed KeyPair. func CreateUser() (KeyPair, error) { - return createPair(PrefixByteUser) + return CreatePair(PrefixByteUser) } // CreateAccount will create an Account typed KeyPair. func CreateAccount() (KeyPair, error) { - return createPair(PrefixByteAccount) + return CreatePair(PrefixByteAccount) } // CreateServer will create a Server typed KeyPair. func CreateServer() (KeyPair, error) { - return createPair(PrefixByteServer) + return CreatePair(PrefixByteServer) } // CreateCluster will create a Cluster typed KeyPair. func CreateCluster() (KeyPair, error) { - return createPair(PrefixByteCluster) + return CreatePair(PrefixByteCluster) } // CreateOperator will create an Operator typed KeyPair. func CreateOperator() (KeyPair, error) { - return createPair(PrefixByteOperator) + return CreatePair(PrefixByteOperator) } // FromPublicKey will create a KeyPair capable of verifying signatures. -func FromPublicKey(public []byte) (KeyPair, error) { - raw, err := decode(public) +func FromPublicKey(public string) (KeyPair, error) { + raw, err := decode([]byte(public)) if err != nil { return nil, err } diff --git a/vendor/github.com/nats-io/nkeys/nk/main.go b/vendor/github.com/nats-io/nkeys/nk/main.go index 19c49a45..8274e5a6 100644 --- a/vendor/github.com/nats-io/nkeys/nk/main.go +++ b/vendor/github.com/nats-io/nkeys/nk/main.go @@ -164,7 +164,7 @@ func verify(fname, keyFile, pubFile, sigFile string) { if err != nil { log.Fatal(err) } - kp, err = nkeys.FromPublicKey(public) + kp, err = nkeys.FromPublicKey(string(public)) } if err != nil { log.Fatal(err) @@ -249,7 +249,7 @@ func createVanityKey(keyType, vanity, entropy string, max int) nkeys.KeyPair { fmt.Fprintf(os.Stderr, "\r\033[mcomputing\033[m %s ", string(spin)) kp := genKeyPair(pre, entropy) pub, _ := kp.PublicKey() - if bytes.HasPrefix(pub[1:], []byte(vanity)) { + if bytes.HasPrefix([]byte(pub)[1:], []byte(vanity)) { fmt.Fprintf(os.Stderr, "\r") return kp } diff --git a/vendor/github.com/nats-io/nkeys/public.go b/vendor/github.com/nats-io/nkeys/public.go index 19819efa..cb7927a6 100644 --- a/vendor/github.com/nats-io/nkeys/public.go +++ b/vendor/github.com/nats-io/nkeys/public.go @@ -28,8 +28,12 @@ type pub struct { // PublicKey will return the encoded public key associated with the KeyPair. // All KeyPairs have a public key. -func (p *pub) PublicKey() ([]byte, error) { - return Encode(p.pre, p.pub) +func (p *pub) PublicKey() (string, error) { + pk, err := Encode(p.pre, p.pub) + if err != nil { + return "", err + } + return string(pk), nil } // Seed will return an error since this is not available for public key only KeyPairs. diff --git a/vendor/github.com/nats-io/nkeys/strkey.go b/vendor/github.com/nats-io/nkeys/strkey.go index 25907fb5..7a2ce98a 100644 --- a/vendor/github.com/nats-io/nkeys/strkey.go +++ b/vendor/github.com/nats-io/nkeys/strkey.go @@ -25,26 +25,29 @@ import ( type PrefixByte byte const ( - //PrefixByteSeed is the version byte used for encoded NATS Seeds + // PrefixByteSeed is the version byte used for encoded NATS Seeds PrefixByteSeed PrefixByte = 18 << 3 // Base32-encodes to 'S...' - //PrefixBytePrivate is the version byte used for encoded NATS Private keys + // PrefixBytePrivate is the version byte used for encoded NATS Private keys PrefixBytePrivate PrefixByte = 15 << 3 // Base32-encodes to 'P...' - //PrefixByteServer is the version byte used for encoded NATS Servers + // PrefixByteServer is the version byte used for encoded NATS Servers PrefixByteServer PrefixByte = 13 << 3 // Base32-encodes to 'N...' - //PrefixByteCluster is the version byte used for encoded NATS Clusters + // PrefixByteCluster is the version byte used for encoded NATS Clusters PrefixByteCluster PrefixByte = 2 << 3 // Base32-encodes to 'C...' - //PrefixByteOperator is the version byte used for encoded NATS Operators + // PrefixByteOperator is the version byte used for encoded NATS Operators PrefixByteOperator PrefixByte = 14 << 3 // Base32-encodes to 'O...' - //PrefixByteAccount is the version byte used for encoded NATS Accounts + // PrefixByteAccount is the version byte used for encoded NATS Accounts PrefixByteAccount PrefixByte = 0 // Base32-encodes to 'A...' - //PrefixByteUser is the version byte used for encoded NATS Users + // PrefixByteUser is the version byte used for encoded NATS Users PrefixByteUser PrefixByte = 20 << 3 // Base32-encodes to 'U...' + + // PrefixByteUnknown is for unknown prefixes. + PrefixByteUknown PrefixByte = 23 << 3 // Base32-encodes to 'X...' ) // Set our encoding to not include padding '==' @@ -155,12 +158,10 @@ func Decode(expectedPrefix PrefixByte, src []byte) ([]byte, error) { if err := checkValidPrefixByte(expectedPrefix); err != nil { return nil, err } - raw, err := decode(src) if err != nil { return nil, err } - if prefix := PrefixByte(raw[0]); prefix != expectedPrefix { return nil, ErrInvalidPrefixByte } @@ -187,58 +188,72 @@ func DecodeSeed(src []byte) (PrefixByte, []byte, error) { return PrefixByte(b2), raw[2:], nil } +func Prefix(src string) PrefixByte { + b, err := decode([]byte(src)) + if err != nil { + return PrefixByteUknown + } + prefix := PrefixByte(b[0]) + err = checkValidPrefixByte(prefix) + if err == nil { + return prefix + } + // Might be a seed. + b1 := b[0] & 248 + if PrefixByte(b1) == PrefixByteSeed { + return PrefixByteSeed + } + return PrefixByteUknown +} + +// IsValidPublicKey will decode and verify that the string is a valid encoded public key. +func IsValidPublicKey(src string) bool { + b, err := decode([]byte(src)) + if err != nil { + return false + } + if prefix := PrefixByte(b[0]); checkValidPublicPrefixByte(prefix) != nil { + return false + } + return true +} + // IsValidPublicUserKey will decode and verify the string is a valid encoded Public User Key. -func IsValidPublicUserKey(src []byte) bool { - _, err := Decode(PrefixByteUser, src) +func IsValidPublicUserKey(src string) bool { + _, err := Decode(PrefixByteUser, []byte(src)) return err == nil } // IsValidPublicAccountKey will decode and verify the string is a valid encoded Public Account Key. -func IsValidPublicAccountKey(src []byte) bool { - _, err := Decode(PrefixByteAccount, src) +func IsValidPublicAccountKey(src string) bool { + _, err := Decode(PrefixByteAccount, []byte(src)) return err == nil } // IsValidPublicServerKey will decode and verify the string is a valid encoded Public Server Key. -func IsValidPublicServerKey(src []byte) bool { - _, err := Decode(PrefixByteServer, src) +func IsValidPublicServerKey(src string) bool { + _, err := Decode(PrefixByteServer, []byte(src)) return err == nil } // IsValidPublicClusterKey will decode and verify the string is a valid encoded Public Cluster Key. -func IsValidPublicClusterKey(src []byte) bool { - _, err := Decode(PrefixByteCluster, src) +func IsValidPublicClusterKey(src string) bool { + _, err := Decode(PrefixByteCluster, []byte(src)) return err == nil } // IsValidPublicOperatorKey will decode and verify the string is a valid encoded Public Operator Key. -func IsValidPublicOperatorKey(src []byte) bool { - _, err := Decode(PrefixByteOperator, src) +func IsValidPublicOperatorKey(src string) bool { + _, err := Decode(PrefixByteOperator, []byte(src)) return err == nil } // checkValidPrefixByte returns an error if the provided value // is not one of the defined valid prefix byte constants. func checkValidPrefixByte(prefix PrefixByte) error { - if prefix == PrefixByteOperator { - return nil - } - if prefix == PrefixByteServer { - return nil - } - if prefix == PrefixByteCluster { - return nil - } - if prefix == PrefixByteAccount { - return nil - } - if prefix == PrefixByteUser { - return nil - } - if prefix == PrefixByteSeed { - return nil - } - if prefix == PrefixBytePrivate { + switch prefix { + case PrefixByteOperator, PrefixByteServer, PrefixByteCluster, + PrefixByteAccount, PrefixByteUser, PrefixByteSeed, PrefixBytePrivate: return nil } return ErrInvalidPrefixByte @@ -247,20 +262,29 @@ func checkValidPrefixByte(prefix PrefixByte) error { // checkValidPublicPrefixByte returns an error if the provided value // is not one of the public defined valid prefix byte constants. func checkValidPublicPrefixByte(prefix PrefixByte) error { - if prefix == PrefixByteServer { - return nil - } - if prefix == PrefixByteCluster { - return nil - } - if prefix == PrefixByteOperator { - return nil - } - if prefix == PrefixByteAccount { - return nil - } - if prefix == PrefixByteUser { + switch prefix { + case PrefixByteServer, PrefixByteCluster, PrefixByteOperator, PrefixByteAccount, PrefixByteUser: return nil } return ErrInvalidPrefixByte } + +func (p PrefixByte) String() string { + switch p { + case PrefixByteOperator: + return "operator" + case PrefixByteServer: + return "server" + case PrefixByteCluster: + return "cluster" + case PrefixByteAccount: + return "account" + case PrefixByteUser: + return "user" + case PrefixByteSeed: + return "seed" + case PrefixBytePrivate: + return "private" + } + return "unknown" +} diff --git a/vendor/golang.org/x/crypto/bcrypt/bcrypt.go b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go index f8b807f9..aeb73f81 100644 --- a/vendor/golang.org/x/crypto/bcrypt/bcrypt.go +++ b/vendor/golang.org/x/crypto/bcrypt/bcrypt.go @@ -12,9 +12,10 @@ import ( "crypto/subtle" "errors" "fmt" - "golang.org/x/crypto/blowfish" "io" "strconv" + + "golang.org/x/crypto/blowfish" ) const ( @@ -205,7 +206,6 @@ func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { } func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { - csalt, err := base64Decode(salt) if err != nil { return nil, err @@ -213,7 +213,8 @@ func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cip // Bug compatibility with C bcrypt implementations. They use the trailing // NULL in the key string during expansion. - ckey := append(key, 0) + // We copy the key to prevent changing the underlying array. + ckey := append(key[:len(key):len(key)], 0) c, err := blowfish.NewSaltedCipher(ckey, csalt) if err != nil { @@ -240,11 +241,11 @@ func (p *hashed) Hash() []byte { n = 3 } arr[n] = '$' - n += 1 + n++ copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) n += 2 arr[n] = '$' - n += 1 + n++ copy(arr[n:], p.salt) n += encodedSaltSize copy(arr[n:], p.hash) diff --git a/vendor/golang.org/x/crypto/blowfish/cipher.go b/vendor/golang.org/x/crypto/blowfish/cipher.go index a73954f3..2641dadd 100644 --- a/vendor/golang.org/x/crypto/blowfish/cipher.go +++ b/vendor/golang.org/x/crypto/blowfish/cipher.go @@ -6,7 +6,7 @@ package blowfish // import "golang.org/x/crypto/blowfish" // The code is a port of Bruce Schneier's C implementation. -// See http://www.schneier.com/blowfish.html. +// See https://www.schneier.com/blowfish.html. import "strconv" diff --git a/vendor/golang.org/x/crypto/blowfish/const.go b/vendor/golang.org/x/crypto/blowfish/const.go index 8c5ee4cb..d0407759 100644 --- a/vendor/golang.org/x/crypto/blowfish/const.go +++ b/vendor/golang.org/x/crypto/blowfish/const.go @@ -4,7 +4,7 @@ // The startup permutation array and substitution boxes. // They are the hexadecimal digits of PI; see: -// http://www.schneier.com/code/constants.txt. +// https://www.schneier.com/code/constants.txt. package blowfish diff --git a/vendor/manifest b/vendor/manifest index ac1e8b0f..a7dcc811 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -1,11 +1,19 @@ { "version": 0, "dependencies": [ + { + "importpath": "github.com/nats-io/jwt", + "repository": "https://github.com/nats-io/jwt", + "vcs": "git", + "revision": "c9990f0bf372fd746f55af838ab0b64bbdf8d8ed", + "branch": "master", + "notests": true + }, { "importpath": "github.com/nats-io/nkeys", "repository": "https://github.com/nats-io/nkeys", "vcs": "git", - "revision": "f9a6cffeb5910dad4674c41f28abde89ce590d50", + "revision": "cdc461ca8f466aeeea6eca667cde553efe40bc31", "branch": "master", "notests": true }, @@ -13,7 +21,15 @@ "importpath": "github.com/nats-io/nuid", "repository": "https://github.com/nats-io/nuid", "vcs": "git", - "revision": "28b996b57a46dd0c2aa3a3dc7fa8780878331d00", + "revision": "3024a71c3cbe30667286099921591e6fcc328230", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/pkg/errors", + "repository": "https://github.com/pkg/errors", + "vcs": "git", + "revision": "059132a15dd08d6704c67711dae0cf35ab991756", "branch": "master", "notests": true }, @@ -21,7 +37,7 @@ "importpath": "golang.org/x/crypto/bcrypt", "repository": "https://go.googlesource.com/crypto", "vcs": "git", - "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", + "revision": "3d3f9f413869b949e48070b5bc593aa22cc2b8f2", "branch": "master", "path": "/bcrypt", "notests": true @@ -30,7 +46,7 @@ "importpath": "golang.org/x/crypto/blowfish", "repository": "https://go.googlesource.com/crypto", "vcs": "git", - "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", + "revision": "3d3f9f413869b949e48070b5bc593aa22cc2b8f2", "branch": "master", "path": "/blowfish", "notests": true @@ -39,7 +55,7 @@ "importpath": "golang.org/x/crypto/ed25519", "repository": "https://go.googlesource.com/crypto", "vcs": "git", - "revision": "0e37d006457bf46f9e6692014ba72ef82c33022c", + "revision": "3d3f9f413869b949e48070b5bc593aa22cc2b8f2", "branch": "master", "path": "/ed25519", "notests": true