About a year ago I moved into a new apartment complex with managed smart door locks. As cool as it was, the problems with the system were obvious from the start. Not to mention that the system, made by Chirp (owned by RealPage), was the subject of a big security controversy and government advisory that was later retracted. The Chirp app does provide a convenient experience for its users, but it severely restricts any kind of automation capabilities by disabling a lot of functionality I think it should have. The app’s real customers are the owners of the apartment buildings, so I guess that makes sense that they would prefer to restrict what actual tenant users would want to do with the app. However, we have a powerful friend that is going to help us break free of these restrictions: Charles Proxy.
Introducing Our Hero: Charles Link to heading
Charles Proxy for iOS (www.charlesproxy.com), very similar to the (in)famous app for Windows/Linux/Mac, allows for proxying network requests, stripping TLS connections, and inspecting them. It can even install a Root CA on the phone for you to help with the TLS proxying and provisioning certificates to appear like the remote HTTPS endpoint apps are talking to. Unless an app is pinning their certificates1
The Problem with Chirp’s App Link to heading
How Chirp is structured makes automation in general extremely difficult. They don’t provide a public API, most of the building doors are opened via the internet, and their iOS app doesn’t expose any kind of native shortcuts to perform actions.
I’ve already figured out how to make Apple Shortcuts that “open” URLs that correspond to various doors in the complex by reading the QR codes you’re supposed to scan to quickly open that specific door in the app. That was a nice Quality-of-Life improvement so I could more quickly open the app and open the specific door I wanted (e.g. using the programmable button to open the Garage Gate without having to wait for the app to load and then tap around while driving in the garage). I did look at how the API works, which appears to be some kind of GraphQL API, but I haven’t bothered to talk to the API directly yet. Maybe a future project…2
But for the actual apartment doors Yale-branded locks are used via Bluetooth. Even though these locks actually have the Z-Wave and HomeKit capabilities I would want, Chirp apparently configures the Yale-branded locks so they can only be controlled via Bluetooth.
When I opened up my lock once and scanned the HomeKit bar-code I somehow misconfigured my lock to the point someone needed to come back out and reconfigure it so I could manage the offline PIN codes and open/close the lock via the App again.
But thanks to Charles Proxy I was able to see all the requests the App made when it was trying to unlock my front door. And that’s all we needed to fix my problems…
Solving the Problem with Charles Proxy Link to heading
With Charles Proxy I was able to MITM the TLS connections from the Chirp app to their API and Yale’s API.
From the responses is how I realized that HomeKit and Z-Wave were in-fact supported; they were just turned off.
But also from the responses I was able to pull back the Offline Private Key
I needed to free control of the lock from the App.
Home Assistant Configuration Link to heading
As a (big) fan of NixOS, I manage my local server using a private Nix Flake repo for managing all my personal servers and infrastructure. So, even though it’s not recommended by Home Assistant, I manage my Home Assistant entirely via NixOS as well. I learned that I already had an option for enabling the Yale Bluetooth component, so I did that in my config and deployed my changes.
The Yale integration quickly found my lock via Bluetooth (and several others). I compared the MAC Address in Home Assistant with what was in the API response from the Yale API to confirm this was my lock and then I configured the new HASS Yale Lock device with my offline key I pulled from the request, which allowed me to finally control my door lock without the Chirp app.
The API Response also helped me feel confident about other options when configuring the lock in HASS like which “slot” to use and etc.
It was surprisingly simple!
(Click here to see my NixOS configuration snippets)
Some important notes:
- I had to enable quite a few things to get bluetooth: DBus, bluetooth hardware support, experimental support in BlueZ, and Blueman.
- (Not shown) I have made a daily SystemD task to restart the power to the bluetooth dongle because it gets in a weird state that prevents communicating with anything bluetooth related.
- I created a simple “button” that is not a lock so iOS shortcuts/automations can control the lock without requiring additional confirmation.
- Obviously be careful with this one because iOS does that by default to avoid someone with access to your phone being able to unlock the door without additional confirmation.
- You must expose the Lock as an accessory, not via the normal bridge, because otherwise HomeKit can’t properly see all the information it should and it also makes inefficient calls that can sometimes lead to the bluetooth connection failing.
{ config, lib, pkgs, ... }: {
config = {
services.dbus = {
enable = true;
implementation = "broker";
};
# We have USB Bluetooth device now
hardware.bluetooth = {
enable = true; # enables support for Bluetooth
settings = {
General = {
Experimental = true;
};
};
};
services.blueman.enable = true;
hardware.usb-modeswitch.enable = true; # Experiment
services.home-assistant = {
enable = true;
package =
(pkgs.home-assistant.override {
extraPackages = py: with py; [ psycopg2 ];
}).overrideAttrs
(oldAttrs: {
doInstallCheck = false;
});
config = {
# Includes dependencies for a basic setup
# https://www.home-assistant.io/integrations/default_config/
default_config = { };
input_boolean = {
lock_control = {
name = "Lock Control";
icon = "mdi:lock-smart";
};
};
homeassistant = {
name = "Home";
latitude = "!secret latitude";
longitude = "!secret longitude";
elevation = "!secret elevation";
unit_system = "us_customary";
time_zone = "America/New_York";
};
homekit = [
{
name = "HASS Bridge";
port = 21065;
mode = "bridge";
filter = {
include_entities = [
# ...
"input_boolean.lock_control"
];
};
entity_config = {
# ...
};
}
{
name = "Apartment Door";
port = 21066;
mode = "accessory";
filter.include_domains = [ "lock" ];
entity_config = {
"lock.apartment_lock" = {
name = "Apartment Lock";
};
};
}
];
};
extraComponents = [
# (... all the existing components you need)
"yale"
"yale_home"
];
};
};
}
I have not upgraded to Grafana Alloy yet, but I have created a PromTail config which helps with processing the logs from Home Assistant. In particular dealing with the Python Exceptions that happen when it cannot connect to the lock.
I have trimmed my config again to just the relevant config. In particular the working multi-line filter for PromTail. I recommend simply maintaining a config.yaml
for your PromTail config instead trying to force things into Nix because getting the Multi-Line filter working required debugging how exactly what is written here was being converted into YAML. You could mix both worlds by simply importing the YAML and merging it with whatever Nix specific config you have, but I highly recommend simply maintaining the configuration in a real YAML file. </rant>
{ config, lib, pkgs, ... }: {
config = {
services.promtail = {
enable = true;
# configFile = null;
configuration = {
# positions.filename = "/tmp/positions.yaml";
# clients = [ { url = "http://127.0.0.1:3100/loki/api/v1/push"; } ];
scrape_configs = [ {
# job_name = "journal";
# journal = {
# max_age = "12h";
# labels.job = "systemd-journal";
# };
# relabel_configs = [
# {
# source_labels = [ "__journal__systemd_unit" ];
# target_label = "unit";
# }
# {
# source_labels = [ "__journal__hostname" ];
# target_label = "hostname";
# }
# {
# source_labels = [ "__journal_syslog_identifier" ];
# target_label = "syslog_identifier";
# }
# {
# source_labels = [ "__journal_transport" ];
# target_label = "transport";
# }
# {
# source_labels = [ "__journal_priority" ];
# target_label = "priority";
# }
# ];
pipeline_stages = [ {
match = {
selector = ''{unit="home-assistant.service"}'';
stages = [
{
multiline = {
firstline = ''^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[.,]\d{3} (?:DEBUG|INFO|WARNING|ERROR|CRITICAL) \S+ \[[^\]]+\]'';
max_wait_time = "3s";
max_lines = "256";
};
}
{
regex = {
expression = ''^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) (?P<level>DEBUG|INFO|WARNING|ERROR|CRITICAL) (?P<thread>\S+) \[(?P<namespace>[^\]]+)\] (?s)(?P<message>.*)$$'';
};
}
{
labels = {
level = "";
thread = "";
namespace = "";
};
}
{
timestamp = {
source = "timestamp";
format = ''format: "2006-01-02 15:04:05,000"'';
};
}
];
};
} ];
} /* Other PromTail config blocks.... */ ];
};
};
};
}
Challenges and Workarounds Link to heading
However, as simple as it was to setup, I started running into an additional challenges when actually trying to control the lock. My personal server is a fan-less N305 computer without built-in wireless or Bluetooth. I had already ordered a cheap Bluetooth dongle from Amazon, but thanks to running on NixOS where I have to configure everything there were all kinds of instability problems.
Conclusion Link to heading
Thanks to Charles Proxy and Home Assistant support for Yale Locks I have been empowered to [un]lock my front door via a button press on my phone that doesn’t require me to be nearby, providing a convenient and secure way to control my front door. While it’s not been an easy journey sometimes it has been absolutely worth it to be able to quickly and reliably check that my front door is actually locked when I’m away or quickly unlock the door for someone coming over while I’m in the middle of something.
So if you haven’t bought Charles Proxy for your iOS device I would encourage you to do so so you can understand what these apps are doing. And who knows, maybe you’ll figure out a way to automate something annoying or slow in your life.
Something Apple advises against unless it (developer.apple.com) unless it is really needed, because certificates do rotate (eventually) and old apps should continue working even if the user hasn’t updated. And not just the leaf certificate for the service; the intermediate and root certificates can rotate too. Before the expiration too because of possible compromises. That is why we have CRLs in the TLS system. (en.wikipedia.org) ↩︎
I don’t yet see the purpose of doing that though because it still requires internet access to do anything. Although I bet using Home Assistant or similar to trigger the doors opening is going to be much more reliable. I could also delay when the door opens. Sometimes opening a door happens when I am not getting signal near the door for some reason. Being able to “open the door in 5 seconds” while in a good signal spot and then having time to walk to the door might be worth the effort… but so far I haven’t been that motivated to fix it. ↩︎