/// <summary> /// Downloads the message payload referred to by the specified <see cref="PayloadReference"/>. /// </summary> /// <param name="notification">The payload reference.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> public virtual async Task <Payload> DownloadPayloadAsync(PayloadReference notification, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(notification, "notification"); var responseMessage = await this.HttpClient.GetAsync(notification.Location, cancellationToken); var messageBuffer = await responseMessage.Content.ReadAsByteArrayAsync(); // Calculate hash of downloaded message and check that it matches the referenced message hash. if (!this.CryptoServices.IsHashMatchWithTolerantHashAlgorithm(messageBuffer, notification.Hash, notification.HashAlgorithmName)) { throw new InvalidMessageException(); } var encryptionVariables = new SymmetricEncryptionVariables(notification.Key, notification.IV); var cipherStream = new MemoryStream(messageBuffer); var plainTextStream = new MemoryStream(); await this.CryptoServices.DecryptAsync(cipherStream, plainTextStream, encryptionVariables, cancellationToken); plainTextStream.Position = 0; var plainTextReader = new BinaryReader(plainTextStream); var message = Utilities.DeserializeDataContract <Payload>(plainTextReader); message.PayloadReferenceUri = notification.ReferenceLocation; return(message); }
/// <summary> /// Downloads the message payload referred to by the specified <see cref="PayloadReference"/>. /// </summary> /// <param name="notification">The payload reference.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task <Payload> DownloadPayloadAsync(PayloadReference notification, CancellationToken cancellationToken) { Requires.NotNull(notification, "notification"); var responseMessage = await this.HttpClient.GetAsync(notification.Location, cancellationToken); var messageBuffer = await responseMessage.Content.ReadAsByteArrayAsync(); // Calculate hash of downloaded message and check that it matches the referenced message hash. var messageHash = this.CryptoServices.Hash(messageBuffer); if (!Utilities.AreEquivalent(messageHash, notification.Hash)) { throw new InvalidMessageException(); } var encryptedResult = new SymmetricEncryptionResult( notification.Key, notification.IV, messageBuffer); var plainTextBuffer = this.CryptoServices.Decrypt(encryptedResult); var plainTextStream = new MemoryStream(plainTextBuffer); var plainTextReader = new BinaryReader(plainTextStream); var message = Utilities.DeserializeDataContract <Payload>(plainTextReader); message.PayloadReferenceUri = notification.ReferenceLocation; return(message); }
/// <summary> /// Shares the reference to a message payload with the specified set of recipients. /// </summary> /// <param name="messageReference">The payload reference to share.</param> /// <param name="recipients">The set of recipients that should be notified of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task PostPayloadReferenceAsync(PayloadReference messageReference, ReadOnlyCollectionOfEndpoint recipients, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(messageReference, "messageReference"); Requires.NotNullOrEmpty(recipients, "recipients"); // Kick off individual tasks concurrently for each recipient. // Each recipient requires cryptography (CPU intensive) to be performed, so don't block the calling thread. await TaskEx.WhenAll( recipients.Select(recipient => TaskEx.Run(() => this.PostPayloadReferenceAsync(messageReference, recipient, cancellationToken)))); }
/// <summary> /// Shares the reference to a message payload with the specified recipient. /// </summary> /// <param name="messageReference">The payload reference to share.</param> /// <param name="recipient">The recipient that should be notified of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task PostPayloadReferenceAsync(PayloadReference messageReference, Endpoint recipient, CancellationToken cancellationToken) { Requires.NotNull(recipient, "recipient"); Requires.NotNull(messageReference, "messageReference"); cancellationToken.ThrowIfCancellationRequested(); // Prepare the payload. var plainTextPayloadStream = new MemoryStream(); var plainTextPayloadWriter = new BinaryWriter(plainTextPayloadStream); // Include the intended recipient's signing certificate so the recipient knows that // the message author intended the recipient to receive it (defeats fowarding and re-encrypting // a message notification with the intent to deceive a victim that a message was intended for them when it was not.) plainTextPayloadWriter.WriteSizeAndBuffer(recipient.SigningKeyPublicMaterial); plainTextPayloadWriter.Write(DateTime.UtcNow.ToBinary()); // Write out the author of this notification (which may be different from the author of the // message itself in the case of a "forward"). plainTextPayloadWriter.SerializeDataContract(this.Endpoint.PublicEndpoint); plainTextPayloadWriter.SerializeDataContract(messageReference); plainTextPayloadWriter.Flush(); this.Log("Message invite plaintext", plainTextPayloadStream.ToArray()); byte[] notificationSignature = this.CryptoServices.Sign(plainTextPayloadStream.ToArray(), this.Endpoint.SigningKeyPrivateMaterial); var signedPlainTextPayloadStream = new MemoryStream((int)plainTextPayloadStream.Length + notificationSignature.Length + 4); await signedPlainTextPayloadStream.WriteSizeAndBufferAsync(notificationSignature, cancellationToken); plainTextPayloadStream.Position = 0; await plainTextPayloadStream.CopyToAsync(signedPlainTextPayloadStream, 4096, cancellationToken); var encryptedPayload = this.CryptoServices.Encrypt(signedPlainTextPayloadStream.ToArray()); this.Log("Message invite ciphertext", encryptedPayload.Ciphertext); this.Log("Message invite key", encryptedPayload.Key); this.Log("Message invite IV", encryptedPayload.IV); var builder = new UriBuilder(recipient.MessageReceivingEndpoint); var lifetimeInMinutes = (int)(messageReference.ExpiresUtc - DateTime.UtcNow).TotalMinutes; builder.Query += "&lifetime=" + lifetimeInMinutes.ToString(CultureInfo.InvariantCulture); var postContent = new MemoryStream(); var encryptedKey = this.CryptoServices.Encrypt(recipient.EncryptionKeyPublicMaterial, encryptedPayload.Key); this.Log("Message invite encrypted key", encryptedKey); await postContent.WriteSizeAndBufferAsync(encryptedKey, cancellationToken); await postContent.WriteSizeAndBufferAsync(encryptedPayload.IV, cancellationToken); await postContent.WriteSizeAndBufferAsync(encryptedPayload.Ciphertext, cancellationToken); await postContent.FlushAsync(); postContent.Position = 0; using (var response = await this.HttpClient.PostAsync(builder.Uri, new StreamContent(postContent), cancellationToken)) { response.EnsureSuccessStatusCode(); } }
/// <summary> /// Shares the reference to a message payload with the specified recipient. /// </summary> /// <param name="messageReference">The payload reference to share.</param> /// <param name="recipient">The recipient that should be notified of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task <NotificationPostedReceipt> PostPayloadReferenceAsync(PayloadReference messageReference, Endpoint recipient, CancellationToken cancellationToken) { Requires.NotNull(recipient, "recipient"); Requires.NotNull(messageReference, "messageReference"); cancellationToken.ThrowIfCancellationRequested(); // Prepare the payload. var plainTextPayloadStream = new MemoryStream(); var plainTextPayloadWriter = new BinaryWriter(plainTextPayloadStream); // Include the intended recipient's signing certificate so the recipient knows that // the message author intended the recipient to receive it (defeats fowarding and re-encrypting // a message notification with the intent to deceive a victim that a message was intended for them when it was not.) plainTextPayloadWriter.WriteSizeAndBuffer(recipient.SigningKeyPublicMaterial); plainTextPayloadWriter.Write(DateTime.UtcNow.ToBinary()); // Write out the author of this notification (which may be different from the author of the // message itself in the case of a "forward"). plainTextPayloadWriter.SerializeDataContract(this.Endpoint.PublicEndpoint); plainTextPayloadWriter.SerializeDataContract(messageReference); plainTextPayloadWriter.Flush(); this.Log("Message invite plaintext", plainTextPayloadStream.ToArray()); byte[] notificationSignature = WinRTCrypto.CryptographicEngine.Sign(this.Endpoint.SigningKey, plainTextPayloadStream.ToArray()); var signedPlainTextPayloadStream = new MemoryStream((int)plainTextPayloadStream.Length + notificationSignature.Length + 4); ////await signedPlainTextPayloadStream.WriteSizeAndBufferAsync(Encoding.UTF8.GetBytes(this.CryptoServices.HashAlgorithmName), cancellationToken); await signedPlainTextPayloadStream.WriteSizeAndBufferAsync(notificationSignature, cancellationToken).ConfigureAwait(false); plainTextPayloadStream.Position = 0; await plainTextPayloadStream.CopyToAsync(signedPlainTextPayloadStream, 4096, cancellationToken).ConfigureAwait(false); signedPlainTextPayloadStream.Position = 0; var cipherTextStream = new MemoryStream(); var encryptedVariables = await this.CryptoServices.EncryptAsync(signedPlainTextPayloadStream, cipherTextStream, cancellationToken : cancellationToken).ConfigureAwait(false); this.Log("Message invite ciphertext", cipherTextStream.ToArray()); this.Log("Message invite key", encryptedVariables.Key); this.Log("Message invite IV", encryptedVariables.IV); var builder = new UriBuilder(recipient.MessageReceivingEndpoint); var lifetimeInMinutes = (int)(messageReference.ExpiresUtc - DateTime.UtcNow).TotalMinutes; builder.Query += "&lifetime=" + lifetimeInMinutes.ToString(CultureInfo.InvariantCulture); var postContent = new MemoryStream(); var encryptionKey = CryptoSettings.EncryptionAlgorithm.ImportPublicKey( recipient.EncryptionKeyPublicMaterial, CryptoSettings.PublicKeyFormat); var encryptedKey = WinRTCrypto.CryptographicEngine.Encrypt(encryptionKey, encryptedVariables.Key); this.Log("Message invite encrypted key", encryptedKey); await postContent.WriteSizeAndBufferAsync(encryptedKey, cancellationToken).ConfigureAwait(false); await postContent.WriteSizeAndBufferAsync(encryptedVariables.IV, cancellationToken).ConfigureAwait(false); cipherTextStream.Position = 0; await postContent.WriteSizeAndStreamAsync(cipherTextStream, cancellationToken).ConfigureAwait(false); await postContent.FlushAsync().ConfigureAwait(false); postContent.Position = 0; using (var response = await this.HttpClient.PostAsync(builder.Uri, new StreamContent(postContent), cancellationToken).ConfigureAwait(false)) { if (response.Content != null) { // Just to help in debugging. string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); } response.EnsureSuccessStatusCode(); var receipt = new NotificationPostedReceipt(recipient, response.Headers.Date); return(receipt); } }
/// <summary> /// Shares the reference to a message payload with the specified set of recipients. /// </summary> /// <param name="messageReference">The payload reference to share.</param> /// <param name="recipients">The set of recipients that should be notified of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task <IReadOnlyCollection <NotificationPostedReceipt> > PostPayloadReferenceAsync(PayloadReference messageReference, IReadOnlyCollection <Endpoint> recipients, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(messageReference, "messageReference"); Requires.NotNullOrEmpty(recipients, "recipients"); // Kick off individual tasks concurrently for each recipient. // Each recipient requires cryptography (CPU intensive) to be performed, so don't block the calling thread. var postTasks = recipients.Select(recipient => Task.Run(() => this.PostPayloadReferenceAsync(messageReference, recipient, cancellationToken))).ToList(); return(await Task.WhenAll(postTasks).ConfigureAwait(false)); }
/// <summary> /// Downloads the message payload referred to by the specified <see cref="PayloadReference"/>. /// </summary> /// <param name="notification">The payload reference.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> public virtual async Task <Payload> DownloadPayloadAsync(PayloadReference notification, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(notification, "notification"); byte[] messageBuffer = null; const int MaxAttempts = 2; int retry; Exception exceptionLeadingToRetry = null; for (retry = 0; retry < MaxAttempts; retry++) { exceptionLeadingToRetry = null; try { var responseMessage = await this.HttpClient.GetAsync(notification.Location, cancellationToken).ConfigureAwait(false); messageBuffer = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false); // Calculate hash of downloaded message and check that it matches the referenced message hash. if (!this.CryptoServices.IsHashMatchWithTolerantHashAlgorithm(messageBuffer, notification.Hash, CryptoProviderExtensions.ParseHashAlgorithmName(notification.HashAlgorithmName))) { // Sometimes when the hash mismatches, it's because the download was truncated // (from switching out the application and then switching back, for example). // Check for that before throwing. if (responseMessage.Content.Headers.ContentLength.HasValue && responseMessage.Content.Headers.ContentLength.Value > messageBuffer.Length) { // It looks like the message was truncated. Retry. exceptionLeadingToRetry = new InvalidMessageException(); continue; } throw new InvalidMessageException(); } // Stop retrying. We got something that worked! break; } catch (HttpRequestException ex) { exceptionLeadingToRetry = ex; continue; } } if (exceptionLeadingToRetry != null) { if (exceptionLeadingToRetry.StackTrace != null) { ExceptionDispatchInfo.Capture(exceptionLeadingToRetry).Throw(); } else { throw exceptionLeadingToRetry; } } var encryptionVariables = new SymmetricEncryptionVariables(notification.Key, notification.IV); var cipherStream = new MemoryStream(messageBuffer); var plainTextStream = new MemoryStream(); await this.CryptoServices.DecryptAsync(cipherStream, plainTextStream, encryptionVariables, cancellationToken).ConfigureAwait(false); plainTextStream.Position = 0; var plainTextReader = new BinaryReader(plainTextStream); var message = Utilities.DeserializeDataContract <Payload>(plainTextReader); message.PayloadReferenceUri = notification.ReferenceLocation; return(message); }
/// <summary> /// Shares the reference to a message payload with the specified recipient. /// </summary> /// <param name="messageReference">The payload reference to share.</param> /// <param name="recipient">The recipient that should be notified of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task<NotificationPostedReceipt> PostPayloadReferenceAsync(PayloadReference messageReference, Endpoint recipient, CancellationToken cancellationToken) { Requires.NotNull(recipient, "recipient"); Requires.NotNull(messageReference, "messageReference"); cancellationToken.ThrowIfCancellationRequested(); // Prepare the payload. var plainTextPayloadStream = new MemoryStream(); var plainTextPayloadWriter = new BinaryWriter(plainTextPayloadStream); // Include the intended recipient's signing certificate so the recipient knows that // the message author intended the recipient to receive it (defeats fowarding and re-encrypting // a message notification with the intent to deceive a victim that a message was intended for them when it was not.) plainTextPayloadWriter.WriteSizeAndBuffer(recipient.SigningKeyPublicMaterial); plainTextPayloadWriter.Write(DateTime.UtcNow.ToBinary()); // Write out the author of this notification (which may be different from the author of the // message itself in the case of a "forward"). plainTextPayloadWriter.SerializeDataContract(this.Endpoint.PublicEndpoint); plainTextPayloadWriter.SerializeDataContract(messageReference); plainTextPayloadWriter.Flush(); this.Log("Message invite plaintext", plainTextPayloadStream.ToArray()); byte[] notificationSignature = WinRTCrypto.CryptographicEngine.Sign(this.Endpoint.SigningKey, plainTextPayloadStream.ToArray()); var signedPlainTextPayloadStream = new MemoryStream((int)plainTextPayloadStream.Length + notificationSignature.Length + 4); ////await signedPlainTextPayloadStream.WriteSizeAndBufferAsync(Encoding.UTF8.GetBytes(this.CryptoServices.HashAlgorithmName), cancellationToken); await signedPlainTextPayloadStream.WriteSizeAndBufferAsync(notificationSignature, cancellationToken).ConfigureAwait(false); plainTextPayloadStream.Position = 0; await plainTextPayloadStream.CopyToAsync(signedPlainTextPayloadStream, 4096, cancellationToken).ConfigureAwait(false); signedPlainTextPayloadStream.Position = 0; var cipherTextStream = new MemoryStream(); var encryptedVariables = await this.CryptoServices.EncryptAsync(signedPlainTextPayloadStream, cipherTextStream, cancellationToken: cancellationToken).ConfigureAwait(false); this.Log("Message invite ciphertext", cipherTextStream.ToArray()); this.Log("Message invite key", encryptedVariables.Key); this.Log("Message invite IV", encryptedVariables.IV); var builder = new UriBuilder(recipient.MessageReceivingEndpoint); var lifetimeInMinutes = (int)(messageReference.ExpiresUtc - DateTime.UtcNow).TotalMinutes; builder.Query += "&lifetime=" + lifetimeInMinutes.ToString(CultureInfo.InvariantCulture); var postContent = new MemoryStream(); var encryptionKey = CryptoSettings.EncryptionAlgorithm.ImportPublicKey( recipient.EncryptionKeyPublicMaterial, CryptoSettings.PublicKeyFormat); var encryptedKey = WinRTCrypto.CryptographicEngine.Encrypt(encryptionKey, encryptedVariables.Key); this.Log("Message invite encrypted key", encryptedKey); await postContent.WriteSizeAndBufferAsync(encryptedKey, cancellationToken).ConfigureAwait(false); await postContent.WriteSizeAndBufferAsync(encryptedVariables.IV, cancellationToken).ConfigureAwait(false); cipherTextStream.Position = 0; await postContent.WriteSizeAndStreamAsync(cipherTextStream, cancellationToken).ConfigureAwait(false); await postContent.FlushAsync().ConfigureAwait(false); postContent.Position = 0; using (var response = await this.HttpClient.PostAsync(builder.Uri, new StreamContent(postContent), cancellationToken).ConfigureAwait(false)) { if (response.Content != null) { // Just to help in debugging. string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); } response.EnsureSuccessStatusCode(); var receipt = new NotificationPostedReceipt(recipient, response.Headers.Date); return receipt; } }
/// <summary> /// Shares the reference to a message payload with the specified set of recipients. /// </summary> /// <param name="messageReference">The payload reference to share.</param> /// <param name="recipients">The set of recipients that should be notified of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task<IReadOnlyCollection<NotificationPostedReceipt>> PostPayloadReferenceAsync(PayloadReference messageReference, IReadOnlyCollection<Endpoint> recipients, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(messageReference, "messageReference"); Requires.NotNullOrEmpty(recipients, "recipients"); // Kick off individual tasks concurrently for each recipient. // Each recipient requires cryptography (CPU intensive) to be performed, so don't block the calling thread. var postTasks = recipients.Select(recipient => Task.Run(() => this.PostPayloadReferenceAsync(messageReference, recipient, cancellationToken))).ToList(); return await Task.WhenAll(postTasks).ConfigureAwait(false); }
/// <summary> /// Downloads the message payload referred to by the specified <see cref="PayloadReference"/>. /// </summary> /// <param name="notification">The payload reference.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> public virtual async Task<Payload> DownloadPayloadAsync(PayloadReference notification, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(notification, "notification"); byte[] messageBuffer = null; const int MaxAttempts = 2; int retry; Exception exceptionLeadingToRetry = null; for (retry = 0; retry < MaxAttempts; retry++) { exceptionLeadingToRetry = null; try { var responseMessage = await this.HttpClient.GetAsync(notification.Location, cancellationToken).ConfigureAwait(false); messageBuffer = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false); // Calculate hash of downloaded message and check that it matches the referenced message hash. if (!this.CryptoServices.IsHashMatchWithTolerantHashAlgorithm(messageBuffer, notification.Hash, CryptoProviderExtensions.ParseHashAlgorithmName(notification.HashAlgorithmName))) { // Sometimes when the hash mismatches, it's because the download was truncated // (from switching out the application and then switching back, for example). // Check for that before throwing. if (responseMessage.Content.Headers.ContentLength.HasValue && responseMessage.Content.Headers.ContentLength.Value > messageBuffer.Length) { // It looks like the message was truncated. Retry. exceptionLeadingToRetry = new InvalidMessageException(); continue; } throw new InvalidMessageException(); } // Stop retrying. We got something that worked! break; } catch (HttpRequestException ex) { exceptionLeadingToRetry = ex; continue; } } if (exceptionLeadingToRetry != null) { if (exceptionLeadingToRetry.StackTrace != null) { ExceptionDispatchInfo.Capture(exceptionLeadingToRetry).Throw(); } else { throw exceptionLeadingToRetry; } } var encryptionVariables = new SymmetricEncryptionVariables(notification.Key, notification.IV); var cipherStream = new MemoryStream(messageBuffer); var plainTextStream = new MemoryStream(); await this.CryptoServices.DecryptAsync(cipherStream, plainTextStream, encryptionVariables, cancellationToken).ConfigureAwait(false); plainTextStream.Position = 0; var plainTextReader = new BinaryReader(plainTextStream); var message = Utilities.DeserializeDataContract<Payload>(plainTextReader); message.PayloadReferenceUri = notification.ReferenceLocation; return message; }
/// <summary> /// Downloads the message payload referred to by the specified <see cref="PayloadReference"/>. /// </summary> /// <param name="notification">The payload reference.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task<Payload> DownloadPayloadAsync(PayloadReference notification, CancellationToken cancellationToken) { Requires.NotNull(notification, "notification"); var responseMessage = await this.HttpClient.GetAsync(notification.Location, cancellationToken); var messageBuffer = await responseMessage.Content.ReadAsByteArrayAsync(); // Calculate hash of downloaded message and check that it matches the referenced message hash. var messageHash = this.CryptoServices.Hash(messageBuffer); if (!Utilities.AreEquivalent(messageHash, notification.Hash)) { throw new InvalidMessageException(); } var encryptedResult = new SymmetricEncryptionResult( notification.Key, notification.IV, messageBuffer); var plainTextBuffer = this.CryptoServices.Decrypt(encryptedResult); var plainTextStream = new MemoryStream(plainTextBuffer); var plainTextReader = new BinaryReader(plainTextStream); var message = Utilities.DeserializeDataContract<Payload>(plainTextReader); message.PayloadReferenceUri = notification.ReferenceLocation; return message; }