public async Task EnvelopeAsync(byte[] unencryptedBytes, int symmetricKeySizeBits, int symmetricInitializationVectorSizeBits, Stream encryptedOutputStream, CancellationToken cancellationToken) { try { AmazonS3Credentials kmsCredentials = await GetCredentialsAsync(); if (symmetricKeySizeBits != 256) { throw new ArgumentOutOfRangeException(nameof(symmetricKeySizeBits), "Invalid value " + symmetricKeySizeBits + ". Only 256-bit keys are supported."); } if (symmetricInitializationVectorSizeBits != 128) { throw new ArgumentOutOfRangeException(nameof(symmetricInitializationVectorSizeBits), "Invalid value " + symmetricInitializationVectorSizeBits + ". Only 128-bit initialization vectors are supported."); } AmazonKeyManagementServiceClient kmsClient = new AmazonKeyManagementServiceClient(kmsCredentials.AccessKeyId, kmsCredentials.SecretAccessKey, kmsCredentials.SessionToken, kmsCredentials.RegionEndpoint); // generate a symmetric data key GenerateDataKeyResponse dataKeyResponse = await kmsClient.GenerateDataKeyAsync(new GenerateDataKeyRequest { KeyId = kmsCredentials.CustomerMasterKey, KeySpec = DataKeySpec.AES_256 }, cancellationToken); // write encrypted payload // write encrypted data key length and bytes byte[] encryptedDataKeyBytes = dataKeyResponse.CiphertextBlob.ToArray(); byte[] encryptedDataKeyBytesLength = BitConverter.GetBytes(encryptedDataKeyBytes.Length); encryptedOutputStream.Write(encryptedDataKeyBytesLength, 0, encryptedDataKeyBytesLength.Length); encryptedOutputStream.Write(encryptedDataKeyBytes, 0, encryptedDataKeyBytes.Length); // write encrypted random initialization vector length and bytes Random random = new Random(); byte[] initializationVectorBytes = new byte[16]; random.NextBytes(initializationVectorBytes); byte[] encryptedInitializationVectorBytes = (await kmsClient.EncryptAsync(new EncryptRequest { KeyId = kmsCredentials.CustomerMasterKey, Plaintext = new MemoryStream(initializationVectorBytes) }, cancellationToken)).CiphertextBlob.ToArray(); byte[] encryptedInitializationVectorBytesLength = BitConverter.GetBytes(encryptedInitializationVectorBytes.Length); encryptedOutputStream.Write(encryptedInitializationVectorBytesLength, 0, encryptedInitializationVectorBytesLength.Length); encryptedOutputStream.Write(encryptedInitializationVectorBytes, 0, encryptedInitializationVectorBytes.Length); // write symmetrically encrypted bytes byte[] dataKeyBytes = dataKeyResponse.Plaintext.ToArray(); SymmetricEncryption symmetricEncryption = new SymmetricEncryption(dataKeyBytes, initializationVectorBytes); byte[] encryptedBytes = symmetricEncryption.Encrypt(unencryptedBytes); encryptedOutputStream.Write(encryptedBytes, 0, encryptedBytes.Length); } // the following catch statements attempt to filter out expected exceptions (e.g., due to naturally lacking internet connections) // from those that are fixable errors within the app. expected exceptions are logged but not reported to the app center, whereas // all others are reported to the app center. our approach is to whitelist excepted exceptions as we see them appear in the app // center, in order to ensure that we don't miss any fixable errors. catch (HttpRequestException ex) { bool logged = false; if (ex.InnerException is WebException) { WebException webException = ex.InnerException as WebException; if (IGNORED_WEB_EXCEPTION_STATUSES.Contains(webException.Status)) { LogKmsEnvelopeException(ex); logged = true; } } if (!logged) { ReportKmsEnvelopeException(ex); } throw ex; } catch (WebException webException) { if (IGNORED_WEB_EXCEPTION_STATUSES.Contains(webException.Status)) { LogKmsEnvelopeException(webException); } else { ReportKmsEnvelopeException(webException); } throw webException; } catch (OperationCanceledException ex) { LogKmsEnvelopeException(ex); throw ex; } catch (Exception ex) { ReportKmsEnvelopeException(ex); throw ex; } }
public Task <AmazonS3Credentials> GetCredentialsAsync() { lock (_getCredentialsTaskLocker) { // if the get credentials task is in a state from which we wouldn't expect to return a presently // valid set of credentials, then start a new task to check/refresh the credentials. if (_getCredentialsTask == null || _getCredentialsTask.Status == TaskStatus.Canceled || _getCredentialsTask.Status == TaskStatus.Faulted || _getCredentialsTask.Status == TaskStatus.RanToCompletion) { _getCredentialsTask = Task.Run(async() => { // if the credentials we currently hold will be valid for a while, then simply return them. if (AmazonS3Credentials?.WillBeValidFor(TimeSpan.FromHours(1)) ?? false) { return(AmazonS3Credentials); } else { AmazonS3Credentials = null; } // we should always have an account if (Account == null) { Exception noAccountException = new Exception("Tried to get credentials without an account."); SensusException.Report(noAccountException); throw noAccountException; } string credentialsJSON = await new Uri(string.Format(_getCredentialsURL, Account.ParticipantId, Account.Password, SensusServiceHelper.Get().DeviceId)).DownloadStringAsync(); // deserialize credentials try { AmazonS3Credentials = credentialsJSON.DeserializeJson <AmazonS3Credentials>(); } catch (Exception ex) { SensusException.Report("Exception while deserializing AWS S3 credentials.", ex); throw ex; } // check properties. trim while we're at it. if (string.IsNullOrWhiteSpace(AmazonS3Credentials.AccessKeyId = AmazonS3Credentials.AccessKeyId?.Trim())) { SensusException.Report("Empty " + nameof(AmazonS3Credentials.AccessKeyId) + " returned by authentication service for participant " + (Account.ParticipantId ?? "[null].")); } if (string.IsNullOrWhiteSpace(AmazonS3Credentials.CustomerMasterKey = AmazonS3Credentials.CustomerMasterKey?.Trim())) { SensusException.Report("Empty " + nameof(AmazonS3Credentials.CustomerMasterKey) + " returned by authentication service for participant " + (Account.ParticipantId ?? "[null].")); } if (string.IsNullOrWhiteSpace(AmazonS3Credentials.ExpirationUnixTimeMilliseconds = AmazonS3Credentials.ExpirationUnixTimeMilliseconds?.Trim())) { SensusException.Report("Empty " + nameof(AmazonS3Credentials.ExpirationUnixTimeMilliseconds) + " returned by authentication service for participant " + (Account.ParticipantId ?? "[null].")); } if (string.IsNullOrWhiteSpace(AmazonS3Credentials.ProtocolId = AmazonS3Credentials.ProtocolId?.Trim())) { SensusException.Report("Empty " + nameof(AmazonS3Credentials.ProtocolId) + " returned by authentication service for participant " + (Account.ParticipantId ?? "[null].")); } if (string.IsNullOrWhiteSpace(AmazonS3Credentials.ProtocolURL = AmazonS3Credentials.ProtocolURL?.Trim())) { SensusException.Report("Empty " + nameof(AmazonS3Credentials.ProtocolURL) + " returned by authentication service for participant " + (Account.ParticipantId ?? "[null].")); } if (string.IsNullOrWhiteSpace(AmazonS3Credentials.Region = AmazonS3Credentials.Region?.Trim())) { SensusException.Report("Empty " + nameof(AmazonS3Credentials.Region) + " returned by authentication service for participant " + (Account.ParticipantId ?? "[null].")); } if (string.IsNullOrWhiteSpace(AmazonS3Credentials.SecretAccessKey = AmazonS3Credentials.SecretAccessKey?.Trim())) { SensusException.Report("Empty " + nameof(AmazonS3Credentials.SecretAccessKey) + " returned by authentication service for participant " + (Account.ParticipantId ?? "[null].")); } if (string.IsNullOrWhiteSpace(AmazonS3Credentials.SessionToken = AmazonS3Credentials.SessionToken?.Trim())) { SensusException.Report("Empty " + nameof(AmazonS3Credentials.SessionToken) + " returned by authentication service for participant " + (Account.ParticipantId ?? "[null].")); } // save the app state to hang on to the credentials await SensusServiceHelper.Get().SaveAsync(); return(AmazonS3Credentials); }); } return(_getCredentialsTask); } }