From 677bd5b6ff3bf7f02df64a9d8374a0369e6ce72a Mon Sep 17 00:00:00 2001 From: scottf Date: Mon, 8 Apr 2024 14:34:57 -0400 Subject: [PATCH 1/8] Ability to set user credentials from strings(s) --- src/NATS.Client/BaseUserJWTHandler.cs | 81 ++++++++++++++++ src/NATS.Client/ConnectionFactory.cs | 14 +++ src/NATS.Client/DefaultUserJWTHandler.cs | 71 ++++---------- src/NATS.Client/NKeys.cs | 7 +- src/NATS.Client/Options.cs | 14 +++ src/NATS.Client/StringUserJWTHandler.cs | 95 +++++++++++++++++++ .../IntegrationTests/IntegrationTests.csproj | 9 ++ src/Tests/IntegrationTests/TestConnection.cs | 36 +++++-- .../certs/test.creds.nkey-seed-block.txt | 7 ++ .../certs/test.creds.nkey-seed-only.txt | 1 + .../config/certs/test.creds.user-block.txt | 3 + 11 files changed, 275 insertions(+), 63 deletions(-) create mode 100644 src/NATS.Client/BaseUserJWTHandler.cs create mode 100644 src/NATS.Client/StringUserJWTHandler.cs create mode 100644 src/Tests/IntegrationTests/config/certs/test.creds.nkey-seed-block.txt create mode 100644 src/Tests/IntegrationTests/config/certs/test.creds.nkey-seed-only.txt create mode 100644 src/Tests/IntegrationTests/config/certs/test.creds.user-block.txt diff --git a/src/NATS.Client/BaseUserJWTHandler.cs b/src/NATS.Client/BaseUserJWTHandler.cs new file mode 100644 index 000000000..650eeeda2 --- /dev/null +++ b/src/NATS.Client/BaseUserJWTHandler.cs @@ -0,0 +1,81 @@ +// Copyright 2019-2024 The NATS Authors +// 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. + +using System.IO; +using System.Security; + +namespace NATS.Client +{ + /// + /// This class is contains the default handlers for the + /// and the + /// . This class is + /// not normally used directly, but is provided to extend or use for + /// utility methods to read a private seed or user JWT. + /// + public class BaseUserJWTHandler + { + protected static string _loadUser(string text) + { + StringReader reader = null; + try + { + reader = new StringReader(text.ToString()); + for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) + { + if (line.Contains("-----BEGIN NATS USER JWT-----")) + { + return reader.ReadLine(); + } + } + + return null; + } + finally + { + reader?.Dispose(); + } + } + + protected static NkeyPair _loadNkeyPair(string secure) + { + StringReader reader = null; + try + { + string text = secure.ToString(); + + // if it's a nk file, it only has the nkey + if (text.StartsWith("SU")) + { + return Nkeys.FromSeed(text); + } + + // otherwise assume it's a creds file. + reader = new StringReader(text); + for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) + { + if (line.Contains("-----BEGIN USER NKEY SEED-----")) + { + return Nkeys.FromSeed(reader.ReadLine()); + } + } + + return null; + } + finally + { + reader?.Dispose(); + } + } + } +} diff --git a/src/NATS.Client/ConnectionFactory.cs b/src/NATS.Client/ConnectionFactory.cs index 615256145..82d69b0b1 100644 --- a/src/NATS.Client/ConnectionFactory.cs +++ b/src/NATS.Client/ConnectionFactory.cs @@ -104,6 +104,20 @@ public IConnection CreateConnection(string url, string jwt, string privateNkey, opts.SetUserCredentials(jwt, privateNkey); return CreateConnection(opts, reconnectOnConnect); } + + // public IConnection G(string url, string credentials, bool reconnectOnConnect = false) + // { + // Options opts = GetDefaultOptions(url); + // opts.SetUserCredentials(jwt, privateNkey); + // return CreateConnection(opts, reconnectOnConnect); + // } + + // public IConnection CreateConnectionWithCredentials(string url, string userJwt, string nkeySeed, bool reconnectOnConnect = false) + // { + // Options opts = GetDefaultOptions(url); + // opts.SetUserCredentials(jwt, privateNkey); + // return CreateConnection(opts, reconnectOnConnect); + // } /// /// Retrieves the default set of client options. diff --git a/src/NATS.Client/DefaultUserJWTHandler.cs b/src/NATS.Client/DefaultUserJWTHandler.cs index 7158d987d..cb271984c 100644 --- a/src/NATS.Client/DefaultUserJWTHandler.cs +++ b/src/NATS.Client/DefaultUserJWTHandler.cs @@ -12,6 +12,7 @@ // limitations under the License. using System.IO; +using System.Security; namespace NATS.Client { @@ -22,7 +23,7 @@ namespace NATS.Client /// not normally used directly, but is provided to extend or use for /// utility methods to read a private seed or user JWT. /// - public class DefaultUserJWTHandler + public class DefaultUserJWTHandler : BaseUserJWTHandler { private string jwtFile; private string credsFile; @@ -56,33 +57,19 @@ public DefaultUserJWTHandler(string jwtFilePath, string credsFilePath) /// The encoded JWT public static string LoadUserFromFile(string path) { - string text = null; - string line = null; - StringReader reader = null; - try + string text = File.ReadAllText(path).Trim(); + if (string.IsNullOrEmpty(text)) { - text = File.ReadAllText(path).Trim(); - if (string.IsNullOrEmpty(text)) throw new NATSException("Credentials file is empty"); - - reader = new StringReader(text); - for (line = reader.ReadLine(); line != null; line = reader.ReadLine()) - { - if (line.Contains("-----BEGIN NATS USER JWT-----")) - { - return reader.ReadLine(); - } - Nkeys.Wipe(line); - } - throw new NATSException("Credentials file does not contain a JWT"); + throw new NATSException("Credentials file is empty"); } - finally + string user = _loadUser(text); + if (user == null) { - Nkeys.Wipe(text); - Nkeys.Wipe(line); - reader?.Dispose(); + throw new NATSException("Credentials file does not contain a JWT"); } + return user.ToString(); } - + /// /// Generates a NATS Ed25519 keypair, used to sign server nonces, from a /// private credentials file. @@ -91,47 +78,23 @@ public static string LoadUserFromFile(string path) /// A NATS Ed25519 KeyPair public static NkeyPair LoadNkeyPairFromSeedFile(string path) { - NkeyPair kp = null; - string text = null; - string line = null; - string seed = null; StringReader reader = null; - try { - text = File.ReadAllText(path).Trim(); - if (string.IsNullOrEmpty(text)) throw new NATSException("Credentials file is empty"); - - // if it's a nk file, it only has the nkey - if (text.StartsWith("SU")) + string text = File.ReadAllText(path).Trim(); + if (string.IsNullOrEmpty(text)) { - kp = Nkeys.FromSeed(text); - return kp; + throw new NATSException("Credentials file is empty"); } - - // otherwise assume it's a creds file. - reader = new StringReader(text); - for (line = reader.ReadLine(); line != null; line = reader.ReadLine()) - { - if (line.Contains("-----BEGIN USER NKEY SEED-----")) - { - seed = reader.ReadLine(); - kp = Nkeys.FromSeed(seed); - Nkeys.Wipe(seed); - } - Nkeys.Wipe(line); - } - + NkeyPair kp = _loadNkeyPair(text); if (kp == null) + { throw new NATSException("Seed not found in credentials file."); - else - return kp; + } + return kp; } finally { - Nkeys.Wipe(line); - Nkeys.Wipe(text); - Nkeys.Wipe(seed); reader?.Dispose(); } } diff --git a/src/NATS.Client/NKeys.cs b/src/NATS.Client/NKeys.cs index f83906d7d..c12af6a8e 100644 --- a/src/NATS.Client/NKeys.cs +++ b/src/NATS.Client/NKeys.cs @@ -283,9 +283,10 @@ public static void Wipe(ref byte[] src) /// string to wipe public static void Wipe(string src) { - // best effort to wipe. - if (src != null && src.Length > 0) - src.Remove(0); + // This code commented out b/c string.remove does not touch the original string. + // There is not way to really wipe the contents of a string. + // if (src != null && src.Length > 0) + // src.Remove(0); } internal static byte[] DecodeSeed(byte[] raw) diff --git a/src/NATS.Client/Options.cs b/src/NATS.Client/Options.cs index ef3509bc8..2a6a8d1f3 100644 --- a/src/NATS.Client/Options.cs +++ b/src/NATS.Client/Options.cs @@ -173,6 +173,20 @@ public void SetUserCredentials(string credentialsPath, string privateKeyPath) UserSignatureEventHandler = handler.DefaultUserSignatureHandler; } + public void SetUserCredentialsFromString(string credentialsText) + { + var handler = new StringUserJWTHandler(credentialsText, credentialsText); + UserJWTEventHandler = handler.DefaultUserJWTEventHandler; + UserSignatureEventHandler = handler.DefaultUserSignatureHandler; + } + + public void SetUserCredentialsFromStrings(string userJwtText, string nkeySeedText) + { + var handler = new StringUserJWTHandler(userJwtText, nkeySeedText); + UserJWTEventHandler = handler.DefaultUserJWTEventHandler; + UserSignatureEventHandler = handler.DefaultUserSignatureHandler; + } + /// /// Sets user credentials using the NATS 2.0 security scheme. /// diff --git a/src/NATS.Client/StringUserJWTHandler.cs b/src/NATS.Client/StringUserJWTHandler.cs new file mode 100644 index 000000000..1cc136cfe --- /dev/null +++ b/src/NATS.Client/StringUserJWTHandler.cs @@ -0,0 +1,95 @@ +// Copyright 2024 The NATS Authors +// 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. + +using System.IO; +using System.Security; + +namespace NATS.Client +{ + /// + /// TODO + /// + public class StringUserJWTHandler : BaseUserJWTHandler + { + /// + /// Gets the JWT file. + /// + public string UserJwt { get; } + + /// + /// Gets the credentials files. + /// + public string NkeySeed { get; } + + /// + /// Creates a static user jwt handler. + /// + /// The text containing the "-----BEGIN NATS USER JWT-----" block + /// and the text containing the "-----BEGIN USER NKEY SEED-----" block + /// May be the same as the jwt file if they are chained. + public StringUserJWTHandler(string credentialsText) : this(credentialsText, credentialsText) {} + + /// + /// Creates a static user jwt handler. + /// + /// The text containing the "-----BEGIN NATS USER JWT-----" block + /// The text containing the "-----BEGIN USER NKEY SEED-----" block or the seed begining with "SU" + /// May be the same as the jwt file if they are chained. + public StringUserJWTHandler(string userJwt, string nkeySeed) + { + UserJwt = _loadUser(userJwt); + if (UserJwt == null) + { + throw new NATSException("Credentials do not contain a JWT"); + } + + NkeySeed = nkeySeed; + if (_loadNkeyPair(nkeySeed) == null) + { + throw new NATSException("Seed not found."); + } + } + + /// + /// The default User JWT Event Handler. + /// + /// Usually the connection. + /// Arguments + public void DefaultUserJWTEventHandler(object sender, UserJWTEventArgs args) + { + args.JWT = UserJwt; + } + + /// + /// Utility method to signs the UserSignatureEventArgs server nonce from + /// a private credentials file. + /// + /// A file with the private Nkey + /// Arguments + public void SignNonceFromFile(UserSignatureEventArgs args) + { + // you have to load this every time b/c signing actually wipes data + args.SignedNonce = _loadNkeyPair(NkeySeed).Sign(args.ServerNonce); + } + + /// + /// The default User Signature event handler. + /// + /// + /// + public void DefaultUserSignatureHandler(object sender, UserSignatureEventArgs args) + { + SignNonceFromFile(args); + } + } +} diff --git a/src/Tests/IntegrationTests/IntegrationTests.csproj b/src/Tests/IntegrationTests/IntegrationTests.csproj index bab278193..487244868 100644 --- a/src/Tests/IntegrationTests/IntegrationTests.csproj +++ b/src/Tests/IntegrationTests/IntegrationTests.csproj @@ -103,6 +103,15 @@ Always + + Always + + + Always + + + Always + diff --git a/src/Tests/IntegrationTests/TestConnection.cs b/src/Tests/IntegrationTests/TestConnection.cs index 6dd0ed6a2..2321acb0d 100644 --- a/src/Tests/IntegrationTests/TestConnection.cs +++ b/src/Tests/IntegrationTests/TestConnection.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.IO; using System.Threading; using System.Threading.Tasks; using NATS.Client; @@ -736,15 +737,38 @@ public void TestInvalidNKey() [Fact] public void Test20Security() + { + var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port); + // opts.SetUserCredentials("./config/certs/test.creds"); + // _testCredsSecurity(opts); + + string path = Directory.GetCurrentDirectory(); + string credentialsText = File.ReadAllText("./config/certs/test.creds"); + string userJwt = File.ReadAllText("./config/certs/test.creds.user-block.txt"); + string nkeySeedBlock = File.ReadAllText("./config/certs/test.creds.nkey-seed-block.txt"); + string nkeySeedOnly = File.ReadAllText("./config/certs/test.creds.nkey-seed-only.txt"); + + opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port); + opts.SetUserCredentialsFromString(credentialsText); + _testCredsSecurity(opts); + + opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port); + opts.SetUserCredentialsFromStrings(userJwt, nkeySeedBlock); + _testCredsSecurity(opts); + + opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port); + opts.SetUserCredentialsFromStrings(userJwt, nkeySeedOnly); + _testCredsSecurity(opts); + } + + private void _testCredsSecurity(Options opts) { AutoResetEvent ev = new AutoResetEvent(false); + opts.ReconnectedEventHandler += (obj, args) => { + ev.Set(); + }; using (var s1 = NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf")) { - var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port); - opts.ReconnectedEventHandler += (obj, args) => { - ev.Set(); - }; - opts.SetUserCredentials("./config/certs/test.creds"); using (Context.ConnectionFactory.CreateConnection(opts)) { s1.Shutdown(); @@ -753,7 +777,7 @@ public void Test20Security() using (NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf")) { // wait for reconnect. - Assert.True(ev.WaitOne(60000)); + Assert.True(ev.WaitOne(30000)); } } } diff --git a/src/Tests/IntegrationTests/config/certs/test.creds.nkey-seed-block.txt b/src/Tests/IntegrationTests/config/certs/test.creds.nkey-seed-block.txt new file mode 100644 index 000000000..647103af0 --- /dev/null +++ b/src/Tests/IntegrationTests/config/certs/test.creds.nkey-seed-block.txt @@ -0,0 +1,7 @@ +************************* IMPORTANT ************************* +NKEY Seed printed below can be used sign and prove identity. +NKEYs are sensitive and should be treated as secrets. + +-----BEGIN USER NKEY SEED----- +SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ2KFE4PEJUA44CNHTC4 +------END USER NKEY SEED------ diff --git a/src/Tests/IntegrationTests/config/certs/test.creds.nkey-seed-only.txt b/src/Tests/IntegrationTests/config/certs/test.creds.nkey-seed-only.txt new file mode 100644 index 000000000..777a66f16 --- /dev/null +++ b/src/Tests/IntegrationTests/config/certs/test.creds.nkey-seed-only.txt @@ -0,0 +1 @@ +SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ2KFE4PEJUA44CNHTC4 \ No newline at end of file diff --git a/src/Tests/IntegrationTests/config/certs/test.creds.user-block.txt b/src/Tests/IntegrationTests/config/certs/test.creds.user-block.txt new file mode 100644 index 000000000..b8fd49846 --- /dev/null +++ b/src/Tests/IntegrationTests/config/certs/test.creds.user-block.txt @@ -0,0 +1,3 @@ +-----BEGIN NATS USER JWT----- +eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJFU1VQS1NSNFhGR0pLN0FHUk5ZRjc0STVQNTZHMkFGWERYQ01CUUdHSklKUEVNUVhMSDJBIiwiaWF0IjoxNTQ0MjE3NzU3LCJpc3MiOiJBQ1pTV0JKNFNZSUxLN1FWREVMTzY0VlgzRUZXQjZDWENQTUVCVUtBMzZNSkpRUlBYR0VFUTJXSiIsInN1YiI6IlVBSDQyVUc2UFY1NTJQNVNXTFdUQlAzSDNTNUJIQVZDTzJJRUtFWFVBTkpYUjc1SjYzUlE1V002IiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ.kCR9Erm9zzux4G6M-V2bp7wKMKgnSNqMBACX05nwePRWQa37aO_yObbhcJWFGYjo1Ix-oepOkoyVLxOJeuD8Bw +------END NATS USER JWT------ From 3f7aedcf378f5d5cbbc3e88e18e6ce20985959bf Mon Sep 17 00:00:00 2001 From: scottf Date: Mon, 8 Apr 2024 15:42:51 -0400 Subject: [PATCH 2/8] Ability to set user credentials from strings(s) --- src/NATS.Client/StringUserJWTHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NATS.Client/StringUserJWTHandler.cs b/src/NATS.Client/StringUserJWTHandler.cs index 1cc136cfe..4401689c6 100644 --- a/src/NATS.Client/StringUserJWTHandler.cs +++ b/src/NATS.Client/StringUserJWTHandler.cs @@ -36,14 +36,13 @@ public class StringUserJWTHandler : BaseUserJWTHandler /// /// The text containing the "-----BEGIN NATS USER JWT-----" block /// and the text containing the "-----BEGIN USER NKEY SEED-----" block - /// May be the same as the jwt file if they are chained. public StringUserJWTHandler(string credentialsText) : this(credentialsText, credentialsText) {} /// /// Creates a static user jwt handler. /// /// The text containing the "-----BEGIN NATS USER JWT-----" block - /// The text containing the "-----BEGIN USER NKEY SEED-----" block or the seed begining with "SU" + /// The text containing the "-----BEGIN USER NKEY SEED-----" block or the seed begining with "SU". /// May be the same as the jwt file if they are chained. public StringUserJWTHandler(string userJwt, string nkeySeed) { From 23b5f61877954c84ae9b3c1aa8e61344944855ef Mon Sep 17 00:00:00 2001 From: scottf Date: Mon, 8 Apr 2024 16:42:55 -0400 Subject: [PATCH 3/8] Ability to set user credentials from strings(s) --- src/NATS.Client/StringUserJWTHandler.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NATS.Client/StringUserJWTHandler.cs b/src/NATS.Client/StringUserJWTHandler.cs index 4401689c6..0b35fa9db 100644 --- a/src/NATS.Client/StringUserJWTHandler.cs +++ b/src/NATS.Client/StringUserJWTHandler.cs @@ -73,9 +73,8 @@ public void DefaultUserJWTEventHandler(object sender, UserJWTEventArgs args) /// Utility method to signs the UserSignatureEventArgs server nonce from /// a private credentials file. /// - /// A file with the private Nkey /// Arguments - public void SignNonceFromFile(UserSignatureEventArgs args) + public void SignNonce(UserSignatureEventArgs args) { // you have to load this every time b/c signing actually wipes data args.SignedNonce = _loadNkeyPair(NkeySeed).Sign(args.ServerNonce); @@ -88,7 +87,7 @@ public void SignNonceFromFile(UserSignatureEventArgs args) /// public void DefaultUserSignatureHandler(object sender, UserSignatureEventArgs args) { - SignNonceFromFile(args); + SignNonce(args); } } } From 8a4dab8468ac201d3d3138e7bb82a68e47d5ac4c Mon Sep 17 00:00:00 2001 From: scottf Date: Mon, 8 Apr 2024 18:28:11 -0400 Subject: [PATCH 4/8] api doc --- src/NATS.Client/ConnectionFactory.cs | 49 ++++++++++++++++++------- src/NATS.Client/Options.cs | 11 ++++++ src/NATS.Client/StringUserJWTHandler.cs | 2 +- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/NATS.Client/ConnectionFactory.cs b/src/NATS.Client/ConnectionFactory.cs index 82d69b0b1..0ac1b6520 100644 --- a/src/NATS.Client/ConnectionFactory.cs +++ b/src/NATS.Client/ConnectionFactory.cs @@ -105,19 +105,42 @@ public IConnection CreateConnection(string url, string jwt, string privateNkey, return CreateConnection(opts, reconnectOnConnect); } - // public IConnection G(string url, string credentials, bool reconnectOnConnect = false) - // { - // Options opts = GetDefaultOptions(url); - // opts.SetUserCredentials(jwt, privateNkey); - // return CreateConnection(opts, reconnectOnConnect); - // } - - // public IConnection CreateConnectionWithCredentials(string url, string userJwt, string nkeySeed, bool reconnectOnConnect = false) - // { - // Options opts = GetDefaultOptions(url); - // opts.SetUserCredentials(jwt, privateNkey); - // return CreateConnection(opts, reconnectOnConnect); - // } + /// + /// + /// + /// + /// The text containing the "-----BEGIN NATS USER JWT-----" block + /// and the text containing the "-----BEGIN USER NKEY SEED-----" block + /// + /// + public IConnection CreateConnectionWithCredentials(string url, string credentialsText, bool reconnectOnConnect = false) + { + Options opts = GetDefaultOptions(url); + opts.SetUserCredentialsFromString(credentialsText); + return CreateConnection(opts, reconnectOnConnect); + } + + /// + /// Attempt to connect to the NATS server referenced by + /// with NATS 2.0 credentials provided directly via strings. + /// + /// + /// + /// Comma seperated arrays are also supported, e.g. "urlA, urlB". + /// + /// A string containing the URL (or URLs) to the NATS Server. See the Remarks + /// section for more information. + /// The text containing the "-----BEGIN NATS USER JWT-----" block + /// The text containing the "-----BEGIN USER NKEY SEED-----" block or the seed begining with "SU". + /// May be the same as the jwt string if they are chained. + /// + /// + public IConnection CreateConnectionWithCredentials(string url, string userJwtText, string nkeySeedText, bool reconnectOnConnect = false) + { + Options opts = GetDefaultOptions(url); + opts.SetUserCredentialsFromStrings(userJwtText, nkeySeedText); + return CreateConnection(opts, reconnectOnConnect); + } /// /// Retrieves the default set of client options. diff --git a/src/NATS.Client/Options.cs b/src/NATS.Client/Options.cs index 2a6a8d1f3..bf4f782d5 100644 --- a/src/NATS.Client/Options.cs +++ b/src/NATS.Client/Options.cs @@ -173,6 +173,11 @@ public void SetUserCredentials(string credentialsPath, string privateKeyPath) UserSignatureEventHandler = handler.DefaultUserSignatureHandler; } + /// + /// Sets user credentials from text instead of a file using the NATS 2.0 security scheme. + /// + /// The text containing the "-----BEGIN NATS USER JWT-----" block + /// and the text containing the "-----BEGIN USER NKEY SEED-----" block public void SetUserCredentialsFromString(string credentialsText) { var handler = new StringUserJWTHandler(credentialsText, credentialsText); @@ -180,6 +185,12 @@ public void SetUserCredentialsFromString(string credentialsText) UserSignatureEventHandler = handler.DefaultUserSignatureHandler; } + /// + /// Sets user credentials from text instead of a file using the NATS 2.0 security scheme. + /// + /// The text containing the "-----BEGIN NATS USER JWT-----" block + /// The text containing the "-----BEGIN USER NKEY SEED-----" block or the seed begining with "SU". + /// May be the same as the jwt string if they are chained. public void SetUserCredentialsFromStrings(string userJwtText, string nkeySeedText) { var handler = new StringUserJWTHandler(userJwtText, nkeySeedText); diff --git a/src/NATS.Client/StringUserJWTHandler.cs b/src/NATS.Client/StringUserJWTHandler.cs index 0b35fa9db..2066fc953 100644 --- a/src/NATS.Client/StringUserJWTHandler.cs +++ b/src/NATS.Client/StringUserJWTHandler.cs @@ -43,7 +43,7 @@ public StringUserJWTHandler(string credentialsText) : this(credentialsText, cred /// /// The text containing the "-----BEGIN NATS USER JWT-----" block /// The text containing the "-----BEGIN USER NKEY SEED-----" block or the seed begining with "SU". - /// May be the same as the jwt file if they are chained. + /// May be the same as the jwt string if they are chained. public StringUserJWTHandler(string userJwt, string nkeySeed) { UserJwt = _loadUser(userJwt); From a31336ebb088671b31de7626854d89793976f988 Mon Sep 17 00:00:00 2001 From: scottf Date: Tue, 9 Apr 2024 07:10:20 -0400 Subject: [PATCH 5/8] address review comments --- src/NATS.Client/DefaultUserJWTHandler.cs | 6 +++--- ...{BaseUserJWTHandler.cs => JWTHandlerUtils.cs} | 16 +++++++--------- src/NATS.Client/StringUserJWTHandler.cs | 10 +++++----- 3 files changed, 15 insertions(+), 17 deletions(-) rename src/NATS.Client/{BaseUserJWTHandler.cs => JWTHandlerUtils.cs} (86%) diff --git a/src/NATS.Client/DefaultUserJWTHandler.cs b/src/NATS.Client/DefaultUserJWTHandler.cs index cb271984c..29d8e185f 100644 --- a/src/NATS.Client/DefaultUserJWTHandler.cs +++ b/src/NATS.Client/DefaultUserJWTHandler.cs @@ -23,7 +23,7 @@ namespace NATS.Client /// not normally used directly, but is provided to extend or use for /// utility methods to read a private seed or user JWT. /// - public class DefaultUserJWTHandler : BaseUserJWTHandler + public class DefaultUserJWTHandler { private string jwtFile; private string credsFile; @@ -62,7 +62,7 @@ public static string LoadUserFromFile(string path) { throw new NATSException("Credentials file is empty"); } - string user = _loadUser(text); + string user = JWTHandlerUtils.LoadUser(text); if (user == null) { throw new NATSException("Credentials file does not contain a JWT"); @@ -86,7 +86,7 @@ public static NkeyPair LoadNkeyPairFromSeedFile(string path) { throw new NATSException("Credentials file is empty"); } - NkeyPair kp = _loadNkeyPair(text); + NkeyPair kp = JWTHandlerUtils.LoadNkeyPair(text); if (kp == null) { throw new NATSException("Seed not found in credentials file."); diff --git a/src/NATS.Client/BaseUserJWTHandler.cs b/src/NATS.Client/JWTHandlerUtils.cs similarity index 86% rename from src/NATS.Client/BaseUserJWTHandler.cs rename to src/NATS.Client/JWTHandlerUtils.cs index 650eeeda2..192736c7b 100644 --- a/src/NATS.Client/BaseUserJWTHandler.cs +++ b/src/NATS.Client/JWTHandlerUtils.cs @@ -23,14 +23,14 @@ namespace NATS.Client /// not normally used directly, but is provided to extend or use for /// utility methods to read a private seed or user JWT. /// - public class BaseUserJWTHandler + public class JWTHandlerUtils { - protected static string _loadUser(string text) + public static string LoadUser(string text) { StringReader reader = null; try { - reader = new StringReader(text.ToString()); + reader = new StringReader(text); for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (line.Contains("-----BEGIN NATS USER JWT-----")) @@ -47,21 +47,19 @@ protected static string _loadUser(string text) } } - protected static NkeyPair _loadNkeyPair(string secure) + public static NkeyPair LoadNkeyPair(string nkeySeed) { StringReader reader = null; try { - string text = secure.ToString(); - // if it's a nk file, it only has the nkey - if (text.StartsWith("SU")) + if (nkeySeed.StartsWith("SU")) { - return Nkeys.FromSeed(text); + return Nkeys.FromSeed(nkeySeed); } // otherwise assume it's a creds file. - reader = new StringReader(text); + reader = new StringReader(nkeySeed); for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (line.Contains("-----BEGIN USER NKEY SEED-----")) diff --git a/src/NATS.Client/StringUserJWTHandler.cs b/src/NATS.Client/StringUserJWTHandler.cs index 2066fc953..064d48d0b 100644 --- a/src/NATS.Client/StringUserJWTHandler.cs +++ b/src/NATS.Client/StringUserJWTHandler.cs @@ -19,7 +19,7 @@ namespace NATS.Client /// /// TODO /// - public class StringUserJWTHandler : BaseUserJWTHandler + public class StringUserJWTHandler { /// /// Gets the JWT file. @@ -46,17 +46,17 @@ public StringUserJWTHandler(string credentialsText) : this(credentialsText, cred /// May be the same as the jwt string if they are chained. public StringUserJWTHandler(string userJwt, string nkeySeed) { - UserJwt = _loadUser(userJwt); + UserJwt = JWTHandlerUtils.LoadUser(userJwt); if (UserJwt == null) { throw new NATSException("Credentials do not contain a JWT"); } - NkeySeed = nkeySeed; - if (_loadNkeyPair(nkeySeed) == null) + if (JWTHandlerUtils.LoadNkeyPair(nkeySeed) == null) { throw new NATSException("Seed not found."); } + NkeySeed = nkeySeed; } /// @@ -77,7 +77,7 @@ public void DefaultUserJWTEventHandler(object sender, UserJWTEventArgs args) public void SignNonce(UserSignatureEventArgs args) { // you have to load this every time b/c signing actually wipes data - args.SignedNonce = _loadNkeyPair(NkeySeed).Sign(args.ServerNonce); + args.SignedNonce = JWTHandlerUtils.LoadNkeyPair(NkeySeed).Sign(args.ServerNonce); } /// From 98ff2584f4ee5578a5c3cab333d1e1d829658de4 Mon Sep 17 00:00:00 2001 From: scottf Date: Tue, 9 Apr 2024 07:16:42 -0400 Subject: [PATCH 6/8] address review comments --- src/NATS.Client/DefaultUserJWTHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NATS.Client/DefaultUserJWTHandler.cs b/src/NATS.Client/DefaultUserJWTHandler.cs index 29d8e185f..f82465ab6 100644 --- a/src/NATS.Client/DefaultUserJWTHandler.cs +++ b/src/NATS.Client/DefaultUserJWTHandler.cs @@ -67,7 +67,7 @@ public static string LoadUserFromFile(string path) { throw new NATSException("Credentials file does not contain a JWT"); } - return user.ToString(); + return user; } /// From 75271c56bd987df13bfe4221121f265a17dba2e7 Mon Sep 17 00:00:00 2001 From: scottf Date: Tue, 9 Apr 2024 07:18:25 -0400 Subject: [PATCH 7/8] address review comments --- src/NATS.Client/JWTHandlerUtils.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/NATS.Client/JWTHandlerUtils.cs b/src/NATS.Client/JWTHandlerUtils.cs index 192736c7b..fd4138197 100644 --- a/src/NATS.Client/JWTHandlerUtils.cs +++ b/src/NATS.Client/JWTHandlerUtils.cs @@ -16,13 +16,6 @@ namespace NATS.Client { - /// - /// This class is contains the default handlers for the - /// and the - /// . This class is - /// not normally used directly, but is provided to extend or use for - /// utility methods to read a private seed or user JWT. - /// public class JWTHandlerUtils { public static string LoadUser(string text) From 17087b710f753d660d7fe2328944a59f3870bd76 Mon Sep 17 00:00:00 2001 From: scottf Date: Tue, 9 Apr 2024 15:31:45 -0400 Subject: [PATCH 8/8] fixed doc --- src/NATS.Client/ConnectionFactory.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NATS.Client/ConnectionFactory.cs b/src/NATS.Client/ConnectionFactory.cs index 0ac1b6520..b4577cb38 100644 --- a/src/NATS.Client/ConnectionFactory.cs +++ b/src/NATS.Client/ConnectionFactory.cs @@ -106,7 +106,8 @@ public IConnection CreateConnection(string url, string jwt, string privateNkey, } /// - /// + /// Attempt to connect to the NATS server referenced by + /// with NATS 2.0 the user jwt and nkey seed credentials provided directly in the string. /// /// /// The text containing the "-----BEGIN NATS USER JWT-----" block @@ -122,7 +123,7 @@ public IConnection CreateConnectionWithCredentials(string url, string credential /// /// Attempt to connect to the NATS server referenced by - /// with NATS 2.0 credentials provided directly via strings. + /// with NATS 2.0 the user jwt and nkey seed credentials provided directly via strings. /// /// ///