private (string passwordToken, PasswordSalts passwordSalts, PasswordBasedBackup passwordBasedBackup) GeneratePasswordToken(string password, byte[] seed) { var passwordSalt = Scrypt.GenerateSalt(); var passwordHash = Scrypt.Hash(password, passwordSalt); //var passwordHkdfKey = crypto.hkdf.importHkdfKeyFromString(passwordHash) var passwordTokenSalt = Hkdf.GenerateSalt(); var passwordToken = Hkdf.GetPasswordToken(/*passwordHkdfKey*/ passwordHash, passwordTokenSalt); var passwordBasedEncryptionKeySalt = Hkdf.GenerateSalt(); var passwordBasedEncryptionKey = AesGcmUtils.GetPasswordBasedEncryptionKey(passwordHash, passwordBasedEncryptionKeySalt); var passwordEncryptedSeed = AesGcmUtils.Encrypt(passwordBasedEncryptionKey, seed); var passwordSalts = new PasswordSalts { PasswordSalt = Convert.ToBase64String(passwordSalt), PasswordTokenSalt = Convert.ToBase64String(passwordTokenSalt), }; var passwordBasedBackup = new PasswordBasedBackup { PasswordBasedEncryptionKeySalt = Convert.ToBase64String(passwordBasedEncryptionKeySalt), PasswordEncryptedSeed = Convert.ToBase64String(passwordEncryptedSeed), }; return(passwordToken, passwordSalts, passwordBasedBackup); }
/// <summary> /// Returns encryption instructions to encrypt content with AES/GCM/NoPadding algorithm /// Creates encryption key used for AES/GCM/NoPadding and encrypt it with AES/GCM /// Encrypted key follows nonce(12 bytes) + key cipher text(16 or 32 bytes) + tag(16 bytes) format /// Tag is appended by the AES/GCM cipher with encryption process /// </summary> /// <param name="materials"></param> /// <returns></returns> private static EncryptionInstructions EncryptEnvelopeKeyUsingSymmetricKeyV2(EncryptionMaterialsV2 materials) { var aes = materials.SymmetricProvider as Aes; if (aes == null) { throw new NotSupportedException("AES is the only supported algorithm with this method."); } switch (materials.SymmetricProviderType) { case SymmetricAlgorithmType.AesGcm: { var aesObject = Aes.Create(); var nonce = aesObject.IV.Take(DefaultNonceSize).ToArray(); var associatedText = Encoding.UTF8.GetBytes(XAmzAesGcmCekAlgValue); var cipher = AesGcmUtils.CreateCipher(true, materials.SymmetricProvider.Key, DefaultTagBitsLength, nonce, associatedText); var envelopeKey = cipher.DoFinal(aesObject.Key); var encryptedEnvelopeKey = nonce.Concat(envelopeKey).ToArray(); var instructions = new EncryptionInstructions(materials.MaterialsDescription, aesObject.Key, encryptedEnvelopeKey, nonce, XAmzWrapAlgAesGcmValue, XAmzAesGcmCekAlgValue); return(instructions); } default: { throw new NotSupportedException($"{materials.SymmetricProviderType} isn't supported with SymmetricProvider"); } } }
private async Task SetKeys(string seedString) { if (Keys.Init) { return; } if (string.IsNullOrEmpty(seedString)) { throw new WebSocketError("Missing seed", Session.Username); } if (Keys.Salts == null) { throw new WebSocketError("Missing salts", Session.Username); } if (string.IsNullOrEmpty(_seedString)) { _seedString = seedString; } var seed = Convert.FromBase64String(seedString); Keys.EncryptionKey = AesGcmUtils.ImportKeyFromMaster(seed, Convert.FromBase64String(Keys.Salts.EncryptionKeySalt)); Keys.DhPrivateKey = DiffieHellmanUtils.ImportKeyFromMaster(seed, Convert.FromBase64String(Keys.Salts.DhKeySalt)); Keys.HmacKey = HmacUtils.ImportKeyFromMaster(seed, Convert.FromBase64String(Keys.Salts.HmacKeySalt)); await ValidateKey(); Keys.Init = true; _resolveConnection?.Invoke(); _connectionResolved = true; }
private static byte[] DecryptEnvelopeKeyUsingSymmetricKeyV2(SymmetricAlgorithm symmetricAlgorithm, byte[] encryptedEnvelopeKey) { var nonce = encryptedEnvelopeKey.Take(DefaultNonceSize).ToArray(); var encryptedKey = encryptedEnvelopeKey.Skip(nonce.Length).ToArray(); var associatedText = Encoding.UTF8.GetBytes(XAmzAesGcmCekAlgValue); var cipher = AesGcmUtils.CreateCipher(false, symmetricAlgorithm.Key, DefaultTagBitsLength, nonce, associatedText); var envelopeKey = cipher.DoFinal(encryptedKey); return(envelopeKey); }
private async Task ValidateKey() { if (_dh == null) { _ = _logger.Log("FETCH SERVER PUBLIC KEY"); _dh = new DiffieHellmanUtils(await GetServerPublicKey()); } var sharedKey = _dh.GetSharedKeyWithServer(Keys.DhPrivateKey); var validationMessage = Convert.ToBase64String(AesGcmUtils.Decrypt(sharedKey, _encryptedValidationMessage)); await Request("ValidateKey", new RequestParams { ValidationMessage = validationMessage }); }
public void HashesShouldMatch() { // ARRANGE const string correctHash = ""; byte[] passwordKeyHash = null; var passwordBasedBackup = new SignInPasswordBasedBackup { PasswordEncryptedSeed = "", PasswordBasedEncryptionKeySalt = "", }; // ACT var resultHash = AesGcmUtils.GetSeedStringFromPasswordBasedBackup(passwordKeyHash, passwordBasedBackup); // ASSERT Assert.Equal(correctHash, resultHash); }
public async Task <SignInResponse> SignIn(SignInRequest signInRequest) { try { ValidateSignUpOrSignInInput(signInRequest.Username, signInRequest.Password); ValidateRememberMeInput(signInRequest.RememberMe); var lowerCaseUsername = signInRequest.Username.ToLower(); var passwordSalts = await GetPasswordSaltsOverRestEndpoint(lowerCaseUsername); // should it be awaited? var rebuildPasswordTokenResponse = RebuildPasswordToken(signInRequest.Password, passwordSalts); var signInDto = await ActualSignIn(lowerCaseUsername, rebuildPasswordTokenResponse.PasswordToken); var savedSeedString = _localData.GetSeedString(_config.AppId, lowerCaseUsername); var seedStringFromBackup = string.Empty; // TODO: usedTempPassword if (string.IsNullOrEmpty(savedSeedString)) { seedStringFromBackup = AesGcmUtils.GetSeedStringFromPasswordBasedBackup(rebuildPasswordTokenResponse.PasswordHkdfKey, signInDto.PasswordBasedBackup); _localData.SaveSeedString(signInRequest.RememberMe, _config.AppId, lowerCaseUsername, seedStringFromBackup); } var seedString = !string.IsNullOrEmpty(savedSeedString) ? savedSeedString : seedStringFromBackup; _localData.SignInSession(signInRequest.RememberMe, lowerCaseUsername, signInDto.Session.SessionId, signInDto.Session.CreationDate); // TODO await ConnectWebSocket(signInDto.Session, seedString, signInRequest.RememberMe); // TODO usedTempPassword return(new SignInResponse { Username = lowerCaseUsername, UserId = signInDto.UserId, Email = signInDto.Email, Profile = signInDto.Profile, InternalProfile = signInDto.InternalProfile, }); } catch (Exception ex) { if (!(ex is IError e)) { throw new UnknownServiceUnavailable(ex); } switch (e.Name) { case "ParamsMustBeObject": case "UsernameOrPasswordMismatch": case "UsernameCannotBeBlank": case "UsernameTooLong": case "UsernameMustBeString": case "PasswordCannotBeBlank": case "PasswordTooShort": case "PasswordTooLong": case "PasswordMustBeString": case "PasswordAttemptLimitExceeded": case "RememberMeValueNotValid": case "AppIdNotSet": case "AppIdNotValid": case "UserAlreadySignedIn": case "ServiceUnavailable": throw; default: throw new UnknownServiceUnavailable(ex); } } }
private async Task OnMessageReceived(object sender, MessageReceivedEventArgs e, WebSocket webSocket, string seedString, SignInSession session) { //_logger.Log(sender.ToString()); _ = _logger.Log($"RECEIVED - {e.Message}"); try { var msg = JObject.Parse(e.Message); var route = msg["route"]?.ToString().ToLower() ?? ""; switch (route) { case "connection": // TODO: pass parameters Init(null, null, session, seedString, null, null); Instance4Net = webSocket; //this.heartbeat() _connected = true; var connectionMessage = JsonConvert.DeserializeObject <ConnectionMessage>(e.Message); Keys.Salts = connectionMessage.KeySalts; _encryptedValidationMessage = connectionMessage.EncryptedValidationMessage.Data; await SetKeys(seedString); break; case "ping": //this.heartbeat() _ = _logger.Log("SENT - PONG"); const string action = "Pong"; Instance4Net.Send(JsonConvert.SerializeObject(new { action })); break; case "applytransactions": // TODO: in progress var dbId = msg["dbId"].ToString(); var dbNameHash = msg["dbNameHash"].ToString(); // || State.dbIdToHash[dbId] if (!State.Databases.TryGetValue(dbNameHash, out var database)) { throw new Exception("Missing database"); } // queue guarantees transactions will be applied in the order they are received from the server if (database.ApplyTransactionsQueue.Count == 0) { // take a spot in the queue and proceed applying so the next caller knows queue is not empty database.ApplyTransactionsQueue.Enqueue(null); } else { // wait until prior batch in queue finishes applying successfully var promise = new TaskCompletionSource <int>(); void Resolve() { var localPromise = promise; localPromise.SetResult(0); } database.ApplyTransactionsQueue.Enqueue(Resolve); await promise.Task; } var openingDatabase = msg["dbNameHash"] != null && msg["dbKey"] != null; if (openingDatabase) { var dbKeyString = AesGcmUtils.DecryptString(Keys.EncryptionKey, msg["dbKey"].ToString()); database.DbKeyString = dbKeyString; // TODO //database.dbKey = await AesGcmUtils.GetKeyFromKeyString(dbKeyString); } //if (!database.dbKey) throw new Error('Missing db key') if (msg["bundle"] != null) { /*const bundleSeqNo = message.bundleSeqNo * const base64Bundle = message.bundle * const compressedString = await crypto.aesGcm.decryptString(database.dbKey, base64Bundle) * const plaintextString = LZString.decompress(compressedString) * const bundle = JSON.parse(plaintextString) * * database.applyBundle(bundle, bundleSeqNo)*/ } /*const newTransactions = message.transactionLog * await database.applyTransactions(newTransactions)*/ if (!database.Init) { State.DbIdToHash[dbId] = dbNameHash; database.DbId = dbId; database.Init = true; _ = _logger.Log("DB - RECEIVED MESSAGE"); database.ReceivedMessage(); } if (msg["buildBundle"] != null) { BuildBundle(database); } // start applying next batch in queue when this one is finished applying successfully database.ApplyTransactionsQueue.Dequeue(); if (database.ApplyTransactionsQueue.Count > 0) { var startApplyingNextBatchInQueue = database.ApplyTransactionsQueue.Peek(); startApplyingNextBatchInQueue(); } break; case "signout": case "updateuser": case "deleteuser": case "createdatabase": case "getdatabase": case "opendatabase": case "insert": case "update": case "delete": case "batchtransaction": case "bundle": case "validatekey": case "getpasswordsalts": var requestId = msg["requestId"]?.ToString().ToLower() ?? ""; if (string.IsNullOrEmpty(requestId)) { // TODO: log warning Console.WriteLine("Missing request id"); return; } //var request = this.requests[requestId]; if (!_pendingRequests.TryGetValue(requestId, out var request)) { // TODO: log warning Console.WriteLine($"Request {requestId} no longer exists!"); return; } else if (request.Resolve == null || request.Reject == null) { return; } var response = msg["response"]?.ToString().ToLower() ?? ""; var status = msg["response"]["status"]?.ToString().ToLower() ?? ""; var statusCode = HttpStatusCode.BadRequest; var successfulResponse = !string.IsNullOrEmpty(response) && !string.IsNullOrEmpty(status) && Enum.TryParse(status, out statusCode) && statusCode == HttpStatusCode.OK; if (successfulResponse) { request.Resolve(requestId); } else { request.Reject(requestId, route, statusCode, response); } break; default: Console.WriteLine($"Received unknown message from backend: {msg}"); break; } } catch (Exception exception) { _ = _logger.Log($"ERROR - {exception.Message}"); } }