示例#1
0
        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);
        }
示例#2
0
        /// <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");
            }
            }
        }
示例#3
0
        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;
        }
示例#4
0
        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);
        }
示例#5
0
        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);
        }
示例#7
0
        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);
                }
            }
        }
示例#8
0
        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}");
            }
        }