Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ApplyId.cs
1using Paiwise;
2using System;
4using System.Net;
7using System.Text;
8using System.Threading.Tasks;
9using System.Xml;
10using Waher.Content;
12using Waher.Events;
25using Waher.Script;
27using Waher.Security;
33
35{
40 {
44 public ApplyId()
45 : base("Legal/ApplyId",
46 new KeyValuePair<Type, Expression>(typeof(Dictionary<string, object>), new Expression(jsonPattern)),
47 new KeyValuePair<Type, Expression>(typeof(XmlDocument), new Expression(xmlPattern)))
48 {
49 }
50
51 private static readonly string jsonPattern = Resources.LoadResourceAsText(typeof(ApplyId).Namespace + ".JSON.ApplyId.req");
52 private static readonly string xmlPattern = Resources.LoadResourceAsText(typeof(ApplyId).Namespace + ".XML.ApplyId.req");
53
62 public override async Task POST(HttpRequest Request, HttpResponse Response, Dictionary<string, IElement> Parameters)
63 {
65
66 string KeyId = (string)Parameters["PKeyId"].AssociatedObjectValue;
67 string Nonce = (string)Parameters["PNonce"].AssociatedObjectValue;
68 string KeySignature = (string)Parameters["PKeySignature"].AssociatedObjectValue;
69 string RequestSignature = (string)Parameters["PRequestSignature"].AssociatedObjectValue;
70 object[] PropertyNames = (object[])Parameters["PPropertyName"].AssociatedObjectValue;
71 object[] PropertyValues = (object[])Parameters["PPropertyValue"].AssociatedObjectValue;
72 Dictionary<CaseInsensitiveString, Property> PropertiesByName = new Dictionary<CaseInsensitiveString, Property>();
73 List<Property> Properties = new List<Property>();
74 StringBuilder sb = new StringBuilder();
75 string Agent = Request.Header.Referer?.Value;
76
77 if (string.IsNullOrEmpty(Agent))
78 throw new BadRequestException("Missing Referer header.");
79
80 if (string.IsNullOrEmpty(KeyId))
81 throw new BadRequestException("Key ID cannot be empty.");
82
83 if (string.IsNullOrEmpty(Nonce) || Nonce.Length < 32)
84 throw new ForbiddenException(Request, "Nonce too short.");
85
86 if (Agent.Contains(':') || Agent.Contains('/'))
87 {
88 if (!Uri.TryCreate(Agent, UriKind.Absolute, out Uri AgentUri))
89 throw new BadRequestException("Invalid Referer header.");
90
91 ContentResponse Content;
92
93 try
94 {
95 Content = await InternetContent.GetAsync(AgentUri);
96 }
97 catch (Exception ex)
98 {
99 if (Gateway.HasDomain ||
100 !XmppServerModule.Server.IsServerDomain(AgentUri.Host, true))
101 {
102 throw new BadRequestException("Referer header invalid. Unable to resolve URI: " + ex.Message);
103 }
104
105 Content = null;
106 }
107
108 if (Content?.HasError ?? false)
109 {
110 if (Content.Error is Content.Getters.WebException ex)
111 {
112 if (ex.StatusCode == HttpStatusCode.NotFound)
113 throw new BadRequestException("Referer header invalid. Referer resource not found.");
114 else if ((int)ex.StatusCode >= 500)
115 throw new BadRequestException("Referer header invalid. Referer returns server error: " + ex.Message);
116 }
117 else
118 throw new BadRequestException("Referer header invalid. Unable to resolve URI: " + Content.Error.Message);
119 }
120 }
121 else if (IPAddress.TryParse(Request.RemoteEndPoint.RemovePortNumber(), out IPAddress RemoteAddress))
122 {
123 ResourceRecord[] Records;
124
125 if (RemoteAddress.AddressFamily == AddressFamily.InterNetwork)
126 Records = await DnsResolver.TryResolve(Agent, QTYPE.A, QCLASS.IN) ?? Array.Empty<ResourceRecord>();
127 else if (RemoteAddress.AddressFamily == AddressFamily.InterNetworkV6)
128 Records = await DnsResolver.TryResolve(Agent, QTYPE.AAAA, QCLASS.IN) ?? Array.Empty<ResourceRecord>();
129 else
130 Records = null;
131
132 if (Records is null)
133 throw new BadRequestException("Referer header invalid. Invalid domain.");
134
135 bool Match = false;
136
137 foreach (ResourceRecord Rec in Records)
138 {
139 if (Rec is ResourceAddressRecord AddressRecord &&
140 AddressRecord.Address == RemoteAddress)
141 {
142 Match = true;
143 break;
144 }
145 }
146
147 if (!Match)
148 throw new BadRequestException("Referer header invalid. Domain does not match caller IP Address.");
149 }
150 else
151 {
152 if (Agent != Request.RemoteEndPoint)
153 throw new BadRequestException("Referer header invalid. Domain does not match caller domain name, as provided in certificate.");
154 }
155
156 AgentKey AgentKey = await Database.FindFirstDeleteRest<AgentKey>(new FilterAnd(
157 new FilterFieldEqualTo("Account", User.UserName),
158 new FilterFieldEqualTo("Id", KeyId)))
159 ?? throw new NotFoundException("Key not found.");
160
161 sb.Append(User.UserName);
162 sb.Append(':');
163 sb.Append(Request.Header.Host.Value);
164 sb.Append(':');
165 sb.Append(AgentKey.LocalName);
166 sb.Append(':');
167 sb.Append(AgentKey.Namespace);
168 sb.Append(':');
169 sb.Append(KeyId);
170
171 //string s1 = sb.ToString();
172
173 sb.Append(':');
174 sb.Append(KeySignature);
175
176 string s2 = sb.ToString();
177 sb.Append(':');
178 sb.Append(Nonce);
179
180 int i, c = PropertyNames?.Length ?? 0;
181 if ((PropertyValues?.Length ?? 0) != c)
182 throw new BadRequestException("Invalid Properties.");
183
184 bool JidAdded = false;
185 bool EMailAdded = false;
186 bool PhoneNrAdded = false;
187
188 for (i = 0; i < c; i++)
189 {
190 if (!(PropertyNames[i] is string PropertyName) || string.IsNullOrEmpty(PropertyName))
191 throw new BadRequestException("Invalid Property Name.");
192
193 if (!(PropertyValues[i] is string PropertyValue) || string.IsNullOrEmpty(PropertyValue))
194 throw new BadRequestException("Invalid Property Value.");
195
196 if (!LegalComponent.CheckNameWhitespace(PropertyValue))
197 throw new BadRequestException("Invalid space characters used in " + PropertyName);
198
199 switch (PropertyName.ToUpper())
200 {
202 throw new BadRequestException("AGENT property is reserved.");
203
205 int j = PropertyValue.IndexOf('@');
206 if (j < 0)
207 throw new BadRequestException("Invalid JID.");
208
209 if (PropertyValue[..j] != User.UserName)
210 throw new BadRequestException("JID does not match sender Bare JID.");
211
212 if (!Gateway.IsDomain(PropertyValue[(j + 1)..], true))
213 throw new BadRequestException("JID does not match sender Bare JID.");
214
215 JidAdded = true;
216 break;
217
219 if (PropertyValue != User.Account.EMail && !string.IsNullOrEmpty(User.Account.EMail))
220 throw new BadRequestException("EMAIL does not match account e-mail.");
221
222 EMailAdded = true;
223 break;
224
226 if (PropertyValue != User.Account.PhoneNr && !string.IsNullOrEmpty(User.Account.PhoneNr))
227 throw new BadRequestException("PHONE does not match account phone number.");
228
229 PhoneNrAdded = true;
230 break;
231 }
232
233 Property Property = new Property(PropertyName, PropertyValue);
234
235 if (PropertiesByName.ContainsKey(Property.Name))
236 throw new BadRequestException("Duplicate property.");
237
238 Properties.Add(Property);
239 PropertiesByName[Property.Name] = Property;
240
241 sb.Append(':');
242 sb.Append(Property.Name.Value);
243 sb.Append(':');
244 sb.Append(Property.Value.Value);
245 }
246
247 string s3 = sb.ToString();
248
249 string s = Convert.ToBase64String(
251 Encoding.UTF8.GetBytes(User.Account.Password),
252 Encoding.UTF8.GetBytes(s3)));
253
254 if (s != RequestSignature)
255 {
256 string Msg = "Request Signature invalid.";
257 throw new ForbiddenException(Request, Msg);
258 }
259
260 if (await Gateway.HasNonceBeenUsed(Nonce))
261 {
262 string Msg = "Nonce value has already been used.";
263 throw new ForbiddenException(Request, Msg);
264 }
265
266 await Gateway.RegisterNonceValue(Nonce);
267
268 EllipticCurveEndpoint KeyEndpoint = GetEndpoint(Request, AgentKey, s2);
269 string BareJid = User.UserName + "@" + Gateway.Domain;
270
271 if (!JidAdded)
272 Properties.Add(new Property(PersonalInformation.JidTag, BareJid));
273
274 if (!EMailAdded && !string.IsNullOrEmpty(User.Account.EMail))
275 Properties.Add(new Property(PersonalInformation.EMailTag, User.Account.EMail));
276
277 if (!PhoneNrAdded && !string.IsNullOrEmpty(User.Account.PhoneNr))
278 Properties.Add(new Property(PersonalInformation.PhoneTag, User.Account.PhoneNr));
279
280 Properties.Add(new Property(PersonalInformation.AgentTag, Agent));
281
282 DateTime TP;
283
284 if (!(Gateway.LoginAuditor is null))
285 {
286 DateTime? Next = await Gateway.LoginAuditor.GetEarliestLoginOpportunity(BareJid, "XMPP");
287
288 if (Next.HasValue)
289 {
290 sb.Clear();
291
292 TP = Next.Value;
293 DateTime Today = DateTime.Today;
294
295 if (Next.Value == DateTime.MaxValue)
296 {
297 sb.Append("This endpoint (");
298 sb.Append(BareJid);
299 sb.Append(") has been blocked from the system.");
300 }
301 else
302 {
303 sb.Append("Too many failed identity applications in a row registered. Try again after ");
304 sb.Append(TP.ToLongTimeString());
305
306 if (TP.Date != Today)
307 {
308 if (TP.Date == Today.AddDays(1))
309 sb.Append(" tomorrow");
310 else
311 {
312 sb.Append(", ");
313 sb.Append(TP.ToShortDateString());
314 }
315 }
316
317 sb.Append(". Remote Endpoint: ");
318 sb.Append(BareJid);
319 }
320
321 throw new TooManyRequestsException(sb.ToString());
322 }
323 }
324
326 DateTime From = TP.Date;
327 CaseInsensitiveString LegalDomain = XmppServerModule.Legal?.MainDomain.Address ?? "legal.example.com";
328
329 LegalIdentity Identity = new LegalIdentity()
330 {
331 Account = User.UserName,
332 ClientKeyName = KeyEndpoint.LocalName,
333 ClientPubKey = KeyEndpoint.PublicKey,
334 Created = TP,
335 Updated = DateTime.MinValue,
336 From = From,
337 To = From.AddMonths((int)await RuntimeSettings.GetAsync("LegalIdentity.Months", 24)),
338 State = IoTBroker.Legal.Identity.IdentityState.Created,
339 Provider = LegalDomain,
340 Properties = Properties.ToArray(),
341 Version = NamespaceSet.Current
342 };
343
344 StringBuilder Xml = new StringBuilder();
345 Identity.Serialize(Xml, false, false, false, false, false, false, false, null, XmppServerModule.Legal);
346 Identity.ClientSignature = KeyEndpoint.Sign(Encoding.UTF8.GetBytes(Xml.ToString()));
347
348 foreach (LegalIdentity ToRemove in await Database.FindDelete<LegalIdentity>(new FilterAnd(
349 new FilterFieldEqualTo("Account", User.UserName),
350 new FilterFieldEqualTo("State", IoTBroker.Legal.Identity.IdentityState.Created))))
351 {
352 Log.Informational("Obsolete Legal Identity Registration deleted.",
353 ToRemove.Id.Value, BareJid, "LegalIdDeleted", ToRemove.GetTags());
354 }
355
356 await Database.Insert(Identity);
357
358 Identity.Id = Identity.ObjectId + "@" + LegalDomain;
359 Identity.Sign(XmppServerModule.Legal); // Adds server signature
360
361 await Database.Update(Identity);
362 await RuntimeCounters.IncrementCounter("Legal.ID." + Identity.State.ToString());
363
364 KeyValuePair<string, object>[] Tags = Identity.GetTags();
365
366 Log.Informational("Legal Identity application registered.", Identity.Id.Value, BareJid,
367 "LegalIdRegistered", Tags);
368
369 XmppServerModule.Legal?.IdentityAuthorization(BareJid, BareJid, Identity.Id, true);
370
371 Xml.Clear();
372 Identity.Serialize(Xml, true, true, true, true, true, true, true, null, XmppServerModule.Legal);
373 string IdentityXml = Xml.ToString();
374
375 XmlDocument IdentityDoc = XML.ParseXml(IdentityXml, true);
376
377 await Response.Return(new NamedDictionary<string, object>("IdentityResponse", AgentNamespace)
378 {
379 { "Identity", IdentityDoc }
380 });
381
383 {
384 StringBuilder Markdown = new StringBuilder();
385
386 Markdown.Append("Legal identity application received: [`");
387 Markdown.Append(Identity.Id);
388 Markdown.Append("`](");
389 Markdown.Append(Gateway.GetUrl("/LegalIdentity.md?Id="));
390 Markdown.Append(Identity.Id);
391 Markdown.AppendLine(")");
392 Markdown.AppendLine();
393 LegalComponent.Output(Markdown, Tags);
394
395 await Gateway.SendNotification(Markdown.ToString());
396
398 {
399 bool First = true;
400
401 foreach (LegalIdentity ID in await Database.Find<LegalIdentity>(new FilterAnd(
402 new FilterFieldEqualTo("Account", Identity.Account),
403 new FilterFieldNotEqualTo("Id", Identity.Id)), "Created"))
404 {
405 switch (ID.State)
406 {
407 case IoTBroker.Legal.Identity.IdentityState.Created:
408 case IoTBroker.Legal.Identity.IdentityState.Approved:
409 if (First)
410 {
411 First = false;
412 Markdown.Clear();
413 Markdown.AppendLine("Other identities registered for the same account:");
414
415 await Gateway.SendNotification(Markdown.ToString());
416 }
417
418 Markdown.Clear();
419
420 Markdown.Append("[`");
421 Markdown.Append(ID.Id);
422 Markdown.Append("`](");
423 Markdown.Append(Gateway.GetUrl("/LegalIdentity.md?Id="));
424 Markdown.Append(ID.Id);
425 Markdown.AppendLine(")");
426 Markdown.AppendLine();
427
428 LegalComponent.Output(Markdown, ID.GetTags());
429 await Gateway.SendNotification(Markdown.ToString());
430 break;
431 }
432 }
433 }
434 }
435
436 if (!(XmppServerModule.Server is null))
437 {
438 await XmppServerModule.Server.SendMessage(string.Empty, string.Empty,
439 new XmppAddress(LegalDomain), new XmppAddress(BareJid),
440 string.Empty, IdentityXml);
441 }
442 }
443
444 internal static EllipticCurveEndpoint GetEndpoint(HttpRequest Request,
445 AgentKey AgentKey, string s2)
446 {
448 out EllipticCurveEndpoint Endpoint))
449 {
450 throw new ServiceUnavailableException("Key algorithm no longer supported. Please generate a new key.");
451 }
452
453 string s4 = s2 + ":" + Convert.ToBase64String(AgentKey.Salt);
454 byte[] Key = Hashes.ComputeSHA256Hash(Encoding.UTF8.GetBytes(s4));
455 byte[] IV = new byte[16];
456
457 Buffer.BlockCopy(AgentKey.Salt, 0, IV, 0, 16);
458
459 Aes Aes = Aes.Create();
460
461 Aes.BlockSize = 128;
462 Aes.KeySize = 256;
463 Aes.Mode = CipherMode.CBC;
464 Aes.Padding = PaddingMode.PKCS7;
465
466 using ICryptoTransform Decryptor = Aes.CreateDecryptor(Key, IV);
467
468 XmlDocument Doc;
469
470 try
471 {
472 byte[] Decrypted = Decryptor.TransformFinalBlock(AgentKey.EncryptedKey, 0, AgentKey.EncryptedKey.Length);
473 Doc = XML.ParseXml(Encoding.UTF8.GetString(Decrypted));
474 }
475 catch (Exception)
476 {
477 throw new ForbiddenException(Request, "Invalid key signature.");
478 }
479
480 EllipticCurveEndpoint KeyEndpoint = (EllipticCurveEndpoint)Endpoint.Parse(Doc.DocumentElement)
481 ?? throw new ServiceUnavailableException("Key no longer supported.");
482
483 return KeyEndpoint;
484 }
485
486 }
487}
Contains personal information found in a legal identity.
Contains information about a response to a content request.
Exception Error
Error response.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > GetAsync(Uri Uri, params KeyValuePair< string, string >[] Headers)
Gets a resource, given its URI.
A Named dictionary is a dictionary, with a local name and a namespace. Use it to return content that ...
Helps with common XML-related tasks.
Definition: XML.cs:21
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Definition: Log.cs:344
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:3087
static bool IsDomain(string DomainOrHost, bool IncludeAlternativeDomains)
If a domain or host name represents the gateway.
Definition: Gateway.cs:5174
static Task< bool > HasNonceBeenUsed(string Nonce)
Checks if a Nonce value has been used.
Definition: Gateway.cs:6342
static LoginAuditor LoginAuditor
Current Login Auditor. Should be used by modules accepting user logins, to protect the system from un...
Definition: Gateway.cs:3860
static Task SendNotification(Graph Graph)
Sends a graph as a notification message to configured notification recipients.
Definition: Gateway.cs:4677
static string GetUrl(string LocalResource)
Gets a URL for a resource.
Definition: Gateway.cs:5079
static Task RegisterNonceValue(string Nonce)
Registers a nonce value.
Definition: Gateway.cs:6351
static bool HasDomain
If a domain name is configured.
Definition: Gateway.cs:3093
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static Task< ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, QCLASS CLASS)
Tries to resolve a DNS name.
Definition: DnsResolver.cs:208
Abstract base class for Resource Address Records.
Abstract base class for a resource record.
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
The server understood the request, but is refusing to fulfill it. Authorization will not help and the...
HttpFieldReferer Referer
Referer HTTP Field header. (RFC 2616, §14.36)
HttpFieldHost Host
Host HTTP Field header. (RFC 2616, §14.23)
Represents an HTTP request.
Definition: HttpRequest.cs:22
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:182
string RemoteEndPoint
Remote end-point.
Definition: HttpRequest.cs:243
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
The server has not found anything matching the Request-URI. No indication is given of whether the con...
The server is currently unable to handle the request due to a temporary overloading or maintenance of...
The user has sent too many requests in a given amount of time. Intended for use with rate limiting sc...
IE2eEndpoint Parse(XmlElement Xml)
Parses endpoint information from an XML element.
Definition: E2eEndpoint.cs:98
abstract string LocalName
Local name of the E2E encryption scheme
Definition: E2eEndpoint.cs:55
Abstract base class for Elliptic Curve endpoints.
override byte[] Sign(byte[] Data)
Signs binary data using the local private key.
XmppAddress MainDomain
Main/principal domain address
Definition: Component.cs:87
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
Task< bool > SendMessage(string Type, string Id, string From, string To, string Language, string ContentXml)
Sends a Message stanza to a recipient.
Definition: XmppServer.cs:3862
bool IsServerDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the server domain, or optionally, an alternative domain.
Definition: XmppServer.cs:898
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:1442
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field not equal to a given value.
Static class managing persistent counters.
static Task< long > IncrementCounter(CaseInsensitiveString Key)
Increments a counter.
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:13
static string LoadResourceAsText(string ResourceName)
Loads a text resource from an embedded resource.
Definition: Resources.cs:55
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
Class managing a script expression.
Definition: Expression.cs:41
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static byte[] ComputeHMACSHA256Hash(byte[] Key, byte[] Data)
Computes the HMAC-SHA-256 hash of a block of binary data.
Definition: Hashes.cs:735
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:469
Contains information about a broker account.
Definition: Account.cs:41
CaseInsensitiveString EMail
E-mail address associated with account.
Definition: Account.cs:176
string Password
Password of account
Definition: Account.cs:151
CaseInsensitiveString PhoneNr
Phone number associated with account.
Definition: Account.cs:185
Provides the user with options to control notifications from the Broker.
static BrokerNotificationConfiguration Instance
Current instance of configuration.
bool OtherLegalIds
If a notification should be sent for every existing valid Legal ID that exists when a new Legal ID ap...
bool LegalIdReceived
If a notification should be sent when a new a Legal ID application is received.
Abstract base class for agent resources supporting the POST method.
static AccountUser AssertUserAuthenticated(HttpRequest Request)
Makes sure the request is made by an authenticated API user.
const string AgentNamespace
https://waher.se/Schema/BrokerAgent.xsd
Contains an encrypted key for an agent.
Definition: AgentKey.cs:13
static bool TryGetAlgorithm(string LocalName, string Namespace, out EllipticCurveEndpoint Algorithm)
Tries to get an algorithm given its fully qualified name.
Service Module hosting the XMPP broker and its components.
QTYPE
QTYPE fields appear in the question part of a query.
Definition: QTYPE.cs:7
QCLASS
QCLASS fields appear in the question section of a query.
Definition: QCLASS.cs:7