public Tuple <string, byte[]> Get(List <Tuple <int, int> > characteristics, ControllerSession session) { JArray characteristicsArray = new JArray(); foreach (var characteristic in characteristics) { JObject characteristicObject = new JObject(); characteristicObject.Add("aid", characteristic.Item1); characteristicObject.Add("iid", characteristic.Item2); if (characteristic.Item1 == 2) { characteristicObject.Add("value", true); } else if (characteristic.Item1 == 3) { characteristicObject.Add("value", 1); } characteristicsArray.Add(characteristicObject); } JObject jsonObj = new JObject(); jsonObj.Add("characteristics", characteristicsArray); var characteristicJson = jsonObj.ToString(); var output = Encoding.UTF8.GetBytes(characteristicJson); return(new Tuple <string, byte[]>("application/hap+json", output)); }
internal Tuple <string, byte[]> Put(byte[] v, ControllerSession session) { var json = Encoding.UTF8.GetString(v); JObject jsonObject = JObject.Parse(json); return(new Tuple <string, byte[]>("application/hap+json", new byte[0])); }
public Tuple <string, byte[]> Get(ControllerSession session) { Console.WriteLine("* Accessories Controller"); Console.WriteLine("* List accessories"); var output = File.ReadAllBytes(@"C:\Development\dotnet-homebridge\ColdBear.Console\accessories.json"); return(new Tuple <string, byte[]>("application/hap+json", output)); }
public Tuple <string, byte[]> Post(byte[] body, ControllerSession session) { var parts = TLVParser.Parse(body); var state = parts.GetTypeAsInt(Constants.State); var method = parts.GetTypeAsInt(Constants.Method); Console.WriteLine("***********************"); Console.WriteLine("* Pairings Controller *"); Console.WriteLine($"* State: {state} *"); Console.WriteLine($"* Method: {state} *"); Console.WriteLine("***********************"); TLV responseTLV = new TLV(); if (state == 1) { if (method == 3) // Add Pairing { Console.WriteLine("* Add Pairing"); var identifier = parts.GetType(Constants.Identifier); var publickey = parts.GetType(Constants.PublicKey); var permissions = parts.GetType(Constants.Permissions); LiteDB.LiteDatabase database = new LiteDB.LiteDatabase("Filename=Hap.db"); var pairingsCollection = database.GetCollection("pairings"); var existingPairing = pairingsCollection.FindById(Encoding.UTF8.GetString(identifier)); if (existingPairing == null) { var pairing = new LiteDB.BsonDocument(); var doc = new LiteDB.BsonDocument(); doc.Add("publickey", new LiteDB.BsonValue(publickey)); doc.Add("permissions", new LiteDB.BsonValue(permissions)); pairing.Add(Encoding.UTF8.GetString(identifier), doc); pairingsCollection.Insert(pairing); } else { // TODO DO something here. } responseTLV.AddType(Constants.State, 2); byte[] output1 = TLVParser.Serialise(responseTLV); return(new Tuple <string, byte[]>("application/pairing+tlv8", output1)); } else if (method == 4) // Remove Pairing { Console.WriteLine("* Remove Pairing"); responseTLV = new TLV(); responseTLV.AddType(Constants.State, 2); byte[] output2 = TLVParser.Serialise(responseTLV); return(new Tuple <string, byte[]>("application/pairing+tlv8", output2)); } if (method == 5) // List Pairing { Console.WriteLine("* List Pairings"); responseTLV = new TLV(); responseTLV.AddType(Constants.State, 2); byte[] output3 = TLVParser.Serialise(responseTLV); return(new Tuple <string, byte[]>("application/pairing+tlv8", output3)); } } responseTLV.AddType(Constants.State, 2); responseTLV.AddType(Constants.Error, ErrorCodes.Busy); byte[] output = TLVParser.Serialise(responseTLV); return(new Tuple <string, byte[]>("application/pairing+tlv8", output)); }
public Tuple <string, byte[]> Post(byte[] body, ControllerSession session) { var parts = TLVParser.Parse(body); var state = parts.GetTypeAsInt(Constants.State); if (state == 1) { Console.WriteLine("* Pair Verify Step 1/4"); Console.WriteLine("* Verify Start Request"); var clientPublicKey = parts.GetType(Constants.PublicKey); byte[] privateKey = new byte[32]; SecureRandom random = new SecureRandom(); random.NextBytes(privateKey); var publicKey = Curve25519.GetPublicKey(privateKey); var sharedSecret = Curve25519.GetSharedSecret(privateKey, clientPublicKey); var serverUsername = Encoding.UTF8.GetBytes(Program.ID); byte[] material = publicKey.Concat(serverUsername).Concat(clientPublicKey).ToArray(); var accessoryLTSK = File.ReadAllBytes("PrivateKey"); byte[] proof = Ed25519.Sign(material, accessoryLTSK); HKDF g = new HKDF(() => { return(new HMACSHA512()); }, sharedSecret, Encoding.UTF8.GetBytes("Pair-Verify-Encrypt-Salt"), Encoding.UTF8.GetBytes("Pair-Verify-Encrypt-Info")); var outputKey = g.GetBytes(32); TLV encoder = new TLV(); encoder.AddType(Constants.Identifier, serverUsername); encoder.AddType(Constants.Signature, proof); byte[] plaintext = TLVParser.Serialise(encoder); var nonce = Cnv.FromHex("00000000").Concat(Encoding.UTF8.GetBytes("PV-Msg02")).ToArray(); var aad = new byte[0]; byte[] outputTag = new byte[0]; var encryptedOutput = Aead.Encrypt(out outputTag, plaintext, outputKey, nonce, aad, Aead.Algorithm.Chacha20_Poly1305); byte[] ret = encryptedOutput.Concat(outputTag).ToArray(); TLV responseTLV = new TLV(); responseTLV.AddType(Constants.State, 2); responseTLV.AddType(Constants.PublicKey, publicKey); responseTLV.AddType(Constants.EncryptedData, ret); // Store the details on the session. // session.ClientPublicKey = clientPublicKey; session.PrivateKey = privateKey; session.PublicKey = publicKey; session.SharedSecret = sharedSecret; session.HkdfPairEncKey = outputKey; var encSalt = Encoding.UTF8.GetBytes("Control-Salt"); var infoRead = Encoding.UTF8.GetBytes("Control-Read-Encryption-Key"); var infoWrite = Encoding.UTF8.GetBytes("Control-Write-Encryption-Key"); g = new HKDF(() => { return(new HMACSHA512()); }, sharedSecret, encSalt, infoRead); session.AccessoryToControllerKey = g.GetBytes(32); g = new HKDF(() => { return(new HMACSHA512()); }, sharedSecret, encSalt, infoWrite); session.ControllerToAccessoryKey = g.GetBytes(32); var output = TLVParser.Serialise(responseTLV); return(new Tuple <string, byte[]>("application/pairing+tlv8", output)); } else if (state == 3) { Console.WriteLine("* Pair Verify Step 3/4"); Console.WriteLine("* Verify Start Request"); // We're looking good here. Need to set the encryption/settings on this session. // session.IsVerified = true; session.SkipFirstEncryption = true; TLV responseTLV = new TLV(); responseTLV.AddType(Constants.State, 4); var output = TLVParser.Serialise(responseTLV); return(new Tuple <string, byte[]>("application/pairing+tlv8", output)); } return(null); }
private static void HandleControllerConnection(object obj) { TcpClient tcpClient = (TcpClient)obj; // Set keepalive to true! // //tcpClient.Client.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, true); string clientEndPoint = tcpClient.Client.RemoteEndPoint.ToString(); Console.WriteLine($"Handling a new controller connection from {clientEndPoint}"); ControllerSession session = new ControllerSession(); CurrentSession = session; using (var networkStream = tcpClient.GetStream()) { byte[] receiveBuffer = new byte[tcpClient.ReceiveBufferSize]; var keepListening = true; while (keepListening) { Console.WriteLine("Waiting for more data from the client...."); // This is blocking and will wait for data to come from the client. // var bytesRead = networkStream.Read(receiveBuffer, 0, (int)tcpClient.ReceiveBufferSize); Console.WriteLine("**************************** REQUEST RECEIVED *************************"); if (bytesRead == 0) { // Read returns 0 if the client closes the connection. // break; } var content = receiveBuffer.CopySlice(0, bytesRead); if (session.IsVerified) { Console.WriteLine("* DECRYPTING REQUEST *"); var encryptionResult = new byte[0]; for (int offset = 0; offset < bytesRead;) { // The first type bytes represent the length of the data. // byte[] twoBytes = new Byte[] { content[0], content[1] }; offset += 2; UInt16 frameLength = BitConverter.ToUInt16(twoBytes, 0); int availableDataLength = bytesRead - offset - 16; byte[] messageData = new byte[availableDataLength]; Buffer.BlockCopy(content, offset, messageData, 0, availableDataLength); offset += availableDataLength; byte[] authTag = new byte[16]; Buffer.BlockCopy(content, offset, authTag, 0, 16); var nonce = Cnv.FromHex("00000000").Concat(BitConverter.GetBytes(session.InboundBinaryMessageCount++)).ToArray(); // Use the AccessoryToController key to decrypt the data. // var decryptedData = Aead.Decrypt(messageData, session.ControllerToAccessoryKey, nonce, twoBytes, authTag, Aead.Algorithm.Chacha20_Poly1305); encryptionResult = encryptionResult.Concat(decryptedData).ToArray(); offset += (18 + frameLength); } content = encryptionResult; } var ms = new MemoryStream(content); StreamReader sr = new StreamReader(ms); String request = sr.ReadLine(); string[] tokens = request.Split(' '); if (tokens.Length != 3) { throw new Exception("Invalid HTTP request line"); } var method = tokens[0].ToUpper(); var url = tokens[1].Trim('/'); var version = tokens[2]; string line; Dictionary <string, string> httpHeaders = new Dictionary <string, string>(); while ((line = sr.ReadLine()) != null) { if (line.Equals("")) { break; } int separator = line.IndexOf(':'); if (separator == -1) { throw new Exception("invalid http header line: " + line); } String name = line.Substring(0, separator); int pos = separator + 1; while ((pos < line.Length) && (line[pos] == ' ')) { pos++; // strip any spaces } string value = line.Substring(pos, line.Length - pos); Console.WriteLine("* Header: {0}:{1}", name, value); httpHeaders[name.ToLower()] = value; } Console.WriteLine($"* URL: {url}"); int BUF_SIZE = 4096; int content_len = 0; MemoryStream contentMs = new MemoryStream(); if (httpHeaders.ContainsKey("content-length")) { content_len = Convert.ToInt32(httpHeaders["content-length"]); if (content_len > 20000) { throw new Exception(String.Format("POST Content-Length({0}) too big for this simple server", content_len)); } ms.Position = ms.Position - content_len; var temp = new byte[ms.Length - ms.Position]; Array.Copy(ms.ToArray(), (int)ms.Position, temp, 0, (int)ms.Length - ms.Position); BinaryReader br = new BinaryReader(ms); if (httpHeaders.ContainsKey("content-length")) { byte[] buf = new byte[BUF_SIZE]; int to_read = content_len; while (to_read > 0) { int numread = br.Read(buf, 0, Math.Min(BUF_SIZE, to_read)); if (numread == 0) { if (to_read == 0) { break; } else { throw new Exception("client disconnected during post"); } } to_read -= numread; contentMs.Write(buf, 0, numread); } contentMs.Seek(0, SeekOrigin.Begin); } } Tuple <string, byte[]> result = null; if (url == "pair-setup") { PairSetupController controller = new PairSetupController(); result = controller.Post(contentMs.ToArray()); } else if (url == "pair-verify") { PairVerifyController controller = new PairVerifyController(); result = controller.Post(contentMs.ToArray(), session); } else if (url == "accessories") { AccessoriesController controller = new AccessoriesController(); result = controller.Get(session); } else if (url == "pairings") { PairingsController controller = new PairingsController(); result = controller.Post(contentMs.ToArray(), session); } else if (url.StartsWith("characteristics")) { // The url will contain a query string e.g. id=1.1 meaning accessoryId 1 with characteristic 1 // CharacteristicsController controller = new CharacteristicsController(); if (method == "PUT") { result = controller.Put(contentMs.ToArray(), session); } else if (method == "GET") { var parts = url.Split('?'); var queryStringing = parts[1].Replace("id=", ""); var accessoriesParts = queryStringing.Split(','); List <Tuple <int, int> > accessories = new List <Tuple <int, int> >(); foreach (var accessoryString in accessoriesParts) { var accessoryParts = accessoryString.Split('.'); var aid = int.Parse(accessoryParts[0]); var iid = int.Parse(accessoryParts[1]); accessories.Add(new Tuple <int, int>(aid, iid)); } result = controller.Get(accessories, session); } } else { Console.WriteLine($"* Request for {url} is not yet supported!"); throw new Exception("Not Supported"); } // Construct the response. We're assuming 100% success, all of the time, for now. // var response = new byte[0]; var returnChars = new byte[2]; returnChars[0] = 0x0D; returnChars[1] = 0x0A; var contentLength = $"Content-Length: {result.Item2.Length}"; if (result.Item2.Length == 0) { response = response.Concat(Encoding.ASCII.GetBytes("HTTP/1.1 204 No Content")).Concat(returnChars).ToArray(); } else { response = response.Concat(Encoding.ASCII.GetBytes("HTTP/1.1 200 OK")).Concat(returnChars).ToArray(); response = response.Concat(Encoding.ASCII.GetBytes(contentLength)).Concat(returnChars).ToArray(); response = response.Concat(Encoding.ASCII.GetBytes($"Content-Type: {result.Item1}")).Concat(returnChars).ToArray(); } response = response.Concat(returnChars).ToArray(); response = response.Concat(result.Item2).ToArray(); if (session.IsVerified && !session.SkipFirstEncryption) { // We need to decrypt the request! // Console.WriteLine("* ENCRYPTING RESPONSE"); var resultData = new byte[0]; for (int offset = 0; offset < response.Length;) { int length = Math.Min(response.Length - offset, 1024); var dataLength = BitConverter.GetBytes((short)length); resultData = resultData.Concat(dataLength).ToArray(); var nonce = Cnv.FromHex("00000000").Concat(BitConverter.GetBytes(session.OutboundBinaryMessageCount++)).ToArray(); var dataToEncrypt = new byte[length]; Array.Copy(response, offset, dataToEncrypt, 0, length); // Use the AccessoryToController key to decrypt the data. // var authTag = new byte[16]; var encryptedData = Aead.Encrypt(out authTag, dataToEncrypt, session.AccessoryToControllerKey, nonce, dataLength, Aead.Algorithm.Chacha20_Poly1305); resultData = resultData.Concat(encryptedData).Concat(authTag).ToArray(); offset += length; } response = resultData; networkStream.Write(response, 0, response.Length); networkStream.Flush(); } else { networkStream.Write(response, 0, response.Length); networkStream.Flush(); if (session.SkipFirstEncryption) { session.SkipFirstEncryption = false; } } Console.WriteLine("**************************** RESPONSE SENT ******************************"); } } Console.WriteLine($"Connection from {clientEndPoint} will be closed!"); tcpClient.Close(); tcpClient.Dispose(); }
public byte[] Put(ControllerSession session, int aid, int iid, object value) { Console.WriteLine("* Accessories Controller"); Console.WriteLine("* Update accessory"); if (session == null) { return(new byte[0]); } //var output = File.ReadAllBytes(@"C:\Development\dotnet-homebridge\ColdBear.Console\accessories.json"); JArray characteristicsArray = new JArray(); JObject characteristicObject = new JObject(); characteristicObject.Add("aid", aid); characteristicObject.Add("iid", iid); characteristicObject.Add("value", (int)value); characteristicsArray.Add(characteristicObject); JObject jsonObj = new JObject(); jsonObj.Add("characteristics", characteristicsArray); var characteristicJson = jsonObj.ToString(); var output = Encoding.UTF8.GetBytes(characteristicJson); var response = new byte[0]; var returnChars = new byte[2]; returnChars[0] = 0x0D; returnChars[1] = 0x0A; var contentLength = $"Content-Length: {output.Length}"; response = response.Concat(Encoding.ASCII.GetBytes("EVENT/1.0 200 OK")).Concat(returnChars).ToArray(); response = response.Concat(Encoding.ASCII.GetBytes($"Content-Type: application/hap+json")).Concat(returnChars).ToArray(); response = response.Concat(Encoding.ASCII.GetBytes(contentLength)).Concat(returnChars).ToArray(); response = response.Concat(returnChars).ToArray(); response = response.Concat(output).ToArray(); Console.WriteLine("* ENCRYPTING EVENT"); var resultData = new byte[0]; for (int offset = 0; offset < response.Length;) { int length = Math.Min(response.Length - offset, 1024); var dataLength = BitConverter.GetBytes((short)length); resultData = resultData.Concat(dataLength).ToArray(); var nonce = Cnv.FromHex("00000000").Concat(BitConverter.GetBytes(session.OutboundBinaryMessageCount++)).ToArray(); var dataToEncrypt = new byte[length]; Array.Copy(response, offset, dataToEncrypt, 0, length); // Use the AccessoryToController key to decrypt the data. // var authTag = new byte[16]; var encryptedData = Aead.Encrypt(out authTag, dataToEncrypt, session.AccessoryToControllerKey, nonce, dataLength, Aead.Algorithm.Chacha20_Poly1305); resultData = resultData.Concat(encryptedData).Concat(authTag).ToArray(); offset += length; } response = resultData; return(response); }