1+ using System . Net . Security ;
2+ using System . Security . Cryptography ;
3+ using System . Security . Cryptography . X509Certificates ;
14using Moq ;
25using NRedisStack . DataTypes ;
36using NRedisStack . RedisStackCommands ;
47using NRedisStack . Search ;
58using NRedisStack . Search . Aggregation ;
69using NRedisStack . Search . Literals . Enums ;
10+ using Org . BouncyCastle . Crypto ;
11+ using Org . BouncyCastle . Crypto . Parameters ;
12+ using Org . BouncyCastle . Math ;
13+ using Org . BouncyCastle . OpenSsl ;
714using StackExchange . Redis ;
815using Xunit ;
16+ using Xunit . Abstractions ;
917using static NRedisStack . Search . Schema ;
1018
1119namespace NRedisStack . Tests ;
1220
1321public class ExaplesTests : AbstractNRedisStackTest , IDisposable
1422{
23+ private readonly ITestOutputHelper testOutputHelper ;
1524 Mock < IDatabase > _mock = new Mock < IDatabase > ( ) ;
1625 private readonly string key = "EXAMPLES_TESTS" ;
17- public ExaplesTests ( RedisFixture redisFixture ) : base ( redisFixture ) { }
26+ public ExaplesTests ( RedisFixture redisFixture , ITestOutputHelper testOutputHelper ) : base ( redisFixture )
27+ {
28+ this . testOutputHelper = testOutputHelper ;
29+ }
1830
1931 public void Dispose ( )
2032 {
@@ -292,6 +304,272 @@ public void TestJsonConvert()
292304 Assert . Equal ( 10 , docs . Count ( ) ) ;
293305 }
294306
307+ #if NET481
308+ [ Fact ]
309+ public void TestRedisCloudConnection_net481 ( )
310+ {
311+ var root = Path . GetFullPath ( Directory . GetCurrentDirectory ( ) ) ;
312+ var redisCaPath = Path . GetFullPath ( Path . Combine ( root , "redis_ca.pem" ) ) ;
313+ var redisUserCrtPath = Path . GetFullPath ( Path . Combine ( root , "redis_user.crt" ) ) ;
314+ var redisUserPrivateKeyPath = Path . GetFullPath ( Path . Combine ( root , "redis_user_private.key" ) ) ;
315+
316+ var password = Environment . GetEnvironmentVariable ( "PASSWORD" ) ?? throw new Exception ( "PASSWORD is not set." ) ;
317+ var endpoint = Environment . GetEnvironmentVariable ( "ENDPOINT" ) ?? throw new Exception ( "ENDPOINT is not set." ) ;
318+
319+ // Load the Redis credentials
320+ var redisUserCertificate = new X509Certificate2 ( File . ReadAllBytes ( redisUserCrtPath ) ) ;
321+ var redisCaCertificate = new X509Certificate2 ( File . ReadAllBytes ( redisCaPath ) ) ;
322+
323+ var rsa = RSA . Create ( ) ;
324+
325+ var redisUserPrivateKeyText = File . ReadAllText ( redisUserPrivateKeyPath ) . Trim ( ) ;
326+ rsa . ImportParameters ( ImportPrivateKey ( redisUserPrivateKeyText ) ) ;
327+
328+ var clientCert = redisUserCertificate . CopyWithPrivateKey ( rsa ) ;
329+
330+ // Connect to Redis Cloud
331+ var redisConfiguration = new ConfigurationOptions
332+ {
333+ EndPoints = { endpoint } ,
334+ Ssl = true ,
335+ Password = password
336+ } ;
337+
338+ redisConfiguration . CertificateSelection += ( _ , _ , _ , _ , _ ) => new X509Certificate2 ( clientCert . Export ( X509ContentType . Pfx ) ) ;
339+
340+ redisConfiguration . CertificateValidation += ( _ , cert , _ , errors ) =>
341+ {
342+ if ( errors == SslPolicyErrors . None )
343+ {
344+ return true ;
345+ }
346+
347+ var privateChain = new X509Chain ( ) ;
348+ privateChain . ChainPolicy = new X509ChainPolicy { RevocationMode = X509RevocationMode . NoCheck } ;
349+ X509Certificate2 cert2 = new X509Certificate2 ( cert ! ) ;
350+ privateChain . ChainPolicy . ExtraStore . Add ( redisCaCertificate ) ;
351+ privateChain . Build ( cert2 ) ;
352+
353+ bool isValid = true ;
354+
355+ // we're establishing the trust chain so if the only complaint is that that the root CA is untrusted, and the root CA root
356+ // matches our certificate, we know it's ok
357+ foreach ( X509ChainStatus chainStatus in privateChain . ChainStatus . Where ( x =>
358+ x . Status != X509ChainStatusFlags . UntrustedRoot ) )
359+ {
360+ if ( chainStatus . Status != X509ChainStatusFlags . NoError )
361+ {
362+ isValid = false ;
363+ break ;
364+ }
365+ }
366+
367+ return isValid ;
368+ } ;
369+
370+
371+ var redis = ConnectionMultiplexer . Connect ( redisConfiguration ) ;
372+ var db = redis . GetDatabase ( ) ;
373+ db . Ping ( ) ;
374+ }
375+
376+ public static RSAParameters ImportPrivateKey ( string pem )
377+ {
378+ using var sr = new StringReader ( pem ) ;
379+ PemReader pr = new PemReader ( sr ) ;
380+ RSAParameters rp = new RSAParameters ( ) ;
381+ while ( sr . Peek ( ) != - 1 )
382+ {
383+ var privKey = pr . ReadObject ( ) as AsymmetricCipherKeyPair ;
384+ if ( privKey != null )
385+ {
386+ var pkParamaters = ( RsaPrivateCrtKeyParameters ) privKey . Private ;
387+ rp . Modulus = pkParamaters . Modulus . ToByteArrayUnsigned ( ) ;
388+ rp . Exponent = pkParamaters . PublicExponent . ToByteArrayUnsigned ( ) ;
389+ rp . P = pkParamaters . P . ToByteArrayUnsigned ( ) ;
390+ rp . Q = pkParamaters . Q . ToByteArrayUnsigned ( ) ;
391+ rp . D = ConvertRSAParametersField ( pkParamaters . Exponent , rp . Modulus . Length ) ;
392+ rp . DP = ConvertRSAParametersField ( pkParamaters . DP , rp . P . Length ) ;
393+ rp . DQ = ConvertRSAParametersField ( pkParamaters . DQ , rp . Q . Length ) ;
394+ rp . InverseQ = ConvertRSAParametersField ( pkParamaters . QInv , rp . Q . Length ) ;
395+ }
396+ else
397+ {
398+ throw new ArgumentException ( "Pem is malformed and could not be parsed" ) ;
399+ }
400+ }
401+ pr . ReadObject ( ) ;
402+ return rp ;
403+ }
404+
405+ private static byte [ ] ConvertRSAParametersField ( BigInteger n , int size )
406+ {
407+ byte [ ] bs = n . ToByteArrayUnsigned ( ) ;
408+ if ( bs . Length == size )
409+ return bs ;
410+ if ( bs . Length > size )
411+ throw new ArgumentException ( "Specified size too small" , "size" ) ;
412+ byte [ ] padded = new byte [ size ] ;
413+ Array . Copy ( bs , 0 , padded , size - bs . Length , bs . Length ) ;
414+ return padded ;
415+ }
416+ #endif
417+
418+ #if NET6_0_OR_GREATER
419+ [ Fact ]
420+ public void TestRedisCloudConnection ( )
421+ {
422+ var root = Path . GetFullPath ( Directory . GetCurrentDirectory ( ) ) ;
423+ var redisCaPath = Path . GetFullPath ( Path . Combine ( root , "redis_ca.pem" ) ) ;
424+ var redisUserCrtPath = Path . GetFullPath ( Path . Combine ( root , "redis_user.crt" ) ) ;
425+ var redisUserPrivateKeyPath = Path . GetFullPath ( Path . Combine ( root , "redis_user_private.key" ) ) ;
426+
427+ var password = Environment . GetEnvironmentVariable ( "PASSWORD" ) ?? throw new Exception ( "PASSWORD is not set." ) ;
428+ var endpoint = Environment . GetEnvironmentVariable ( "ENDPOINT" ) ?? throw new Exception ( "ENDPOINT is not set." ) ;
429+
430+ // Load the Redis credentials
431+ var redisUserCertificate = new X509Certificate2 ( File . ReadAllBytes ( redisUserCrtPath ) ) ;
432+ var redisCaCertificate = new X509Certificate2 ( File . ReadAllBytes ( redisCaPath ) ) ;
433+
434+ var rsa = RSA . Create ( ) ;
435+
436+ var redisUserPrivateKeyText = File . ReadAllText ( redisUserPrivateKeyPath ) ;
437+ var pemFileData = File . ReadAllLines ( redisUserPrivateKeyPath ) . Where ( x => ! x . StartsWith ( "-" ) ) ;
438+ var binaryEncoding = Convert . FromBase64String ( string . Join ( null , pemFileData ) ) ;
439+
440+ rsa . ImportRSAPrivateKey ( binaryEncoding , out _ ) ;
441+ redisUserCertificate . CopyWithPrivateKey ( rsa ) ;
442+ rsa . ImportFromPem ( redisUserPrivateKeyText . ToCharArray ( ) ) ;
443+ var clientCert = redisUserCertificate . CopyWithPrivateKey ( rsa ) ;
444+
445+ // Connect to Redis Cloud
446+ var redisConfiguration = new ConfigurationOptions
447+ {
448+ EndPoints = { endpoint } ,
449+ Ssl = true ,
450+ Password = password
451+ } ;
452+
453+ redisConfiguration . CertificateSelection += ( _ , _ , _ , _ , _ ) => clientCert ;
454+
455+ redisConfiguration . CertificateValidation += ( _ , cert , _ , errors ) =>
456+ {
457+ if ( errors == SslPolicyErrors . None )
458+ {
459+ return true ;
460+ }
461+
462+ var privateChain = new X509Chain ( ) ;
463+ privateChain . ChainPolicy = new X509ChainPolicy { RevocationMode = X509RevocationMode . NoCheck } ;
464+ X509Certificate2 cert2 = new X509Certificate2 ( cert ! ) ;
465+ privateChain . ChainPolicy . ExtraStore . Add ( redisCaCertificate ) ;
466+ privateChain . Build ( cert2 ) ;
467+
468+ bool isValid = true ;
469+
470+ // we're establishing the trust chain so if the only complaint is that that the root CA is untrusted, and the root CA root
471+ // matches our certificate, we know it's ok
472+ foreach ( X509ChainStatus chainStatus in privateChain . ChainStatus . Where ( x =>
473+ x . Status != X509ChainStatusFlags . UntrustedRoot ) )
474+ {
475+ if ( chainStatus . Status != X509ChainStatusFlags . NoError )
476+ {
477+ isValid = false ;
478+ break ;
479+ }
480+ }
481+
482+ return isValid ;
483+ } ;
484+
485+
486+ var redis = ConnectionMultiplexer . Connect ( redisConfiguration ) ;
487+ var db = redis . GetDatabase ( ) ;
488+ db . Ping ( ) ;
489+ }
490+
491+ [ Fact ]
492+ public void TestRedisCloudConnection_DotnetCore3 ( )
493+ {
494+ // Replace this with your own Redis Cloud credentials
495+ var root = Path . GetFullPath ( Directory . GetCurrentDirectory ( ) ) ;
496+ var redisCaPath = Path . GetFullPath ( Path . Combine ( root , "redis_ca.pem" ) ) ;
497+ var redisUserCrtPath = Path . GetFullPath ( Path . Combine ( root , "redis_user.crt" ) ) ;
498+ var redisUserPrivateKeyPath = Path . GetFullPath ( Path . Combine ( root , "redis_user_private.key" ) ) ;
499+
500+ var password = Environment . GetEnvironmentVariable ( "PASSWORD" ) ?? throw new Exception ( "PASSWORD is not set." ) ;
501+ var endpoint = Environment . GetEnvironmentVariable ( "ENDPOINT" ) ?? throw new Exception ( "ENDPOINT is not set." ) ;
502+
503+ // Load the Redis credentials
504+ var redisUserCertificate = new X509Certificate2 ( File . ReadAllBytes ( redisUserCrtPath ) ) ;
505+ var redisCaCertificate = new X509Certificate2 ( File . ReadAllBytes ( redisCaPath ) ) ;
506+
507+ var rsa = RSA . Create ( ) ;
508+
509+ var redisUserPrivateKeyText = File . ReadAllText ( redisUserPrivateKeyPath ) ;
510+ var pemFileData = File . ReadAllLines ( redisUserPrivateKeyPath ) . Where ( x => ! x . StartsWith ( "-" ) ) ;
511+ var binaryEncoding = Convert . FromBase64String ( string . Join ( null , pemFileData ) ) ;
512+
513+ rsa . ImportRSAPrivateKey ( binaryEncoding , out _ ) ;
514+ redisUserCertificate . CopyWithPrivateKey ( rsa ) ;
515+ rsa . ImportFromPem ( redisUserPrivateKeyText . ToCharArray ( ) ) ;
516+ var clientCert = redisUserCertificate . CopyWithPrivateKey ( rsa ) ;
517+
518+ var sslOptions = new SslClientAuthenticationOptions
519+ {
520+ CertificateRevocationCheckMode = X509RevocationMode . NoCheck ,
521+ LocalCertificateSelectionCallback = ( _ , _ , _ , _ , _ ) => clientCert ,
522+ RemoteCertificateValidationCallback = ( _ , cert , _ , errors ) =>
523+ {
524+ if ( errors == SslPolicyErrors . None )
525+ {
526+ return true ;
527+ }
528+
529+ var privateChain = new X509Chain ( ) ;
530+ privateChain . ChainPolicy = new X509ChainPolicy { RevocationMode = X509RevocationMode . NoCheck } ;
531+ X509Certificate2 cert2 = new X509Certificate2 ( cert ! ) ;
532+ privateChain . ChainPolicy . ExtraStore . Add ( redisCaCertificate ) ;
533+ privateChain . Build ( cert2 ) ;
534+
535+ bool isValid = true ;
536+
537+ // we're establishing the trust chain so if the only complaint is that that the root CA is untrusted, and the root CA root
538+ // matches our certificate, we know it's ok
539+ foreach ( X509ChainStatus chainStatus in privateChain . ChainStatus . Where ( x=> x . Status != X509ChainStatusFlags . UntrustedRoot ) )
540+ {
541+ if ( chainStatus . Status != X509ChainStatusFlags . NoError )
542+ {
543+ isValid = false ;
544+ break ;
545+ }
546+ }
547+
548+ return isValid ;
549+ } ,
550+ TargetHost = endpoint
551+ } ;
552+ // Connect to Redis Cloud
553+ var redisConfiguration = new ConfigurationOptions
554+ {
555+ EndPoints = { endpoint } ,
556+ Ssl = true ,
557+ SslHost = sslOptions . TargetHost ,
558+ SslClientAuthenticationOptions = host => sslOptions ,
559+ Password = password
560+ } ;
561+
562+
563+ var redis = ConnectionMultiplexer . Connect ( redisConfiguration ) ;
564+ var db = redis . GetDatabase ( ) ;
565+ db . Ping ( ) ;
566+
567+ db . StringSet ( "testKey" , "testValue" ) ;
568+ var value = db . StringGet ( "testKey" ) ;
569+ Assert . Equal ( "testValue" , value ) ;
570+ }
571+ #endif
572+
295573 [ Fact ]
296574 public void BasicJsonExamplesTest ( )
297575 {
@@ -1067,4 +1345,4 @@ private static void SortAndCompare(List<string> expectedList, List<string> res)
10671345 Assert . Equal ( expectedList [ i ] , res [ i ] . ToString ( ) ) ;
10681346 }
10691347 }
1070- }
1348+ }
0 commit comments