tiistai 3. syyskuuta 2019

Glue älylukon rajapintojen tutkimista

Tuossa aikaisemmassa postauksessa https://omakotikotitalomme.blogspot.com/2019/09/etaohjattava-glue-alylukko-wifi.html asensimme älylukon varaston oveen. Ja siinä vähän harmittelin, ettei valmistaja ole virallisesti julkaissut avointa REST API:a, että voisin itse ohjelmallisesti lukkoa ohjata.

Ekana tiputamme Glue lukon Android paketin https://www.apkdecompilers.com/-sivustolle, ja katsomme löytyykö paketin sisältä jotain mielenkiintoista, jolla voisimme saavuttaa yhteensopivuuden curl ohjelman kanssa.
root@jonni-ThinkPad-W520:~/gluehome# grep https.*api apk/*
Binääritiedosto apk/resources\classes.dex täsmää hakuun
apk/sources\com\gluehome\gluecontrol\h.java:        aVar.a("https://api.gluehome.com/api/");
apk/sources\com\gluehome\gluecontrol\h.java:        return new g.s.a().a("https").d(str.equals("dev") ? "gluebackend-tc-api.azurewebsites.net" : "api.gluehome.com").e("api").e(CoreConstants.EMPTY_STRING).c();
apk/sources\com\google\android\gms\b\ix.java:        return new Builder().scheme("https").path("//pagead2.googlesyndication.com/pagead/gen_204").appendQueryParameter("id", "gmob-apps-report-exception").appendQueryParameter("os", VERSION.RELEASE).appendQueryParameter("api", String.valueOf(VERSION.SDK_INT)).appendQueryParameter("device", u.e().e()).appendQueryParameter("js", this.f8897e.f7780b).appendQueryParameter("appid", this.f8896d).appendQueryParameter("exceptiontype", cls.getName()).appendQueryParameter("stacktrace", stringWriter.toString()).appendQueryParameter("eids", TextUtils.join(",", df.a())).appendQueryParameter("exceptionkey", str).appendQueryParameter("cl", "135396225").appendQueryParameter("rc", "dev").toString();

Bingo, tuolta löytyi, että Android applikaatio käyttää API rajapintanaan "https://api.gluehome.com/api/", ja näyttääpä heillä jääneen kehitysympäristön azure sitekin koodiin mukaan, mutta sen voimme ignoroida. Joten keskitymme tuon api urlin service rajapinnan tutkimiseen. Ja sitten seuraava luova greppi:
root@jonni-ThinkPad-W520:~/gluehome# grep "o(a = \"" apk/*Service*
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "UserRegistrations/{id}/Activate")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "AutoUnlocks")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "AutoUnlocks")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Invites")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Hubs/{id}/Commands")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Accesses")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Accesses")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "AccessSchedules/")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "AccessSchedules/")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Locations/{id}/Images")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Locations/{id}/Images")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Locations")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Locations")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "LockEvents")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "LockEvents")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Locks")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Locks")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Hubs/{id}/PairLocks")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Hubs")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "RegisterPushNotifications")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "RegisterPushNotifications")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "UserRegistrations")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "UserRegistrations")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "SecurityQuestions")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "SecurityQuestions")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Users/{id}/Icons")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Users/{id}/Icons")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Users/{id}/modifications")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Users/{id}/modifications")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "ResetPassword")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "ResetPassword")
apk/sources\com\gluehome\backend\glue\GlueHomeService.java:    @o(a = "Validate")

Ja tuossahan lista sitten majaileekin, ja sorsaa tutkimalla näkee syökö se sisäänsä GET, PUT tai DELETE metodia. Ja tavoitteenahan meillä on curlilla päästä ovea lukitsemaan ja avaamaan. Sitten luomme loginpass-tiedoston:
machine api.gluehome.com login <email> password <password>

Ja yläpuolella olevaa apilistaa kun luemme, niin kaipaamme lukon tai hubin {id} arvoa aika monessa tapauksessa. Ja kaikki wifi hubissa olevat linkatut lukot saamme /Hubs-komennolla (GET).
root@jonni-ThinkPad-W520:~/gluehome# curl -k -H "Content-type: application/json" --netrc-file loginpass https://api.gluehome.com/api/Hubs/ | jq '.[].LockIds[], .[].Id'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   405  100   405    0     0    605      0 --:--:-- --:--:-- --:--:--   639
"11111111-1111-1111-1111-111111111111"
"22222222-2222-2222-2222-222222222222"

Ja sieltähän lukon id pullahti (lukko vaihdettu ykköseksi ja hubi id kakkoseksi ihan vaan blogin takia). Ja tuon tiedon perusteella voimme jo lukkoa ohjata "Hubs/{id}/Commands"-APIlla. Tai kaivetaan vielä tuosta rajapinnasta tiedot ulos.
root@jonni-ThinkPad-W520:~/gluehome# cat "apk/sources\com\gluehome\backend\glue\HubCommandRequest.java"
package com.gluehome.backend.glue;

import com.google.a.a.c;
import java.util.UUID;

public class HubCommandRequest {
    public static int COMMAND_HUB_FIRMWARE_UPDATE = 4;
    public static int COMMAND_LOCK = 1;
    public static int COMMAND_LOCK_FIRMWARE_UPDATE = 3;
    public static int COMMAND_UNLOCK = 0;
    public static int LOCK_COMMAND_LIST_UPDATE = 5;
    @c(a = "HubCommand")
    public int hubCommand;
    @c(a = "LockId")
    public UUID lockId;
}

Tuosta näkee, että kyseinen API haluaa POST komenossa formissa kaksi parametriä, toinen on "LockId", johon siis pistämme juuri kaivamamme lukon ID:n ja toisena parametrinä "HubCommand", joka on INT. Ja intin arvot onkin tuossa mukavasti listattuna samalla, eli nollalla unlock ja ykkösellä lukkoon. Ja isommat numerot on sitten firmiksen päivityksia yms(joita emme tarvitse).

Ja sitten onko lopullisen testin aika, syötämme curlille formin tuossa muodossa ja katsotaan mitä tapahtuu.

root@jonni-ThinkPad-W520:~/gluehome# curl -X POST -k -d "LockId=11111111-1111-1111-1111-111111111111&HubCommand=0" --netrc-file loginpass https://api.gluehome.com/api/Hubs/22222222-2222-2222-2222-222222222222/Commands | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   112  100    56  100    56     80     80 --:--:-- --:--:-- --:--:--   160
{
    "Id": "6550eb7c-5fe6-4dc5-8f0d-2dccaa077777",
    "Status": 1
}
root@jonni-ThinkPad-W520:~/gluehome# curl -X POST -k -d "LockId=11111111-1111-1111-1111-111111111111&HubCommand=1" --netrc-file loginpass https://api.gluehome.com/api/Hubs/22222222-2222-2222-2222-222222222222/Commands | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   112  100    56  100    56     98     98 --:--:-- --:--:-- --:--:--   197
{
    "Id": "a95c39ec-2c47-4a0c-bb7b-3f154a4e00000",
    "Status": 1
}

Ja siellähän se möllöttää, varaston ovi aukesi ja meni kiinni, ja Androidiin tuli notifikaatiot myös komentojen onnistumisesta.

Disclaimerina, että koska REST API ei ole virallisesti avoin, niin valmistaja voi softapäivitysten välillä vapaasti sitä muutella, mutta "works for me". Samalla tässä pitää painottaa sen tärkeyttä, kun rekisteröitte tuota lukkoa, niin keksikää mahdollisimman pitkä ja uniikki salasana käyttäjätunnukselle, sillä jos se vuotaa, niin kuka vaan saa ovesi auki tietämällä sähköpostin ja salasanan. Muuten tuolla ei nyt nopealla silmäyksellä mitään hälyttävää löytynyt, datakanava on https:n päällä, joten siltä osin turvallinen. Näytti tuolla olevan muutama google mainos ja statistiikka urli, joten softa saattaa kerätä statistiikka, mutta niinpä taitaa about kaikki applikaatiot tehdä.

Niin ja tässä postauksessa ei mitään maailmaa mullistavaa uutta tietoa. Näin jälkeenpäin kun googlettaa, niin Gluehomen gitistä löytyy samat tiedot sorsia lukemalla, josta näkee esim, että miten betterylevel lasketaan (https://github.com/GlueHome/homebridge-glue/blob/master/index.js) ja hyvää lisälukemista aiheesta: Security evaluation of smart door locks.

Nyt kun pystyn REST rajapintaa itse kutsumaan, niin saatan tulevaisuudessa hankkia toisenkin lukon talon puolelle.

EDIT: Vanha API lakkasi toimimasta, mutta tässä on uudempi blogipostaus jossa uutta API:a hyödynnetään: http://omakotikotitalomme.blogspot.com/2021/07/home-assistant-gluehome-lukkointegraatio.html

maanantai 2. syyskuuta 2019

Etäohjattava Glue älylukko wifi-keskittimellä

Tuossa kesällä oli Clasulla kivijalassa erikoistarjouksena Glue älylukot, jolloin opiskelijakortin vilauttamisen jälkeen taisi loppuhinnaksi muodostua noin 140e, niin oli pakko hankkia kun halvalla sai. Verkkokauppa.com:ssa sama on https://www.verkkokauppa.com/fi/product/14156/knckx/Glue-langaton-alylukko-wifi-keskittimella-hopea

Ja asennuskohteeksi valitsin varaston oven, sillä käyttöscenaariolla, jos tarvii esim. lähettifirmoille availla ovia jos tulee pakettia kun en ole kotona. Ja toinen syy varastoon pistämiseksi on se, että 'älylukoilla' on vielä hieman huono maine tietoturvan osalta. Joten tuota pitää tutkia hieman lähemmin, ennenkuin uskaltaa kotioveen pistää. Tästä onkin lupa odottaa jatkopostausta aiheesta, kun valmistaja ei ole public API:a toiminnallisuuteen julkaissut, niin taitaa edessä olla 'challenge accepted'-hetki, jos vaikka katsoo mitä vaatii oven avaaminen curlilla Linuxin konsolilla.

Lähtötilanteessa tuommoinen perus Abloyn lukkopesä, johon asennusta sitten ruvetaan suorittamaan.

Pyöritysnuppi ekana ruuvataan irti...

Jonka jälkeen ruuvataan Glue lukon aluslevy tilalle ja sovittemen kanssa sitten Glue laite paikoilleen.

Sitten vuorossa olikin Glue softan asennus Android puhelimeeni, jonka avulla lukko rekisteröidään ja calibroidaan. Jouduin tekemään calibroinnin pariin kertaan kun ekalla kerralla en osannut vääntää nuppia tarpeeksi auki. Ja wifi keskittimen kanssa hetken ihmettelin kunnes keksin, että varaston wlan ap:ssa oli dhcp:n dns asetukset vinossa. Lopulta kaikkiaan taisi vähän alle tunti mennä aloituksesta kun systeemi oli pystyssä.

Kun lopulta saa kaikki onnistuneesti conffattua, niin silloin näkyy puhelimesta iloisesti unlock ja lock napit, ja logitiedot historiasta.

Muutaman päivän testausten perusteella, unlock ja lock komennot joskus eivät mene täysin luotetusti läpi, vasta kun historia logissa näkyy eventti, niin silloin tietää menikö komento läpi. Mutta kun tuon tiedostaa, niin ei ole iso ongelma. Jossain vaiheessa on luvassa toinen postaus aiheesta, jossa koitetaan tutkia enemmän API ja/tai protokollatasoa toiminnallisuuden suhteen.