/// <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> /// Symmetrically encrypts the specified buffer using a randomly generated key. /// </summary> /// <param name="data">The data to encrypt.</param> /// <param name="encryptionVariables">Optional encryption variables to use; or <c>null</c> to use randomly generated ones.</param> /// <returns> /// The result of the encryption. /// </returns> public virtual SymmetricEncryptionResult Encrypt(byte[] data, SymmetricEncryptionVariables encryptionVariables) { Requires.NotNull(data, "data"); var plaintext = new MemoryStream(data); var ciphertext = new MemoryStream(); var result = this.EncryptAsync(plaintext, ciphertext, encryptionVariables, CancellationToken.None).Result; return(new SymmetricEncryptionResult(result, ciphertext.ToArray())); }
/// <summary> /// Initializes a new instance of the <see cref="SymmetricEncryptionResult"/> class. /// </summary> /// <param name="encryptionVariables">The key and IV used to encrypt the ciphertext.</param> /// <param name="ciphertext">The encrypted data.</param> public SymmetricEncryptionResult(SymmetricEncryptionVariables encryptionVariables, byte[] ciphertext) : this(Requires.NotNull(encryptionVariables, "encryptionVariables").Key, encryptionVariables.IV, ciphertext) { }
/// <summary> /// Downloads a <see cref="PayloadReference"/> that is referenced from an incoming inbox item. /// </summary> /// <param name="inboxItem">The inbox item that referenced the <see cref="PayloadReference"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task <PayloadReference> DownloadPayloadReferenceAsync(IncomingList.IncomingItem inboxItem, CancellationToken cancellationToken) { Requires.NotNull(inboxItem, "inboxItem"); var responseMessage = await this.HttpClient.GetAsync(inboxItem.Location, cancellationToken).ConfigureAwait(false); if (responseMessage.StatusCode == HttpStatusCode.NotFound) { // delete inbox item and move on. await this.DeletePayloadReferenceAsync(inboxItem.Location, cancellationToken).ConfigureAwait(false); this.Log("Missing payload reference.", null); return(null); } responseMessage.EnsureSuccessStatusCode(); var responseStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); var responseStreamCopy = new MemoryStream(); await responseStream.CopyToAsync(responseStreamCopy, 4096, cancellationToken).ConfigureAwait(false); responseStreamCopy.Position = 0; var encryptedKey = await responseStreamCopy.ReadSizeAndBufferAsync(cancellationToken).ConfigureAwait(false); var key = WinRTCrypto.CryptographicEngine.Decrypt(this.Endpoint.EncryptionKey, encryptedKey); var iv = await responseStreamCopy.ReadSizeAndBufferAsync(cancellationToken).ConfigureAwait(false); var ciphertextStream = await responseStreamCopy.ReadSizeAndStreamAsync(cancellationToken).ConfigureAwait(false); var encryptedVariables = new SymmetricEncryptionVariables(key, iv); var plainTextPayloadStream = new MemoryStream(); await this.CryptoServices.DecryptAsync(ciphertextStream, plainTextPayloadStream, encryptedVariables, cancellationToken).ConfigureAwait(false); plainTextPayloadStream.Position = 0; AsymmetricAlgorithm?signingHashAlgorithm = null; //// Encoding.UTF8.GetString(await plainTextPayloadStream.ReadSizeAndBufferAsync(cancellationToken)); byte[] signature = await plainTextPayloadStream.ReadSizeAndBufferAsync(cancellationToken).ConfigureAwait(false); long payloadStartPosition = plainTextPayloadStream.Position; var signedBytes = new byte[plainTextPayloadStream.Length - plainTextPayloadStream.Position]; await plainTextPayloadStream.ReadAsync(signedBytes, 0, signedBytes.Length).ConfigureAwait(false); plainTextPayloadStream.Position = payloadStartPosition; var plainTextPayloadReader = new BinaryReader(plainTextPayloadStream); var recipientPublicSigningKeyBuffer = plainTextPayloadReader.ReadSizeAndBuffer(); var creationDateUtc = DateTime.FromBinary(plainTextPayloadReader.ReadInt64()); var notificationAuthor = Utilities.DeserializeDataContract <Endpoint>(plainTextPayloadReader); var messageReference = Utilities.DeserializeDataContract <PayloadReference>(plainTextPayloadReader); messageReference.ReferenceLocation = inboxItem.Location; if (messageReference.HashAlgorithmName == null) { messageReference.HashAlgorithmName = Utilities.GuessHashAlgorithmFromLength(messageReference.Hash.Length).GetHashAlgorithmName(); } if (!CryptoProviderExtensions.VerifySignatureWithTolerantHashAlgorithm(notificationAuthor.SigningKeyPublicMaterial, signedBytes, signature, signingHashAlgorithm)) { throw new InvalidMessageException(); } if (!Utilities.AreEquivalent(recipientPublicSigningKeyBuffer, this.Endpoint.PublicEndpoint.SigningKeyPublicMaterial)) { throw new InvalidMessageException(Strings.MisdirectedMessage); } return(messageReference); }
/// <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> /// Symmetrically encrypts a stream. /// </summary> /// <param name="cryptoProvider">The crypto provider.</param> /// <param name="plaintext">The stream of plaintext to encrypt.</param> /// <param name="ciphertext">The stream to receive the ciphertext.</param> /// <param name="encryptionVariables">An optional key and IV to use. May be <c>null</c> to use randomly generated values.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns> /// A task that completes when encryption has completed, whose result is the key and IV to use to decrypt the ciphertext. /// </returns> public static async Task<SymmetricEncryptionVariables> EncryptAsync(this CryptoSettings cryptoProvider, Stream plaintext, Stream ciphertext, SymmetricEncryptionVariables encryptionVariables = null, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(plaintext, "plaintext"); Requires.NotNull(ciphertext, "ciphertext"); encryptionVariables = ThisOrNewEncryptionVariables(cryptoProvider, encryptionVariables); var key = CryptoSettings.SymmetricAlgorithm.CreateSymmetricKey(encryptionVariables.Key); using (var encryptor = WinRTCrypto.CryptographicEngine.CreateEncryptor(key, encryptionVariables.IV)) { var cryptoStream = new CryptoStream(ciphertext, encryptor, CryptoStreamMode.Write); await plaintext.CopyToAsync(cryptoStream, 4096, cancellationToken).ConfigureAwait(false); cryptoStream.FlushFinalBlock(); } return encryptionVariables; }
/// <summary> /// Symmetrically encrypts the specified buffer using a randomly generated key. /// </summary> /// <param name="cryptoProvider">The crypto provider.</param> /// <param name="data">The data to encrypt.</param> /// <param name="encryptionVariables">Optional encryption variables to use; or <c>null</c> to use randomly generated ones.</param> /// <returns> /// The result of the encryption. /// </returns> public static SymmetricEncryptionResult Encrypt(this CryptoSettings cryptoProvider, byte[] data, SymmetricEncryptionVariables encryptionVariables = null) { Requires.NotNull(data, "data"); encryptionVariables = ThisOrNewEncryptionVariables(cryptoProvider, encryptionVariables); var symmetricKey = CryptoSettings.SymmetricAlgorithm.CreateSymmetricKey(encryptionVariables.Key); var cipherTextBuffer = WinRTCrypto.CryptographicEngine.Encrypt(symmetricKey, data, encryptionVariables.IV); return new SymmetricEncryptionResult(encryptionVariables, cipherTextBuffer); }
/// <summary> /// Returns the specified encryption variables if they are non-null, or generates new ones. /// </summary> /// <param name="cryptoProvider">The crypto provider.</param> /// <param name="encryptionVariables">The encryption variables.</param> /// <returns> /// A valid set of encryption variables. /// </returns> private static SymmetricEncryptionVariables ThisOrNewEncryptionVariables(CryptoSettings cryptoProvider, SymmetricEncryptionVariables encryptionVariables) { if (encryptionVariables == null) { return NewSymmetricEncryptionVariables(cryptoProvider); } else { Requires.Argument(encryptionVariables.Key.Length == cryptoProvider.SymmetricKeySize / 8, "key", "Incorrect length."); Requires.Argument(encryptionVariables.IV.Length == CryptoSettings.SymmetricAlgorithm.BlockLength, "iv", "Incorrect length."); return encryptionVariables; } }
/// <summary> /// Downloads a <see cref="PayloadReference"/> that is referenced from an incoming inbox item. /// </summary> /// <param name="inboxItem">The inbox item that referenced the <see cref="PayloadReference"/>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task representing the asynchronous operation.</returns> protected virtual async Task<PayloadReference> DownloadPayloadReferenceAsync(IncomingList.IncomingItem inboxItem, CancellationToken cancellationToken) { Requires.NotNull(inboxItem, "inboxItem"); var responseMessage = await this.HttpClient.GetAsync(inboxItem.Location, cancellationToken).ConfigureAwait(false); if (responseMessage.StatusCode == HttpStatusCode.NotFound) { // delete inbox item and move on. await this.DeletePayloadReferenceAsync(inboxItem.Location, cancellationToken).ConfigureAwait(false); this.Log("Missing payload reference.", null); return null; } responseMessage.EnsureSuccessStatusCode(); var responseStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); var responseStreamCopy = new MemoryStream(); await responseStream.CopyToAsync(responseStreamCopy, 4096, cancellationToken).ConfigureAwait(false); responseStreamCopy.Position = 0; var encryptedKey = await responseStreamCopy.ReadSizeAndBufferAsync(cancellationToken).ConfigureAwait(false); var key = WinRTCrypto.CryptographicEngine.Decrypt(this.Endpoint.EncryptionKey, encryptedKey); var iv = await responseStreamCopy.ReadSizeAndBufferAsync(cancellationToken).ConfigureAwait(false); var ciphertextStream = await responseStreamCopy.ReadSizeAndStreamAsync(cancellationToken).ConfigureAwait(false); var encryptedVariables = new SymmetricEncryptionVariables(key, iv); var plainTextPayloadStream = new MemoryStream(); await this.CryptoServices.DecryptAsync(ciphertextStream, plainTextPayloadStream, encryptedVariables, cancellationToken).ConfigureAwait(false); plainTextPayloadStream.Position = 0; AsymmetricAlgorithm? signingHashAlgorithm = null; //// Encoding.UTF8.GetString(await plainTextPayloadStream.ReadSizeAndBufferAsync(cancellationToken)); byte[] signature = await plainTextPayloadStream.ReadSizeAndBufferAsync(cancellationToken).ConfigureAwait(false); long payloadStartPosition = plainTextPayloadStream.Position; var signedBytes = new byte[plainTextPayloadStream.Length - plainTextPayloadStream.Position]; await plainTextPayloadStream.ReadAsync(signedBytes, 0, signedBytes.Length).ConfigureAwait(false); plainTextPayloadStream.Position = payloadStartPosition; var plainTextPayloadReader = new BinaryReader(plainTextPayloadStream); var recipientPublicSigningKeyBuffer = plainTextPayloadReader.ReadSizeAndBuffer(); var creationDateUtc = DateTime.FromBinary(plainTextPayloadReader.ReadInt64()); var notificationAuthor = Utilities.DeserializeDataContract<Endpoint>(plainTextPayloadReader); var messageReference = Utilities.DeserializeDataContract<PayloadReference>(plainTextPayloadReader); messageReference.ReferenceLocation = inboxItem.Location; if (messageReference.HashAlgorithmName == null) { messageReference.HashAlgorithmName = Utilities.GuessHashAlgorithmFromLength(messageReference.Hash.Length).GetHashAlgorithmName(); } if (!CryptoProviderExtensions.VerifySignatureWithTolerantHashAlgorithm(notificationAuthor.SigningKeyPublicMaterial, signedBytes, signature, signingHashAlgorithm)) { throw new InvalidMessageException(); } if (!Utilities.AreEquivalent(recipientPublicSigningKeyBuffer, this.Endpoint.PublicEndpoint.SigningKeyPublicMaterial)) { throw new InvalidMessageException(Strings.MisdirectedMessage); } return messageReference; }
/// <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> /// Symmetrically decrypts a stream. /// </summary> /// <param name="ciphertext">The stream of ciphertext to decrypt.</param> /// <param name="plaintext">The stream to receive the plaintext.</param> /// <param name="encryptionVariables">The key and IV to use.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation.</returns> public abstract Task DecryptAsync(Stream ciphertext, Stream plaintext, SymmetricEncryptionVariables encryptionVariables, CancellationToken cancellationToken);