internal static byte[] GenerateKeyFile(ITestOutputHelper output, IKeyCapture capture, IKeyFileService service)
 {
     lock (Sync)
     {
         var @out  = new XunitDuplexTextWriter(output, Console.Out);
         var error = new XunitDuplexTextWriter(output, Console.Error);
         Assert.True(KeyFileManager.TryGenerateKeyFile(service.GetKeyFileStream(), @out, error, capture));
         capture.Reset();
         return(Crypto.SigningPublicKeyFromSigningKey(service, capture));
     }
 }
示例#2
0
        public unsafe byte *GetSecretKeyPointer(IKeyCapture capture, [CallerMemberName] string callerMemberName = null)
        {
            var keyFilePath   = GetKeyFilePath();
            var keyFileStream = GetKeyFileStream();

            var ptr = capture is IPersistedKeyCapture persisted
                ? Crypto.LoadSecretKeyPointerFromFileStream(keyFilePath, keyFileStream, persisted, callerMemberName)
                : Crypto.LoadSecretKeyPointerFromFileStream(keyFilePath, keyFileStream, capture, callerMemberName);

            return(ptr);
        }
示例#3
0
 public static unsafe byte *LoadSecretKeyPointerFromFileStream(string keyFilePath, FileStream keyFileStream,
                                                               IKeyCapture capture, [CallerMemberName] string callerMemberName = null)
 {
     if (keyFileStream.CanSeek)
     {
         keyFileStream.Seek(0, SeekOrigin.Begin);
     }
     if (!KeyFileManager.TryLoadKeyFile(keyFileStream, Console.Out, Console.Error, out var sk, capture))
     {
         throw new InvalidOperationException(
                   $"{callerMemberName}: Cannot load key file at path '{keyFilePath}'");
     }
     return(sk);
 }
示例#4
0
        public void Sign(IKeyFileService keyFileService, IKeyCapture capture,
                         ulong formatVersion = LogSerializeContext.FormatVersion)
        {
            unsafe
            {
                var sk      = keyFileService.GetSecretKeyPointer(capture);
                var message = GetMessage(formatVersion);

                var signature       = new byte[Crypto.SecretKeyBytes].AsSpan();
                var signatureLength = Crypto.SignDetached(message, sk, signature);
                if (signatureLength < (ulong)signature.Length)
                {
                    signature = signature.Slice(0, (int)signatureLength);
                }

                Signature = signature.ToArray();
            }
        }
示例#5
0
        public static unsafe byte *SigningKeyToEncryptionKey(IKeyFileService keyFileService, IKeyCapture capture)
        {
            var sk = keyFileService.GetSecretKeyPointer(capture);

            try
            {
                return(SigningKeyToEncryptionKey(sk));
            }
            finally
            {
                NativeMethods.sodium_free(sk);
            }
        }
示例#6
0
 public static byte[] SigningPublicKeyFromSigningKey(IKeyFileService keyFileService, IKeyCapture capture)
 {
     unsafe
     {
         var sk = keyFileService.GetSecretKeyPointer(capture);
         try
         {
             var ed25519PublicKey = new byte[PublicKeyBytes];
             SigningPublicKeyFromSigningKey(sk, ed25519PublicKey);
             return(ed25519PublicKey);
         }
         finally
         {
             NativeMethods.sodium_free(sk);
         }
     }
 }
示例#7
0
        public static string AddPublicKeyIdentifier(this IServiceCollection services, string eggPath, int port, IKeyCapture capture, IWebHostEnvironment env, IConfiguration config)
        {
            var keyFileService = new ServerKeyFileService();

            services.AddSingleton <IKeyFileService>(keyFileService);

            capture ??= new ServerConsoleKeyCapture();
            services.AddSingleton(capture);

            if (capture is IPersistedKeyCapture persisted)
            {
                services.AddSingleton(persisted);
            }

            var publicKey = Crypto.SigningPublicKeyFromSigningKey(keyFileService, capture);

            capture.Reset();

            var appString = $"{env.ApplicationName}:" +
                            $"{env.EnvironmentName}:" +
                            $"{port}";

            var serverId        = Crypto.Fingerprint(publicKey, appString);
            var publicKeyString = Crypto.ToHexString(publicKey);

            services.Configure <WebServerOptions>(config.GetSection("WebServer"));
            services.Configure <WebServerOptions>(o =>
            {
                o.PublicKey       = publicKey;
                o.ServerPort      = port;
                o.PublicKeyString = publicKeyString;
                o.ServerId        = serverId;
                o.EggPath         = eggPath;
            });

            return(publicKeyString);
        }
示例#8
0
        public static IServiceCollection AddWebServer(this IServiceCollection services, string eggPath, int port, IKeyCapture capture, IWebHostEnvironment env, IConfiguration config)
        {
            var publicKeyString = AddPublicKeyIdentifier(services, eggPath, port, capture, env, config);

            services.AddAntiforgery(o =>
            {
                o.Cookie.Name         = $"_{publicKeyString}_xsrf";
                o.Cookie.HttpOnly     = true;
                o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                o.HeaderName          = "X-XSRF-Token";
            });

            return(services);
        }
示例#9
0
        internal static IHostBuilder CreateHostBuilder(int?port, string eggPath, IKeyCapture capture, params string[] args)
        {
            var builder = Host.CreateDefaultBuilder(args);

            builder.ConfigureWebHostDefaults(webBuilder =>
            {
                Activity.DefaultIdFormat = ActivityIdFormat.W3C;

                webBuilder.ConfigureAppConfiguration((context, configBuilder) =>
                {
                    configBuilder.AddEnvironmentVariables();
                });

                webBuilder.ConfigureKestrel((context, options) =>
                {
                    var x509 = CertificateBuilder.GetOrCreateSelfSignedCert(Console.Out);

                    options.AddServerHeader = false;
                    options.ListenLocalhost(port.GetValueOrDefault(Constants.DefaultPort), x =>
                    {
                        x.Protocols = HttpProtocols.Http1AndHttp2;
                        x.UseHttps(a =>
                        {
                            a.SslProtocols = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
                                ? SslProtocols.Tls12
                                : SslProtocols.Tls13;
                            a.ServerCertificate = x509;
                        });
                    });
                });

                webBuilder.ConfigureLogging((context, loggingBuilder) =>
                {
                    loggingBuilder.ClearProviders();

                    loggingBuilder.AddConfiguration(context.Configuration.GetSection("Logging"));
                    loggingBuilder.AddDebug();
                    loggingBuilder.AddEventSourceLogger();
                    loggingBuilder.AddLogging(() =>
                    {
                        var serviceProvider = loggingBuilder.Services.BuildServiceProvider();
                        return(Path.Combine(Constants.DefaultRootPath, $"{serviceProvider.GetRequiredService<IOptions<WebServerOptions>>().Value.PublicKeyString}_logs.egg"));
                    });

                    if (context.HostingEnvironment.IsDevelopment())
                    {
                        loggingBuilder.AddColorConsole(); // unnecessary overhead
                    }
                });

                webBuilder.ConfigureServices((context, services) =>
                {
                    services.AddWebServer(eggPath, port.GetValueOrDefault(Constants.DefaultPort), capture, context.HostingEnvironment, context.Configuration);
                });

                webBuilder.UseStartup <Startup>();

                var contentRoot = Directory.GetCurrentDirectory();
                var webRoot     = Path.Combine(contentRoot, "wwwroot");
                if (!File.Exists(Path.Combine(webRoot, "css", "signin.css")))
                {
                    webRoot = Path.GetFullPath(Path.Combine(contentRoot, "..", "egregore.Client", "wwwroot"));
                }
                webBuilder.UseContentRoot(contentRoot);
                webBuilder.UseWebRoot(webRoot);
            });

            return(builder);
        }
示例#10
0
        private static void RunAsServer(int?port, Queue <string> arguments, IKeyCapture capture, bool interactive)
        {
            var keyPath = arguments.EndOfSubArguments() ? Constants.DefaultKeyFilePath : arguments.Dequeue();

            if (!KeyFileManager.TryResolveKeyPath(keyPath, out keyFilePath, false, true))
            {
                return;
            }

            var shouldCreateKeyFile = !File.Exists(keyFilePath) || new FileInfo(keyFilePath).Length == 0;

            if (shouldCreateKeyFile)
            {
                File.WriteAllBytes(keyFilePath, new byte[KeyFileManager.KeyFileBytes]);
            }

            Console.Out.WriteInfoLine($"Key file path resolved to '{keyFilePath}'");

            if (shouldCreateKeyFile &&
                !KeyFileManager.Create(keyFilePath, false, true, capture ?? Constants.ConsoleKeyCapture))
            {
                Console.Error.WriteErrorLine("Cannot start server without a key file");
                return;
            }

            if (_exclusiveLock)
            {
                try
                {
                    keyFileStream = new FileStream(keyFilePath, FileMode.Open, FileAccess.Read, FileShare.None);
                }
                catch (IOException)
                {
                    Console.Error.WriteErrorLine("Could not obtain exclusive lock on key file");
                    return;
                }
            }
            else
            {
                try
                {
                    keyFileStream = new FileStream(keyFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                }
                catch (IOException)
                {
                    Console.Error.WriteErrorLine("Could not open key file");
                    return;
                }
            }

            var eggPath = Environment.GetEnvironmentVariable(Constants.EnvVars.EggFilePath);

            if (string.IsNullOrWhiteSpace(eggPath))
            {
                eggPath = Constants.DefaultEggPath;
            }

            Console.Out.WriteInfoLine($"Egg file path resolved to '{eggPath}'");

            if (!File.Exists(eggPath) && !EggFileManager.Create(eggPath))
            {
                Console.Error.WriteWarningLine("Server started without an egg");
            }

            capture?.Reset();

            if (!interactive)
            {
                LaunchBrowserUrl($"https://localhost:{port.GetValueOrDefault(Constants.DefaultPort)}");
            }

            PrintMasthead();
            var builder = CreateHostBuilder(port, eggPath, capture, arguments.ToArray());
            var host    = builder.Build();

            host.Run();
        }
示例#11
0
        public static bool Create(string pathArgument, bool warnIfExists, bool allowMissing, IKeyCapture capture)
        {
            if (!TryResolveKeyPath(pathArgument, out var keyFilePath, warnIfExists, allowMissing))
            {
                return(false);
            }
            var keyFileStream =
                new FileStream(keyFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);

            if (!TryGenerateKeyFile(keyFileStream, Console.Out, Console.Error, capture))
            {
                return(false);
            }
            keyFileStream.Dispose();
            Console.Out.WriteLine(Strings.KeyFileSuccess);
            return(true);
        }
示例#12
0
 public static unsafe bool TryLoadKeyFile(FileStream keyFileStream, TextWriter @out, TextWriter error, out byte *secretKey, IKeyCapture capture)
 {
     if (capture == default)
     {
         throw new InvalidOperationException(Strings.InvalidKeyCapture);
     }
     secretKey = default;
     return(TryCapturePassword(Strings.LoadKeyInstructions, capture, @out, error, out var password, out var passwordLength) &&
            TryLoadKeyFile(keyFileStream, @out, error, ref secretKey, password, passwordLength, false));
 }
示例#13
0
        public static unsafe bool TryGenerateKeyFile(FileStream keyFileStream, TextWriter @out, TextWriter error,
                                                     IKeyCapture keyCapture)
        {
            keyCapture ??= Constants.ConsoleKeyCapture;

            if (!TryCapturePassword(Strings.GenerateKeyInstructions, keyCapture, @out, error, out var password,
                                    out var passwordLength))
            {
                return(false);
            }

            var sk = (byte *)sodium_malloc(Crypto.SecretKeyBytes);

            try
            {
                var pk = (byte *)sodium_malloc(Crypto.PublicKeyBytes);

                try
                {
                    // Create a new signing key pair:
                    if (crypto_sign_keypair(pk, sk) != 0)
                    {
                        throw new InvalidOperationException(nameof(crypto_sign_keypair));
                    }
                }
                finally
                {
                    sodium_free(pk);
                }

                // Create key number to mix with the checksum:
                var keyNumber = (byte *)sodium_malloc(KeyNumBytes);

                // Checksum = Blake2B(SigAlg || KeyNumber || SecretKey):
                var offset        = 0;
                var checksumInput = (byte *)sodium_malloc(ChecksumInputBytes);
                var checksum      = (byte *)sodium_malloc(ChecksumBytes);
                try
                {
                    randombytes_buf(keyNumber, KeyNumBytes);

                    fixed(byte *src = SigAlg)
                    {
                        for (var i = 0; i < SigAlgBytes; i++)
                        {
                            checksumInput[offset++] = src[i];
                        }
                    }

                    for (var i = 0; i < KeyNumBytes; i++)
                    {
                        checksumInput[offset++] = keyNumber[i];
                    }
                    for (var i = 0; i < Crypto.SecretKeyBytes; i++)
                    {
                        checksumInput[offset++] = sk[i];
                    }

                    if (crypto_generichash(checksum, ChecksumBytes, checksumInput, ChecksumInputBytes,
                                           null, 0) != 0)
                    {
                        throw new InvalidOperationException(nameof(crypto_generichash));
                    }
                }
                finally
                {
                    sodium_free(checksumInput);
                }

                //
                // Prepare cipher block for encryption: (KeyNum || SecretKey || Checksum)
                var cipher = (byte *)sodium_malloc(CipherBytes);
                try
                {
                    offset = 0;
                    for (var i = 0; i < KeyNumBytes; i++)
                    {
                        cipher[offset++] = keyNumber[i];
                    }
                    for (var i = 0; i < Crypto.SecretKeyBytes; i++)
                    {
                        cipher[offset++] = sk[i];
                    }
                    for (var i = 0; i < ChecksumBytes; i++)
                    {
                        cipher[offset++] = checksum[i];
                    }
                }
                finally
                {
                    sodium_free(sk);
                }

                @out.Write(Strings.EncryptionInProgressMessage);

                //
                // Encrypt the secret key by mixing it with another key derived from the password:
                // SecretKey ^ crypto_pwhash_scryptsalsa208sha256(password || salt || opsLimit || memLimit)):
                const ulong opsLimit = KdfOpsLimit;
                const int   memLimit = KdfMemLimit;
                var         kdfSalt  = (byte *)sodium_malloc(KdfSaltBytes);
                var         stream   = (byte *)sodium_malloc(CipherBytes);
                byte *      xor;
                try
                {
                    randombytes_buf(kdfSalt, KdfSaltBytes);

                    if (crypto_pwhash_scryptsalsa208sha256(stream, CipherBytes, password,
                                                           (ulong)passwordLength, kdfSalt, opsLimit, memLimit) != 0)
                    {
                        throw new InvalidOperationException(nameof(crypto_pwhash_scryptsalsa208sha256));
                    }

                    xor = Xor(cipher, stream, CipherBytes);
                }
                finally
                {
                    sodium_free(password);
                    sodium_free(cipher);
                    sodium_free(stream);
                }

                @out.WriteLine(Strings.EncryptionCompleteMessage);

                //
                // Write key file: (SigAlg || KdfAlg || ChkAlg || KeyNum || KdfSalt || OpsLimit || MemLimit || Cipher || Checksum)
                var file = (byte *)sodium_malloc(KeyFileBytes);
                try
                {
                    offset = 0;

                    fixed(byte *src = SigAlg)
                    {
                        for (var i = 0; i < SigAlgBytes; i++)
                        {
                            file[offset++] = src[i];
                        }
                    }

                    fixed(byte *src = KdfAlg)
                    {
                        for (var i = 0; i < KdfAlgBytes; i++)
                        {
                            file[offset++] = src[i];
                        }
                    }

                    fixed(byte *src = ChkAlg)
                    {
                        for (var i = 0; i < ChkAlgBytes; i++)
                        {
                            file[offset++] = src[i];
                        }
                    }

                    for (var i = 0; i < KeyNumBytes; i++)
                    {
                        file[offset++] = keyNumber[i];
                    }

                    for (var i = 0; i < KdfSaltBytes; i++)
                    {
                        file[offset++] = kdfSalt[i];
                    }

                    var ops = BitConverter.GetBytes(opsLimit);
                    foreach (var b in ops)
                    {
                        file[offset++] = b;
                    }

                    var mem = BitConverter.GetBytes(memLimit);
                    foreach (var b in mem)
                    {
                        file[offset++] = b;
                    }

                    for (var i = 0; i < CipherBytes; i++)
                    {
                        file[offset++] = xor[i];
                    }

                    for (var i = 0; i < ChecksumBytes; i++)
                    {
                        file[offset++] = checksum[i];
                    }

                    if (offset != KeyFileBytes)
                    {
                        error.WriteErrorLine(Strings.InvalidKeyFileBuffer);
                        sodium_free(file);
                        return(false);
                    }
                }
                finally
                {
                    sodium_free(keyNumber);
                    sodium_free(kdfSalt);
                    sodium_free(checksum);
                    sodium_free(xor);
                }

                try
                {
                    using var mmf = MemoryMappedFile.CreateFromFile(keyFileStream, null, KeyFileBytes,
                                                                    MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, true);
                    using var uvs = mmf.CreateViewStream(0, KeyFileBytes, MemoryMappedFileAccess.ReadWrite);
                    for (var i = 0; i < (int)KeyFileBytes; i++)
                    {
                        uvs.WriteByte(file[i]);
                    }
                    uvs.Flush();
                }
                finally
                {
                    sodium_free(file);
                }

                return(true);
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.ToString());
                error.WriteErrorLine(Strings.KeyFileGenerateFailure);
                return(false);
            }
        }
示例#14
0
        public static unsafe bool TryCapturePassword(string instructions, IKeyCapture @in, TextWriter @out, TextWriter error, out byte *password, out int passwordLength)
        {
            const int passwordMaxBytes = 1024;

            password = (byte *)sodium_malloc(passwordMaxBytes);
            var passwordConfirm = sodium_malloc(passwordMaxBytes);

            passwordLength = 0;

            try
            {
                @out.WriteLine(instructions);

                var initPwdLength = 0;
                using var initPwd = new UnmanagedMemoryStream(password, 0, passwordMaxBytes, FileAccess.Write);
                @out.Write(Strings.PasswordPrompt);
                ConsoleKeyInfo key;

                Console.ForegroundColor = ConsoleColor.Cyan;
                do
                {
                    key = @in.ReadKey();
                    if (key.Key == ConsoleKey.Enter)
                    {
                        break;
                    }
                    if (key.Key == ConsoleKey.Backspace)
                    {
                        if (initPwdLength > 0)
                        {
                            initPwd.Position--;
                            initPwd.WriteByte(0);
                            initPwd.Position--;
                            initPwdLength--;
                            @out.Write(BackspaceSpaceBackspace);
                        }

                        continue;
                    }

                    initPwd.WriteByte((byte)key.KeyChar);
                    key = default;
                    @in.OnKeyRead(@out);
                    initPwdLength++;
                } while (key.Key != ConsoleKey.Enter && initPwdLength < passwordMaxBytes);

                Console.ResetColor();

                var confirmPwdLength = 0;
                using var confirmPwd =
                          new UnmanagedMemoryStream((byte *)passwordConfirm, 0, passwordMaxBytes, FileAccess.Write);

                @out.WriteLine();
                @out.Write(Strings.ConfirmPasswordPrompt);

                Console.ForegroundColor = ConsoleColor.Cyan;
                do
                {
                    key = @in.ReadKey();
                    if (key.Key == ConsoleKey.Enter)
                    {
                        break;
                    }
                    if (key.Key == ConsoleKey.Backspace)
                    {
                        if (confirmPwdLength > 0)
                        {
                            confirmPwd.Position--;
                            confirmPwd.WriteByte(0);
                            confirmPwd.Position--;
                            confirmPwdLength--;
                            @out.Write(BackspaceSpaceBackspace);
                        }

                        continue;
                    }

                    confirmPwd.WriteByte((byte)key.KeyChar);
                    key = default;
                    @in.OnKeyRead(@out);
                    confirmPwdLength++;
                } while (key.Key != ConsoleKey.Enter && confirmPwdLength < passwordMaxBytes);

                Console.ResetColor();

                @out.WriteLine();

                passwordLength = Math.Max(initPwdLength, confirmPwdLength);
                if (passwordLength == 0)
                {
                    passwordLength = -1;
                    error.WriteErrorLine(Strings.InvalidPasswordLength);
                    sodium_free(password);
                    password = default;
                    return(false);
                }

                if (initPwdLength != confirmPwdLength)
                {
                    passwordLength = -1;
                    error.WriteErrorLine(Strings.PasswordMismatch);
                    sodium_free(password);
                    password = default;
                    return(false);
                }

                if (sodium_memcmp(password, passwordConfirm, passwordLength) != 0)
                {
                    error.WriteErrorLine(Strings.PasswordMismatch);
                    sodium_free(password);
                    password = default;
                    return(false);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.ToString());
                error.WriteErrorLine(Strings.PasswordFailure);
                sodium_free(password);
                password = default;
                return(false);
            }
            finally
            {
                sodium_free(passwordConfirm);
                Console.ResetColor();
            }

            return(true);
        }
示例#15
0
 public unsafe byte *GetSecretKeyPointer(IKeyCapture capture, [CallerMemberName] string callerMemberName = null)
 {
     return(Crypto.LoadSecretKeyPointerFromFileStream(GetKeyFilePath(), GetKeyFileStream(), capture,
                                                      callerMemberName));
 }