/// <summary> /// Deserializes an endpoint from an address book entry and validates that the signatures are correct. /// </summary> /// <returns>The deserialized endpoint.</returns> /// <exception cref="BadAddressBookEntryException">Thrown if the signatures are invalid.</exception> public Endpoint ExtractEndpoint() { var reader = new BinaryReader(new MemoryStream(this.SerializedEndpoint)); Endpoint endpoint; try { endpoint = reader.DeserializeDataContract <Endpoint>(); } catch (SerializationException ex) { throw new BadAddressBookEntryException(ex.Message, ex); } try { if (!CryptoProviderExtensions.VerifySignatureWithTolerantHashAlgorithm(endpoint.SigningKeyPublicMaterial, this.SerializedEndpoint, this.Signature, this.HashAlgorithmName != null ? (AsymmetricAlgorithm?)CryptoProviderExtensions.GetSignatureProvider(this.HashAlgorithmName) : null)) { throw new BadAddressBookEntryException(Strings.AddressBookEntrySignatureDoesNotMatch); } } catch (Exception ex) { // all those platform-specific exceptions that aren't available to portable libraries. throw new BadAddressBookEntryException(Strings.AddressBookEntrySignatureDoesNotMatch, ex); } return(endpoint); }
/// <summary> /// Downloads the endpoint described by the address book entry found at the given URL and verifies the signature. /// </summary> /// <param name="entryLocation">The entry location.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>A task whose result is the endpoint described and signed by the address book entry.</returns> /// <exception cref="BadAddressBookEntryException">Thrown when deserialization or signature verification of the address book entry fails.</exception> protected async Task <Endpoint> DownloadEndpointAsync(Uri entryLocation, CancellationToken cancellationToken) { Requires.NotNull(entryLocation, "entryLocation"); var entry = await this.DownloadAddressBookEntryAsync(entryLocation, cancellationToken).ConfigureAwait(false); if (entry == null) { return(null); } var endpoint = entry.ExtractEndpoint(); if (!string.IsNullOrEmpty(entryLocation.Fragment)) { if (!CryptoProviderExtensions.IsThumbprintMatch(endpoint.SigningKeyPublicMaterial, entryLocation.Fragment.Substring(1))) { throw new BadAddressBookEntryException("Fragment thumbprint mismatch."); } } return(endpoint); }
/// <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); }