private void AddMember(string friendlyName, Endpoint endpoint) { if (this.members.Values.Contains(endpoint)) { throw new InvalidOperationException("That member is already in the chatroom."); } this.members.Add(friendlyName, endpoint); this.ChatroomMembersList.Items.Add(friendlyName); }
/// <summary> /// Initializes a new instance of the <see cref="OwnEndpoint" /> class. /// </summary> /// <param name="contact">The public information for this contact.</param> /// <param name="signingPrivateKeyMaterial">The private signing key.</param> /// <param name="encryptionPrivateKeyMaterial">The private encryption key.</param> /// <param name="inboxOwnerCode">The secret that proves ownership of the inbox at the <see cref="Endpoint.MessageReceivingEndpoint"/>.</param> public OwnEndpoint(Endpoint contact, byte[] signingPrivateKeyMaterial, byte[] encryptionPrivateKeyMaterial, string inboxOwnerCode = null) { Requires.NotNull(contact, "contact"); Requires.NotNull(signingPrivateKeyMaterial, "signingPrivateKeyMaterial"); Requires.NotNull(encryptionPrivateKeyMaterial, "encryptionPrivateKeyMaterial"); this.PublicEndpoint = contact; this.SigningKeyPrivateMaterial = signingPrivateKeyMaterial; this.EncryptionKeyPrivateMaterial = encryptionPrivateKeyMaterial; this.InboxOwnerCode = inboxOwnerCode; }
/// <summary> /// Executes the send/receive loop until the user exits the chat session with the "#quit" command. /// </summary> /// <param name="friend">The remote endpoint to send messages to.</param> /// <returns>A task representing the asynchronous operation.</returns> private async Task ChatLoopAsync(Endpoint friend) { while (true) { Console.Write("> "); var line = Console.ReadLine(); if (line == "#quit") { return; } if (line.Length > 0) { var payload = new Payload(Encoding.UTF8.GetBytes(line), "text/plain"); await this.Channel.PostAsync(payload, new[] { friend }, DateTime.UtcNow + TimeSpan.FromMinutes(5)); } Console.WriteLine("Awaiting friend's reply..."); var incoming = await this.Channel.ReceiveAsync(longPoll: true); foreach (var payloadReceipt in incoming) { var message = Encoding.UTF8.GetString(payloadReceipt.Payload.Content); Console.WriteLine("< {0}", message); } await Task.WhenAll(incoming.Select(receipt => this.Channel.DeleteInboxItemAsync(receipt.Payload))); } }
/// <summary> /// Queries the user for the remote endpoint to send messages to. /// </summary> /// <param name="defaultEndpoint">The user's own endpoint, to use for loopback demos in the event the user has no friend to talk to.</param> /// <returns>A task whose result is the remote endpoint to use.</returns> private async Task<Endpoint> GetFriendEndpointAsync(Endpoint defaultEndpoint) { do { Console.Write("Enter your friend's public endpoint URL (leave blank for loopback): "); string url = Console.ReadLine(); if (string.IsNullOrWhiteSpace(url)) { return defaultEndpoint; } var addressBook = new DirectEntryAddressBook(new System.Net.Http.HttpClient()); var endpoint = await addressBook.LookupAsync(url); if (endpoint != null) { return endpoint; } else { Console.WriteLine("Unable to find endpoint."); continue; } } while (true); }
/// <summary> /// Initializes a new instance of the <see cref="NotificationPostedReceipt"/> class. /// </summary> /// <param name="recipient">The inbox that received the notification.</param> /// <param name="cloudInboxReceiptTimestamp">The timestamp included in the HTTP response from the server.</param> public NotificationPostedReceipt(Endpoint recipient, DateTimeOffset? cloudInboxReceiptTimestamp) { Requires.NotNull(recipient, "recipient"); this.Recipient = recipient; this.CloudInboxReceiptTimestamp = cloudInboxReceiptTimestamp; }
/// <summary> /// Checks whether the specified identifier yields an endpoint equivalent to this one. /// </summary> /// <param name="claimingEndpoint">The endpoint that claims to be resolvable from a given identifier.</param> /// <param name="claimedIdentifier">The identifier to check.</param> /// <param name="cancellationToken">A general cancellation token on the request.</param> /// <returns>A task whose result is <c>true</c> if the identifier verified correctly; otherwise <c>false</c>.</returns> private async Task<bool> IsVerifiableIdentifierAsync(Endpoint claimingEndpoint, string claimedIdentifier, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(claimingEndpoint, "claimingEndpoint"); Requires.NotNullOrEmpty(claimedIdentifier, "claimedIdentifier"); Endpoint cachedEndpoint; lock (this.resolvedIdentifiersCache) { if (this.resolvedIdentifiersCache.TryGetValue(claimedIdentifier, out cachedEndpoint)) { return cachedEndpoint.Equals(claimingEndpoint); } } var matchingEndpoint = await Utilities.FastestQualifyingResultAsync( this.AddressBooks, (ct, addressBook) => addressBook.LookupAsync(claimedIdentifier, ct), resolvedEndpoint => claimingEndpoint.Equals(resolvedEndpoint), cancellationToken).ConfigureAwait(false); if (matchingEndpoint != null) { lock (this.resolvedIdentifiersCache) { if (!this.resolvedIdentifiersCache.ContainsKey(claimedIdentifier)) { this.resolvedIdentifiersCache.Add(claimedIdentifier, matchingEndpoint); } } } return matchingEndpoint != null; }
/// <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> /// Gets the set of identifiers this endpoint claims that are verifiable. /// </summary> /// <param name="endpoint">The endpoint whose authorized identifiers are to be verified.</param> /// <param name="cancellationToken">A general cancellation token on the request.</param> /// <returns>A task whose result is the set of verified identifiers.</returns> public async Task<IReadOnlyCollection<string>> GetVerifiableIdentifiersAsync(Endpoint endpoint, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(endpoint, "endpoint"); var verifiedIdentifiers = new List<string>(); if (endpoint.AuthorizedIdentifiers != null) { var map = endpoint.AuthorizedIdentifiers.Where(id => id != null).ToDictionary( id => id, id => this.IsVerifiableIdentifierAsync(endpoint, id, cancellationToken)); await Task.WhenAll(map.Values).ConfigureAwait(false); foreach (var result in map) { if (result.Value.Result) { verifiedIdentifiers.Add(result.Key); } } } return verifiedIdentifiers; }
/// <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> /// Generates a new receiving endpoint. /// </summary> /// <returns>The newly generated endpoint.</returns> /// <remarks> /// Depending on the length of the keys set in the provider and the amount of buffered entropy in the operating system, /// this method can take an extended period (several seconds) to complete. /// </remarks> private OwnEndpoint CreateEndpointWithKeys() { byte[] privateEncryptionKey, publicEncryptionKey; byte[] privateSigningKey, publicSigningKey; this.CryptoProvider.GenerateEncryptionKeyPair(out privateEncryptionKey, out publicEncryptionKey); this.CryptoProvider.GenerateSigningKeyPair(out privateSigningKey, out publicSigningKey); var contact = new Endpoint() { EncryptionKeyPublicMaterial = publicEncryptionKey, SigningKeyPublicMaterial = publicSigningKey, }; var ownContact = new OwnEndpoint(contact, privateSigningKey, privateEncryptionKey); return ownContact; }