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 System;
2using System.Collections.Generic;
3using System.Security.Cryptography;
4using System.Text;
5using System.Threading.Tasks;
6using System.Xml;
7using Waher.Content;
9using Waher.Events;
17using Waher.Script;
19using Waher.Security;
24
26{
31 {
35 public ApplyId()
36 : base("Legal/ApplyId",
37 new KeyValuePair<Type, Expression>(typeof(Dictionary<string, object>), new Expression(jsonPattern)),
38 new KeyValuePair<Type, Expression>(typeof(XmlDocument), new Expression(xmlPattern)))
39 {
40 }
41
42 private static readonly string jsonPattern = Resources.LoadResourceAsText(typeof(ApplyId).Namespace + ".JSON.ApplyId.req");
43 private static readonly string xmlPattern = Resources.LoadResourceAsText(typeof(ApplyId).Namespace + ".XML.ApplyId.req");
44
53 public override async Task POST(HttpRequest Request, HttpResponse Response, Dictionary<string, IElement> Parameters)
54 {
56
57 string KeyId = (string)Parameters["PKeyId"].AssociatedObjectValue;
58 string Nonce = (string)Parameters["PNonce"].AssociatedObjectValue;
59 string KeySignature = (string)Parameters["PKeySignature"].AssociatedObjectValue;
60 string RequestSignature = (string)Parameters["PRequestSignature"].AssociatedObjectValue;
61 object[] PropertyNames = (object[])Parameters["PPropertyName"].AssociatedObjectValue;
62 object[] PropertyValues = (object[])Parameters["PPropertyValue"].AssociatedObjectValue;
63 Dictionary<CaseInsensitiveString, Property> PropertiesByName = new Dictionary<CaseInsensitiveString, Property>();
64 List<Property> Properties = new List<Property>();
65 StringBuilder sb = new StringBuilder();
66 string Agent = Request.Header.Referer?.Value;
67
68 if (string.IsNullOrEmpty(Agent))
69 throw new BadRequestException("Missing Referer header.");
70
71 if (string.IsNullOrEmpty(KeyId))
72 throw new BadRequestException("Key ID cannot be empty.");
73
74 if (string.IsNullOrEmpty(Nonce) || Nonce.Length < 32)
75 throw new ForbiddenException("Nonce too short.");
76
77 AgentKey AgentKey = await Database.FindFirstDeleteRest<AgentKey>(new FilterAnd(
78 new FilterFieldEqualTo("Account", User.UserName),
79 new FilterFieldEqualTo("Id", KeyId)))
80 ?? throw new NotFoundException("Key not found.");
81
82 sb.Append(User.UserName);
83 sb.Append(':');
84 sb.Append(Request.Header.Host.Value);
85 sb.Append(':');
86 sb.Append(AgentKey.LocalName);
87 sb.Append(':');
88 sb.Append(AgentKey.Namespace);
89 sb.Append(':');
90 sb.Append(KeyId);
91
92 //string s1 = sb.ToString();
93
94 sb.Append(':');
95 sb.Append(KeySignature);
96
97 string s2 = sb.ToString();
98
99 sb.Append(':');
100 sb.Append(Nonce);
101
102 int i, c = PropertyNames?.Length ?? 0;
103 if ((PropertyValues?.Length ?? 0) != c)
104 throw new BadRequestException("Invalid Properties.");
105
106 bool JidAdded = false;
107 bool EMailAdded = false;
108 bool PhoneNrAdded = false;
109
110 for (i = 0; i < c; i++)
111 {
112 if (!(PropertyNames[i] is string PropertyName) || string.IsNullOrEmpty(PropertyName))
113 throw new BadRequestException("Invalid Property Name.");
114
115 if (!(PropertyValues[i] is string PropertyValue) || string.IsNullOrEmpty(PropertyValue))
116 throw new BadRequestException("Invalid Property Value.");
117
118 switch (PropertyName.ToUpper())
119 {
120 case "AGENT":
121 throw new BadRequestException("AGENT property is reserved.");
122
123 case "JID":
124 int j = PropertyValue.IndexOf('@');
125 if (j < 0)
126 throw new BadRequestException("Invalid JID.");
127
128 if (PropertyValue.Substring(0, j) != User.UserName)
129 throw new BadRequestException("JID does not match sender Bare JID.");
130
131 if (!Gateway.IsDomain(PropertyValue.Substring(j + 1), true))
132 throw new BadRequestException("JID does not match sender Bare JID.");
133
134 JidAdded = true;
135 break;
136
137 case "EMAIL":
138 if (PropertyValue != User.Account.EMail && !string.IsNullOrEmpty(User.Account.EMail))
139 throw new BadRequestException("EMAIL does not match account e-mail.");
140
141 EMailAdded = true;
142 break;
143
144 case "PHONE":
145 if (PropertyValue != User.Account.PhoneNr && !string.IsNullOrEmpty(User.Account.PhoneNr))
146 throw new BadRequestException("PHONE does not match account phone number.");
147
148 PhoneNrAdded = true;
149 break;
150 }
151
152 Property Property = new Property(PropertyName, PropertyValue);
153
154 if (PropertiesByName.ContainsKey(Property.Name))
155 throw new BadRequestException("Duplicate property.");
156
157 Properties.Add(Property);
158 PropertiesByName[Property.Name] = Property;
159
160 sb.Append(':');
161 sb.Append(Property.Name.Value);
162 sb.Append(':');
163 sb.Append(Property.Value.Value);
164 }
165
166 string s3 = sb.ToString();
167
168 string s = Convert.ToBase64String(
170 Encoding.UTF8.GetBytes(User.Account.Password),
171 Encoding.UTF8.GetBytes(s3)));
172
173 if (s != RequestSignature)
174 {
175 string Msg = "Request Signature invalid.";
176 throw new ForbiddenException(Msg);
177 }
178
179 if (await this.Api.HasNonceBeenUsed(Nonce))
180 {
181 string Msg = "Nonce value has already been used.";
182 throw new ForbiddenException(Msg);
183 }
184
185 await this.Api.RegisterNonceValue(Nonce);
186
187 EllipticCurveEndpoint KeyEndpoint = GetEndpoint(AgentKey, s2);
188 string BareJid = User.UserName + "@" + Gateway.Domain;
189
190 if (!JidAdded)
191 Properties.Add(new Property("JID", BareJid));
192
193 if (!EMailAdded && !string.IsNullOrEmpty(User.Account.EMail))
194 Properties.Add(new Property("EMAIL", User.Account.EMail));
195
196 if (!PhoneNrAdded && !string.IsNullOrEmpty(User.Account.PhoneNr))
197 Properties.Add(new Property("PHONE", User.Account.PhoneNr));
198
199 Properties.Add(new Property("AGENT", Agent));
200
201 DateTime TP = LegalComponent.NowSecond;
202 DateTime From = TP.Date;
203 CaseInsensitiveString LegalDomain = XmppServerModule.Legal?.MainDomain.Address ?? "legal.example.com";
204
205 LegalIdentity Identity = new LegalIdentity()
206 {
207 Account = User.UserName,
208 ClientKeyName = KeyEndpoint.LocalName,
209 ClientPubKey = KeyEndpoint.PublicKey,
210 Created = TP,
211 Updated = DateTime.MinValue,
212 From = From,
213 To = From.AddMonths((int)await RuntimeSettings.GetAsync("LegalIdentity.Months", 24)),
214 State = IdentityState.Created,
215 Provider = LegalDomain,
216 Properties = Properties.ToArray(),
217 Version = NamespaceSet.Current
218 };
219
220 StringBuilder Xml = new StringBuilder();
221 Identity.Serialize(Xml, false, false, false, false, false, false, false, null, XmppServerModule.Legal);
222 Identity.ClientSignature = KeyEndpoint.Sign(Encoding.UTF8.GetBytes(Xml.ToString()));
223
224 foreach (LegalIdentity ToRemove in await Database.FindDelete<LegalIdentity>(new FilterAnd(
225 new FilterFieldEqualTo("Account", User.UserName),
226 new FilterFieldEqualTo("State", IdentityState.Created))))
227 {
228 Log.Informational("Obsolete Legal Identity Registration deleted.",
229 ToRemove.Id.Value, BareJid, "LegalIdDeleted", ToRemove.GetTags());
230 }
231
232 await Database.Insert(Identity);
233
234 Identity.Id = Identity.ObjectId + "@" + LegalDomain;
235 Identity.Sign(XmppServerModule.Legal); // Adds server signature
236
237 await Database.Update(Identity);
238
239 KeyValuePair<string, object>[] Tags = Identity.GetTags();
240
241 Log.Informational("Legal Identity application registered.", Identity.Id.Value, BareJid,
242 "LegalIdRegistered", Tags);
243
244 XmppServerModule.Legal?.IdentityAuthorization(BareJid, BareJid, Identity.Id, true);
245
246 Xml.Clear();
247 Identity.Serialize(Xml, true, true, true, true, true, true, true, null, XmppServerModule.Legal);
248 string IdentityXml = Xml.ToString();
249
250 XmlDocument IdentityDoc = new XmlDocument()
251 {
252 PreserveWhitespace = true
253 };
254 IdentityDoc.LoadXml(IdentityXml);
255
256 await Response.Return(new NamedDictionary<string, object>("IdentityResponse", AgentNamespace)
257 {
258 { "Identity", IdentityDoc }
259 });
260
261 StringBuilder Markdown = new StringBuilder();
262
263 Markdown.Append("Legal identity application received: [`");
264 Markdown.Append(Identity.Id);
265 Markdown.Append("`](");
266 Markdown.Append(Gateway.GetUrl("/LegalIdentity.md?Id="));
267 Markdown.Append(Identity.Id);
268 Markdown.AppendLine(")");
269 Markdown.AppendLine();
270 LegalComponent.Output(Markdown, Tags);
271
272 await Gateway.SendNotification(Markdown.ToString());
273
274 bool First = true;
275
276 foreach (LegalIdentity ID in await Database.Find<LegalIdentity>(new FilterAnd(
277 new FilterFieldEqualTo("Account", Identity.Account),
278 new FilterFieldNotEqualTo("Id", Identity.Id)), "Created"))
279 {
280 switch (ID.State)
281 {
282 case IdentityState.Created:
283 case IdentityState.Approved:
284 if (First)
285 {
286 First = false;
287 Markdown.Clear();
288 Markdown.AppendLine("Other identities registered for the same account:");
289
290 await Gateway.SendNotification(Markdown.ToString());
291 }
292
293 Markdown.Clear();
294
295 Markdown.Append("[`");
296 Markdown.Append(ID.Id);
297 Markdown.Append("`](");
298 Markdown.Append(Gateway.GetUrl("/LegalIdentity.md?Id="));
299 Markdown.Append(ID.Id);
300 Markdown.AppendLine(")");
301 Markdown.AppendLine();
302
303 LegalComponent.Output(Markdown, ID.GetTags());
304 await Gateway.SendNotification(Markdown.ToString());
305 break;
306 }
307 }
308
309 if (!(XmppServerModule.Server is null))
310 {
311 await XmppServerModule.Server.SendMessage(string.Empty, string.Empty,
312 new XmppAddress(LegalDomain), new XmppAddress(BareJid),
313 string.Empty, IdentityXml);
314 }
315 }
316
317 internal static EllipticCurveEndpoint GetEndpoint(AgentKey AgentKey, string s2)
318 {
320 out EllipticCurveEndpoint Endpoint))
321 {
322 throw new ServiceUnavailableException("Key algorithm no longer supported. Please generate a new key.");
323 }
324
325 string s4 = s2 + ":" + Convert.ToBase64String(AgentKey.Salt);
326 byte[] Key = Hashes.ComputeSHA256Hash(Encoding.UTF8.GetBytes(s4));
327 byte[] IV = new byte[16];
328
329 Array.Copy(AgentKey.Salt, 0, IV, 0, 16);
330
331 Aes Aes = Aes.Create();
332
333 Aes.BlockSize = 128;
334 Aes.KeySize = 256;
335 Aes.Mode = CipherMode.CBC;
336 Aes.Padding = PaddingMode.PKCS7;
337
338 using (ICryptoTransform Decryptor = Aes.CreateDecryptor(Key, IV))
339 {
340 XmlDocument Doc;
341
342 try
343 {
344 byte[] Decrypted = Decryptor.TransformFinalBlock(AgentKey.EncryptedKey, 0, AgentKey.EncryptedKey.Length);
345 Doc = new XmlDocument();
346 Doc.LoadXml(Encoding.UTF8.GetString(Decrypted));
347 }
348 catch (Exception)
349 {
350 throw new ForbiddenException("Invalid key signature.");
351 }
352
353 EllipticCurveEndpoint KeyEndpoint = (EllipticCurveEndpoint)Endpoint.Parse(Doc.DocumentElement)
354 ?? throw new ServiceUnavailableException("Key no longer supported.");
355
356 return KeyEndpoint;
357 }
358 }
359
360 }
361}
A Named dictionary is a dictionary, with a local name and a namespace. Use it to return content that ...
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:15
static string LoadResourceAsText(string ResourceName)
Loads a text resource from an embedded resource.
Definition: Resources.cs:96
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
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:334
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static CaseInsensitiveString Domain
Domain name.
Definition: Gateway.cs:2354
static bool IsDomain(string DomainOrHost, bool IncludeAlternativeDomains)
If a domain or host name represents the gateway.
Definition: Gateway.cs:4258
static Task SendNotification(Graph Graph)
Sends a graph as a notification message to configured notification recipients.
Definition: Gateway.cs:3826
static string GetUrl(string LocalResource)
Gets a URL for a resource.
Definition: Gateway.cs:4167
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:18
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:134
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:21
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...
IE2eEndpoint Parse(XmlElement Xml)
Parses endpoint information from an XML element.
Definition: E2eEndpoint.cs:107
abstract string LocalName
Local name of the E2E encryption scheme
Definition: E2eEndpoint.cs:56
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:86
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:3412
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:19
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:879
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
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 settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
Class managing a script expression.
Definition: Expression.cs:39
Contains methods for simple hash calculations.
Definition: Hashes.cs:59
static byte[] ComputeHMACSHA256Hash(byte[] Key, byte[] Data)
Computes the HMAC-SHA-256 hash of a block of binary data.
Definition: Hashes.cs:585
static byte[] ComputeSHA256Hash(byte[] Data)
Computes the SHA-256 hash of a block of binary data.
Definition: Hashes.cs:348
Contains information about a broker account.
Definition: Account.cs:28
CaseInsensitiveString EMail
E-mail address associated with account.
Definition: Account.cs:129
string Password
Password of account
Definition: Account.cs:109
CaseInsensitiveString PhoneNr
Phone number associated with account.
Definition: Account.cs:138
Task< bool > HasNonceBeenUsed(string Nonce)
Checks if a Nonce value has been used.
Definition: AgentApi.cs:113
Task RegisterNonceValue(string Nonce)
Registers a nonce value.
Definition: AgentApi.cs:122
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.