Ejemplo n.º 1
0
        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));
        }
Ejemplo n.º 2
0
        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]));
        }
Ejemplo n.º 3
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));
        }
Ejemplo n.º 4
0
        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();
        }
Ejemplo n.º 5
0
        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);
        }
Ejemplo n.º 6
0
        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);
        }
        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));
        }