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)); } }
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); }
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); }
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(); } }
public static unsafe byte *SigningKeyToEncryptionKey(IKeyFileService keyFileService, IKeyCapture capture) { var sk = keyFileService.GetSecretKeyPointer(capture); try { return(SigningKeyToEncryptionKey(sk)); } finally { NativeMethods.sodium_free(sk); } }
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); } } }
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); }
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); }
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); }
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(); }
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); }
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)); }
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); } }
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); }
public unsafe byte *GetSecretKeyPointer(IKeyCapture capture, [CallerMemberName] string callerMemberName = null) { return(Crypto.LoadSecretKeyPointerFromFileStream(GetKeyFilePath(), GetKeyFileStream(), capture, callerMemberName)); }