-
Notifications
You must be signed in to change notification settings - Fork 55
Chaser Authentication
The Chaser service uses an authentication system which does not require login. Keystores are used to sign a nonce and sent when generating an access token which can be used to send location updates for other SmartTags, as well as getting encryption keys for Tag locations where a Tag has end to end encryption enabled.
Certificate generation must be done on an Android device. libfmm_ct.so provides the native code to generate the certificate, and has some integrity checks that must be defeated for it to return valid data:
- The caller must have the package name
com.samsung.android.fmm - The package
com.samsung.android.fmmmust have the shared user IDandroid.uid.system
It's possible to defeat both of these checks without needing to be a system app, root, or even using Shizuku. Check the classes FmmContext and FmmPackageManager for implementation.
JNI calls to the native library generate the keystore, alias and passwords. Calls must be made from com.samsung.android.fmm.maze.FmmFontJNI:
| Method | Parameters | Returns |
|---|---|---|
| getFont1 | None | Keystore password |
| getFont2 | Context* | Base64 encoded Keystore |
| getFont3 | None | Key alias |
| getFont4 | None | Key password |
* This Context must be the Context which fakes the existence of the FMM package & its shared user ID from above.
Note: While it is possible to reuse a keystore, it's also possible that Samsung may revoke them. To prevent this as much as possible, uTag generates a new keystore every time the app is restarted. The same native library is used on generations of Samsung devices, so blocking all keystores generated using this method would in theory also block a significant number of real users. If you are intending to use this API outside of Android and are not going to be the only one using it, consider creating a small companion app which generates a unique keystore for your usage from a real device, rather than hardcoding it.
The Keystore is in BKS (Bouncy Castle) format. For inspection, it can be loaded using Keystore Explorer, using the provided passwords & alias. On Android, it's loaded as follows:
val cert = Base64.decode(rawCertificate.certificate, Base64.NO_WRAP).inputStream()
KeyStore.getInstance("BKS").apply {
load(cert, rawCertificate.keystorePassword.toCharArray())
}
Where rawCertificate is a data class containing the results of the JNI calls.
After the keystore has been loaded, get the certificate chain for the provided alias, then combine the 2nd and 1st certificates (in that order) in the chain and Base64 encode it. For example, in Kotlin:
private fun encodeChain(certificates: Array<Certificate>): String? {
if (certificates.size >= 2) {
val certs = certificates[1].encoded + certificates[0].encoded
return Base64.encodeToString(certs, Base64.NO_WRAP)
}
return null
}
val certificate = try {
keyStore.getCertificateChain(rawCertificate.alias)?.let {
encodeChain(it)
}
} catch (e: Exception) {
null
}
Where rawCertificate is a data class containing the results of the JNI calls, and keyStore is the loaded Keystore.
After the keystore has been loaded, get the private key for the provided alias using the key password. For example, in Kotlin:
val privateKey = try {
keyStore.getKey(
rawCertificate.alias, rawCertificate.keyPass.toCharArray()
) as? PrivateKey
} catch (e: Exception) {
null
}
Where rawCertificate is a data class containing the results of the JNI calls, and keyStore is the loaded Keystore.
There are several regions a Tag can be registered to, and location updates must be sent to the right one. The Tag's service data contains a region ID (see the service data documentation for how to decode it), which are mapped to URLs as follows:
| Region | ID | URL |
|---|---|---|
| NA03D | 1 | chaser-na03d-useast2.samsungiots.com |
| NA03S | 3 | chaser-na03s-useast2.samsungiots.com |
| EU02S | 5 | chaser-eu02s-euwest1.samsungiots.com |
| NA03A | 7 | chaser-na03a-useast2.samsungiots.com |
| NA03 | 10 | chaser-na03-useast2.samsungiotcloud.com |
| EU02 | 11 | chaser-eu02-euwest1.samsungiotcloud.com |
| AP03 | 12 | chaser-ap03-apnortheast2.samsungiotcloud.com |
The relevant URL will from this point be referred to as the "base URL".
You can generate a nonce by making an unauthenticated call:
GET https://<base URL>/nonce
None
| Key | Type | Value |
|---|---|---|
nonce |
String | The generated nonce |
Sign the generated nonce using the private key loaded from the keystore, in the SHA256withRSA algorithm, then encode it in Base64. For example:
val signature = try {
Signature.getInstance("SHA256withRSA").apply {
initSign(privateKey)
update(nonce.toByteArray())
}.sign().let {
Base64.encodeToString(it, Base64.NO_WRAP)
}
} catch (e: Exception) {
return null
}
Where privateKey is the loaded private key from the keystore, and nonce is the server-generated nonce.
In addition to the certificate generated by FMM, we also need to generate and store our own key pair which will be used in the encryption process. It must match the following specifications:
| Spec | Value |
|---|---|
| Algorithm | RSA |
| Subject | Name: CN=ChaserKeyStore
|
| Digests | SHA-256, SHA-512 |
| Signature Padding | PKCS1 |
| Encryption Padding | PKCS1 |
| Serial Number | 0x539 |
| Key Size | 0x800 |
Store this key pair, on Android it should be stored in the Android keystore.
Now we are ready to get an access token. Make a call to the same server as before:
POST https://<base URL>/accesstoken
| Key | Type | Value |
|---|---|---|
x-iot-findnode-version |
Long | FMM version code, eg. 731802100 |
signature |
String | Signed nonce |
certificate |
String | Encoded certificate chain |
X-Iot-Findnode-Publickey |
String | Base64 encoded public key from key pair |
x-iot-findnode-type |
String | MOVING |
x-iot-findnode-host |
String | GALAXY_PHONE |
nonce |
String | Server-generated nonce |
| Key | Type | Value |
|---|---|---|
accessToken |
String | The access token |
expirationTime |
Long | Expiry timestamp of token |
findNode |
Find Node | Configuration information that gets sent back to the server later |