mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2024-11-30 07:52:53 +01:00
Add new Auth API features
This commit is contained in:
parent
854f879da4
commit
10de88b89f
@ -0,0 +1,8 @@
|
||||
package com.google.android.gms.auth.api.internal;
|
||||
|
||||
import com.google.android.gms.auth.api.proxy.ProxyResponse;
|
||||
|
||||
interface IAuthCallbacks {
|
||||
void onProxyResponse(in ProxyResponse response) = 0;
|
||||
void onSpatulaHeader(String spatulaHeader) = 1;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.google.android.gms.auth.api.internal;
|
||||
|
||||
import com.google.android.gms.auth.api.internal.IAuthCallbacks;
|
||||
//import com.google.android.gms.auth.api.proxy.ProxyGrpcRequest;
|
||||
import com.google.android.gms.auth.api.proxy.ProxyRequest;
|
||||
|
||||
interface IAuthService {
|
||||
void performProxyRequest(IAuthCallbacks callbacks, in ProxyRequest request) = 0;
|
||||
// void performProxyGrpcRequest(IAuthCallback callbacks, in ProxyGrpcRequest request) = 1;
|
||||
void getSpatulaHeader(IAuthCallbacks callbacks) = 2;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.auth.api.proxy;
|
||||
|
||||
parcelable ProxyRequest;
|
@ -0,0 +1,3 @@
|
||||
package com.google.android.gms.auth.api.proxy;
|
||||
|
||||
parcelable ProxyResponse;
|
@ -0,0 +1,6 @@
|
||||
package com.google.android.gms.auth.appcert;
|
||||
|
||||
interface IAppCertService {
|
||||
boolean fetchDeviceKey() = 0;
|
||||
String getSpatulaHeader(String packageName) = 1;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.auth.api.proxy;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class ProxyRequest extends AutoSafeParcelable {
|
||||
public static final int HTTP_METHOD_GET = 0;
|
||||
public static final int HTTP_METHOD_POST = 1;
|
||||
public static final int HTTP_METHOD_PUT = 2;
|
||||
public static final int HTTP_METHOD_DELETE = 3;
|
||||
public static final int HTTP_METHOD_HEAD = 4;
|
||||
public static final int HTTP_METHOD_OPTIONS = 5;
|
||||
public static final int HTTP_METHOD_TRACE = 6;
|
||||
public static final int HTTP_METHOD_PATCH = 7;
|
||||
|
||||
@Field(1000)
|
||||
private int versionCode = 2;
|
||||
@Field(1)
|
||||
public String url;
|
||||
@Field(2)
|
||||
public int httpMethod;
|
||||
@Field(3)
|
||||
public long timeoutMillis;
|
||||
@Field(4)
|
||||
public byte[] body;
|
||||
@Field(5)
|
||||
public Bundle headers;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public static final Creator<ProxyRequest> CREATOR = new AutoCreator<>(ProxyRequest.class);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.google.android.gms.auth.api.proxy;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.microg.safeparcel.AutoSafeParcelable;
|
||||
|
||||
public class ProxyResponse extends AutoSafeParcelable {
|
||||
public static final int STATUS_CODE_NO_CONNECTION = -1;
|
||||
|
||||
@Field(1000)
|
||||
private int versionCode = 1;
|
||||
@Field(1)
|
||||
public int gmsStatusCode;
|
||||
@Field(2)
|
||||
public PendingIntent recoveryAction;
|
||||
@Field(3)
|
||||
public int httpStatusCode;
|
||||
@Field(4)
|
||||
public Bundle headers;
|
||||
@Field(5)
|
||||
public byte[] body;
|
||||
|
||||
public static final Creator<ProxyResponse> CREATOR = new AutoCreator<>(ProxyResponse.class);
|
||||
}
|
33
play-services-core-proto/src/main/proto/appcert.proto
Normal file
33
play-services-core-proto/src/main/proto/appcert.proto
Normal file
@ -0,0 +1,33 @@
|
||||
option java_package = "org.microg.gms.auth.appcert";
|
||||
option java_outer_classname = "AppCertProto";
|
||||
|
||||
message DeviceKeyRequest {
|
||||
optional string droidGuardResult = 1;
|
||||
optional uint64 androidId = 2;
|
||||
optional uint64 sessionId = 3;
|
||||
message VersionInfo {
|
||||
optional uint32 sdkVersion = 1;
|
||||
optional uint32 gmsVersion = 2;
|
||||
}
|
||||
optional VersionInfo versionInfo = 4;
|
||||
optional string token = 5;
|
||||
}
|
||||
|
||||
message DeviceKey {
|
||||
optional uint64 keyId = 1;
|
||||
optional uint64 deviceId = 3;
|
||||
optional bytes macSecret = 4;
|
||||
optional bytes keyCert = 5;
|
||||
}
|
||||
|
||||
message SpatulaHeaderProto {
|
||||
message PackageInfo {
|
||||
optional string packageName = 1;
|
||||
optional string packageCertificateHash = 3;
|
||||
}
|
||||
optional PackageInfo packageInfo = 1;
|
||||
optional bytes hmac = 2;
|
||||
optional uint64 deviceId = 3;
|
||||
optional uint64 keyId = 4;
|
||||
optional bytes keyCert = 5;
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.auth.appcert
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.os.SystemClock
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.android.volley.NetworkResponse
|
||||
import com.android.volley.Request
|
||||
import com.android.volley.Response
|
||||
import com.android.volley.VolleyError
|
||||
import com.android.volley.toolbox.Volley
|
||||
import com.google.android.gms.BuildConfig
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import okio.ByteString.Companion.of
|
||||
import org.microg.gms.checkin.LastCheckinInfo
|
||||
import org.microg.gms.common.Constants
|
||||
import org.microg.gms.common.PackageUtils
|
||||
import org.microg.gms.droidguard.core.DroidGuardResultCreator
|
||||
import org.microg.gms.gcm.GcmConstants
|
||||
import org.microg.gms.gcm.GcmDatabase
|
||||
import org.microg.gms.gcm.RegisterRequest
|
||||
import org.microg.gms.gcm.completeRegisterRequest
|
||||
import org.microg.gms.profile.Build
|
||||
import org.microg.gms.profile.ProfileManager
|
||||
import org.microg.gms.settings.SettingsContract.CheckIn
|
||||
import org.microg.gms.settings.SettingsContract.getSettings
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.random.Random
|
||||
|
||||
class AppCertManager(private val context: Context) {
|
||||
private val queue = Volley.newRequestQueue(context)
|
||||
|
||||
suspend fun fetchDeviceKey(): Boolean {
|
||||
ProfileManager.ensureInitialized(context)
|
||||
deviceKeyLock.withLock {
|
||||
try {
|
||||
val elapsedRealtime = SystemClock.elapsedRealtime()
|
||||
if (elapsedRealtime - deviceKeyCacheTime < DEVICE_KEY_TIMEOUT) {
|
||||
return deviceKey != null
|
||||
}
|
||||
Log.w(TAG, "DeviceKeys for app certifications are experimental")
|
||||
deviceKeyCacheTime = elapsedRealtime
|
||||
val lastCheckinInfo = LastCheckinInfo.read(context)
|
||||
val androidId = lastCheckinInfo.androidId
|
||||
val sessionId = Random.nextLong()
|
||||
val data = hashMapOf(
|
||||
"dg_androidId" to androidId.toString(16),
|
||||
"dg_session" to sessionId.toString(16),
|
||||
"dg_gmsCoreVersion" to BuildConfig.VERSION_CODE.toString(),
|
||||
"dg_sdkVersion" to Build.VERSION.SDK_INT.toString()
|
||||
)
|
||||
val droidGuardResult = try {
|
||||
Base64.encodeToString(DroidGuardResultCreator.getResult(context, "devicekey", data), Base64.NO_WRAP)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
val token = completeRegisterRequest(context, GcmDatabase(context), RegisterRequest().build(context)
|
||||
.checkin(lastCheckinInfo)
|
||||
.app("com.google.android.gms", Constants.GMS_PACKAGE_SIGNATURE_SHA1, BuildConfig.VERSION_CODE)
|
||||
.sender(REGISTER_SENDER)
|
||||
.extraParam("subscription", REGISTER_SUBSCIPTION)
|
||||
.extraParam("X-subscription", REGISTER_SUBSCIPTION)
|
||||
.extraParam("subtype", REGISTER_SUBTYPE)
|
||||
.extraParam("X-subtype", REGISTER_SUBTYPE)
|
||||
.extraParam("scope", REGISTER_SCOPE))
|
||||
.getString(GcmConstants.EXTRA_REGISTRATION_ID)
|
||||
val request = DeviceKeyRequest(
|
||||
droidGuardResult = droidGuardResult,
|
||||
androidId = lastCheckinInfo.androidId,
|
||||
sessionId = sessionId,
|
||||
versionInfo = DeviceKeyRequest.VersionInfo(Build.VERSION.SDK_INT, BuildConfig.VERSION_CODE),
|
||||
token = token
|
||||
)
|
||||
Log.d(TAG, "Request: ${request.toString().chunked(128).joinToString("\n")}")
|
||||
val deferredResponse = CompletableDeferred<ByteArray?>()
|
||||
queue.add(object : Request<ByteArray?>(Method.POST, "https://android.googleapis.com/auth/devicekey", null) {
|
||||
override fun getBody(): ByteArray = request.encode()
|
||||
|
||||
override fun getBodyContentType(): String = "application/octet-stream"
|
||||
|
||||
override fun parseNetworkResponse(response: NetworkResponse): Response<ByteArray?> {
|
||||
return if (response.statusCode == 200) {
|
||||
Response.success(response.data, null)
|
||||
} else {
|
||||
Response.success(null, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deliverError(error: VolleyError) {
|
||||
Log.d(TAG, "Error: ${Base64.encodeToString(error.networkResponse.data, 2)}")
|
||||
deferredResponse.complete(null)
|
||||
}
|
||||
|
||||
override fun deliverResponse(response: ByteArray?) {
|
||||
deferredResponse.complete(response)
|
||||
}
|
||||
|
||||
override fun getHeaders(): Map<String, String> {
|
||||
return mapOf(
|
||||
"User-Agent" to "GoogleAuth/1.4 (${Build.DEVICE} ${Build.ID}); gzip",
|
||||
"content-type" to "application/octet-stream",
|
||||
"app" to "com.google.android.gms",
|
||||
"device" to androidId.toString(16)
|
||||
)
|
||||
}
|
||||
})
|
||||
val deviceKeyBytes = deferredResponse.await() ?: return false
|
||||
deviceKey = DeviceKey.ADAPTER.decode(deviceKeyBytes)
|
||||
Log.d(TAG, "Response: $deviceKey")
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSpatulaHeader(packageName: String): String? {
|
||||
val deviceKey = deviceKey ?: if (fetchDeviceKey()) deviceKey else null
|
||||
val packageCertificateHash = Base64.encodeToString(PackageUtils.firstSignatureDigestBytes(context, packageName), Base64.NO_WRAP)
|
||||
val proto = if (deviceKey != null) {
|
||||
val macSecret = deviceKey.macSecret?.toByteArray()
|
||||
if (macSecret == null) {
|
||||
Log.w(TAG, "Invalid device key: $deviceKey")
|
||||
return null
|
||||
}
|
||||
val mac = Mac.getInstance("HMACSHA256")
|
||||
mac.init(SecretKeySpec(macSecret, "HMACSHA256"))
|
||||
val hmac = mac.doFinal("$packageName$packageCertificateHash".toByteArray())
|
||||
SpatulaHeaderProto(
|
||||
packageInfo = SpatulaHeaderProto.PackageInfo(packageName, packageCertificateHash),
|
||||
hmac = of(*hmac),
|
||||
deviceId = deviceKey.deviceId,
|
||||
keyId = deviceKey.keyId,
|
||||
keyCert = deviceKey.keyCert ?: of()
|
||||
)
|
||||
} else {
|
||||
Log.d(TAG, "Using fallback spatula header based on Android ID")
|
||||
val androidId = getSettings(context, CheckIn.getContentUri(context), arrayOf(CheckIn.ANDROID_ID, CheckIn.SECURITY_TOKEN)) { cursor: Cursor -> cursor.getLong(0) }
|
||||
SpatulaHeaderProto(
|
||||
packageInfo = SpatulaHeaderProto.PackageInfo(packageName, packageCertificateHash),
|
||||
deviceId = androidId
|
||||
)
|
||||
}
|
||||
Log.d(TAG, "Spatula Header: $proto")
|
||||
return Base64.encodeToString(proto.encode(), Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AppCertManager"
|
||||
private const val DEVICE_KEY_TIMEOUT = 60 * 60 * 1000L
|
||||
private const val REGISTER_SENDER = "745476177629"
|
||||
private const val REGISTER_SUBTYPE = "745476177629"
|
||||
private const val REGISTER_SUBSCIPTION = "745476177629"
|
||||
private const val REGISTER_SCOPE = "DeviceKeyRequest"
|
||||
private val deviceKeyLock = Mutex()
|
||||
private var deviceKey: DeviceKey? = null
|
||||
private var deviceKeyCacheTime = 0L
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.auth.appcert
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.util.Log
|
||||
import com.google.android.gms.auth.appcert.IAppCertService
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.microg.gms.common.PackageUtils
|
||||
import org.microg.gms.utils.warnOnTransactionIssues
|
||||
|
||||
private const val TAG = "AppCertService"
|
||||
|
||||
class AppCertService : Service() {
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
Log.d(TAG, "onBind: $intent")
|
||||
return AppCertServiceImpl(this).asBinder()
|
||||
}
|
||||
}
|
||||
|
||||
class AppCertServiceImpl(private val context: Context) : IAppCertService.Stub() {
|
||||
private val manager = AppCertManager(context)
|
||||
|
||||
override fun fetchDeviceKey(): Boolean {
|
||||
PackageUtils.assertExtendedAccess(context)
|
||||
return runBlocking { manager.fetchDeviceKey() }
|
||||
}
|
||||
|
||||
override fun getSpatulaHeader(packageName: String): String? {
|
||||
PackageUtils.assertExtendedAccess(context)
|
||||
return runBlocking { manager.getSpatulaHeader(packageName) }
|
||||
}
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) }
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.auth
|
||||
package org.microg.gms.auth.credentials
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
@ -1,11 +1,12 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021, microG Project Team
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.auth
|
||||
package org.microg.gms.auth.credentials
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.util.Log
|
||||
import com.google.android.gms.auth.api.credentials.CredentialRequest
|
||||
import com.google.android.gms.auth.api.credentials.internal.*
|
||||
@ -15,8 +16,9 @@ import com.google.android.gms.common.internal.GetServiceRequest
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks
|
||||
import org.microg.gms.BaseService
|
||||
import org.microg.gms.common.GmsService
|
||||
import org.microg.gms.utils.warnOnTransactionIssues
|
||||
|
||||
const val TAG = "GmsCredentials"
|
||||
private const val TAG = "CredentialService"
|
||||
|
||||
class CredentialsService : BaseService(TAG, GmsService.CREDENTIALS) {
|
||||
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
|
||||
@ -50,4 +52,5 @@ class CredentialsServiceImpl : ICredentialsService.Stub() {
|
||||
callbacks.onStatus(Status.SUCCESS)
|
||||
}
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) }
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 microG Project Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.auth.proxy
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.gms.auth.api.internal.IAuthCallbacks
|
||||
import com.google.android.gms.auth.api.internal.IAuthService
|
||||
import com.google.android.gms.auth.api.proxy.ProxyRequest
|
||||
import com.google.android.gms.auth.api.proxy.ProxyResponse
|
||||
import com.google.android.gms.common.api.CommonStatusCodes
|
||||
import com.google.android.gms.common.internal.GetServiceRequest
|
||||
import com.google.android.gms.common.internal.IGmsCallbacks
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.microg.gms.BaseService
|
||||
import org.microg.gms.auth.appcert.AppCertManager
|
||||
import org.microg.gms.common.GmsService
|
||||
import org.microg.gms.common.PackageUtils
|
||||
import org.microg.gms.utils.warnOnTransactionIssues
|
||||
|
||||
private const val TAG = "AuthProxyService"
|
||||
|
||||
class AuthProxyService : BaseService(TAG, GmsService.AUTH_PROXY) {
|
||||
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
|
||||
val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName)
|
||||
?: throw IllegalArgumentException("Missing package name")
|
||||
val consumerPackageName = request.extras.getString("consumerPkg")
|
||||
if (consumerPackageName != null) PackageUtils.assertExtendedAccess(this)
|
||||
val serviceImpl = AuthServiceImpl(this, lifecycle, consumerPackageName ?: packageName)
|
||||
callback.onPostInitComplete(CommonStatusCodes.SUCCESS, serviceImpl, Bundle())
|
||||
}
|
||||
}
|
||||
|
||||
class AuthServiceImpl(private val context: Context, private val lifecycle: Lifecycle, private val packageName: String) : IAuthService.Stub(), LifecycleOwner {
|
||||
override fun performProxyRequest(callbacks: IAuthCallbacks, request: ProxyRequest) {
|
||||
Log.d(TAG, "performProxyRequest($packageName, $request)")
|
||||
lifecycleScope.launchWhenStarted {
|
||||
callbacks.onProxyResponse(ProxyResponse().apply { gmsStatusCode = CommonStatusCodes.CANCELED })
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSpatulaHeader(callbacks: IAuthCallbacks) {
|
||||
Log.d(TAG, "getSpatulaHeader($packageName)")
|
||||
lifecycleScope.launchWhenStarted {
|
||||
val result = withContext(Dispatchers.IO) { AppCertManager(context).getSpatulaHeader(packageName) }
|
||||
Log.d(TAG, "Result: $result")
|
||||
callbacks.onSpatulaHeader(result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLifecycle(): Lifecycle = lifecycle
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) }
|
||||
}
|
Loading…
Reference in New Issue
Block a user