diff --git a/README.md b/README.md index ffcefc7b9fccedc1e3c4cf558b9e2faa04abd0eb..f9136e0563ee0222b47c1ae4ec3279dae7fc0279 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ microG GmsCore is a FLOSS (Free/Libre Open Source Software) framework to allow a License ------- - Copyright 2013-2021 microG Project Team + Copyright 2013-2022 microG Project Team Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/build.gradle b/build.gradle index cdd038487288b357674cd10e22093dd15a46cbc6..df68b6d18cd921ef0e5a7cb8abe9460c1fb3092d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,22 +1,24 @@ /* - * SPDX-FileCopyrightText: 2013, microG Project Team + * SPDX-FileCopyrightText: 2013 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ buildscript { - ext.cronetVersion = '91.0.4472.120.1' - ext.nlpVersion = '2.0-alpha8' + ext.cronetVersion = '102.5005.125' + ext.nlpVersion = '2.0-alpha10' ext.safeParcelVersion = '1.7.0' ext.wearableVersion = '0.1.1' ext.kotlinVersion = '1.6.10' ext.coroutineVersion = '1.5.2' - ext.annotationVersion = '1.2.0' + ext.annotationVersion = '1.3.0' ext.appcompatVersion = '1.4.1' + ext.biometricVersion = '1.1.0' ext.coreVersion = '1.7.0' ext.fragmentVersion = '1.4.0' ext.lifecycleVersion = '2.4.0' + ext.loaderVersion = '1.1.0' ext.mediarouterVersion = '1.2.5' ext.multidexVersion = '2.0.1' ext.navigationVersion = '2.3.5' @@ -37,7 +39,7 @@ buildscript { ext.androidCompileSdk = 31 repositories { - jcenter() + mavenCentral() google() } @@ -69,17 +71,6 @@ def ourVersionCode = gmsVersionCode * 1000 + ourVersionMinor * 2 + (gitCommitCo def ourVersionName = "$ourVersionBase.$gmsVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") + (gitCommitCount > 0 && !gitDirty ? " ($gitCommitId)" : "") logger.lifecycle('Starting build for version {} ({})...', ourVersionName, ourVersionCode) -@Deprecated -String getMyVersionName() { - return ourVersionName -} - -@Deprecated -int getMyVersionCode() { - return ourVersionCode -} - - allprojects { apply plugin: 'idea' @@ -89,28 +80,9 @@ allprojects { ext.isReleaseVersion = false } -@Deprecated -def androidCompileSdk() { return androidCompileSdk } - -@Deprecated -def androidTargetSdk() { return androidTargetSdk } - -@Deprecated -def androidMinSdk() { return androidMinSdk } - -@Deprecated -def versionCode() { - return ourVersionCode -} - -@Deprecated -def versionName() { - return ourVersionName -} - subprojects { repositories { - jcenter() + mavenCentral() google() } } diff --git a/gradle/publish-android.gradle b/gradle/publish-android.gradle index f299b7ce6a1b36996941ee2008f2424e13c81b86..b4464167384f7059c516dd7547daa2ba0ea65287 100644 --- a/gradle/publish-android.gradle +++ b/gradle/publish-android.gradle @@ -31,10 +31,6 @@ afterEvaluate { id = 'microg' name = 'microG Team' } - developer { - id = 'mar-v-in' - name = 'Marvin W.' - } } scm { url = 'https://github.com/microg/GmsCore' diff --git a/gradle/publish-java.gradle b/gradle/publish-java.gradle index b4c2a03191d4f8bfc6891dbc8d153ac94ea52107..95d9ad9a9d348d3320d59b5139f497c9cc3f3805 100644 --- a/gradle/publish-java.gradle +++ b/gradle/publish-java.gradle @@ -42,10 +42,6 @@ afterEvaluate { id = 'microg' name = 'microG Team' } - developer { - id = 'mar-v-in' - name = 'Marvin W.' - } } scm { url = 'https://github.com/microg/GmsCore' diff --git a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl index 3bc93136a80bccabdfba1702569da322f65141c2..ae33a3349a0778b147a08965ad29def46afed4cf 100644 --- a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl @@ -5,13 +5,19 @@ import android.accounts.Account; import com.google.android.gms.auth.AccountChangeEventsResponse; import com.google.android.gms.auth.AccountChangeEventsRequest; +import com.google.android.gms.auth.GetHubTokenRequest; +import com.google.android.gms.auth.GetHubTokenInternalResponse; +import com.google.android.gms.auth.HasCababilitiesRequest; interface IAuthManagerService { Bundle getToken(String accountName, String scope, in Bundle extras) = 0; Bundle clearToken(String token, in Bundle extras) = 1; AccountChangeEventsResponse getChangeEvents(in AccountChangeEventsRequest request) = 2; + Bundle getTokenWithAccount(in Account account, String scope, in Bundle extras) = 4; Bundle getAccounts(in Bundle extras) = 5; Bundle removeAccount(in Account account) = 6; Bundle requestGoogleAccountsAccess(String packageName) = 7; + int hasCapabilities(in HasCababilitiesRequest request) = 8; + GetHubTokenInternalResponse getHubToken(in GetHubTokenRequest request, in Bundle extras) = 9; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenInternalResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenInternalResponse.aidl new file mode 100644 index 0000000000000000000000000000000000000000..f73f91bb72747d8127d2d096916c7bb0770930cc --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenInternalResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth; + +parcelable GetHubTokenInternalResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenRequest.aidl new file mode 100644 index 0000000000000000000000000000000000000000..74de2776614cb20e00d8493ff27d4ea06cf8433e --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/GetHubTokenRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth; + +parcelable GetHubTokenRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl new file mode 100644 index 0000000000000000000000000000000000000000..c0c080cd11048f1eec56ea7aa21ec06aef25601c --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/HasCababilitiesRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth; + +parcelable HasCababilitiesRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/maps/internal/ICreator.aidl b/play-services-api/src/main/aidl/com/google/android/gms/maps/internal/ICreator.aidl index f10ab345ed4a975e1a98cf46ec578414c537bdc9..849492ef65c077079fc54c1a92ec4891fc5a83e8 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/maps/internal/ICreator.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/maps/internal/ICreator.aidl @@ -8,10 +8,14 @@ import com.google.android.gms.maps.internal.ICameraUpdateFactoryDelegate; import com.google.android.gms.maps.model.internal.IBitmapDescriptorFactoryDelegate; interface ICreator { - void init(IObjectWrapper resources); - IMapFragmentDelegate newMapFragmentDelegate(IObjectWrapper activity); - IMapViewDelegate newMapViewDelegate(IObjectWrapper context, in GoogleMapOptions options); - ICameraUpdateFactoryDelegate newCameraUpdateFactoryDelegate(); - IBitmapDescriptorFactoryDelegate newBitmapDescriptorFactoryDelegate(); - void initV2(IObjectWrapper resources, int flags); + void init(IObjectWrapper resources) = 0; + IMapFragmentDelegate newMapFragmentDelegate(IObjectWrapper activity) = 1; + IMapViewDelegate newMapViewDelegate(IObjectWrapper context, in GoogleMapOptions options) = 2; + ICameraUpdateFactoryDelegate newCameraUpdateFactoryDelegate() = 3; + IBitmapDescriptorFactoryDelegate newBitmapDescriptorFactoryDelegate() = 4; + void initV2(IObjectWrapper resources, int versionCode) = 5; + //IStreetViewPanoramaViewDelegate newStreetViewPanoramaViewDelegate(IObjectWrapper context, in StreetViewPanoramaOptions options) = 6; + //IStreetViewPanoramaFragmentDelegate newStreetViewPanoramaFragmentDelegate(IObjectWrapper activity) = 7; + int getRendererType() = 8; + void logInitialization(IObjectWrapper context, int preferredRenderer) = 9; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/maps/model/PatternItem.aidl b/play-services-api/src/main/aidl/com/google/android/gms/maps/model/PatternItem.aidl new file mode 100644 index 0000000000000000000000000000000000000000..f64b3f0441522487ce1b551d5b9c4112117ce6fe --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/maps/model/PatternItem.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.maps.model; + +parcelable PatternItem; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/maps/model/internal/IPolygonDelegate.aidl b/play-services-api/src/main/aidl/com/google/android/gms/maps/model/internal/IPolygonDelegate.aidl index 67f0517b1a9614ac47b7a881d1fac84bfbef5a72..79292fc7a081457d43ae5b4d62119b0249b1aecb 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/maps/model/internal/IPolygonDelegate.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/maps/model/internal/IPolygonDelegate.aidl @@ -1,27 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2015 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + package com.google.android.gms.maps.model.internal; +import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.PatternItem; interface IPolygonDelegate { - void remove(); - String getId(); - void setPoints(in List points); - List getPoints(); - void setHoles(in List holes); - List getHoles(); - void setStrokeWidth(float width); - float getStrokeWidth(); - void setStrokeColor(int color); - int getStrokeColor(); - void setFillColor(int color); - int getFillColor(); - void setZIndex(float zIndex); - float getZIndex(); - void setVisible(boolean visible); - boolean isVisible(); - void setGeodesic(boolean geod); - boolean isGeodesic(); - boolean equalsRemote(IPolygonDelegate other); - int hashCodeRemote(); - + void remove() = 0; + String getId() = 1; + void setPoints(in List points) = 2; + List getPoints() = 3; + void setHoles(in List holes) = 4; + List getHoles() = 5; + void setStrokeWidth(float width) = 6; + float getStrokeWidth() = 7; + void setStrokeColor(int color) = 8; + int getStrokeColor() = 9; + void setFillColor(int color) = 10; + int getFillColor() = 11; + void setZIndex(float zIndex) = 12; + float getZIndex() = 13; + void setVisible(boolean visible) = 14; + boolean isVisible() = 15; + void setGeodesic(boolean geod) = 16; + boolean isGeodesic() = 17; + boolean equalsRemote(IPolygonDelegate other) = 18; + int hashCodeRemote() = 19; + void setClickable(boolean click) = 20; + void setStrokeJointType(int type) = 22; + int getStrokeJointType() = 23; + void setStrokePattern(in List items) = 24; + List getStrokePattern() = 25; + void setTag(IObjectWrapper obj) = 26; + IObjectWrapper getTag() = 27; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/DogfoodsToken.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/DogfoodsToken.aidl new file mode 100644 index 0000000000000000000000000000000000000000..991fca0f79e89bcf1a5dc600de16db65f32bcaeb --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/DogfoodsToken.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable DogfoodsToken; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/ExperimentTokens.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/ExperimentTokens.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4b6ba4f80578479042aaa3669834210c3619800f --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/ExperimentTokens.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable ExperimentTokens; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/Flag.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/Flag.aidl new file mode 100644 index 0000000000000000000000000000000000000000..f91175f8accce76d6a9848c3d52967fa7a727f0d --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/Flag.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable Flag; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/FlagOverrides.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/FlagOverrides.aidl new file mode 100644 index 0000000000000000000000000000000000000000..bdf15a656d8b7bb5d96fa03ded367cf2a9dbb35d --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/FlagOverrides.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable FlagOverrides; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/RegistrationInfo.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/RegistrationInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..644ba2d81f500db67ccbc2e96804e879797476d2 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/RegistrationInfo.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.phenotype; + +parcelable RegistrationInfo; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeCallbacks.aidl index acda3c576ccdc6b9f00521a15c228140f941cdac..3c29b8b6eb7ab63d893fd2990497bb4f81061302 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeCallbacks.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeCallbacks.aidl @@ -2,8 +2,26 @@ package com.google.android.gms.phenotype.internal; import com.google.android.gms.common.api.Status; import com.google.android.gms.phenotype.Configurations; +import com.google.android.gms.phenotype.DogfoodsToken; +import com.google.android.gms.phenotype.ExperimentTokens; +import com.google.android.gms.phenotype.Flag; +import com.google.android.gms.phenotype.FlagOverrides; interface IPhenotypeCallbacks { - oneway void onRegister(in Status status) = 0; - oneway void onConfigurations(in Status status, in Configurations configurations) = 3; + oneway void onRegistered(in Status status) = 0; + oneway void onWeakRegistered(in Status status) = 1; + oneway void onUnregistered(in Status status) = 2; + oneway void onConfiguration(in Status status, in Configurations configurations) = 3; + oneway void onCommitedToConfiguration(in Status status) = 4; + oneway void onExperimentTokens(in Status status, in ExperimentTokens experimentTokens) = 5; + oneway void onDogfoodsToken(in Status status, in DogfoodsToken dogfoodsToken) = 6; + oneway void onDogfoodsTokenSet(in Status status) = 7; + oneway void onFlag(in Status status, in Flag flag) = 8; + oneway void onCommittedConfiguration(in Status status, in Configurations configuration) = 9; + oneway void onSyncFinished(in Status status, long p1) = 10; + oneway void onFlagOverridesSet(in Status status) = 11; + oneway void onFlagOverrides(in Status status, in FlagOverrides overrides) = 12; + oneway void onAppSpecificPropertiesSet(in Status status) = 13; + + oneway void onServingVersion(in Status status, long version) = 15; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl index abba647dba3817101d29244d3508e83fd600c6bc..77d315b741c260cea0153cf5b25d7310592c9afe 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl @@ -1,9 +1,31 @@ package com.google.android.gms.phenotype.internal; import com.google.android.gms.phenotype.internal.IPhenotypeCallbacks; +import com.google.android.gms.phenotype.Flag; +import com.google.android.gms.phenotype.RegistrationInfo; interface IPhenotypeService { - void register(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in byte[] p4) = 0; - void register2(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in int[] p4, in byte[] p5) = 1; - void getConfigurationSnapshot(IPhenotypeCallbacks callbacks, String p1, String p2, String p3) = 10; + oneway void register(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in byte[] p4) = 0; // returns via callbacks.onRegistered() + oneway void weakRegister(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in int[] p4, in byte[] p5) = 1; // returns via callbacks.onWeakRegistered() + oneway void unregister(IPhenotypeCallbacks callbacks, String p1) = 2; // returns via callbacks.onUnregistered() + oneway void getConfigurationSnapshot(IPhenotypeCallbacks callbacks, String p1, String p2) = 3; // returns via callbacks.onConfiguration() + oneway void commitToConfiguration(IPhenotypeCallbacks callbacks, String p1) = 4; // returns via callbacks.onCommitedToConfiguration() + oneway void getExperimentTokens(IPhenotypeCallbacks callbacks, String p1, String logSourceName) = 5; // returns via callbacks.onExperimentTokens() + oneway void getDogfoodsToken(IPhenotypeCallbacks callbacks) = 6; // returns via callbacks.onDogfoodsToken() + oneway void setDogfoodsToken(IPhenotypeCallbacks callbacks, in byte[] p1) = 7; // returns via callbacks.onDogfoodsTokenSet() + oneway void getFlag(IPhenotypeCallbacks callbacks, String packageName, String name, int type) = 8; // returns via callbacks.onFlag() + oneway void getCommitedConfiguration(IPhenotypeCallbacks callbacks, String p1) = 9; // returns via callbacks.onCommittedConfiguration() + oneway void getConfigurationSnapshot2(IPhenotypeCallbacks callbacks, String p1, String p2, String p3) = 10; // returns via callbacks.onConfiguration() + oneway void syncAfterOperation(IPhenotypeCallbacks callbacks, String p1, long p2) = 11; // returns via callbacks.onSyncFinished() + oneway void registerSync(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in byte[] p4, String p5, String p6) = 12; // returns via callbacks.onConfiguration() + oneway void setFlagOverrides(IPhenotypeCallbacks callbacks, String packageName, String user, String flagName, int flagType, int flagDataType, String flagValue) = 13; // returns via callbacks.onFlagOverridesSet() + oneway void deleteFlagOverrides(IPhenotypeCallbacks callbacks, String packageName, String user, String flagName) = 14; // returns via callbacks.onFlagOverrides() + oneway void listFlagOverrides(IPhenotypeCallbacks callbacks, String packageName, String user, String flagName) = 15; // returns via callbacks.onFlagOverrides() + + oneway void clearFlagOverrides(IPhenotypeCallbacks callbacks, String packageName, String user) = 17; // returns via callbacks.onFlagOverridesSet() + oneway void bulkRegister(IPhenotypeCallbacks callbacks, in RegistrationInfo[] infos) = 18; // returns via callbacks.onRegister() + oneway void setAppSpecificProperties(IPhenotypeCallbacks callbacks, String p1, in byte[] p2) = 19; // returns via callbacks.onAppSpecificPropertiesSet() + + oneway void getServingVersion(IPhenotypeCallbacks callbacks) = 21; // returns via callbacks.onServingVersion() + oneway void getExperimentTokens2(IPhenotypeCallbacks callbacks, String p1, String p2, String p3, String p4) = 22; // returns via callbacks.onExperimentTokens() } diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java index 0d7f5cd8f33ed242500c708f456a2918e60b5024..f746a64396859cb9f0242126e91b729a44a3e529 100644 --- a/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java +++ b/play-services-api/src/main/java/com/google/android/gms/auth/AccountChangeEventsRequest.java @@ -16,16 +16,29 @@ package com.google.android.gms.auth; +import static org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE; + +import android.accounts.Account; + import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; public class AccountChangeEventsRequest extends AutoSafeParcelable { - @SafeParceled(1) + @Field(1) private int versionCode = 1; - @SafeParceled(2) - private int i; - @SafeParceled(3) - private String s; + @Field(2) + private int since; + @Field(3) + @Deprecated + private String accountName; + @Field(4) + private Account account; + + public Account getAccount() { + if (account != null) return account; + if (accountName != null) return new Account(accountName, DEFAULT_ACCOUNT_TYPE); + return null; + } public static Creator CREATOR = new AutoCreator(AccountChangeEventsRequest.class); diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..ccecf29148cab7e857fb9fc236fbe88720b1d8bd --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenInternalResponse.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth; + +import android.accounts.Account; +import android.content.Intent; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetHubTokenInternalResponse extends AutoSafeParcelable { + @Field(1) + public TokenData tokenData; + @Field(2) + public String status; + @Field(3) + public Intent recoveryIntent; + public static final Creator CREATOR = new AutoCreator<>(GetHubTokenInternalResponse.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..cdaffb519e7c7c4badcd54bd0b777a9495471f74 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/GetHubTokenRequest.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class GetHubTokenRequest extends AutoSafeParcelable { + @Field(1) + public String accountName; + @Field(2) + public String service; + @Field(3) + public String packageName; + @Field(4) + public int callerUid; + public static final Creator CREATOR = new AutoCreator<>(GetHubTokenRequest.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..27b94732cea69780e277850be6a892799c1fb6bc --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/HasCababilitiesRequest.java @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth; + +import android.accounts.Account; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class HasCababilitiesRequest extends AutoSafeParcelable { + @Field(1) + public Account account; + @Field(2) + public String[] capabilities; + public static final Creator CREATOR = new AutoCreator<>(HasCababilitiesRequest.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/maps/model/Dash.java b/play-services-api/src/main/java/com/google/android/gms/maps/model/Dash.java new file mode 100644 index 0000000000000000000000000000000000000000..27736cb80e0017f9bcae0a5da4c92657286e9871 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/maps/model/Dash.java @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.maps.model; + +import androidx.annotation.NonNull; + +import org.microg.gms.common.PublicApi; + +/** + * An immutable class representing a dash used in the stroke pattern for a Polyline or the outline of a Polygon or Circle. + */ +@PublicApi +public final class Dash extends PatternItem { + /** + * Length in pixels (non-negative). + */ + public final float length; + + /** + * Constructs a {@code Dash}. + * @param length Length in pixels. Negative value will be clamped to zero. + */ + public Dash(float length) { + super(0, Math.max(length, 0)); + this.length = Math.max(length, 0); + } + + @NonNull + @Override + public String toString() { + return "[Dash: length=" + length + "]"; + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/maps/model/Dot.java b/play-services-api/src/main/java/com/google/android/gms/maps/model/Dot.java new file mode 100644 index 0000000000000000000000000000000000000000..f9d6726ac4abe94e789aa6e9bcc162ee9e50f52c --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/maps/model/Dot.java @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.maps.model; + +import androidx.annotation.NonNull; + +import org.microg.gms.common.PublicApi; + +/** + * An immutable class representing a dot used in the stroke pattern for a Polyline or the outline of a Polygon or Circle. + */ +@PublicApi +public final class Dot extends PatternItem { + /** + * Constructs a {@code Dot}. + */ + public Dot() { + super(1, null); + } + + @NonNull + @Override + public String toString() { + return "[Dot]"; + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/maps/model/Gap.java b/play-services-api/src/main/java/com/google/android/gms/maps/model/Gap.java new file mode 100644 index 0000000000000000000000000000000000000000..d973941db8946d033930a0e82dc47eb4b4f77590 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/maps/model/Gap.java @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.maps.model; + +import androidx.annotation.NonNull; + +import org.microg.gms.common.PublicApi; + +/** + * An immutable class representing a gap used in the stroke pattern for a Polyline or the outline of a Polygon or Circle. + */ +@PublicApi +public final class Gap extends PatternItem { + /** + * Length in pixels (non-negative). + */ + public final float length; + + /** + * Constructs a {@code Gap}. + * @param length Length in pixels. Negative value will be clamped to zero. + */ + public Gap(float length) { + super(2, Math.max(length, 0)); + this.length = Math.max(length, 0); + } + + @NonNull + @Override + public String toString() { + return "[Gap: length=" + length + "]"; + } +} diff --git a/play-services-api/src/main/java/com/google/android/gms/maps/model/JointType.java b/play-services-api/src/main/java/com/google/android/gms/maps/model/JointType.java new file mode 100644 index 0000000000000000000000000000000000000000..79fafb325e1d2a593279b2787144f140653cd484 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/maps/model/JointType.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.maps.model; + +/** + * Joint types for Polyline and outline of Polygon. + */ +public final class JointType { + /** + * Default: Mitered joint, with fixed pointed extrusion equal to half the stroke width on the outside of the joint. + */ + public static final int DEFAULT = 0; + /** + * Flat bevel on the outside of the joint. + */ + public static final int BEVEL = 1; + /** + * Rounded on the outside of the joint by an arc of radius equal to half the stroke width, centered at the vertex. + */ + public static final int ROUND = 2; +} diff --git a/play-services-api/src/main/java/com/google/android/gms/maps/model/PatternItem.java b/play-services-api/src/main/java/com/google/android/gms/maps/model/PatternItem.java new file mode 100644 index 0000000000000000000000000000000000000000..c964cb86c6c5e5fb154b58f85493aba476b34240 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/maps/model/PatternItem.java @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.maps.model; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +/** + * Immutable item used in the stroke pattern for a Polyline or the outline of a Polygon or Circle. + */ +@PublicApi +public class PatternItem extends AutoSafeParcelable { + @Field(2) + private int type; + @Field(3) + private Float length; + + @PublicApi(exclude = true) + PatternItem(int type, Float length) { + this.type = type; + this.length = length; + } + + @Override + public String toString() { + return "[PatternItem: type=" + type + " length=" + length + "]"; + } + + public static final Creator CREATOR = new AutoCreator<>(PatternItem.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/maps/model/PolygonOptions.java b/play-services-api/src/main/java/com/google/android/gms/maps/model/PolygonOptions.java index 94fca7e196eeb2240b4f53185560f9fc3430430f..af93063c53ae690b62194ff303cb14afdaa8321a 100644 --- a/play-services-api/src/main/java/com/google/android/gms/maps/model/PolygonOptions.java +++ b/play-services-api/src/main/java/com/google/android/gms/maps/model/PolygonOptions.java @@ -1,17 +1,9 @@ /* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileCopyrightText: 2015 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. */ package com.google.android.gms.maps.model; @@ -20,35 +12,41 @@ import android.graphics.Color; import org.microg.gms.common.PublicApi; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; import java.util.ArrayList; import java.util.List; /** * Defines options for a polygon. - * TODO: Docs + *

+ * For more information, read the Shapes developer guide. */ @PublicApi public class PolygonOptions extends AutoSafeParcelable { - @SafeParceled(1) + @Field(1) private int versionCode = 1; - @SafeParceled(value = 2, subClass = LatLng.class) + @Field(value = 2, subClass = LatLng.class) private List points = new ArrayList(); - @SafeParceled(value = 3, subClass = LatLng.class, useClassLoader = true) + @Field(value = 3, subClass = LatLng.class, useValueParcel = true) private List> holes = new ArrayList>(); - @SafeParceled(4) + @Field(4) private float strokeWidth = 10; - @SafeParceled(5) + @Field(5) private int strokeColor = Color.BLACK; - @SafeParceled(6) - private int fillColor = Color.TRANSPARENT; - @SafeParceled(7) + @Field(6) + private int fillColor = Color.BLACK; + @Field(7) private float zIndex = 0; - @SafeParceled(8) + @Field(8) private boolean visible = true; - @SafeParceled(9) + @Field(9) private boolean geodesic = false; + @Field(10) + private boolean clickable = false; + @Field(11) + private int strokeJointType = JointType.DEFAULT; + @Field(value = 12, subClass = PatternItem.class) + private List strokePattern = null; /** * Creates polygon options. @@ -56,11 +54,12 @@ public class PolygonOptions extends AutoSafeParcelable { public PolygonOptions() { } - public PolygonOptions add(LatLng point) { - points.add(point); - return this; - } - + /** + * Adds vertices to the outline of the polygon being built. + * + * @param points an array of {@link LatLng}s that are added to the outline of the polygon. Must not be {@code null}. + * @return this {@link PolygonOptions} object with the given points added to the outline. + */ public PolygonOptions add(LatLng... points) { for (LatLng point : points) { this.points.add(point); @@ -68,6 +67,23 @@ public class PolygonOptions extends AutoSafeParcelable { return this; } + /** + * Adds a vertex to the outline of the polygon being built. + * + * @param point a {@link LatLng} that is added to the outline of the polygon. Must not be {@code null}. + * @return this {@link PolygonOptions} object with the given point added to the outline. + */ + public PolygonOptions add(LatLng point) { + points.add(point); + return this; + } + + /** + * Adds vertices to the outline of the polygon being built. + * + * @param points a list of {@link LatLng}s that are added to the outline of the polygon. Must not be {@code null}. + * @return this {@link PolygonOptions} object with the given points added to the outline. + */ public PolygonOptions add(Iterable points) { for (LatLng point : points) { this.points.add(point); @@ -75,6 +91,12 @@ public class PolygonOptions extends AutoSafeParcelable { return this; } + /** + * Adds a hole to the polygon being built. + * + * @param points an iterable of {@link LatLng}s that represents a hole. Must not be {@code null}. + * @return this {@link PolygonOptions} object with the given hole added. + */ public PolygonOptions addHole(Iterable points) { ArrayList hole = new ArrayList(); for (LatLng point : points) { @@ -84,63 +106,193 @@ public class PolygonOptions extends AutoSafeParcelable { return this; } + /** + * Specifies whether this polygon is clickable. The default setting is {@code false} + * + * @return this {@link PolygonOptions} object with a new clickability setting. + */ + public PolygonOptions clickable(boolean clickable) { + this.clickable = clickable; + return this; + } + + /** + * Specifies the polygon's fill color, as 32-bit ARGB. The default color is black ({@code 0xff000000}). + * + * @return this {@link PolygonOptions} object with a new fill color set. + */ public PolygonOptions fillColor(int color) { this.fillColor = color; return this; } + /** + * Specifies whether to draw each segment of this polygon as a geodesic. The default setting is {@code false} + * + * @return this {@link PolygonOptions} object with a new geodesic setting. + */ public PolygonOptions geodesic(boolean geodesic) { this.geodesic = geodesic; return this; } + /** + * Gets the fill color set for this {@link PolygonOptions} object. + * + * @return the fill color of the polygon in screen pixels. + */ public int getFillColor() { return fillColor; } + /** + * Gets the holes set for this {@link PolygonOptions} object. + * + * @return the list of {@code List}s specifying the holes of the polygon. + */ public List> getHoles() { return holes; } + /** + * Gets the outline set for this {@link PolygonOptions} object. + * + * @return the list of {@link LatLng}s specifying the vertices of the outline of the polygon. + */ public List getPoints() { return points; } + /** + * Gets the stroke color set for this {@link PolygonOptions} object. + * + * @return the stroke color of the polygon in screen pixels. + */ public int getStrokeColor() { return strokeColor; } + /** + * Gets the stroke joint type set in this {@link PolygonOptions} object for all vertices of the polygon's outline. See {@link JointType} for possible values. + * + * @return the stroke joint type of the polygon's outline. + */ + public int getStrokeJointType() { + return strokeJointType; + } + + /** + * Gets the stroke pattern set in this {@link PolygonOptions} object for the polygon's outline. + * + * @return the stroke pattern of the polygon's outline. + */ + public List getStrokePattern() { + return strokePattern; + } + + /** + * Gets the stroke width set for this {@link PolygonOptions} object. + * + * @return the stroke width of the polygon in screen pixels. + */ public float getStrokeWidth() { return strokeWidth; } + /** + * Gets the zIndex set for this {@link PolygonOptions} object. + * + * @return the zIndex of the polygon. + */ public float getZIndex() { return zIndex; } + /** + * Gets the clickability setting for this {@link PolygonOptions} object. + * + * @return {@code true} if the polygon is clickable; {@code false} if it is not. + */ + public boolean isClickable() { + return clickable; + } + + /** + * Gets the geodesic setting for this {@link PolygonOptions} object. + * + * @return {@code true} if the polygon segments should be geodesics; {@code false} if they should not be. + */ public boolean isGeodesic() { return geodesic; } + /** + * Gets the visibility setting for this {@link PolygonOptions} object. + * + * @return {@code true} if the polygon is to be visible; {@code false} if it is not. + */ public boolean isVisible() { return visible; } + /** + * Specifies the polygon's stroke color, as 32-bit ARGB. The default color is black ({@code 0xff000000}). + * + * @return this {@link PolygonOptions} object with a new stroke color set. + */ public PolygonOptions strokeColor(int color) { this.strokeColor = color; return this; } + /** + * Specifies the joint type for all vertices of the polygon's outline. + *

+ * See {@link JointType} for allowed values. The default value {@link JointType#DEFAULT} will be used if joint type + * is undefined or is not one of the allowed values. + * + * @return this {@link PolygonOptions} object with a new stroke joint type set. + */ + public PolygonOptions strokeJointType(int jointType) { + this.strokeJointType = jointType; + return this; + } + + /** + * Specifies a stroke pattern for the polygon's outline. The default stroke pattern is solid, represented by {@code null}. + * + * @return this {@link PolygonOptions} object with a new stroke pattern set. + */ + public PolygonOptions strokePattern(List pattern) { + this.strokePattern = pattern; + return this; + } + + /** + * Specifies the polygon's stroke width, in display pixels. The default width is 10. + * + * @return this {@link PolygonOptions} object with a new stroke width set. + */ public PolygonOptions strokeWidth(float width) { this.strokeWidth = width; return this; } + /** + * Specifies the visibility for the polygon. The default visibility is true. + * + * @return this {@link PolygonOptions} object with a new visibility setting. + */ public PolygonOptions visible(boolean visible) { this.visible = visible; return this; } + /** + * Specifies the polygon's zIndex, i.e., the order in which it will be drawn. See the documentation at the top of this class for more information about zIndex. + * + * @return this {@link PolygonOptions} object with a new zIndex set. + */ public PolygonOptions zIndex(float zIndex) { this.zIndex = zIndex; return this; diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/Configuration.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/Configuration.java index 20bb9d4cd1dd6e13788c0b44a8546e7e106fcd1a..2433df227737e379a4389aebde145fba9a506e01 100644 --- a/play-services-api/src/main/java/com/google/android/gms/phenotype/Configuration.java +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/Configuration.java @@ -9,10 +9,10 @@ import org.microg.safeparcel.AutoSafeParcelable; public class Configuration extends AutoSafeParcelable { @Field(2) - public int field2; + public int flagType; @Field(3) - public Flag[] field3; + public Flag[] flags; @Field(4) - public String[] field4; + public String[] names; public static final Creator CREATOR = new AutoCreator<>(Configuration.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/DogfoodsToken.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/DogfoodsToken.java new file mode 100644 index 0000000000000000000000000000000000000000..917d3757055c625814732e838835695e207678a1 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/DogfoodsToken.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.phenotype; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class DogfoodsToken extends AutoSafeParcelable { + public static final Creator CREATOR = new AutoCreator<>(DogfoodsToken.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentTokens.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentTokens.java new file mode 100644 index 0000000000000000000000000000000000000000..d3f858fec8c527787b7d1f635ef960b8f67bafd4 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/ExperimentTokens.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.phenotype; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class ExperimentTokens extends AutoSafeParcelable { + @Field(2) + public String field2; + @Field(3) + public byte[] direct; + @Field(4) + public byte[][] gaia; + @Field(5) + public byte[][] pseudo; + @Field(6) + public byte[][] always; + @Field(7) + public byte[][] other; + @Field(8) + public int[] weak; + @Field(9) + public byte[][] directs; + @Field(10) + public int[] genericDimensions; + public static final Creator CREATOR = new AutoCreator<>(ExperimentTokens.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/Flag.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/Flag.java index 0c1fdc8aa666ff3a68c36ba40c0ea63a0b6a9575..4d0a03af7b24dfe94e7e7884f03947beb4b57929 100644 --- a/play-services-api/src/main/java/com/google/android/gms/phenotype/Flag.java +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/Flag.java @@ -8,5 +8,95 @@ package com.google.android.gms.phenotype; import org.microg.safeparcel.AutoSafeParcelable; public class Flag extends AutoSafeParcelable { + @Field(2) + public String name; + @Field(3) + private long longValue; + @Field(4) + private boolean boolValue; + @Field(5) + private double doubleValue; + @Field(6) + private String stringValue; + @Field(7) + private byte[] bytesValue; + @Field(8) + public int dataType; + @Field(9) + public int flagType; + + private Flag() { + } + + public Flag(String name, long longValue, int flagType) { + this.name = name; + this.longValue = longValue; + this.dataType = DATA_TYPE_LONG; + this.flagType = flagType; + } + + public Flag(String name, boolean boolValue, int flagType) { + this.name = name; + this.boolValue = boolValue; + this.dataType = DATA_TYPE_BOOL; + this.flagType = flagType; + } + + public Flag(String name, double doubleValue, int flagType) { + this.name = name; + this.doubleValue = doubleValue; + this.dataType = DATA_TYPE_DOUBLE; + this.flagType = flagType; + } + + public Flag(String name, String stringValue, int flagType) { + this.name = name; + this.stringValue = stringValue; + this.dataType = DATA_TYPE_STRING; + this.flagType = flagType; + } + + public Flag(String name, byte[] bytesValue, int flagType) { + this.name = name; + this.bytesValue = bytesValue; + this.dataType = DATA_TYPE_BYTES; + this.flagType = flagType; + } + + public long getLong() { + if (dataType == DATA_TYPE_LONG) + return longValue; + throw new IllegalArgumentException("Not a long type"); + } + + public boolean getBool() { + if (dataType == DATA_TYPE_BOOL) + return boolValue; + throw new IllegalArgumentException("Not a boolean type"); + } + + public double getDouble() { + if (dataType == DATA_TYPE_DOUBLE) + return doubleValue; + throw new IllegalArgumentException("Not a double type"); + } + + public String getString() { + if (dataType == DATA_TYPE_STRING) + return stringValue; + throw new IllegalArgumentException("Not a String type"); + } + + public byte[] getBytes() { + if (dataType == DATA_TYPE_BYTES) + return bytesValue; + throw new IllegalArgumentException("Not a bytes type"); + } + + public static final int DATA_TYPE_LONG = 1; + public static final int DATA_TYPE_BOOL = 2; + public static final int DATA_TYPE_DOUBLE = 3; + public static final int DATA_TYPE_STRING = 4; + public static final int DATA_TYPE_BYTES = 5; public static final Creator CREATOR = new AutoCreator<>(Flag.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/FlagOverrides.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/FlagOverrides.java new file mode 100644 index 0000000000000000000000000000000000000000..dca1216939eb7e9e92b7210a82f501b7a5db2fcf --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/FlagOverrides.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.phenotype; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class FlagOverrides extends AutoSafeParcelable { + public static final Creator CREATOR = new AutoCreator<>(FlagOverrides.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/phenotype/RegistrationInfo.java b/play-services-api/src/main/java/com/google/android/gms/phenotype/RegistrationInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..d36733c05fbfb540378d4cf4d15e184dcc956daf --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/phenotype/RegistrationInfo.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.phenotype; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class RegistrationInfo extends AutoSafeParcelable { + @Field(1) + public String field1; + @Field(2) + public int field2; + @Field(3) + public String[] field3; + @Field(4) + public byte[] field4; + @Field(5) + public boolean field5; + @Field(6) + public int[] field6; + @Field(7) + public String field7; + + public static final Creator CREATOR = new AutoCreator<>(RegistrationInfo.class); +} diff --git a/play-services-appinvite-api/build.gradle b/play-services-appinvite-api/build.gradle index 19c84661c78f8bf72401110af8e93f879fc2a823..3620567c61287799bfd35ab7baca2fa99c11c766 100644 --- a/play-services-appinvite-api/build.gradle +++ b/play-services-appinvite-api/build.gradle @@ -16,26 +16,16 @@ apply plugin: 'com.android.library' -String getMyVersionName() { - def stdout = new ByteArrayOutputStream() - if (rootProject.file("gradlew").exists()) - exec { commandLine 'git', 'describe', '--tags', '--always', '--dirty'; standardOutput = stdout } - else // automatic build system, don't tag dirty - exec { commandLine 'git', 'describe', '--tags', '--always'; standardOutput = stdout } - return stdout.toString().trim().substring(1) -} - group = 'org.microg' -version = getMyVersionName() android { - compileSdkVersion androidCompileSdk() + compileSdkVersion androidCompileSdk buildToolsVersion "$androidBuildVersionTools" defaultConfig { - versionName getMyVersionName() - minSdkVersion androidMinSdk() - targetSdkVersion androidTargetSdk() + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk } compileOptions { diff --git a/play-services-base-core-ui/src/main/res/values-zh-rCN/strings.xml b/play-services-base-core-ui/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..6919f44d3113184980a138d45c674f73db408870 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,7 @@ + + + 高级 + + 全部显示 + 打开 + \ No newline at end of file diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/HttpFormClient.java b/play-services-base-core/src/main/java/org/microg/gms/common/HttpFormClient.java index 8aee1df7551134bca50903a4c11955928f2413af..a00554e48d5dbf690c7d0a87f517c835f481ed6e 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/HttpFormClient.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/HttpFormClient.java @@ -133,10 +133,12 @@ public class HttpFormClient { String[] keyValuePair = s.split("=", 2); String key = keyValuePair[0].trim(); String value = keyValuePair[1].trim(); + boolean matched = false; try { for (Field field : tClass.getDeclaredFields()) { if (field.isAnnotationPresent(ResponseField.class) && key.equals(field.getAnnotation(ResponseField.class).value())) { + matched = true; if (field.getType().equals(String.class)) { field.set(response, value); } else if (field.getType().equals(boolean.class)) { @@ -151,6 +153,9 @@ public class HttpFormClient { } catch (Exception e) { Log.w(TAG, e); } + if (!matched) { + Log.w(TAG, "Response line '" + s + "' not processed"); + } } for (Field field : tClass.getDeclaredFields()) { if (field.isAnnotationPresent(ResponseHeader.class)) { diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerUtils.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..e4fbc7418ac8ad7aca108a8b31fa405376168a21 --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerUtils.kt @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.utils + +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.NameNotFoundException +import android.content.pm.Signature +import android.util.Base64 +import java.security.MessageDigest + +fun PackageManager.getSignatures(packageName: String): Array = try { + getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures +} catch (e: NameNotFoundException) { + emptyArray() +} + +fun PackageManager.getApplicationLabel(packageName: String): CharSequence = try { + getApplicationLabel(getApplicationInfo(packageName, 0)) +} catch (e: Exception) { + packageName +} + +fun ByteArray.toBase64(vararg flags: Int): String = Base64.encodeToString(this, flags.fold(0) { a, b -> a or b }) + +fun PackageManager.getFirstSignatureDigest(packageName: String, md: String): ByteArray? = + getSignatures(packageName).firstOrNull()?.digest(md) + +fun Signature.digest(md: String): ByteArray = MessageDigest.getInstance(md).digest(toByteArray()) diff --git a/play-services-base-core/src/main/res/values-zh-rCN/strings.xml b/play-services-base-core/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..f2ce0564634766833d36db02fe31cbb0257fee1c --- /dev/null +++ b/play-services-base-core/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,6 @@ + + + 正在后台活动 + %1$s 正在后台运行 + %1$s 忽略电池优化,或者修改通知设置以隐藏此通知。 + \ No newline at end of file diff --git a/play-services-base/build.gradle b/play-services-base/build.gradle index e9c4a1a221a506599d64ffd1a1cd55c1d9e0c5b7..74cd63e50888c1e4ffdaac24b187bbb42e40a834 100644 --- a/play-services-base/build.gradle +++ b/play-services-base/build.gradle @@ -49,9 +49,12 @@ apply from: '../gradle/publish-android.gradle' description = 'microG implementation of play-services-base' dependencies { - api project(':play-services-basement') - api project(':play-services-tasks') api project(':play-services-base-api') - implementation "androidx.fragment:fragment:$fragmentVersion" + // Dependencies from play-services-base:18.0.1 + api "androidx.collection:collection:1.0.0" + api "androidx.core:core:1.2.0" + api "androidx.fragment:fragment:1.0.0" + api project(':play-services-basement') + api project(':play-services-tasks') } diff --git a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java index a03b2c3bd1f3ac47d651b32e5806e81a4fe0646f..9558ebe3039f2615c502a6977f4dae528cb7301e 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java +++ b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java @@ -27,6 +27,7 @@ import android.os.RemoteException; import android.util.Log; import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.internal.ConnectionInfo; import com.google.android.gms.common.internal.GetServiceRequest; import com.google.android.gms.common.internal.IGmsCallbacks; @@ -166,17 +167,23 @@ public abstract class GmsClient implements ApiClient { @Override public void onPostInitComplete(int statusCode, IBinder binder, Bundle params) throws RemoteException { - synchronized (GmsClient.this) { - if (state == ConnectionState.DISCONNECTING) { + if (statusCode != CommonStatusCodes.SUCCESS) { + state = ConnectionState.CONNECTED; + disconnect(); + connectionFailedListener.onConnectionFailed(new ConnectionResult(statusCode)); + } else { + synchronized (GmsClient.this) { + if (state == ConnectionState.DISCONNECTING) { + state = ConnectionState.CONNECTED; + disconnect(); + return; + } state = ConnectionState.CONNECTED; - disconnect(); - return; + serviceInterface = interfaceFromBinder(binder); } - state = ConnectionState.CONNECTED; - serviceInterface = interfaceFromBinder(binder); + Log.d(TAG, "GmsCallbacks : onPostInitComplete(" + serviceInterface + ")"); + callbacks.onConnected(params); } - Log.d(TAG, "GmsCallbacks : onPostInitComplete(" + serviceInterface + ")"); - callbacks.onConnected(params); } @Override diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index 1554ca75b0f31b84ded005f9cdbb59b2e12c50fc..3fc9d01e5e52369aea9c76d24f0f930360a07ec8 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -21,7 +21,10 @@ apply plugin: 'signing' dependencies { api "org.microg:safe-parcel:$safeParcelVersion" - implementation "androidx.annotation:annotation:$annotationVersion" + // Dependencies from play-services-basement:18.0.0 + api "androidx.collection:collection:1.0.0" + api "androidx.core:core:1.2.0" + api "androidx.fragment:fragment:1.0.0" } android { diff --git a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java index 108eb625a880f181f6f72c884b4ba2443126536e..91e54c1ba7eb2392c301ce750509ba4294fa8ca2 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java @@ -125,6 +125,7 @@ public enum GmsService { FIDO2_ZEROPARTY(180, "com.google.android.gms.fido.fido2.zeroparty.START"), G1_RESTORE(181, "com.google.android.gms.backup.G1_RESTORE"), G1_BACKUP(182, "com.google.android.gms.backup.G1_BACKUP"), + OSS_LICENSES(185, "com.google.android.gms.oss.licenses.service.START"), PAYSE(188, "com.google.android.gms.payse.service.BIND"), RCS(189, "com.google.android.gms.rcs.START"), CARRIER_AUTH(191, "com.google.android.gms.carrierauth.service.START"), diff --git a/play-services-basement/src/main/java/org/microg/gms/utils/ToStringHelper.java b/play-services-basement/src/main/java/org/microg/gms/utils/ToStringHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..deb4e0f9866306420f200b0c1d1c19e28d45f5e8 --- /dev/null +++ b/play-services-basement/src/main/java/org/microg/gms/utils/ToStringHelper.java @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.utils; + +import android.util.Base64; + +public class ToStringHelper { + private StringBuilder sb; + private boolean hasField; + private boolean hasValue; + private boolean hasEnd; + + public ToStringHelper(String name) { + this.sb = new StringBuilder(name).append("["); + } + + public static ToStringHelper name(String name) { + return new ToStringHelper(name); + } + + public ToStringHelper value(String val) { + if (!hasField) { + if (hasValue) sb.append(','); + sb.append(val); + hasValue = true; + } + return this; + } + + public ToStringHelper value(long val) { + return value(Long.toString(val)); + } + + public ToStringHelper value(double val) { + return value(Double.toString(val)); + } + + public ToStringHelper value(Object val) { + if (val instanceof Long) value((long) val); + if (val instanceof Double) value((double) val); + return value(val, false); + } + + public ToStringHelper value(Object val, boolean forceNull) { + if (val == null && !forceNull) return this; + return value(val == null ? "null" : val.toString()); + } + + public ToStringHelper value(byte[] val) { + return value(val, false); + } + + public ToStringHelper value(byte[] val, boolean forceNull) { + if (val == null && !forceNull) return this; + return value(val == null ? "null" : Base64.encodeToString(val, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE)); + } + + private ToStringHelper fieldUnquoted(String name, String val) { + if (hasValue || hasField) sb.append(", "); + sb.append(name).append('=').append(val); + hasField = true; + return this; + } + + public ToStringHelper field(String name, String val) { + return field(name, val, false); + } + + public ToStringHelper field(String name, String val, boolean forceNull) { + if (val == null && !forceNull) return this; + if (val == null) return fieldUnquoted(name, "null"); + if (hasValue || hasField) sb.append(", "); + sb.append(name).append("=\"").append(val.replace("\"", "\\\"")).append('"'); + hasField = true; + return this; + } + + public ToStringHelper field(String name, long val) { + return fieldUnquoted(name, Long.toString(val)); + } + + public ToStringHelper field(String name, double val) { + return fieldUnquoted(name, Double.toString(val)); + } + + public ToStringHelper field(String name, boolean val) { + return fieldUnquoted(name, Boolean.toString(val)); + } + + public ToStringHelper field(String name, Object val) { + if (val instanceof Long) return field(name, (long) val); + if (val instanceof Double) return field(name, (double) val); + if (val instanceof Boolean) return field(name, (boolean) val); + return field(name, val, false); + } + + public ToStringHelper field(String name, Object val, boolean forceNull) { + if (val == null && !forceNull) return this; + return fieldUnquoted(name, val == null ? "null" : val.toString()); + } + + public ToStringHelper field(String name, byte[] val) { + return field(name, val, false); + } + + public ToStringHelper field(String name, byte[] val, boolean forceNull) { + if (val == null && !forceNull) return this; + return fieldUnquoted(name, val == null ? "null" : Base64.encodeToString(val, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE)); + } + + public String end() { + if (!hasEnd) sb.append(']'); + hasEnd = true; + return sb.toString(); + } +} diff --git a/play-services-cast/build.gradle b/play-services-cast/build.gradle index a79abba6ed5a54b06b78f2aa8e44aa308be9ad33..cf6d2a6cee02418454ec9051581f07147438823a 100644 --- a/play-services-cast/build.gradle +++ b/play-services-cast/build.gradle @@ -1,38 +1,18 @@ /* - * Copyright 2013-2015 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileCopyrightText: 2015 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.library' -String getMyVersionName() { - def stdout = new ByteArrayOutputStream() - if (rootProject.file("gradlew").exists()) - exec { commandLine 'git', 'describe', '--tags', '--always', '--dirty'; standardOutput = stdout } - else // automatic build system, don't tag dirty - exec { commandLine 'git', 'describe', '--tags', '--always'; standardOutput = stdout } - return stdout.toString().trim().substring(1) -} - android { - compileSdkVersion androidCompileSdk() + compileSdkVersion androidCompileSdk buildToolsVersion "$androidBuildVersionTools" defaultConfig { - versionName getMyVersionName() - minSdkVersion androidMinSdk() - targetSdkVersion androidTargetSdk() + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk } compileOptions { @@ -52,6 +32,13 @@ android { } dependencies { - api project(':play-services-base') api project(':play-services-cast-api') + + // Dependencies from play-services-cast:21.0.1 + api "androidx.core:core:1.0.0" + api "androidx.mediarouter:mediarouter:1.2.2" + api project(':play-services-base') + api project(':play-services-basement') + //api project(':play-services-flags') + api project(':play-services-tasks') } diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 4636686ccd3afbdca7d2f15665e750a9de943432..ec78031000cf1b3eee3337e2ce22fa8a8600eda9 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -1,17 +1,6 @@ /* - * Copyright 2013-2019 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-FileCopyrightText: 2013 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.application' @@ -47,10 +36,13 @@ dependencies { implementation project(':play-services-cronet-core') implementation project(':play-services-droidguard-core') implementation project(':play-services-droidguard-core-ui') + implementation project(':play-services-fido-core') + implementation project(':play-services-gmscompliance-core') implementation project(':play-services-location-core') withNearbyImplementation project(':play-services-nearby-core') withNearbyImplementation project(':play-services-nearby-core-ui') implementation project(':play-services-safetynet-core') + implementation project(':play-services-oss-licenses-core') implementation project(':play-services-safetynet-core-ui') implementation project(':play-services-tapandpay-core') implementation project(':play-services-vision-core') @@ -88,7 +80,7 @@ dependencies { } android { - compileSdkVersion androidCompileSdk() + compileSdkVersion androidCompileSdk buildToolsVersion "$androidBuildVersionTools" defaultConfig { diff --git a/play-services-core/microg-ui-tools/src/main/res/values-zh-rCN/strings.xml b/play-services-core/microg-ui-tools/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..57784a43e1ce30ccc28f3b019a592857afd0fbdf --- /dev/null +++ b/play-services-core/microg-ui-tools/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,15 @@ + + + microG UI Tools + 版本 %1$s + %1$s %2$s + 保留所有权利。 + 设置 + 自我检查 + 确认系统是否已正确配置成 microG 可正常使用的状态 + 已授予权限 + %1$s的权限: + 点击授予权限。不授予权限可能导致应用工作异常。 + 包含的库 + 概要 + \ No newline at end of file diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index f47b3b9b7d840671a82ae3abed69cdcc30fef4e8..9efd52da1b12312022eebd10895bdc80bfc0703b 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -530,12 +530,6 @@ android:process=":ui" android:theme="@style/Theme.AppCompat.DayNight.Dialog.Alert.NoActionBar" /> - - - @@ -781,7 +774,6 @@ - diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java b/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java index b1fb4822250f60d2da89be6a2f29fbca361517c3..bba39ab53e2e9dd956ab0c62610073f82205868a 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AccountContentProvider.java @@ -16,6 +16,8 @@ package org.microg.gms.auth; +import static android.accounts.AccountManager.VISIBILITY_VISIBLE; + import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; @@ -57,7 +59,7 @@ public class AccountContentProvider extends ContentProvider { suggestedPackageName = getCallingPackage(); } String packageName = PackageUtils.getAndCheckCallingPackage(getContext(), suggestedPackageName); - Log.d(TAG, "Call from " + packageName); + Log.d(TAG, "Call " + method + " from " + packageName + " with arg " + arg); if (!PackageUtils.callerHasExtendedAccess(getContext())) { String[] packagesForUid = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid()); if (packagesForUid != null && packagesForUid.length != 0) @@ -71,17 +73,27 @@ public class AccountContentProvider extends ContentProvider { Account[] accounts = null; if (arg != null && (arg.equals(DEFAULT_ACCOUNT_TYPE) || arg.startsWith(DEFAULT_ACCOUNT_TYPE + "."))) { AccountManager am = AccountManager.get(getContext()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (Build.VERSION.SDK_INT >= 18) { accounts = am.getAccountsByTypeForPackage(arg, packageName); } if (accounts == null || accounts.length == 0) { accounts = am.getAccountsByType(arg); } + if (Build.VERSION.SDK_INT >= 26 && accounts != null && arg.equals(DEFAULT_ACCOUNT_TYPE)) { + for (Account account : accounts) { + if (am.getAccountVisibility(account, packageName) == AccountManager.VISIBILITY_UNDEFINED) { + Log.d(TAG, "Make account " + account + " visible to " + packageName); + am.setAccountVisibility(account, packageName, VISIBILITY_VISIBLE); + } + } + } } if (accounts == null) { accounts = new Account[0]; } + result.putParcelableArray(PROVIDER_EXTRA_ACCOUNTS, accounts); + Log.d(TAG, "get_accounts returns: " + Arrays.toString(accounts)); return result; } else if (PROVIDER_METHOD_CLEAR_PASSWORD.equals(method)) { Account a = extras.getParcelable(PROVIDER_EXTRA_CLEAR_PASSWORD); diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index d2f5b6628692a02765533f0cc2d31931f72b487f..59422af29a2415ffe1539824d06fde91c91746a8 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -38,6 +38,9 @@ import com.google.android.auth.IAuthManagerService; import com.google.android.gms.R; import com.google.android.gms.auth.AccountChangeEventsRequest; import com.google.android.gms.auth.AccountChangeEventsResponse; +import com.google.android.gms.auth.GetHubTokenInternalResponse; +import com.google.android.gms.auth.GetHubTokenRequest; +import com.google.android.gms.auth.HasCababilitiesRequest; import com.google.android.gms.auth.TokenData; import com.google.android.gms.common.api.Scope; @@ -45,6 +48,7 @@ import org.microg.gms.common.PackageUtils; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -205,6 +209,18 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { return null; } + @Override + public int hasCapabilities(HasCababilitiesRequest request) throws RemoteException { + Log.w(TAG, "Not implemented: hasCapabilities(" + request.account + ", " + Arrays.toString(request.capabilities) + ")"); + return 1; + } + + @Override + public GetHubTokenInternalResponse getHubToken(GetHubTokenRequest request, Bundle extras) throws RemoteException { + Log.w(TAG, "Not implemented: getHubToken()"); + return null; + } + @Override @SuppressLint("MissingPermission") // Workaround bug in Android Linter public Bundle clearToken(String token, Bundle extras) { diff --git a/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java b/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java index c52565ce0499e8bf09e1d616efac0b93c2883cc6..5b4118480c3cced3bf4b419f8860d46b7d187a8a 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/loginservice/AccountAuthenticator.java @@ -147,8 +147,10 @@ class AccountAuthenticator extends AbstractAccountAuthenticator { if (services != null) { List servicesList = Arrays.asList(services.split(",")); for (String feature : features) { - if (feature.startsWith("service_") && !servicesList.contains(feature.substring(8))) + if (feature.startsWith("service_") && !servicesList.contains(feature.substring(8))) { + Log.d(TAG, "Feature " + feature + " not supported"); res = false; + } } } else { res = false; diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 5a9eb53411c7d9364f3a647977ba902c9d3a0aa6..2142648b1f1a009c8ad705f85e34eabd6f3050e4 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -16,6 +16,7 @@ package org.microg.gms.gcm; +import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; @@ -39,6 +40,8 @@ import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.legacy.content.WakefulBroadcastReceiver; import com.google.android.gms.R; @@ -120,7 +123,10 @@ public class McsService extends Service implements Handler.Callback { public static final String FROM_FIELD = "gcm@android.com"; public static final String SERVICE_HOST = "mtalk.google.com"; - public static final int SERVICE_PORT = 5228; + // A few ports are available: 443, 5228-5230 but also 5222-5223 + // See https://github.com/microg/GmsCore/issues/408 + // Likely if the main port 5228 is blocked by a firewall, the other 52xx are blocked as well + public static final int[] SERVICE_PORTS = {5228, 443}; private static final int WAKELOCK_TIMEOUT = 5000; // On bad mobile network a ping can take >60s, so we wait for an ACK for 90s @@ -155,9 +161,18 @@ public class McsService extends Service implements Handler.Callback { private static int maxTtl = 24 * 60 * 60; - private Object deviceIdleController; + @Nullable private Method getUserIdMethod; + @Nullable + private Object deviceIdleController; + @Nullable private Method addPowerSaveTempWhitelistAppMethod; + @Nullable + @RequiresApi(Build.VERSION_CODES.S) + private Object powerExemptionManager; + @Nullable + @RequiresApi(Build.VERSION_CODES.S) + private Method addToTemporaryAllowListMethod; private class HandlerThread extends Thread { @@ -186,6 +201,7 @@ public class McsService extends Service implements Handler.Callback { } @Override + @SuppressLint("PrivateApi") public void onCreate() { super.onCreate(); TriggerReceiver.register(this); @@ -195,20 +211,27 @@ public class McsService extends Service implements Handler.Callback { powerManager = (PowerManager) getSystemService(POWER_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") == PackageManager.PERMISSION_GRANTED) { try { - String deviceIdleControllerName = "deviceidle"; - try { - Field field = Context.class.getField("DEVICE_IDLE_CONTROLLER"); - deviceIdleControllerName = (String) field.get(null); - } catch (Exception ignored) { - } - IBinder binder = (IBinder) Class.forName("android.os.ServiceManager") - .getMethod("getService", String.class).invoke(null, deviceIdleControllerName); - if (binder != null) { - deviceIdleController = Class.forName("android.os.IDeviceIdleController$Stub") - .getMethod("asInterface", IBinder.class).invoke(null, binder); - getUserIdMethod = UserHandle.class.getMethod("getUserId", int.class); - addPowerSaveTempWhitelistAppMethod = deviceIdleController.getClass() - .getMethod("addPowerSaveTempWhitelistApp", String.class, long.class, int.class, String.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + Class powerExemptionManagerClass = Class.forName("android.os.PowerExemptionManager"); + powerExemptionManager = getSystemService(powerExemptionManagerClass); + addToTemporaryAllowListMethod = + powerExemptionManagerClass.getMethod("addToTemporaryAllowList", String.class, int.class, String.class, long.class); + } else { + String deviceIdleControllerName = "deviceidle"; + try { + Field field = Context.class.getField("DEVICE_IDLE_CONTROLLER"); + deviceIdleControllerName = (String) field.get(null); + } catch (Exception ignored) { + } + IBinder binder = (IBinder) Class.forName("android.os.ServiceManager") + .getMethod("getService", String.class).invoke(null, deviceIdleControllerName); + if (binder != null) { + deviceIdleController = Class.forName("android.os.IDeviceIdleController$Stub") + .getMethod("asInterface", IBinder.class).invoke(null, binder); + getUserIdMethod = UserHandle.class.getMethod("getUserId", int.class); + addPowerSaveTempWhitelistAppMethod = deviceIdleController.getClass() + .getMethod("addPowerSaveTempWhitelistApp", String.class, long.class, int.class, String.class); + } } } catch (Exception e) { Log.w(TAG, e); @@ -440,38 +463,52 @@ public class McsService extends Service implements Handler.Callback { } } + private void connect(int port) throws Exception { + this.wasTornDown = false; + + logd(this, "Starting MCS connection to port " + port + "..."); + Socket socket = new Socket(SERVICE_HOST, port); + logd(this, "Connected to " + SERVICE_HOST + ":" + port); + sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, port, true); + logd(this, "Activated SSL with " + SERVICE_HOST + ":" + port); + inputStream = new McsInputStream(sslSocket.getInputStream(), rootHandler); + outputStream = new McsOutputStream(sslSocket.getOutputStream(), rootHandler); + inputStream.start(); + outputStream.start(); + + startTimestamp = System.currentTimeMillis(); + lastHeartbeatPingElapsedRealtime = SystemClock.elapsedRealtime(); + lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); + lastIncomingNetworkRealtime = SystemClock.elapsedRealtime(); + scheduleHeartbeat(this); + } + private synchronized void connect() { - try { - closeAll(); - ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); - activeNetworkPref = GcmPrefs.get(this).getNetworkPrefForInfo(activeNetworkInfo); - if (!GcmPrefs.get(this).isEnabledFor(activeNetworkInfo)) { - logd(this, "Don't connect, because disabled for " + activeNetworkInfo.getTypeName()); - scheduleReconnect(this); + closeAll(); + + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); + activeNetworkPref = GcmPrefs.get(this).getNetworkPrefForInfo(activeNetworkInfo); + if (!GcmPrefs.get(this).isEnabledFor(activeNetworkInfo)) { + logd(this, "Don't connect, because disabled for " + activeNetworkInfo.getTypeName()); + scheduleReconnect(this); + return; + } + + Exception exception = null; + for (int port : SERVICE_PORTS) { + try { + connect(port); return; + } catch (Exception e) { + exception = e; + Log.w(TAG, "Exception while connecting to " + SERVICE_HOST + ":" + port, e); + closeAll(); } - wasTornDown = false; - - logd(this, "Starting MCS connection..."); - Socket socket = new Socket(SERVICE_HOST, SERVICE_PORT); - logd(this, "Connected to " + SERVICE_HOST + ":" + SERVICE_PORT); - sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, SERVICE_PORT, true); - logd(this, "Activated SSL with " + SERVICE_HOST + ":" + SERVICE_PORT); - inputStream = new McsInputStream(sslSocket.getInputStream(), rootHandler); - outputStream = new McsOutputStream(sslSocket.getOutputStream(), rootHandler); - inputStream.start(); - outputStream.start(); - - startTimestamp = System.currentTimeMillis(); - lastHeartbeatPingElapsedRealtime = SystemClock.elapsedRealtime(); - lastHeartbeatAckElapsedRealtime = SystemClock.elapsedRealtime(); - lastIncomingNetworkRealtime = SystemClock.elapsedRealtime(); - scheduleHeartbeat(this); - } catch (Exception e) { - Log.w(TAG, "Exception while connecting!", e); - rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, e)); } + + logd(this, "Unable to connect to all different ports, retrying later"); + rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, exception)); } private void handleClose(Close close) { @@ -594,7 +631,16 @@ public class McsService extends Service implements Handler.Callback { } private void addPowerSaveTempWhitelistApp(String packageName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + try { + if (addToTemporaryAllowListMethod != null && powerExemptionManager != null) { + logd(this, "Adding app " + packageName + " to the temp allowlist"); + addToTemporaryAllowListMethod.invoke(powerExemptionManager, packageName, 0, "GCM Push", 10000); + } + } catch (Exception e) { + Log.e(TAG, "Error adding app" + packageName + " to the temp allowlist.", e); + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { if (getUserIdMethod != null && addPowerSaveTempWhitelistAppMethod != null && deviceIdleController != null) { int userId = (int) getUserIdMethod.invoke(null, getPackageManager().getApplicationInfo(packageName, 0).uid); diff --git a/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java index 9fa7c11db191242946d0d98b12ffe1be75e72e6e..491b720109596a14660b3be80a9f1ede426d373d 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/AccountSettingsActivity.java @@ -28,13 +28,12 @@ import androidx.preference.Preference; import com.google.android.gms.R; import org.microg.gms.auth.AuthConstants; -import org.microg.gms.auth.AuthManager; import org.microg.tools.ui.AbstractSettingsActivity; import org.microg.tools.ui.ResourceSettingsFragment; import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE; -import static android.accounts.AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE; -import static android.accounts.AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; +import static android.accounts.AccountManager.VISIBILITY_NOT_VISIBLE; +import static android.accounts.AccountManager.VISIBILITY_VISIBLE; import static org.microg.gms.auth.AuthManager.PREF_AUTH_VISIBLE; public class AccountSettingsActivity extends AbstractSettingsActivity { @@ -61,7 +60,7 @@ public class AccountSettingsActivity extends AbstractSettingsActivity { if (newValue instanceof Boolean) { AccountManager am = AccountManager.get(getContext()); for (Account account : am.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)) { - am.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, (Boolean) newValue ? VISIBILITY_USER_MANAGED_VISIBLE : VISIBILITY_USER_MANAGED_NOT_VISIBLE); + am.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, (Boolean) newValue ? VISIBILITY_VISIBLE : VISIBILITY_NOT_VISIBLE); } } return true; diff --git a/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt b/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt index ec83c68162b5bf06fb8a7d3f1e98c955a765b792..332cae4093f80c7007c6a0cf8568561947022094 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-FileCopyrightText: 2020 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ @@ -10,14 +10,14 @@ import android.util.Log import com.google.android.gms.common.api.Status import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.IGmsCallbacks -import com.google.android.gms.phenotype.Configurations +import com.google.android.gms.phenotype.* import com.google.android.gms.phenotype.internal.IPhenotypeCallbacks import com.google.android.gms.phenotype.internal.IPhenotypeService import org.microg.gms.BaseService import org.microg.gms.common.GmsService import org.microg.gms.utils.warnOnTransactionIssues -private const val TAG = "GmsPhenotypeSvc" +private const val TAG = "PhenotypeService" class PhenotypeService : BaseService(TAG, GmsService.PHENOTYPE) { override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest?, service: GmsService?) { @@ -28,20 +28,117 @@ class PhenotypeService : BaseService(TAG, GmsService.PHENOTYPE) { class PhenotypeServiceImpl : IPhenotypeService.Stub() { override fun register(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: ByteArray?) { Log.d(TAG, "register($p1, $p2, $p3, $p4)") - callbacks.onRegister(Status.SUCCESS) + callbacks.onRegistered(Status.SUCCESS) } - override fun register2(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: IntArray?, p5: ByteArray?) { - Log.d(TAG, "register2($p1, $p2, $p3, $p4, $p5)") - callbacks.onRegister(Status.SUCCESS) + override fun weakRegister(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: IntArray?, p5: ByteArray?) { + Log.d(TAG, "weakRegister($p1, $p2, $p3, $p4, $p5)") + callbacks.onWeakRegistered(Status.SUCCESS) } - override fun getConfigurationSnapshot(callbacks: IPhenotypeCallbacks, p1: String?, p2: String?, p3: String?) { - Log.d(TAG, "getConfigurationSnapshot($p1, $p2, $p3)") - callbacks.onConfigurations(Status.SUCCESS, Configurations().apply { + override fun unregister(callbacks: IPhenotypeCallbacks, p1: String?) { + Log.d(TAG, "unregister($p1)") + callbacks.onUnregistered(Status.SUCCESS) + } + + override fun getConfigurationSnapshot(callbacks: IPhenotypeCallbacks, p1: String?, p2: String?) { + Log.d(TAG, "getConfigurationSnapshot($p1, $p2)") + callbacks.onConfiguration(Status.SUCCESS, Configurations().apply { + field4 = emptyArray() + }) + } + + override fun commitToConfiguration(callbacks: IPhenotypeCallbacks, p1: String?) { + Log.d(TAG, "commitToConfiguration($p1)") + callbacks.onCommitedToConfiguration(Status.SUCCESS) + } + + override fun getExperimentTokens(callbacks: IPhenotypeCallbacks, p1: String?, logSourceName: String?) { + Log.d(TAG, "getExperimentTokens($p1, $logSourceName)") + callbacks.onExperimentTokens(Status.SUCCESS, ExperimentTokens()) + } + + override fun getDogfoodsToken(callbacks: IPhenotypeCallbacks) { + Log.d(TAG, "getDogfoodsToken()") + callbacks.onDogfoodsToken(Status.SUCCESS, DogfoodsToken()) + } + + override fun setDogfoodsToken(callbacks: IPhenotypeCallbacks, p1: ByteArray?) { + Log.d(TAG, "setDogfoodsToken($p1)") + callbacks.onDogfoodsTokenSet(Status.SUCCESS) + } + + override fun getFlag(callbacks: IPhenotypeCallbacks, packageName: String?, name: String?, type: Int) { + Log.d(TAG, "setDogfoodsToken($packageName, $name, $type)") + callbacks.onFlag(Status.SUCCESS, null) + } + + override fun getCommitedConfiguration(callbacks: IPhenotypeCallbacks, p1: String?) { + Log.d(TAG, "getCommitedConfiguration($p1)") + callbacks.onCommittedConfiguration(Status.SUCCESS, Configurations().apply { + field4 = emptyArray() + }) + } + + override fun getConfigurationSnapshot2(callbacks: IPhenotypeCallbacks, p1: String?, p2: String?, p3: String?) { + Log.d(TAG, "getConfigurationSnapshot2($p1, $p2, $p3)") + callbacks.onConfiguration(Status.SUCCESS, Configurations().apply { + field4 = emptyArray() + }) + } + + override fun syncAfterOperation(callbacks: IPhenotypeCallbacks, p1: String?, p2: Long) { + Log.d(TAG, "syncAfterOperation($p1, $p2)") + callbacks.onSyncFinished(Status.SUCCESS, p2) + } + + override fun registerSync(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: ByteArray?, p5: String?, p6: String?) { + Log.d(TAG, "registerSync($p1, $p2, $p3, $p4, $p5, $p6)") + callbacks.onConfiguration(Status.SUCCESS, Configurations().apply { field4 = emptyArray() }) } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } + override fun setFlagOverrides(callbacks: IPhenotypeCallbacks, packageName: String?, user: String?, flagName: String?, flagType: Int, flagDataType: Int, flagValue: String?) { + Log.d(TAG, "setFlagOverrides($packageName, $user, $flagName, $flagDataType, $flagType, $flagDataType, $flagValue)") + callbacks.onFlagOverridesSet(Status.SUCCESS) + } + + override fun deleteFlagOverrides(callbacks: IPhenotypeCallbacks, packageName: String?, user: String?, flagName: String?) { + Log.d(TAG, "deleteFlagOverrides($packageName, $user, $flagName)") + callbacks.onFlagOverrides(Status.SUCCESS, FlagOverrides()) + } + + override fun listFlagOverrides(callbacks: IPhenotypeCallbacks, packageName: String?, user: String?, flagName: String?) { + Log.d(TAG, "listFlagOverrides($packageName, $user, $flagName)") + callbacks.onFlagOverrides(Status.SUCCESS, FlagOverrides()) + } + + override fun clearFlagOverrides(callbacks: IPhenotypeCallbacks, packageName: String?, user: String?) { + Log.d(TAG, "clearFlagOverrides($packageName, $user)") + callbacks.onFlagOverridesSet(Status.SUCCESS) + } + + override fun bulkRegister(callbacks: IPhenotypeCallbacks, infos: Array?) { + Log.d(TAG, "bulkRegister($infos)") + callbacks.onRegistered(Status.SUCCESS) + } + + override fun setAppSpecificProperties(callbacks: IPhenotypeCallbacks, p1: String?, p2: ByteArray?) { + Log.d(TAG, "setAppSpecificProperties($p1, $p2)") + callbacks.onAppSpecificPropertiesSet(Status.SUCCESS) + } + + override fun getServingVersion(callbacks: IPhenotypeCallbacks) { + Log.d(TAG, "getServingVersion()") + callbacks.onServingVersion(Status.SUCCESS, 1) + } + + override fun getExperimentTokens2(callbacks: IPhenotypeCallbacks, p1: String?, p2: String?, p3: String?, p4: String?) { + Log.d(TAG, "getExperimentTokens2($p1, $p2, $p3, $p4)") + callbacks.onExperimentTokens(Status.SUCCESS, ExperimentTokens()) + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt index 9512768940286ff9e0e6c158bf4a8e875210def6..5f8dc3743ad9e68b8f347bee2c21af3aba76d147 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt @@ -19,6 +19,10 @@ import org.microg.gms.gcm.setGcmServiceConfiguration class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { lateinit var binding: PushNotificationFragmentBinding + init { + setHasOptionsMenu(true) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = PushNotificationFragmentBinding.inflate(inflater, container, false) binding.switchBarCallback = object : PreferenceSwitchBarCallback { @@ -52,11 +56,6 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - setHasOptionsMenu(true) - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced) super.onCreateOptionsMenu(menu, inflater) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt index 88656d7c3a0e2c208ff8020e3c3d4962712b7dd4..e3cca8cc77abad004111f65cb8d66bfa78c886fe 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt @@ -20,6 +20,10 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { private lateinit var binding: SafetyNetFragmentBinding + init { + setHasOptionsMenu(true) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = SafetyNetFragmentBinding.inflate(inflater, container, false) binding.switchBarCallback = object : PreferenceSwitchBarCallback { @@ -52,11 +56,6 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { } } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - setHasOptionsMenu(true) - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_ADVANCED, 0, R.string.menu_advanced) super.onCreateOptionsMenu(menu, inflater) diff --git a/play-services-core/src/main/res/values-zh-rCN/permissions.xml b/play-services-core/src/main/res/values-zh-rCN/permissions.xml new file mode 100644 index 0000000000000000000000000000000000000000..679bd1fcad992a96370b6ad2cbb310c5028ff808 --- /dev/null +++ b/play-services-core/src/main/res/values-zh-rCN/permissions.xml @@ -0,0 +1,128 @@ + + + + 所有 Google 服务 + 允许应用通过任何已关联的 Google 账号来使用所有 Google 服务。 + + Android 服务 + 允许应用通过任何已关联的 Google 账号来使用所有 Android 服务。 + 允许应用通过任何已关联的 Google 账号来使用 AdSense。 + 允许应用通过任何已关联的 Google 账号来使用 AdWords。 + 允许应用通过任何已关联的 Google 账号来使用 Google App Engine。 + 允许应用通过任何已关联的 Google 账号来使用 Blogger。 + 允许应用通过任何已关联的 Google 账号来使用 Google 日历。 + 允许应用通过任何已关联的 Google 账号来访问联系人。 + 允许应用通过任何已关联的 Google 账号来使用 Dodgeball。 + 允许应用通过任何已关联的 Google 账号来使用 Google 财经。 + 允许应用通过任何已关联的 Google 账号来使用 Google Base。 + Google 财经 + 联系人 + Google 日历 + 允许应用通过任何已关联的 Google 账号来使用 Google Voice。 + 语音搜索 + 允许应用通过任何已关联的 Google 账号来使用语音搜索。 + 个性化语音识别 + 允许应用通过任何已关联的 Google 账号来使用个性化语音识别。 + 允许应用通过任何已关联的 Google 账号来使用 Google Talk。 + 允许应用通过任何已关联的 Google 账号来使用 Google Wi-Fi。 + 允许应用通过任何已关联的 Google 账号来使用 Google Spreadsheets。 + Google 文档 + 允许应用通过任何已关联的 Google 账号来使用 Google 文档。 + 允许应用通过任何已关联的 Google 账号来使用 YouTube。 + YouTube 用户名 + 允许应用通过任何已关联的 Google 账号获知其 YouTube 用户名。 + Picasa 网络相册 + 允许应用通过任何已关联的 Google 账号来使用 Picasa 网络相册。 + Google 地图 + 允许应用通过任何已关联的 Google 账号来使用 Google 地图。 + Google 电子邮件 + 允许应用通过任何已关联的 Google 账号来使用 Google 电子邮件。 + Google 新闻 + 允许应用通过任何已关联的 Google 账号来使用 Google 新闻。 + 允许应用通过任何已关联的 Google 账号来使用 Orkut。 + 允许应用通过任何已关联的 Google 账号来使用 Google 笔记本。 + Google 笔记本 + Google 网上论坛 + 允许应用通过任何已关联的 Google 账号来使用 Google 网上论坛。 + YouTube + Google Spreadsheets + Google Wi-Fi + Google Talk + Google 图书搜索 + 允许应用通过任何已关联的 Google 账号来使用 Google 图书搜索。 + Orkut + Dodgeball + Google Base + Google Voice + AdSense + Adwords + Google App Engine + Blogger + Google Health + 允许应用通过任何已关联的 Google 账号来使用 Google Health。 + + 管理您的 YouTube 账号 + 查看并管理您在 YouTube 上的资产和相关内容 + 查看您的 YouTube 账号 + 管理您的 YouTube 视频 + 查看您 YouTube 内容的分析报告 + 查看您 YouTube 内容的收入报告 + 查看您账号的基本内容 + 查看您的邮件地址 + 管理您的 goo.gl 短网址 + 管理您的任务 + 管理您的任务 + 查看您的任务 + Chrome 的消息推送 + 管理您的 GAN 数据 + 查看您的 GAN 数据 + 从 Google Play 游戏获取资料的权限。 + 查看您的 Freebase 账号 + 用您的账号登录到 Freebase + 管理您的图书 + 管理您的日历 + 查看您的日历 + 查看并管理您的 Google 云端打印数据 + 查看您的 Google Compute Engine 资源 + 查看并管理您的 Google Compute Engine 资源 + 查看您在 Google 应用中的活动历史 + 查看您的 AdSense 数据 + 查看并管理您的 AdSense 数据 + 查看您的 Google Analytics 数据 + 查看并管理您的 Google Analytics 数据 + 使用 Google Play Android 开发者的权限 + 查看您的 Ad Exchange 数据 + 查看并管理您的 Ad Exchange 数据 + 查看您的 Blogger 账号 + 管理您的 Blogger 账号 + 查看您在 Google Big Query 上的数据 + 查看并管理您在 Google Big Query 上的数据 + 管理您 Google 云存储中的数据 + 查看您 Google 云存储中的数据 + 管理您 Google 云存储中的数据与权限 + 查看您的 Google 云端硬盘应用 + 查看并管理此应用在您 Google 云端硬盘中打开 / 创建的文件 + 查看您 Google 云端硬盘中文件和档案的元数据 + 查看您 Google 云端硬盘中的文件和档案 + 查看并管理您 Google 云端硬盘中的文件和档案 + 管理您的 Fusion Tables + "查看您的 Fusion Tables" + 查看并管理您的 AdSense 服务资料和关联账户 + License Manager API 的读写权限。 + 知道您在 Google 是谁 + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-zh-rCN/plurals.xml b/play-services-core/src/main/res/values-zh-rCN/plurals.xml new file mode 100644 index 0000000000000000000000000000000000000000..fccc083881bdd8f690c82e4c068c67400c9e8fc1 --- /dev/null +++ b/play-services-core/src/main/res/values-zh-rCN/plurals.xml @@ -0,0 +1,19 @@ + + + + 已配置 %1$d 个后端 + 已配置%1$d 个后端 + + + %1$d 个应用已注册 + %1$d 个应用已注册 + + + microG 服务核心缺少一项正常工作所需的权限。 + microG 服务核心缺少多项正常工作所需的权限。 + + + 请求缺少的一个权限 + 请求缺少的多个权限 + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-zh-rCN/strings.xml b/play-services-core/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..41db99f84cbfda5ffc986831c04414c9c2949012 --- /dev/null +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,160 @@ + + + + 登录 + 正在建立到 Google 服务器的连接以供您登录。 + +这需要几秒钟时间。 + 您没有互联网连接。 + +这可能是暂时的问题,或者是您的 Android 设备不能使用数据服务。请在连接到移动网络或 Wi-Fi 网络后重试。 + 抱歉… + 允许 + 拒绝 + 需要认证 + 向其他应用发送 C2DM 消息 + Google 云端消息推送 + 已禁用 + 已启用 + 自动 + 手动 + + + Google Play 游戏 + 要使用 Play 游戏,您需要安装“Google Play 游戏”应用。应用在 Play 游戏未安装时可能可以继续运行,也可能发生异常。 + 选择一个地点 + 现在无法选择地点。 + 选择此地点 + 附近的地点 + microG 服务核心:缺少%1$s权限 + 移动网络 + 漫游 + 其他网络 + 签名伪装支持 + 已安装的应用 + 系统 + 系统支持签名伪装: + 您的 ROM 并不自带签名伪装支持。您仍可以利用 Xposed 或刷入支持的 ROM 来伪装签名。请查阅文档以了解哪些 ROM 自带伪装支持,以及如何在不支持的 ROM 上继续使用 microG。 + 系统已授予签名伪装权限: + 这表明您的系统极有可能支持签名伪装,但仍需要一些步骤来激活它。请查阅文档以了解后续步骤。 + Play 服务 (GmsCore) + Play 商店 (PhoneSky) + 服务框架 (GSF) + 已忽略电池优化: + 点击以忽略电池优化。保持电池优化可能导致应用行为异常。 + 关于 + 组件 + 配置 + 位置服务 + 服务 + 测试 + 电池优化正在生效 + 您在电池优化生效的情况下启用了消息推送。为保证消息正常推送,您需要对本应用忽略电池优化。 + 忽略优化 + 缺少权限 + 账号设置 + 个人资料与隐私 + 登录与安全 + 禁用后,应用在向 Google 请求验证前需要得到您的同意。有些程序可能会因此无法使用 Google 账号。 + 未注册 + 上次注册:%1$s + + microG 设置 + 设置 microG 服务 + 请稍等… + 继续则代表您同意此应用和 Google 遵循其相应的服务条款和隐私政策使用您的个人信息。 + %1$s 想要: + %1$s 想要使用: + Google 账号管理员 + "您设备上的某个应用正尝试登录一个 Google 账号。 + +若是您有意为之,点击登录以连接到 Google 的登录页面;否则点击取消以返回到弹出此页面的应用。" + "您的设备正在与 Google 联系以将您的信息存入账号。 + +这可能需要几分钟。" + "与 Google 服务器通信时发生问题。 + +请稍后重试。" + %1$s 需要您的授权以使用 Google 账号。 + 接收 C2DM 消息 + 接收内部状态广播 + 从 Google 服务器交换信息并接收同步通知。 + 向 Google 注册设备 + 允许应用在无用户交互的情况下设置 microG 服务 + %1$s 想要使用 Play 游戏 + 系统签名伪装: + 请查阅文档以了解所需步骤。 + %1$s 已安装: + 安装 %1$s 或与之兼容的应用。请查阅文档以了解有哪些兼容应用。 + %1$s 包含正确签名: + 已安装的 %1$s 不兼容,或者您未对其启用签名伪装。请查阅文档以了解兼容的应用或 ROM。 + 允许应用寻找 Google 账号 + 启用后,您设备上的所有应用将可以在不经您许可的情况下看到与您 Google 账号关联的电子邮件地址。 + 将您的设备注册到 Google 服务,并创建唯一的设备识别码。microG 将去除注册信息中您 Google 账户名以外的用于识别的信息。 + 注册设备 + 状态 + 更多 + 账号 + 添加 Google 账号 + Google 云端消息推送(GCM)是由众多第三方应用选用的消息推送提供者。使用之前您需要先向 Google 注册该设备。 + 云端消息心跳间隔 + 系统向 Google 服务器发送心跳包的间隔秒数。延长该间隔将减少电量消耗,但可能导致推送延迟。\n已废弃,将在未来版本被取代。 + 正使用云端消息推送的应用 + 已经注册使用云端消息推送的应用列表。 + 确认新应用 + 在应用注册使用消息推送之前询问您 + Ping 间隔:%1$s + 关于 microG 服务核心 + 版本信息与使用的库 + 取消注册时出错 + 已被卸载 + 取消注册 + 未注册 + 尚未收到任何消息 + 最后收到消息:%1$s + 已注册 + %1$s起注册 + 取消注册 %1$s + 有些应用并不会自动重新注册,也可能不向您提供重新注册的选项。取消注册后这些应用可能出现异常。\n是否继续? + 您拒绝了一个已经注册过的应用去注册接受推送通知。\n您是否想现在取消它的注册,使其今后不再接收消息推送? + 已接收 %1$d 条消息 (%2$d 字节) + 已断开连接 + 上次连接:%1$s + 接收推送通知 + 允许 %1$s 注册通知推送? + 允许注册 + 允许该应用注册通知推送。 + 推送时启动应用 + 适时在后台唤起应用以接收推送消息。 + 使用通知推送的应用 + 已注册的应用 + 已取消注册的应用 + 接收推送时使用的网络 + 允许设备认证 + 测试 ReCAPTCHA + 所有测试已通过 + 失败:%s + 警告:%s + 正在运行… + 运作模式 + 本地 + 真实 + 自定义:%s + 自动:%s + 系统:%s + "测试 SafetyNet 认证 " + Google SafetyNet 是一套设备认证系统,旨在确认设备被切实保护,并与 Android CTS 兼容。某些应用会出于安全考虑或是防篡改目的而使用 SafetyNet。 + +microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要求 SafetyNet 请求被其专有的 DroidGuard 系统签名。 + Google 服务 + 额外使用 Google 服务 + + + + Google + Wi-Fi + (%1$.7f, %2$.7f) + 预配置 microG 服务 + microG 服务核心 + 允许 Google 向应用提供验证 + \ No newline at end of file diff --git a/play-services-droidguard-core-ui/src/main/res/values-zh-rCN/strings.xml b/play-services-droidguard-core-ui/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..bbbbc5308cdedfbe3119b00cc188a488e25e24d8 --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,8 @@ + + + DroidGuard 运作模式 + 通过网络连接到 DroidGuard 运行时 + 内置 + 远程 + 使用本地 DroidGuard 运行时 + \ No newline at end of file diff --git a/play-services-fido-api/build.gradle b/play-services-fido-api/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..b8f659f52a10e2c3f3016e31e1bcce8645281107 --- /dev/null +++ b/play-services-fido-api/build.gradle @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG API for play-services-fido' + +dependencies { + api project(':play-services-basement') + api project(':play-services-base-api') +} diff --git a/play-services-fido-api/src/main/AndroidManifest.xml b/play-services-fido-api/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..00715eb7ed80686fb4287dbb974d74b0f58f63ef --- /dev/null +++ b/play-services-fido-api/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/IBooleanCallback.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/IBooleanCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..1813d42c60e7a13ebed088b3d4558a862e25c6f0 --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/IBooleanCallback.aidl @@ -0,0 +1,8 @@ +package com.google.android.gms.fido.fido2.api; + +import com.google.android.gms.common.api.Status; + +interface IBooleanCallback { + void onBoolean(boolean value) = 0; + void onError(in Status status) = 1; +} diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.aidl new file mode 100644 index 0000000000000000000000000000000000000000..6e38baa9f7624442a4d53626be81df111a7b8f1a --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.fido.fido2.api.common; + +parcelable BrowserPublicKeyCredentialCreationOptions; diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.aidl new file mode 100644 index 0000000000000000000000000000000000000000..0d338e9674909c018f2f132e8c44d751970e1244 --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.fido.fido2.api.common; + +parcelable BrowserPublicKeyCredentialRequestOptions; diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedCallbacks.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedCallbacks.aidl new file mode 100644 index 0000000000000000000000000000000000000000..6eca9dfa7d5adcd67000b9cc932de0862c2760ab --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedCallbacks.aidl @@ -0,0 +1,8 @@ +package com.google.android.gms.fido.fido2.internal.privileged; + +import android.app.PendingIntent; +import com.google.android.gms.common.api.Status; + +interface IFido2PrivilegedCallbacks { + void onPendingIntent(in Status status, in PendingIntent pendingIntent); +} diff --git a/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedService.aidl b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..b3f86dcc52000c6f22d44e66aee492a10c947849 --- /dev/null +++ b/play-services-fido-api/src/main/aidl/com/google/android/gms/fido/fido2/internal/privileged/IFido2PrivilegedService.aidl @@ -0,0 +1,12 @@ +package com.google.android.gms.fido.fido2.internal.privileged; + +import com.google.android.gms.fido.fido2.internal.privileged.IFido2PrivilegedCallbacks; +import com.google.android.gms.fido.fido2.api.IBooleanCallback; +import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialCreationOptions; +import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialRequestOptions; + +interface IFido2PrivilegedService { + void register(IFido2PrivilegedCallbacks callbacks, in BrowserPublicKeyCredentialCreationOptions options) = 0; + void sign(IFido2PrivilegedCallbacks callbacks, in BrowserPublicKeyCredentialRequestOptions options) = 1; + void isUserVerifyingPlatformAuthenticatorAvailable(IBooleanCallback callbacks) = 2; +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/common/Transport.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/common/Transport.java new file mode 100644 index 0000000000000000000000000000000000000000..ccbcdcfaefe19772e4ea5bb5f29373284d63a673 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/common/Transport.java @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.common; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.microg.gms.common.PublicApi; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * The transport between the authenticator and the client. + */ +@PublicApi +public enum Transport implements Parcelable { + BLUETOOTH_CLASSIC("bt"), + BLUETOOTH_LOW_ENERGY("ble"), + NFC("nfc"), + USB("usb"), + INTERNAL("internal"), + @PublicApi(exclude = true) + CABLE("cable"); + + private final String value; + + Transport(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static Transport fromString(String transport) throws UnsupportedTransportException { + for (Transport value : values()) { + if (value.value.equals(transport)) return value; + } + throw new UnsupportedTransportException("Transport " + transport + " not supported"); + } + + @PublicApi(exclude = true) + public static List parseTransports(JSONArray jsonArray) throws JSONException { + if (jsonArray == null) return null; + Set set = new HashSet<>(); + for (int i = 0; i < jsonArray.length(); i++) { + String transport = jsonArray.getString(i); + if (transport != null && !transport.isEmpty()) { + try { + set.add(fromString(transport)); + } catch (UnsupportedTransportException e) { + Log.w("Transport", "Ignoring unrecognized transport " + transport); + } + } + } + return new ArrayList<>(set); + } + + public static Creator CREATOR = new Creator() { + @Override + public Transport createFromParcel(Parcel source) { + try { + return Transport.fromString(source.readString()); + } catch (UnsupportedTransportException e) { + throw new RuntimeException(e); + } + } + + @Override + public Transport[] newArray(int size) { + return new Transport[size]; + } + }; + + /** + * Exception thrown when an unsupported or unrecognized transport is encountered. + */ + public static class UnsupportedTransportException extends Exception { + public UnsupportedTransportException(String errorMessage) { + super(errorMessage); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Algorithm.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Algorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..3b03044fa5c93994129a0a281c521d68967ff19c --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Algorithm.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +/** + * An interface for an algorithm used in public key encryption. All implementations must conform to the guidelines + * regarding algorithm registrations in RFC8152. + */ +public interface Algorithm { + /** + * Gets the COSE value for the algorithm used in the encryption of the credential. + */ + int getAlgoValue(); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Attachment.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Attachment.java new file mode 100644 index 0000000000000000000000000000000000000000..4f031468c48807fa88249f5e83584e7996d3e1de --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/Attachment.java @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.android.gms.fido.common.Transport; + +import org.microg.gms.common.PublicApi; + +/** + * Clients may communicate with authenticators using a variety of mechanisms. We define authenticators that are + * part of the client's platform as having a platform attachment, and refer to them as platform authenticators. + * While those that are reachable via cross-platform transport protocols are defined as having cross-platform + * attachment, and refer to them as roaming authenticators. + */ +public enum Attachment implements Parcelable { + PLATFORM("platform"), + CROSS_PLATFORM("cross-platform"); + + private final String value; + + Attachment(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static Attachment fromString(String attachment) throws UnsupportedAttachmentException { + for (Attachment value : values()) { + if (value.value.equals(attachment)) return value; + } + throw new UnsupportedAttachmentException("Attachment " + attachment + " not supported"); + } + + public static Creator CREATOR = new Creator() { + @Override + public Attachment createFromParcel(Parcel source) { + try { + return Attachment.fromString(source.readString()); + } catch (Attachment.UnsupportedAttachmentException e) { + throw new RuntimeException(e); + } + } + + @Override + public Attachment[] newArray(int size) { + return new Attachment[size]; + } + }; + + /** + * Exception thrown when an unsupported or unrecognized attachment is encountered. + */ + public static class UnsupportedAttachmentException extends Exception { + public UnsupportedAttachmentException(String message) { + super(message); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AttestationConveyancePreference.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AttestationConveyancePreference.java new file mode 100644 index 0000000000000000000000000000000000000000..d23c8c804f1e30fa97c971e8660385b315b47b26 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AttestationConveyancePreference.java @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +/** + * An enum describing the relying party's preference for attestation conveyance. + */ +public enum AttestationConveyancePreference implements Parcelable { + NONE("none"), + INDIRECT("indirect"), + DIRECT("direct"); + + private final String value; + + AttestationConveyancePreference(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static AttestationConveyancePreference fromString(String attachment) throws UnsupportedAttestationConveyancePreferenceException { + for (AttestationConveyancePreference value : values()) { + if (value.value.equals(attachment)) return value; + } + throw new UnsupportedAttestationConveyancePreferenceException("Attestation conveyance preference " + attachment + " not supported"); + } + + public static Creator CREATOR = new Creator() { + @Override + public AttestationConveyancePreference createFromParcel(Parcel source) { + try { + return AttestationConveyancePreference.fromString(source.readString()); + } catch (UnsupportedAttestationConveyancePreferenceException e) { + throw new RuntimeException(e); + } + } + + @Override + public AttestationConveyancePreference[] newArray(int size) { + return new AttestationConveyancePreference[size]; + } + }; + + /** + * Exception thrown when an unsupported or unrecognized attestation conveyance preference is encountered. + */ + public static class UnsupportedAttestationConveyancePreferenceException extends Exception { + public UnsupportedAttestationConveyancePreferenceException(String message) { + super(message); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..e5acf95c49fca7aa44f23aa859a9bb8819affd4a --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensions.java @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Represents extensions that can be passed into FIDO2 APIs. This container class corresponds to the additional + * parameters requesting additional processing by authenticators. + *

+ * Note that rather than accepting arbitrary objects as specified in WebAuthn, this class requires a structured entry + * for each supported extension. + */ +@PublicApi +public class AuthenticationExtensions extends AutoSafeParcelable { + @Field(2) + private FidoAppIdExtension fidoAppIdExtension; + @Field(3) + private CableAuthenticationExtension cableAuthenticationExtension; + @Field(4) + private UserVerificationMethodExtension userVerificationMethodExtension; + + public FidoAppIdExtension getFidoAppIdExtension() { + return fidoAppIdExtension; + } + + public UserVerificationMethodExtension getUserVerificationMethodExtension() { + return userVerificationMethodExtension; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticationExtensions)) return false; + + AuthenticationExtensions that = (AuthenticationExtensions) o; + + if (fidoAppIdExtension != null ? !fidoAppIdExtension.equals(that.fidoAppIdExtension) : that.fidoAppIdExtension != null) + return false; + if (cableAuthenticationExtension != null ? !cableAuthenticationExtension.equals(that.cableAuthenticationExtension) : that.cableAuthenticationExtension != null) + return false; + return userVerificationMethodExtension != null ? userVerificationMethodExtension.equals(that.userVerificationMethodExtension) : that.userVerificationMethodExtension == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{fidoAppIdExtension, cableAuthenticationExtension, userVerificationMethodExtension}); + } + + @Override + public String toString() { + return "AuthenticationExtensions{" + + "fidoAppIdExtension=" + fidoAppIdExtension + + ", cableAuthenticationExtension=" + cableAuthenticationExtension + + ", userVerificationMethodExtension=" + userVerificationMethodExtension + + '}'; + } + + /** + * Builder for {@link AuthenticationExtensions}. + */ + public static class Builder { + private FidoAppIdExtension fidoAppIdExtension; + private UserVerificationMethodExtension userVerificationMethodExtension; + + /** + * The constructor of {@link AuthenticationExtensions.Builder}. + */ + public Builder() { + } + + /** + * Sets the App ID extension, which allows for authentication of U2F authenticators previously registered + * under the supplied App ID. + */ + public Builder setFido2Extension(FidoAppIdExtension appIdExtension) { + this.fidoAppIdExtension = appIdExtension; + return this; + } + + /** + * Sets the User Verification Method extension, which allows the relying party to ascertain up to three + * authentication methods that were used. + */ + public Builder setUserVerificationMethodExtension(UserVerificationMethodExtension userVerificationMethodExtension) { + this.userVerificationMethodExtension = userVerificationMethodExtension; + return this; + } + + /** + * Builds the {@link AuthenticationExtensions} object. + */ + public AuthenticationExtensions build() { + AuthenticationExtensions extensions = new AuthenticationExtensions(); + extensions.fidoAppIdExtension = fidoAppIdExtension; + extensions.userVerificationMethodExtension = userVerificationMethodExtension; + return extensions; + } + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticationExtensions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java new file mode 100644 index 0000000000000000000000000000000000000000..84bf5770ee105e8282bd6c36f711840b37f022df --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticationExtensionsClientOutputs.java @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * This container class represents client output for extensions that can be passed into FIDO2 APIs. + */ +@PublicApi +public class AuthenticationExtensionsClientOutputs extends AutoSafeParcelable { + @Field(1) + private UvmEntries uvmEntries; + + public UvmEntries getUvmEntries() { + return uvmEntries; + } + + /** + * Serializes the {@link AuthenticationExtensionsClientOutputs} to bytes. + * Use {@link #deserializeFromBytes(byte[])} to deserialize. + */ + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + /** + * De-serializes the {@link AuthenticationExtensionsClientOutputs} from bytes, reversing {@link #serializeToBytes()}. + * + * @return The deserialized {@link AuthenticationExtensionsClientOutputs} + */ + public static AuthenticationExtensionsClientOutputs deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticationExtensionsClientOutputs)) return false; + + AuthenticationExtensionsClientOutputs that = (AuthenticationExtensionsClientOutputs) o; + + return uvmEntries != null ? uvmEntries.equals(that.uvmEntries) : that.uvmEntries == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{uvmEntries}); + } + + /** + * Builder for {@link AuthenticationExtensionsClientOutputs}. + */ + public static class Builder { + private UvmEntries uvmEntries; + + /** + * The constructor of {@link AuthenticationExtensionsClientOutputs.Builder}. + */ + public Builder() { + } + + /** + * Sets the User Verification Method extension, which allows the relying party to ascertain up to three + * authentication methods that were used. + */ + public Builder setUserVerificationMethodEntries(UvmEntries uvmEntries) { + this.uvmEntries = uvmEntries; + return this; + } + + /** + * Builds the {@link AuthenticationExtensionsClientOutputs} object. + */ + public AuthenticationExtensionsClientOutputs build() { + AuthenticationExtensionsClientOutputs extensions = new AuthenticationExtensionsClientOutputs(); + extensions.uvmEntries = uvmEntries; + return extensions; + } + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticationExtensionsClientOutputs.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..1dc01d812e0de8123b314618882550ed62530222 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAssertionResponse.java @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * This structure contains cryptographic signatures produced by scoped credentials that provides proof of possession + * of a private key as well as evidence of user consent to a specific transaction. + */ +@PublicApi +public class AuthenticatorAssertionResponse extends AuthenticatorResponse { + @Field(2) + private byte[] keyHandle; + @Field(3) + private byte[] clientDataJSON; + @Field(4) + private byte[] authenticatorData; + @Field(5) + private byte[] signature; + @Field(6) + private byte[] userHandle; + + private AuthenticatorAssertionResponse() {} + + public AuthenticatorAssertionResponse(byte[] keyHandle, byte[] clientDataJSON, byte[] authenticatorData, byte[] signature, byte[] userHandle) { + this.keyHandle = keyHandle; + this.clientDataJSON = clientDataJSON; + this.authenticatorData = authenticatorData; + this.signature = signature; + this.userHandle = userHandle; + } + + public byte[] getAuthenticatorData() { + return authenticatorData; + } + + @Override + public byte[] getClientDataJSON() { + return clientDataJSON; + } + + /** + * @deprecated use {@link PublicKeyCredential#getRawId()} instead + */ + @Deprecated + public byte[] getKeyHandle() { + return keyHandle; + } + + public byte[] getSignature() { + return signature; + } + + public byte[] getUserHandle() { + return userHandle; + } + + @Override + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticatorAssertionResponse)) return false; + + AuthenticatorAssertionResponse that = (AuthenticatorAssertionResponse) o; + + if (!Arrays.equals(keyHandle, that.keyHandle)) return false; + if (!Arrays.equals(clientDataJSON, that.clientDataJSON)) return false; + if (!Arrays.equals(authenticatorData, that.authenticatorData)) return false; + if (!Arrays.equals(signature, that.signature)) return false; + return Arrays.equals(userHandle, that.userHandle); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{Arrays.hashCode(keyHandle), Arrays.hashCode(clientDataJSON), Arrays.hashCode(authenticatorData), Arrays.hashCode(signature), Arrays.hashCode(userHandle)}); + } + + @Override + public String toString() { + return ToStringHelper.name("AuthenticatorAssertionResponse") + .field("keyHandle", keyHandle) + .field("clientDataJSON", clientDataJSON) + .field("authenticatorData", authenticatorData) + .field("signature", signature) + .field("userHandle", userHandle) + .end(); + } + + public static AuthenticatorAssertionResponse deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticatorAssertionResponse.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..174c5aa07139ffb593cc4381705766d51cb7daea --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorAttestationResponse.java @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * Represents a newly-created scoped credential, aka the response from a registration request. + */ +@PublicApi +public class AuthenticatorAttestationResponse extends AuthenticatorResponse { + @Field(2) + private byte[] keyHandle; + @Field(3) + private byte[] clientDataJSON; + @Field(4) + private byte[] attestationObject; + + private AuthenticatorAttestationResponse() {} + + @PublicApi(exclude = true) + public AuthenticatorAttestationResponse(byte[] keyHandle, byte[] clientDataJSON, byte[] attestationObject) { + this.keyHandle = keyHandle; + this.clientDataJSON = clientDataJSON; + this.attestationObject = attestationObject; + } + + public byte[] getAttestationObject() { + return attestationObject; + } + + @Override + public byte[] getClientDataJSON() { + return clientDataJSON; + } + + /** + * @deprecated use {@link PublicKeyCredential#getRawId()} instead + */ + @Deprecated + public byte[] getKeyHandle() { + return keyHandle; + } + + @Override + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticatorAttestationResponse)) return false; + + AuthenticatorAttestationResponse that = (AuthenticatorAttestationResponse) o; + + if (!Arrays.equals(keyHandle, that.keyHandle)) return false; + if (!Arrays.equals(clientDataJSON, that.clientDataJSON)) return false; + return Arrays.equals(attestationObject, that.attestationObject); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{Arrays.hashCode(keyHandle), Arrays.hashCode(clientDataJSON), Arrays.hashCode(attestationObject)}); + } + + @Override + public String toString() { + return ToStringHelper.name("AuthenticatorAttestationResponse") + .field("keyHandle", keyHandle) + .field("clientDataJSON", clientDataJSON) + .field("attestationObject", attestationObject) + .end(); + } + + public static AuthenticatorAttestationResponse deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticatorAttestationResponse.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..1853902acf751e47a963d43a89ab7777967931cd --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorErrorResponse.java @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * The response after an error occurred. + */ +@PublicApi +public class AuthenticatorErrorResponse extends AuthenticatorResponse { + @Field(2) + private ErrorCode errorCode; + @Field(3) + private String errorMessage; + + private AuthenticatorErrorResponse() { + } + + @PublicApi(exclude = true) + public AuthenticatorErrorResponse(ErrorCode errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + @Override + public byte[] getClientDataJSON() { + throw new UnsupportedOperationException(); + } + + public ErrorCode getErrorCode() { + return errorCode; + } + + public int getErrorCodeAsInt() { + return errorCode.getCode(); + } + + public String getErrorMessage() { + return errorMessage; + } + + @Override + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticatorErrorResponse)) return false; + + AuthenticatorErrorResponse that = (AuthenticatorErrorResponse) o; + + if (errorCode != null ? !errorCode.equals(that.errorCode) : that.errorCode != null) return false; + return errorMessage != null ? errorMessage.equals(that.errorMessage) : that.errorMessage == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{errorCode, errorMessage}); + } + + @Override + public String toString() { + return ToStringHelper.name("AuthenticatorErrorResponse") + .value(errorCode.name()) + .value(errorMessage) + .end(); + } + + public static AuthenticatorErrorResponse deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticatorErrorResponse.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorResponse.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..0a904f92ee1a6630a57d44051f023e8e507d63d0 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorResponse.java @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.safeparcel.AutoSafeParcelable; + +/** + * Authenticators respond to relying party requests by returning an object derived from this interface. + */ +public abstract class AuthenticatorResponse extends AutoSafeParcelable { + public abstract byte[] getClientDataJSON(); + + public abstract byte[] serializeToBytes(); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorSelectionCriteria.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorSelectionCriteria.java new file mode 100644 index 0000000000000000000000000000000000000000..dd93d0bd350a3d9af719a2e1e5d8f75b24e7d7e2 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/AuthenticatorSelectionCriteria.java @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Relying Parties may use {@link AuthenticatorSelectionCriteria} to specify their requirements regarding authenticator + * attributes. + */ +public class AuthenticatorSelectionCriteria extends AutoSafeParcelable { + @Field(2) + private Attachment attachment; + @Field(3) + private Boolean requireResidentKey; + @Field(4) + private UserVerificationRequirement requireUserVerification; + + public Attachment getAttachment() { + return attachment; + } + + public String getAttachmentAsString() { + if (this.attachment == null) { + return null; + } + return attachment.toString(); + } + + public Boolean getRequireResidentKey() { + return requireResidentKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof AuthenticatorSelectionCriteria)) return false; + + AuthenticatorSelectionCriteria that = (AuthenticatorSelectionCriteria) o; + + if (attachment != that.attachment) return false; + if (requireResidentKey != null ? !requireResidentKey.equals(that.requireResidentKey) : that.requireResidentKey != null) + return false; + return requireUserVerification == that.requireUserVerification; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{attachment, requireResidentKey, requireUserVerification}); + } + + @Override + public String toString() { + return ToStringHelper.name("AuthenticatorSelectionCriteria") + .field("attachment", attachment) + .field("requireResidentKey", requireResidentKey) + .field("requireUserVerification", requireUserVerification) + .end(); + } + + /** + * Builder for {@link AuthenticatorSelectionCriteria}. + */ + public static class Builder { + private Attachment attachment; + private Boolean requireResidentKey; + + /** + * Sets the attachment to use for this session. + */ + public Builder setAttachment(Attachment attachment) { + this.attachment = attachment; + return this; + } + + /** + * Sets whether the key created will be a resident key. + */ + public Builder setRequireResidentKey(Boolean requireResidentKey) { + this.requireResidentKey = requireResidentKey; + return this; + } + + public AuthenticatorSelectionCriteria build() { + AuthenticatorSelectionCriteria criteria = new AuthenticatorSelectionCriteria(); + criteria.attachment = attachment; + criteria.requireResidentKey = requireResidentKey; + return criteria; + } + } + + public static final Creator CREATOR = new AutoCreator<>(AuthenticatorSelectionCriteria.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..ffb4a620b7928945100b26d7ec65fad3d19964e0 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialCreationOptions.java @@ -0,0 +1,160 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.net.Uri; +import android.util.Base64; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * Parameters to a make credential request from a Web browser. + */ +@PublicApi +public class BrowserPublicKeyCredentialCreationOptions extends BrowserRequestOptions { + @Field(2) + private PublicKeyCredentialCreationOptions delegate; + @Field(3) + private Uri origin; + @Field(4) + private byte[] clientDataHash; + + @Override + public AuthenticationExtensions getAuthenticationExtensions() { + return delegate.getAuthenticationExtensions(); + } + + @Override + public byte[] getChallenge() { + return delegate.getChallenge(); + } + + @Override + public byte[] getClientDataHash() { + return clientDataHash; + } + + @Override + public Uri getOrigin() { + return origin; + } + + public PublicKeyCredentialCreationOptions getPublicKeyCredentialCreationOptions() { + return delegate; + } + + @Override + public Integer getRequestId() { + return delegate.getRequestId(); + } + + @Override + public Double getTimeoutSeconds() { + return delegate.getTimeoutSeconds(); + } + + @Override + public TokenBinding getTokenBinding() { + return delegate.getTokenBinding(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BrowserPublicKeyCredentialCreationOptions)) return false; + + BrowserPublicKeyCredentialCreationOptions that = (BrowserPublicKeyCredentialCreationOptions) o; + + if (delegate != null ? !delegate.equals(that.delegate) : that.delegate != null) return false; + if (origin != null ? !origin.equals(that.origin) : that.origin != null) return false; + return Arrays.equals(clientDataHash, that.clientDataHash); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{delegate, origin, Arrays.hashCode(clientDataHash)}); + } + + @Override + public String toString() { + return ToStringHelper.name("BrowserPublicKeyCredentialCreationOptions") + .value(delegate) + .field("origin", origin) + .field("clientDataHash", clientDataHash) + .end(); + } + + /** + * Builder for {@link BrowserPublicKeyCredentialCreationOptions}. + */ + public static class Builder { + private PublicKeyCredentialCreationOptions delegate; + private Uri origin; + private byte[] clientDataHash; + + /** + * The constructor of {@link BrowserPublicKeyCredentialCreationOptions.Builder}. + */ + public Builder() { + } + + /** + * Sets a clientDataHash value to sign over in place of assembling and hashing clientDataJSON during the + * signature request. + *

+ * Note: This is optional and only provided for contexts where the unhashed information necessary to assemble + * WebAuthn clientDataJSON is not available. If set, the resulting {@link AuthenticatorAssertionResponse} will + * return an invalid value for {@code getClientDataJSON()}. Generally, browser clients should use + * {@link PublicKeyCredentialCreationOptions.Builder#setChallenge(byte[])} instead. + * + * @return + */ + public BrowserPublicKeyCredentialCreationOptions.Builder setClientDataHash(byte[] clientDataHash) { + this.clientDataHash = clientDataHash; + return this; + } + + /** + * Sets the origin on whose behalf the calling browser is requesting a registration operation. + */ + public BrowserPublicKeyCredentialCreationOptions.Builder setOrigin(Uri origin) { + this.origin = origin; + return this; + } + + /** + * Sets the parameters to dictate the client behavior during this registration session. + */ + public BrowserPublicKeyCredentialCreationOptions.Builder setPublicKeyCredentialRequestOptions(PublicKeyCredentialCreationOptions publicKeyCredentialCreationOptions) { + this.delegate = publicKeyCredentialCreationOptions; + return this; + } + + /** + * Builds the {@link BrowserPublicKeyCredentialRequestOptions} object. + */ + public BrowserPublicKeyCredentialCreationOptions build() { + BrowserPublicKeyCredentialCreationOptions options = new BrowserPublicKeyCredentialCreationOptions(); + options.delegate = delegate; + options.origin = origin; + options.clientDataHash = clientDataHash; + return options; + } + } + + public static BrowserPublicKeyCredentialCreationOptions deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(BrowserPublicKeyCredentialCreationOptions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..84811228494c1b767ea9926651709db7b8d9ed84 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserPublicKeyCredentialRequestOptions.java @@ -0,0 +1,160 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.net.Uri; +import android.util.Base64; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * Parameters for a signature request from a Web Browser. + */ +@PublicApi +public class BrowserPublicKeyCredentialRequestOptions extends BrowserRequestOptions { + @Field(2) + private PublicKeyCredentialRequestOptions delegate; + @Field(3) + private Uri origin; + @Field(4) + private byte[] clientDataHash; + + @Override + public AuthenticationExtensions getAuthenticationExtensions() { + return delegate.getAuthenticationExtensions(); + } + + @Override + public byte[] getChallenge() { + return delegate.getChallenge(); + } + + @Override + public byte[] getClientDataHash() { + return clientDataHash; + } + + @Override + public Uri getOrigin() { + return origin; + } + + public PublicKeyCredentialRequestOptions getPublicKeyCredentialRequestOptions() { + return delegate; + } + + @Override + public Integer getRequestId() { + return delegate.getRequestId(); + } + + @Override + public Double getTimeoutSeconds() { + return delegate.getTimeoutSeconds(); + } + + @Override + public TokenBinding getTokenBinding() { + return delegate.getTokenBinding(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BrowserPublicKeyCredentialRequestOptions)) return false; + + BrowserPublicKeyCredentialRequestOptions that = (BrowserPublicKeyCredentialRequestOptions) o; + + if (delegate != null ? !delegate.equals(that.delegate) : that.delegate != null) return false; + if (origin != null ? !origin.equals(that.origin) : that.origin != null) return false; + return Arrays.equals(clientDataHash, that.clientDataHash); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{delegate, origin, Arrays.hashCode(clientDataHash)}); + } + + @Override + public String toString() { + return ToStringHelper.name("BrowserPublicKeyCredentialRequestOptions") + .value(delegate) + .field("origin", origin) + .field("clientDataHash", clientDataHash) + .end(); + } + + /** + * Builder for {@link BrowserPublicKeyCredentialRequestOptions}. + */ + public static class Builder { + private PublicKeyCredentialRequestOptions delegate; + private Uri origin; + private byte[] clientDataHash; + + /** + * The constructor of {@link BrowserPublicKeyCredentialRequestOptions.Builder}. + */ + public Builder() { + } + + /** + * Sets a clientDataHash value to sign over in place of assembling and hashing clientDataJSON during the + * signature request. + *

+ * Note: This is optional and only provided for contexts where the unhashed information necessary to assemble + * WebAuthn clientDataJSON is not available. If set, the resulting {@link AuthenticatorAssertionResponse} will + * return an invalid value for {@code getClientDataJSON()}. Generally, browser clients should use + * {@link PublicKeyCredentialRequestOptions.Builder#setChallenge(byte[])} instead. + * + * @return + */ + public Builder setClientDataHash(byte[] clientDataHash) { + this.clientDataHash = clientDataHash; + return this; + } + + /** + * Sets the origin on whose behalf the calling browser is requesting an authentication operation. + */ + public Builder setOrigin(Uri origin) { + this.origin = origin; + return this; + } + + /** + * Sets the parameters to dictate client behavior during this authentication session. + */ + public Builder setPublicKeyCredentialRequestOptions(PublicKeyCredentialRequestOptions publicKeyCredentialRequestOptions) { + this.delegate = publicKeyCredentialRequestOptions; + return this; + } + + /** + * Builds the {@link BrowserPublicKeyCredentialRequestOptions} object. + */ + public BrowserPublicKeyCredentialRequestOptions build() { + BrowserPublicKeyCredentialRequestOptions options = new BrowserPublicKeyCredentialRequestOptions(); + options.delegate = delegate; + options.origin = origin; + options.clientDataHash = clientDataHash; + return options; + } + } + + public static BrowserPublicKeyCredentialRequestOptions deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + public static final Creator CREATOR = new AutoCreator<>(BrowserPublicKeyCredentialRequestOptions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserRequestOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserRequestOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..407e9a785a94bbc1bda70893f1fd2c60cabb1e6e --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/BrowserRequestOptions.java @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.net.Uri; + +/** + * An abstract class representing browser-based request parameters. + */ +public abstract class BrowserRequestOptions extends RequestOptions { + /** + * Gets value of the client data hash. + */ + public abstract byte[] getClientDataHash(); + + public abstract Uri getOrigin(); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/COSEAlgorithmIdentifier.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/COSEAlgorithmIdentifier.java new file mode 100644 index 0000000000000000000000000000000000000000..326d44bd7c4057b9adbd01f936e1050de2961ec7 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/COSEAlgorithmIdentifier.java @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; + +@PublicApi +public class COSEAlgorithmIdentifier implements Parcelable { + private Algorithm algorithm; + + private COSEAlgorithmIdentifier() { + } + + private COSEAlgorithmIdentifier(Algorithm algorithm) { + this.algorithm = algorithm; + } + + public static COSEAlgorithmIdentifier fromCoseValue(int value) throws UnsupportedAlgorithmIdentifierException { + if (value == RSAAlgorithm.LEGACY_RS1.getAlgoValue()) return new COSEAlgorithmIdentifier(RSAAlgorithm.RS1); + for (RSAAlgorithm algorithm : RSAAlgorithm.values()) { + if (algorithm.getAlgoValue() == value) return new COSEAlgorithmIdentifier(algorithm); + } + for (EC2Algorithm algorithm : EC2Algorithm.values()) { + if (algorithm.getAlgoValue() == value) return new COSEAlgorithmIdentifier(algorithm); + } + throw new UnsupportedAlgorithmIdentifierException(value); + } + + public int toCoseValue() { + return algorithm.getAlgoValue(); + } + + @Override + public String toString() { + return ToStringHelper.name("COSEAlgorithmIdentifier") + .value(algorithm) + .end(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(algorithm.getAlgoValue()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public COSEAlgorithmIdentifier createFromParcel(Parcel in) { + try { + return fromCoseValue(in.readInt()); + } catch (UnsupportedAlgorithmIdentifierException e) { + throw new RuntimeException(e); + } + } + + @Override + public COSEAlgorithmIdentifier[] newArray(int size) { + return new COSEAlgorithmIdentifier[size]; + } + }; + + public static class UnsupportedAlgorithmIdentifierException extends Exception { + public UnsupportedAlgorithmIdentifierException(int algId) { + super("Algorithm with COSE value " + algId + " not supported"); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationData.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationData.java new file mode 100644 index 0000000000000000000000000000000000000000..23a06cb88e45f7130f089deaef3bff76ec88f303 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationData.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class CableAuthenticationData extends AutoSafeParcelable { + @Field(1) + private long version; + @Field(2) + private byte[] clientEid; + @Field(3) + private byte[] authenticatorEid; + @Field(4) + private byte[] sessionPreKey; + + public static final Creator CREATOR = new AutoCreator<>(CableAuthenticationData.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationExtension.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..58d85b6e5727d9366fa764500bd613bfd5ec299b --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/CableAuthenticationExtension.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.List; + +public class CableAuthenticationExtension extends AutoSafeParcelable { + @Field(1) + private List cableAuthentication; + + public static final Creator CREATOR = new AutoCreator<>(CableAuthenticationExtension.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/EC2Algorithm.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/EC2Algorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..00fa740fc7483fba46ec420d232149ae75bca21d --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/EC2Algorithm.java @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; + +/** + * Algorithm names and COSE identifiers for EC2 (public) keys. + */ +@PublicApi +public enum EC2Algorithm implements Algorithm { + /** + * TPM_ECC_BN_P256 curve w/ SHA-256 + */ + ED256(-260), + /** + * ECC_BN_ISOP512 curve w/ SHA-512 + */ + ED512(-261), + /** + * ECDSA w/ SHA-256 + */ + ES256(-7), + /** + * ECDSA w/ SHA-384 + */ + ES384(-35), + /** + * ECDSA w/ SHA-512 + */ + ES512(-36); + + private final int algoValue; + + EC2Algorithm(int algoValue) { + this.algoValue = algoValue; + } + + @Override + public int getAlgoValue() { + return algoValue; + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/ErrorCode.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/ErrorCode.java new file mode 100644 index 0000000000000000000000000000000000000000..6c5c1ec3af14ebcbfb66a87e1a21a4a22ab0782e --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/ErrorCode.java @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +/** + * Error codes that are referenced by WebAuthn spec. + */ +@PublicApi +public enum ErrorCode implements Parcelable { + /** + * The operation is not supported. + */ + NOT_SUPPORTED_ERR(9), + /** + * The object is in an invalid state. + */ + INVALID_STATE_ERR(11), + /** + * The operation is insecure. + */ + SECURITY_ERR(18), + /** + * A network error occurred. + */ + NETWORK_ERR(19), + /** + * The operation was aborted. + */ + ABORT_ERR(20), + /** + * The operation timed out. + */ + TIMEOUT_ERR(23), + /** + * The encoding operation (either encoded or decoding) failed. + */ + ENCODING_ERR(27), + /** + * The operation failed for an unknown transient reason. + */ + UNKNOWN_ERR(28), + /** + * A mutation operation in a transaction failed because a constraint was not satisfied. + */ + CONSTRAINT_ERR(29), + /** + * Provided data is inadequate. + */ + DATA_ERR(30), + /** + * The request is not allowed by the user agent or the platform in the current context, possibly because the user + * denied permission. + */ + NOT_ALLOWED_ERR(35), + /** + * The authenticator violates the privacy requirements of the {@code AttestationStatementType} it is using. + */ + ATTESTATION_NOT_PRIVATE_ERR(36); + + private int code; + + ErrorCode(int code) { + this.code = code; + } + + @PublicApi(exclude = true) + public int getCode() { + return code; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(code); + } + + @PublicApi(exclude = true) + public static ErrorCode toErrorCode(int errorCode) throws UnsupportedErrorCodeException { + for (ErrorCode value : values()) { + if (value.code == errorCode) return value; + } + throw new UnsupportedErrorCodeException(errorCode); + } + + /** + * Exception thrown when an unsupported or unrecognized error code is encountered. + */ + public static class UnsupportedErrorCodeException extends Exception { + /** + * Constructor for the {@link ErrorCode.UnsupportedErrorCodeException}. + */ + public UnsupportedErrorCodeException(int errorCode) { + super("Error code " + errorCode + " is not supported"); + } + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new Creator() { + @Override + public ErrorCode createFromParcel(Parcel source) { + try { + return ErrorCode.toErrorCode(source.readInt()); + } catch (UnsupportedErrorCodeException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public ErrorCode[] newArray(int size) { + return new ErrorCode[size]; + } + }; +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/FidoAppIdExtension.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/FidoAppIdExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..7011404c67e64ac5f5d6f330fffc08aa83e89e2c --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/FidoAppIdExtension.java @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Extension for FIDO appId, to support U2F backward compatibility in FIDO2 assertion requests. + *

+ * This authentication extension allows Relying Parties that have previously registered a credential using the legacy + * FIDO U2F APIs to request an assertion. Specifically, this extension allows Relying Parties to specify an appId to + * overwrite the computed rpId for U2F authenticators. + *

+ * Note that this extension is only valid if used during the get() call; other usage should result in client error. + */ +@PublicApi +public class FidoAppIdExtension extends AutoSafeParcelable { + @Field(2) + private String appId; + + public String getAppId() { + return appId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FidoAppIdExtension)) return false; + + FidoAppIdExtension that = (FidoAppIdExtension) o; + + return appId.equals(that.appId); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{appId}); + } + + public static final Creator CREATOR = new AutoCreator<>(FidoAppIdExtension.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/KeyProtectionTypes.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/KeyProtectionTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..01ad7938bdb3faaed57e647404898b9e612f8c48 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/KeyProtectionTypes.java @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +/** + * The method used by the authenticator to protect the FIDO registration private key material. Available values are + * defined in Section 3.2 Key Protection Types. + */ +public final class KeyProtectionTypes { + /** + * This flag must be set if the authenticator uses software-based key management. Exclusive in authenticator + * metadata with KEY_PROTECTION_HARDWARE, KEY_PROTECTION_TEE, KEY_PROTECTION_SECURE_ELEMENT. + */ + public static final short KEY_PROTECTION_SOFTWARE = 1; + /** + * This flag should be set if the authenticator uses hardware-based key management. Exclusive in authenticator + * metadata with KEY_PROTECTION_SOFTWARE. + */ + public static final short KEY_PROTECTION_HARDWARE = 2; + /** + * This flag should be set if the authenticator uses the Trusted Execution Environment for key management. In + * authenticator metadata, this flag should be set in conjunction with KEY_PROTECTION_HARDWARE. Mutually exclusive + * in authenticator metadata with KEY_PROTECTION_SOFTWARE, KEY_PROTECTION_SECURE_ELEMENT. + */ + public static final short KEY_PROTECTION_TEE = 4; + /** + * This flag should be set if the authenticator uses a Secure Element for key management. In authenticator metadata, + * this flag should be set in conjunction with KEY_PROTECTION_HARDWARE. Mutually exclusive in authenticator metadata + * with KEY_PROTECTION_TEE, KEY_PROTECTION_SOFTWARE. + */ + public static final short KEY_PROTECTION_SECURE_ELEMENT = 8; + /** + * This flag must be set if the authenticator does not store (wrapped) UAuth keys at the client, but relies on a + * server-provided key handle. This flag must be set in conjunction with one of the other KEY_PROTECTION flags to + * indicate how the local key handle wrapping key and operations are protected. Servers may unset this flag in + * authenticator policy if they are not prepared to store and return key handles, for example, if they have a + * requirement to respond indistinguishably to authentication attempts against userIDs that do and do not exist. + * Refer to for more details. + */ + public static final short KEY_PROTECTION_REMOTE_HANDLE = 16; +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/MatcherProtectionTypes.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/MatcherProtectionTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..a6818f06c2811e54e47b2592aeeffb1061d06bbf --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/MatcherProtectionTypes.java @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +/** + * The method used by the authenticator to protect the matcher that performs user verification. Available values are + * defined in Section 3.3 Matcher Protection Types. + */ +public final class MatcherProtectionTypes { + /** + * This flag must be set if the authenticator's matcher is running in software. Exclusive in authenticator metadata + * with MATCHER_PROTECTION_TEE, MATCHER_PROTECTION_ON_CHIP. + */ + public static final short MATCHER_PROTECTION_SOFTWARE = 1; + /** + * This flag should be set if the authenticator's matcher is running inside the Trusted Execution Environment. + * Mutually exclusive in authenticator metadata with MATCHER_PROTECTION_SOFTWARE, MATCHER_PROTECTION_ON_CHIP. + */ + public static final short MATCHER_PROTECTION_TEE = 2; + /** + * This flag should be set if the authenticator's matcher is running on the chip. Mutually exclusive in + * authenticator metadata with MATCHER_PROTECTION_TEE, MATCHER_PROTECTION_SOFTWARE. + */ + public static final short MATCHER_PROTECTION_ON_CHIP = 4; +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java new file mode 100644 index 0000000000000000000000000000000000000000..ebc5f819a1ae42e5a5b5810116516e333bc06b44 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredential.java @@ -0,0 +1,178 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; + +/** + * This class is contains the attributes that are returned to the caller when a new credential is created, or a new + * assertion is requested. + */ +@PublicApi +public class PublicKeyCredential extends AutoSafeParcelable { + @Field(1) + private String id; + @Field(2) + private String type; + @Field(3) + private byte[] rawId; + @Field(4) + private AuthenticatorAttestationResponse registerResponse; + @Field(5) + private AuthenticatorAssertionResponse signResponse; + @Field(6) + private AuthenticatorErrorResponse errorResponse; + @Field(7) + private AuthenticationExtensionsClientOutputs clientExtensionResults; + + public AuthenticationExtensionsClientOutputs getClientExtensionResults() { + return clientExtensionResults; + } + + public String getId() { + return id; + } + + public byte[] getRawId() { + return rawId; + } + + public AuthenticatorResponse getResponse() { + if (registerResponse != null) return registerResponse; + if (signResponse != null) return signResponse; + if (errorResponse != null) return errorResponse; + throw new IllegalStateException("No response set."); + } + + public String getType() { + return type; + } + + /** + * Builder for {@link PublicKeyCredential}. + */ + public static class Builder { + private String id; + private byte[] rawId; + private AuthenticatorResponse response; + private AuthenticationExtensionsClientOutputs extensionsClientOutputs; + + /** + * The constructor of {@link PublicKeyCredential.Builder}. + */ + public Builder() { + } + + /** + * Sets the output produced by the client's processing of the extensions requested by the relying party. + */ + public PublicKeyCredential.Builder setAuthenticationExtensionsClientOutputs(AuthenticationExtensionsClientOutputs extensionsClientOutputs) { + this.extensionsClientOutputs = extensionsClientOutputs; + return this; + } + + /** + * Sets the base64url encoding of the credential identifier. + */ + public Builder setId(String id) { + this.id = id; + return this; + } + + /** + * Sets the raw value of the credential identifier. + */ + public Builder setRawId(byte[] rawId) { + this.rawId = rawId; + return this; + } + + /** + * Sets the authenticator's response to the clients register or sign request. + *

+ * This attribute contains the authenticator's response to the client’s request to either create a public key + * credential, or generate an authentication assertion. If the {@link PublicKeyCredential} is created in + * response a register request, this attribute’s value will be an {@link AuthenticatorAttestationResponse}, + * otherwise, the {@link PublicKeyCredential} was created in response to a sign request, and this attribute’s + * value will be an {@link AuthenticatorAssertionResponse}. + */ + public Builder setResponse(AuthenticatorResponse response) { + this.response = response; + return this; + } + + /** + * Builds the {@link PublicKeyCredential} object. + */ + public PublicKeyCredential build() { + PublicKeyCredential credential = new PublicKeyCredential(); + credential.id = id; + credential.type = PublicKeyCredentialType.PUBLIC_KEY.toString(); + credential.rawId = rawId; + credential.clientExtensionResults = extensionsClientOutputs; + if (response instanceof AuthenticatorAttestationResponse) { + credential.registerResponse = (AuthenticatorAttestationResponse) response; + } else if (response instanceof AuthenticatorAssertionResponse) { + credential.signResponse = (AuthenticatorAssertionResponse) response; + } else if (response instanceof AuthenticatorErrorResponse) { + credential.errorResponse = (AuthenticatorErrorResponse) response; + } + return credential; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredential)) return false; + + PublicKeyCredential that = (PublicKeyCredential) o; + + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (type != null ? !type.equals(that.type) : that.type != null) return false; + if (!Arrays.equals(rawId, that.rawId)) return false; + if (registerResponse != null ? !registerResponse.equals(that.registerResponse) : that.registerResponse != null) + return false; + if (signResponse != null ? !signResponse.equals(that.signResponse) : that.signResponse != null) return false; + if (errorResponse != null ? !errorResponse.equals(that.errorResponse) : that.errorResponse != null) + return false; + return clientExtensionResults != null ? clientExtensionResults.equals(that.clientExtensionResults) : that.clientExtensionResults == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{id, type, rawId, signResponse, registerResponse, errorResponse, clientExtensionResults}); + } + + /** + * Serializes the {@link PublicKeyCredential} to bytes. Use {@link #deserializeFromBytes(byte[])} to deserialize. + * + * @return the serialized byte array. + */ + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } + + /** + * Deserializes the {@link PublicKeyCredential} from bytes, reversing {@link #serializeToBytes()}. + * + * @param serializedBytes The serialized bytes. + * @return The deserialized {@link PublicKeyCredential}. + */ + public static PublicKeyCredential deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredential.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..fbf4b5d5c8fcaa60a42823ba10709024de76d687 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialCreationOptions.java @@ -0,0 +1,289 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.util.Base64; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; +import java.util.List; + +/** + * This class is used to supply options when creating a new credential. + */ +@PublicApi +public class PublicKeyCredentialCreationOptions extends RequestOptions { + @Field(2) + private PublicKeyCredentialRpEntity rp; + @Field(3) + private PublicKeyCredentialUserEntity user; + @Field(4) + private byte[] challenge; + @Field(5) + private List parameters; + @Field(6) + private Double timeoutSeconds; + @Field(7) + private List excludeList; + @Field(8) + private AuthenticatorSelectionCriteria authenticatorSelection; + @Field(9) + private Integer requestId; + @Field(10) + private TokenBinding tokenBinding; + @Field(11) + private AttestationConveyancePreference attestationConveyancePreference; + @Field(12) + private AuthenticationExtensions authenticationExtensions; + + public AttestationConveyancePreference getAttestationConveyancePreference() { + return attestationConveyancePreference; + } + + public String getAttestationConveyancePreferenceAsString() { + return attestationConveyancePreference.toString(); + } + + @Override + public AuthenticationExtensions getAuthenticationExtensions() { + return authenticationExtensions; + } + + public AuthenticatorSelectionCriteria getAuthenticatorSelection() { + return authenticatorSelection; + } + + @Override + public byte[] getChallenge() { + return challenge; + } + + public List getExcludeList() { + return excludeList; + } + + public List getParameters() { + return parameters; + } + + @Override + public Integer getRequestId() { + return requestId; + } + + public PublicKeyCredentialRpEntity getRp() { + return rp; + } + + @Override + public Double getTimeoutSeconds() { + return timeoutSeconds; + } + + @Override + public TokenBinding getTokenBinding() { + return tokenBinding; + } + + public PublicKeyCredentialUserEntity getUser() { + return user; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialCreationOptions)) return false; + + PublicKeyCredentialCreationOptions that = (PublicKeyCredentialCreationOptions) o; + + if (rp != null ? !rp.equals(that.rp) : that.rp != null) return false; + if (user != null ? !user.equals(that.user) : that.user != null) return false; + if (!Arrays.equals(challenge, that.challenge)) return false; + if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null) return false; + if (timeoutSeconds != null ? !timeoutSeconds.equals(that.timeoutSeconds) : that.timeoutSeconds != null) + return false; + if (excludeList != null ? !excludeList.equals(that.excludeList) : that.excludeList != null) return false; + if (authenticatorSelection != null ? !authenticatorSelection.equals(that.authenticatorSelection) : that.authenticatorSelection != null) + return false; + if (requestId != null ? !requestId.equals(that.requestId) : that.requestId != null) return false; + if (tokenBinding != null ? !tokenBinding.equals(that.tokenBinding) : that.tokenBinding != null) return false; + if (attestationConveyancePreference != that.attestationConveyancePreference) return false; + return authenticationExtensions != null ? authenticationExtensions.equals(that.authenticationExtensions) : that.authenticationExtensions == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{rp, user, Arrays.hashCode(challenge), parameters, timeoutSeconds, excludeList, authenticatorSelection, requestId, tokenBinding, attestationConveyancePreference, authenticationExtensions}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialCreationOptions") + .field("rp", rp) + .field("user", user) + .field("challenge", challenge) + .field("parameters", parameters) + .field("timeoutSeconds", timeoutSeconds) + .field("excludeList", excludeList) + .field("authenticatorSelection", authenticatorSelection) + .field("requestId", requestId) + .field("tokenBinding", tokenBinding) + .field("attestationConveyancePreference", attestationConveyancePreference) + .field("authenticationExtensions", authenticationExtensions) + .end(); + } + + /** + * Builder for {@link PublicKeyCredentialCreationOptions}. + */ + public static class Builder { + private PublicKeyCredentialRpEntity rp; + private PublicKeyCredentialUserEntity user; + private byte[] challenge; + private List parameters; + private Double timeoutSeconds; + private List excludeList; + private AuthenticatorSelectionCriteria authenticatorSelection; + private Integer requestId; + private TokenBinding tokenBinding; + private AttestationConveyancePreference attestationConveyancePreference; + private AuthenticationExtensions authenticationExtensions; + + /** + * The constructor of {@link PublicKeyCredentialCreationOptions.Builder}. + */ + public Builder() { + } + + /** + * Sets the preference for obfuscation level of the returned attestation data. + */ + public Builder setAttestationConveyancePreference(AttestationConveyancePreference attestationConveyancePreference) { + this.attestationConveyancePreference = attestationConveyancePreference; + return this; + } + + /** + * Sets additional extensions that may dictate some client behavior during an exchange with a connected + * authenticator. + */ + public Builder setAuthenticationExtensions(AuthenticationExtensions authenticationExtensions) { + this.authenticationExtensions = authenticationExtensions; + return this; + } + + /** + * Sets constraints on the type of authenticator that is acceptable for this session. + */ + public Builder setAuthenticatorSelection(AuthenticatorSelectionCriteria authenticatorSelection) { + this.authenticatorSelection = authenticatorSelection; + return this; + } + + /** + * Sets the challenge to sign when generating the attestation for this request. + */ + public Builder setChallenge(byte[] challenge) { + this.challenge = challenge; + return this; + } + + /** + * Sets a list of credentials that, if found on a connected authenticator, will preclude registration of that + * authenticator with the relying party. This is often set to prevent re-registration of authenticators that + * the relying party has already registered on behalf of the user. + */ + public Builder setExcludeList(List excludeList) { + this.excludeList = excludeList; + return this; + } + + /** + * Sets the {@link PublicKeyCredentialParameters} that constrain the type of credential to generate. + */ + public Builder setParameters(List parameters) { + this.parameters = parameters; + return this; + } + + /** + * Sets the request id in order to link together events into a single session (the span of events between the + * time that the server initiates a single FIDO2 request to the client and receives reply) on a single device. + */ + public Builder setRequestId(Integer requestId) { + this.requestId = requestId; + return this; + } + + /** + * Sets information for a relying party, on whose behalf a given registration operation is being performed. + *

+ * Note: the RpId should be an effective domain (aka, without scheme or port); and it should also be in secure + * context (aka https connection). Apps-facing API needs to check the package signature against Digital Asset + * Links, whose resource is the RP ID with prepended "//". Privileged (browser) API doesn't need the check. + */ + public Builder setRp(PublicKeyCredentialRpEntity rp) { + this.rp = rp; + return this; + } + + /** + * Sets a timeout that limits the duration of the registration session provided to the user. + */ + public Builder setTimeoutSeconds(Double timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + return this; + } + + /** + * Sets the {@link TokenBinding} associated with the calling origin. + */ + public Builder setTokenBinding(TokenBinding tokenBinding) { + this.tokenBinding = tokenBinding; + return this; + } + + /** + * Sets information about the user on whose behalf the relying party is registering a credential. + */ + public Builder setUser(PublicKeyCredentialUserEntity user) { + this.user = user; + return this; + } + + /** + * Builds the {@link PublicKeyCredentialCreationOptions} object. + */ + public PublicKeyCredentialCreationOptions build() { + PublicKeyCredentialCreationOptions options = new PublicKeyCredentialCreationOptions(); + options.challenge = challenge; + options.timeoutSeconds = timeoutSeconds; + options.requestId = requestId; + options.tokenBinding = tokenBinding; + options.authenticationExtensions = authenticationExtensions; + return options; + } + } + + /** + * Deserializes the {@link PublicKeyCredentialCreationOptions} from bytes, reversing {@link #serializeToBytes()}. + * + * @param serializedBytes The serialized bytes. + * @return The deserialized {@link PublicKeyCredentialCreationOptions}. + */ + public static PublicKeyCredentialCreationOptions deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialCreationOptions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialDescriptor.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialDescriptor.java new file mode 100644 index 0000000000000000000000000000000000000000..950021ef5efc37f756955e1a0c1aa91af19e6826 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialDescriptor.java @@ -0,0 +1,102 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import com.google.android.gms.fido.common.Transport; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; +import java.util.List; + +/** + * This class contains the attributes that are specified by a caller when referring to a credential as an input + * parameter to the registration or authentication method. + */ +@PublicApi +public class PublicKeyCredentialDescriptor extends AutoSafeParcelable { + @Field(2) + private PublicKeyCredentialType type; + @Field(3) + private byte[] id; + @Field(4) + private List transports; + + private PublicKeyCredentialDescriptor() { + } + + public PublicKeyCredentialDescriptor(String type, byte[] id, List transports) throws UnsupportedPubKeyCredDescriptorException { + try { + this.type = PublicKeyCredentialType.fromString(type); + } catch (PublicKeyCredentialType.UnsupportedPublicKeyCredTypeException e) { + throw new UnsupportedPubKeyCredDescriptorException(e.getMessage(), e); + } + this.id = id; + this.transports = transports; + } + + public byte[] getId() { + return id; + } + + public List getTransports() { + return transports; + } + + public PublicKeyCredentialType getType() { + return type; + } + + public String getTypeAsString() { + return type.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialDescriptor)) return false; + + PublicKeyCredentialDescriptor that = (PublicKeyCredentialDescriptor) o; + + if (type != that.type) return false; + if (!Arrays.equals(id, that.id)) return false; + return transports != null ? transports.equals(that.transports) : that.transports == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{type, Arrays.hashCode(id), transports}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialDescriptor") + .value(id) + .field("type", type) + .field("transports", transports) + .end(); + } + + /** + * Exception thrown when an unsupported or unrecognized public key credential descriptor is encountered. + */ + public static class UnsupportedPubKeyCredDescriptorException extends Exception { + public UnsupportedPubKeyCredDescriptorException(String message) { + super(message); + } + + public UnsupportedPubKeyCredDescriptorException(String message, Throwable cause) { + super(message, cause); + } + } + + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialDescriptor.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialParameters.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialParameters.java new file mode 100644 index 0000000000000000000000000000000000000000..e0914c570bf411ba7293ccb7540bfc84e2f010a5 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialParameters.java @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * This class supplies additional parameters when creating a new credential. + */ +@PublicApi +public class PublicKeyCredentialParameters extends AutoSafeParcelable { + @Field(2) + private PublicKeyCredentialType type; + @Field(3) + private COSEAlgorithmIdentifier algorithm; + + private PublicKeyCredentialParameters() { + } + + public PublicKeyCredentialParameters(String type, int algorithm) { + try { + this.type = PublicKeyCredentialType.fromString(type); + } catch (PublicKeyCredentialType.UnsupportedPublicKeyCredTypeException e) { + throw new IllegalArgumentException(e); + } + try { + this.algorithm = COSEAlgorithmIdentifier.fromCoseValue(algorithm); + } catch (COSEAlgorithmIdentifier.UnsupportedAlgorithmIdentifierException e) { + throw new IllegalArgumentException(e); + } + } + + public COSEAlgorithmIdentifier getAlgorithm() { + return algorithm; + } + + public int getAlgorithmIdAsInteger() { + return algorithm.toCoseValue(); + } + + public PublicKeyCredentialType getType() { + return type; + } + + public String getTypeAsString() { + return type.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialParameters)) return false; + + PublicKeyCredentialParameters that = (PublicKeyCredentialParameters) o; + + if (type != that.type) return false; + return algorithm != null ? algorithm.equals(that.algorithm) : that.algorithm == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{type, algorithm}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialParameters") + .field("type", type) + .field("algorithm", algorithm) + .end(); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialParameters.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..0b1ab928a73ba43ae8a0fb140505f05abe19480f --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRequestOptions.java @@ -0,0 +1,220 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.SafeParcelUtil; + +import java.util.Arrays; +import java.util.List; + +/** + * This class is used to supply an authentication request with the data it needs to generate an assertion. + */ +@PublicApi +public class PublicKeyCredentialRequestOptions extends RequestOptions { + @Field(2) + private byte[] challenge; + @Field(3) + private Double timeoutSeconds; + @Field(4) + private String rpId; + @Field(5) + private List allowList; + @Field(6) + private Integer requestId; + @Field(7) + private TokenBinding tokenBinding; + @Field(8) + private UserVerificationRequirement userVerificationRequirement; + @Field(9) + private AuthenticationExtensions authenticationExtensions; + + public List getAllowList() { + return allowList; + } + + @Override + public AuthenticationExtensions getAuthenticationExtensions() { + return authenticationExtensions; + } + + @Override + public byte[] getChallenge() { + return challenge; + } + + @Override + public Integer getRequestId() { + return requestId; + } + + public String getRpId() { + return rpId; + } + + @Override + public Double getTimeoutSeconds() { + return timeoutSeconds; + } + + @Override + public TokenBinding getTokenBinding() { + return tokenBinding; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialRequestOptions)) return false; + + PublicKeyCredentialRequestOptions that = (PublicKeyCredentialRequestOptions) o; + + if (!Arrays.equals(challenge, that.challenge)) return false; + if (timeoutSeconds != null ? !timeoutSeconds.equals(that.timeoutSeconds) : that.timeoutSeconds != null) + return false; + if (rpId != null ? !rpId.equals(that.rpId) : that.rpId != null) return false; + if (allowList != null ? !allowList.equals(that.allowList) : that.allowList != null) return false; + if (requestId != null ? !requestId.equals(that.requestId) : that.requestId != null) return false; + if (tokenBinding != null ? !tokenBinding.equals(that.tokenBinding) : that.tokenBinding != null) return false; + if (userVerificationRequirement != that.userVerificationRequirement) return false; + return authenticationExtensions != null ? authenticationExtensions.equals(that.authenticationExtensions) : that.authenticationExtensions == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{Arrays.hashCode(challenge), timeoutSeconds, rpId, allowList, requestId, tokenBinding, userVerificationRequirement, authenticationExtensions}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialRequestOptions") + .field("challenge", challenge) + .field("timeoutSeconds", timeoutSeconds) + .field("rpId", rpId) + .field("allowList", allowList) + .field("requestId", requestId) + .field("tokenBinding", tokenBinding) + .field("userVerificationRequirement", userVerificationRequirement) + .field("authenticationExtensions", authenticationExtensions) + .end(); + } + + /** + * Builder for {@link PublicKeyCredentialRequestOptions}. + */ + public static class Builder { + private byte[] challenge; + private Double timeoutSeconds; + private String rpId; + private List allowList; + private Integer requestId; + private TokenBinding tokenBinding; + private AuthenticationExtensions authenticationExtensions; + + /** + * The constructor of {@link PublicKeyCredentialRequestOptions.Builder}. + */ + public Builder() { + } + + /** + * Sets a list of public key credentials which constrain authentication to authenticators that contain a + * private key for at least one of the supplied public keys. + */ + public Builder setAllowList(List allowList) { + this.allowList = allowList; + return this; + } + + /** + * Sets additional extensions that may dictate some client behavior during an exchange with a connected + * authenticator. + */ + public Builder setAuthenticationExtensions(AuthenticationExtensions authenticationExtensions) { + this.authenticationExtensions = authenticationExtensions; + return this; + } + + /** + * Sets the nonce value that the authenticator should sign using a private key corresponding to a public key + * credential that is acceptable for this authentication session. + */ + public Builder setChallenge(byte[] challenge) { + this.challenge = challenge; + return this; + } + + /** + * Sets the request id in order to link together events into a single session (the span of events between the + * time that the server initiates a single FIDO2 request to the client and receives reply) on a single device. + * This field is optional. + */ + public Builder setRequestId(Integer requestId) { + this.requestId = requestId; + return this; + } + + /** + * Sets identifier for a relying party, on whose behalf a given authentication operation is being performed. + * A public key credential can only be used for authentication with the same replying party it was registered + * with. + *

+ * Note: the RpId should be an effective domain (aka, without scheme or port); and it should also be in secure + * context (aka https connection). Apps-facing API needs to check the package signature against Digital Asset + * Links, whose resource is the RP ID with prepended "//". Privileged (browser) API doesn't need the check. + */ + public Builder setRpId(String rpId) { + this.rpId = rpId; + return this; + } + + public Builder setTimeoutSeconds(Double timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + return this; + } + + /** + * Sets the {@link TokenBinding} associated with the calling origin. + */ + public Builder setTokenBinding(TokenBinding tokenBinding) { + this.tokenBinding = tokenBinding; + return this; + } + + /** + * Builds the {@link PublicKeyCredentialRequestOptions} object. + */ + public PublicKeyCredentialRequestOptions build() { + PublicKeyCredentialRequestOptions options = new PublicKeyCredentialRequestOptions(); + options.challenge = challenge; + options.timeoutSeconds = timeoutSeconds; + options.rpId = rpId; + options.allowList = allowList; + options.requestId = requestId; + options.tokenBinding = tokenBinding; + options.authenticationExtensions = authenticationExtensions; + return options; + } + } + + /** + * Deserializes the {@link PublicKeyCredentialRequestOptions} from bytes, reversing {@link #serializeToBytes()}. + * + * @param serializedBytes The serialized bytes. + * @return The deserialized {@link PublicKeyCredentialRequestOptions}. + */ + public static PublicKeyCredentialRequestOptions deserializeFromBytes(byte[] serializedBytes) { + return SafeParcelUtil.fromByteArray(serializedBytes, CREATOR); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialRequestOptions.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRpEntity.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRpEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..e1a98009016fe012b6cd94782945cec59cf58aed --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialRpEntity.java @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Represents the information about a relying party with which a credential is associated. + */ +@PublicApi +public class PublicKeyCredentialRpEntity extends AutoSafeParcelable { + @Field(2) + private String id; + @Field(3) + private String name; + @Field(4) + private String icon; + + private PublicKeyCredentialRpEntity() { + } + + public PublicKeyCredentialRpEntity(String id, String name, String icon) { + this.id = id; + this.name = name; + this.icon = icon; + } + + public String getIcon() { + return icon; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialRpEntity)) return false; + + PublicKeyCredentialRpEntity that = (PublicKeyCredentialRpEntity) o; + + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + return icon != null ? icon.equals(that.icon) : that.icon == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{id, name, icon}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialRpEntity") + .value(id) + .field("name", name) + .field("icon", icon) + .end(); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialRpEntity.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialType.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialType.java new file mode 100644 index 0000000000000000000000000000000000000000..d3bd336d6f721720921437b24e8c299ac184958d --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialType.java @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +/** + * This enumeration defines the valid credential types. + */ +public enum PublicKeyCredentialType implements Parcelable { + PUBLIC_KEY("public-key"); + + private final String value; + + PublicKeyCredentialType(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static PublicKeyCredentialType fromString(String type) throws UnsupportedPublicKeyCredTypeException { + for (PublicKeyCredentialType value : values()) { + if (value.value.equals(type)) return value; + } + throw new UnsupportedPublicKeyCredTypeException("PublicKeyCredentialType " + type + " not supported"); + } + + public static Creator CREATOR = new Creator() { + @Override + public PublicKeyCredentialType createFromParcel(Parcel source) { + try { + return PublicKeyCredentialType.fromString(source.readString()); + } catch (UnsupportedPublicKeyCredTypeException e) { + throw new RuntimeException(e); + } + } + + @Override + public PublicKeyCredentialType[] newArray(int size) { + return new PublicKeyCredentialType[size]; + } + }; + + /** + * Exception thrown when an unsupported or unrecognized transport is encountered. + */ + public static class UnsupportedPublicKeyCredTypeException extends Exception { + public UnsupportedPublicKeyCredTypeException(String message) { + super(message); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialUserEntity.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialUserEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..93b88efb538a570df812589520dea80535038e6a --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/PublicKeyCredentialUserEntity.java @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * This class is used to supply additional parameters about the user account when creating a new Credential. + */ +@PublicApi +public class PublicKeyCredentialUserEntity extends AutoSafeParcelable { + @Field(2) + private byte[] id; + @Field(3) + private String name; + @Field(4) + private String icon; + @Field(5) + private String displayName; + + private PublicKeyCredentialUserEntity() { + } + + public PublicKeyCredentialUserEntity(byte[] id, String name, String icon, String displayName) { + this.id = id; + this.name = name; + this.icon = icon; + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + public String getIcon() { + return icon; + } + + public byte[] getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PublicKeyCredentialUserEntity)) return false; + + PublicKeyCredentialUserEntity that = (PublicKeyCredentialUserEntity) o; + + if (!Arrays.equals(id, that.id)) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + if (icon != null ? !icon.equals(that.icon) : that.icon != null) return false; + return displayName != null ? displayName.equals(that.displayName) : that.displayName == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{id, name, icon, displayName}); + } + + @Override + public String toString() { + return ToStringHelper.name("PublicKeyCredentialUserEntity") + .value(id) + .field("name", name) + .field("icon", icon) + .field("displayName", displayName) + .end(); + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(PublicKeyCredentialUserEntity.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RSAAlgorithm.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RSAAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..d3a9f729105957007d0ea62ea0cb739f43b5e610 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RSAAlgorithm.java @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; + +/** + * Algorithm names and COSE identifiers for RSA (public) keys. + */ +@PublicApi +public enum RSAAlgorithm implements Algorithm{ + /** + * RSASSA-PKCS1-v1_5 w/ SHA-256 + */ + RS256(-257), + /** + * RSASSA-PKCS1-v1_5 w/ SHA-384 + */ + RS384(-258), + /** + * RSASSA-PKCS1-v1_5 w/ SHA-512 + */ + RS512(-259), + /** + * The legacy value for "RSASSA-PKCS1-v1_5 w/ SHA-1" + * @deprecated please use {@link #RS1} instead. + */ + @Deprecated + LEGACY_RS1(-262), + /** + * RSASSA-PSS w/ SHA-256 + */ + PS256(-37), + /** + * RSASSA-PSS w/ SHA-384 + */ + PS384(-38), + /** + * RSASSA-PSS w/ SHA-512 + */ + PS512(-39), + /** + * RSASSA-PKCS1-v1_5 w/ SHA-1 + */ + RS1(-65535); + + private final int algoValue; + + RSAAlgorithm(int algoValue) { + this.algoValue = algoValue; + } + + @Override + public int getAlgoValue() { + return algoValue; + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..f2cc361bf3af1f9af314ec79362bb5403f0b7846 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/RequestOptions.java @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParcelUtil; + +/** + * An abstract class representing FIDO2 request options. + */ +@PublicApi +public abstract class RequestOptions extends AutoSafeParcelable { + public abstract byte[] getChallenge(); + public abstract Double getTimeoutSeconds(); + public abstract Integer getRequestId(); + public abstract TokenBinding getTokenBinding(); + public abstract AuthenticationExtensions getAuthenticationExtensions(); + + /** + * Serializes the {@link RequestOptions} to bytes. Use deserializeFromBytes(byte[]) to deserialize. + */ + public byte[] serializeToBytes() { + return SafeParcelUtil.asByteArray(this); + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/TokenBinding.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/TokenBinding.java new file mode 100644 index 0000000000000000000000000000000000000000..bd35485e5f76699b17f5fcbd6db6504049b967e3 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/TokenBinding.java @@ -0,0 +1,179 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.json.JSONException; +import org.json.JSONObject; +import org.microg.gms.common.PublicApi; +import org.microg.gms.utils.ToStringHelper; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Represents the Token binding information provided by the relying party. + */ +@PublicApi +public class TokenBinding extends AutoSafeParcelable { + /** + * A singleton instance representing that token binding is not supported by the client. + */ + public static final TokenBinding NOT_SUPPORTED = new TokenBinding(TokenBindingStatus.NOT_SUPPORTED, null); + /** + * A singleton instance representing that token binding is supported by the client, but unused by the relying party. + */ + public static final TokenBinding SUPPORTED = new TokenBinding(TokenBindingStatus.SUPPORTED, null); + + @Field(2) + private TokenBindingStatus status; + @Field(3) + private String tokenBindingId; + + private TokenBinding() { + } + + /** + * Constructs an instance of a {@link TokenBinding} for a provided token binding id. + */ + public TokenBinding(String tokenBindingId) { + status = TokenBindingStatus.PRESENT; + this.tokenBindingId = tokenBindingId; + } + + private TokenBinding(TokenBindingStatus status, String tokenBindingId) { + this.status = status; + this.tokenBindingId = tokenBindingId; + } + + /** + * Returns the token binding ID if the token binding status is {@code PRESENT}, otherwise returns null. + */ + public String getTokenBindingId() { + return tokenBindingId; + } + + /** + * Returns the stringified {@link TokenBinding.TokenBindingStatus} associated with this instance. + */ + public String getTokenBindingStatusAsString() { + return status.toString(); + } + + /** + * Returns this {@link TokenBinding} object as a {@link JSONObject}. + */ + public JSONObject toJsonObject() { + try { + return new JSONObject().put("status", this.status).put("id", this.tokenBindingId); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TokenBinding)) return false; + + TokenBinding that = (TokenBinding) o; + + if (status != that.status) return false; + return tokenBindingId != null ? tokenBindingId.equals(that.tokenBindingId) : that.tokenBindingId == null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{status, tokenBindingId}); + } + + @Override + public String toString() { + return ToStringHelper.name("TokenBinding") + .value(tokenBindingId) + .field("status", status) + .end(); + } + + /** + * The token binding status specified by the client. + */ + public enum TokenBindingStatus implements Parcelable { + /** + * The client supports token binding and the relying party is using it. + */ + PRESENT("present"), + /** + * The client supports token binding but the relying party is not using it. + */ + SUPPORTED("supported"), + /** + * The client does not support token binding. + */ + NOT_SUPPORTED("not-supported"); + private String value; + + TokenBindingStatus(String value) { + this.value = value; + } + + @PublicApi(exclude = true) + public static TokenBindingStatus fromString(String str) throws UnsupportedTokenBindingStatusException { + for (TokenBindingStatus value : values()) { + if (value.value.equals(str)) return value; + } + throw new UnsupportedTokenBindingStatusException("TokenBindingStatus " + str + " not supported"); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(value); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public TokenBindingStatus createFromParcel(Parcel in) { + try { + return fromString(in.readString()); + } catch (UnsupportedTokenBindingStatusException e) { + throw new RuntimeException(e); + } + } + + @Override + public TokenBindingStatus[] newArray(int size) { + return new TokenBindingStatus[size]; + } + }; + + @Override + public String toString() { + return value; + } + } + + /** + * Exception thrown when an unsupported or unrecognized {@link TokenBinding.TokenBindingStatus} is encountered. + */ + public static class UnsupportedTokenBindingStatusException extends Exception { + public UnsupportedTokenBindingStatusException(String message) { + super(message); + } + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(TokenBinding.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethodExtension.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethodExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..d537142fd0e707be4a96e7913c7ac1ee70ef1850 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethodExtension.java @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Extension for FIDO User Verification Method. + *

+ * This authentication extension allows Relying Parties to ascertain the method(s) used by the user to authorize the + * operation. + *

+ * Note that this extension can be used in only sign calls. + */ +@PublicApi +public class UserVerificationMethodExtension extends AutoSafeParcelable { + @Field(1) + private boolean uvm; + + public boolean getUvm() { + return uvm; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UserVerificationMethodExtension)) return false; + + UserVerificationMethodExtension that = (UserVerificationMethodExtension) o; + + return uvm == that.uvm; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{uvm}); + } + + public static final Creator CREATOR = new AutoCreator<>(UserVerificationMethodExtension.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethods.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethods.java new file mode 100644 index 0000000000000000000000000000000000000000..e647644a97dddc8e8998ccb51103654b355a1c69 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationMethods.java @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; + +/** + * The authentication method/factor used by the authenticator to verify the user. + */ +@PublicApi +public final class UserVerificationMethods { + /** + * This flag must be set if the authenticator is able to confirm user presence in any fashion. If this flag and no + * other is set for user verification, the guarantee is only that the authenticator cannot be operated without some + * human intervention, not necessarily that the sensing of "presence" provides any level of user verification (e.g. + * a device that requires a button press to activate). + */ + public static final int USER_VERIFY_PRESENCE = 1; + /** + * This flag must be set if the authenticator uses any type of measurement of a fingerprint for user verification. + */ + public static final int USER_VERIFY_FINGERPRINT = 2; + /** + * This flag must be set if the authenticator uses a local-only passcode (i.e. a passcode not known by the server) + * for user verification. + */ + public static final int USER_VERIFY_PASSCODE = 4; + /** + * This flag must be set if the authenticator uses a voiceprint (also known as speaker recognition) for user + * verification. + */ + public static final int USER_VERIFY_VOICEPRINT = 8; + /** + * This flag must be set if the authenticator uses any manner of face recognition to verify the user. + */ + public static final int USER_VERIFY_FACEPRINT = 16; + /** + * This flag must be set if the authenticator uses any form of location sensor or measurement for user verification. + */ + public static final int USER_VERIFY_LOCATION = 32; + /** + * This flag must be set if the authenticator uses any form of eye biometrics for user verification. + */ + public static final int USER_VERIFY_EYEPRINT = 64; + /** + * This flag must be set if the authenticator uses a drawn pattern for user verification. + */ + public static final int USER_VERIFY_PATTERN = 128; + /** + * This flag must be set if the authenticator uses any measurement of a full hand (including palm-print, hand + * geometry or vein geometry) for user verification. + */ + public static final int USER_VERIFY_HANDPRINT = 256; + /** + * This flag must be set if the authenticator will respond without any user interaction (e.g. Silent Authenticator). + */ + public static final int USER_VERIFY_NONE = 512; + /** + * If an authenticator sets multiple flags for user verification types, it may also set this flag to indicate that + * all verification methods will be enforced (e.g. faceprint AND voiceprint). If flags for multiple user + * verification methods are set and this flag is not set, verification with only one is necessary (e.g. fingerprint + * OR passcode). + */ + public static final int USER_VERIFY_ALL = 1024; + + private UserVerificationMethods() { + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationRequirement.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationRequirement.java new file mode 100644 index 0000000000000000000000000000000000000000..1bba395c9731c72a8aa263ef4c251c898f280b7b --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UserVerificationRequirement.java @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.fido.fido2.api.common; + +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +public enum UserVerificationRequirement implements Parcelable { + REQUIRED("required"), + PREFERRED("preferred"), + DISCOURAGED("discouraged"); + + private final String value; + + UserVerificationRequirement(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(toString()); + } + + @PublicApi(exclude = true) + public static UserVerificationRequirement fromString(String attachment) throws UnsupportedUserVerificationRequirementException { + for (UserVerificationRequirement value : values()) { + if (value.value.equals(attachment)) return value; + } + throw new UnsupportedUserVerificationRequirementException("User verification requirement " + attachment + " not supported"); + } + + public static Creator CREATOR = new Creator() { + @Override + public UserVerificationRequirement createFromParcel(Parcel source) { + try { + return UserVerificationRequirement.fromString(source.readString()); + } catch (UnsupportedUserVerificationRequirementException e) { + throw new RuntimeException(e); + } + } + + @Override + public UserVerificationRequirement[] newArray(int size) { + return new UserVerificationRequirement[size]; + } + }; + + public static class UnsupportedUserVerificationRequirementException extends Exception { + public UnsupportedUserVerificationRequirementException(String message) { + super(message); + } + } +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntries.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntries.java new file mode 100644 index 0000000000000000000000000000000000000000..5a6d41bf48353191022cede90b6dd3e9782340ec --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntries.java @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +/** + * Represents up to three user verification methods used by the authenticator. + */ +@PublicApi +public class UvmEntries extends AutoSafeParcelable { + @Field(1) + private List uvmEntryList; + + public List getUvmEntryList() { + return uvmEntryList; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UvmEntries)) return false; + + UvmEntries that = (UvmEntries) o; + + if (uvmEntryList == null && that.uvmEntryList == null) return true; + if (uvmEntryList == null || that.uvmEntryList == null) return false; + return uvmEntryList.containsAll(that.uvmEntryList) && that.uvmEntryList.containsAll(uvmEntryList); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{new HashSet<>(uvmEntryList)}); + } + + /** + * Builder for {@link UvmEntries} + */ + public static class Builder { + private List uvmEntryList = new ArrayList<>(); + + /** + * The constructor of {@link UvmEntries.Builder}. + */ + public Builder() { + } + + public Builder addAll(List uvmEntryList) { + if (this.uvmEntryList.size() + uvmEntryList.size() > 3) throw new IllegalStateException(); + this.uvmEntryList.addAll(uvmEntryList); + return this; + } + + public Builder addUvmEntry(UvmEntry uvmEntry) { + if (uvmEntryList.size() >= 3) throw new IllegalStateException(); + uvmEntryList.add(uvmEntry); + return this; + } + + public UvmEntries build() { + UvmEntries uvmEntries = new UvmEntries(); + uvmEntries.uvmEntryList = new ArrayList<>(uvmEntryList); + return uvmEntries; + } + } + + @PublicApi(exclude = true) + public static Creator CREATOR = new AutoCreator<>(UvmEntries.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntry.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..1f8e5d3d26e0478397bba70672e499ba018db5dd --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/fido2/api/common/UvmEntry.java @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.fido2.api.common; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +/** + * Represents a single User Verification Method Entry + */ +@PublicApi +public class UvmEntry extends AutoSafeParcelable { + @Field(1) + private int userVerificationMethod; + @Field(2) + private short keyProtectionType; + @Field(3) + private short matcherProtectionType; + + public int getUserVerificationMethod() { + return userVerificationMethod; + } + + public short getKeyProtectionType() { + return keyProtectionType; + } + + public short getMatcherProtectionType() { + return matcherProtectionType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UvmEntry)) return false; + + UvmEntry uvmEntry = (UvmEntry) o; + + if (userVerificationMethod != uvmEntry.userVerificationMethod) return false; + if (keyProtectionType != uvmEntry.keyProtectionType) return false; + return matcherProtectionType == uvmEntry.matcherProtectionType; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[]{userVerificationMethod, keyProtectionType, matcherProtectionType}); + } + + /** + * Builder for {@link UvmEntry}. + */ + public static class Builder { + private int userVerificationMethod; + private short keyProtectionType; + private short matcherProtectionType; + + public Builder setUserVerificationMethod(int userVerificationMethod) { + this.userVerificationMethod = userVerificationMethod; + return this; + } + + public Builder setKeyProtectionType(short keyProtectionType) { + this.keyProtectionType = keyProtectionType; + return this; + } + + public Builder setMatcherProtectionType(short matcherProtectionType) { + this.matcherProtectionType = matcherProtectionType; + return this; + } + + public UvmEntry build() { + UvmEntry entry = new UvmEntry(); + entry.userVerificationMethod = userVerificationMethod; + entry.keyProtectionType = keyProtectionType; + entry.matcherProtectionType = matcherProtectionType; + return entry; + } + } + + @PublicApi(exclude = true) + public static final Creator CREATOR = new AutoCreator<>(UvmEntry.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceDirectTransferResult.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceDirectTransferResult.java new file mode 100644 index 0000000000000000000000000000000000000000..4ab8c58a457950a42329c5687ae3496439c5a3a5 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceDirectTransferResult.java @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.sourcedevice; + +import android.app.Activity; +import android.content.Intent; + +import com.google.android.gms.common.api.Status; + +import org.microg.gms.common.PublicApi; +import org.microg.safeparcel.AutoSafeParcelable; + +/** + * Result returned from the UI activity in {@link Activity#onActivityResult(int, int, Intent)} after the direct transfer finishes. + */ +@PublicApi +public class SourceDirectTransferResult extends AutoSafeParcelable { + @Field(1) + private Status status; + + private SourceDirectTransferResult() { + } + + public SourceDirectTransferResult(Status status) { + this.status = status; + } + + /** + * Gets the {@link Status} from the returned {@link SourceDirectTransferResult}. + */ + public Status getStatus() { + return status; + } + + public static final Creator CREATOR = new AutoCreator<>(SourceDirectTransferResult.class); +} diff --git a/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceStartDirectTransferOptions.java b/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceStartDirectTransferOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..50aaf81ec30f90bf6c1a7983c723e933fe1c5096 --- /dev/null +++ b/play-services-fido-api/src/main/java/com/google/android/gms/fido/sourcedevice/SourceStartDirectTransferOptions.java @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.android.gms.fido.sourcedevice; + +import org.microg.safeparcel.AutoSafeParcelable; + +/** + * Customized options to start direct transfer. + */ +public class SourceStartDirectTransferOptions extends AutoSafeParcelable { + /** + * Value of the callerType if the caller is unknown. + */ + public static final int CALLER_TYPE_UNKNOWN = 0; + /** + * Value of the callerType if the caller is browser. + */ + public static final int CALLER_TYPE_BROWSER = 2; + + @Field(1) + private int callerType; + + private SourceStartDirectTransferOptions() { + } + + /** + * Constructor for the {@link SourceStartDirectTransferOptions}. + */ + public SourceStartDirectTransferOptions(int callerType) { + this.callerType = callerType; + } + + public static final Creator CREATOR = new AutoCreator<>(SourceStartDirectTransferOptions.class); +} diff --git a/play-services-fido-api/src/main/java/org/microg/gms/fido/api/FidoConstants.java b/play-services-fido-api/src/main/java/org/microg/gms/fido/api/FidoConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..c03055823d237248925b982f9cd1173e15a0c9a5 --- /dev/null +++ b/play-services-fido-api/src/main/java/org/microg/gms/fido/api/FidoConstants.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.api; + +public class FidoConstants { + public static final String FIDO2_KEY_CREDENTIAL_EXTRA = "FIDO2_CREDENTIAL_EXTRA"; + public static final String FIDO2_KEY_ERROR_EXTRA = "FIDO2_ERROR_EXTRA"; + public static final String FIDO2_KEY_RESPONSE_EXTRA = "FIDO2_RESPONSE_EXTRA"; + public static final String KEY_RESPONSE_EXTRA = "RESPONSE_EXTRA"; +} diff --git a/play-services-fido-core/build.gradle b/play-services-fido-core/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..4172f9e3159b815c1cc22177abe8e1065e4369c5 --- /dev/null +++ b/play-services-fido-core/build.gradle @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-fido-api') + + implementation project(':play-services-base-core') + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" + + implementation "androidx.appcompat:appcompat:$appcompatVersion" + implementation "androidx.biometric:biometric:$biometricVersion" + implementation "androidx.core:core-ktx:$coreVersion" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" + + // Navigation + implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" + implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" + + implementation 'com.upokecenter:cbor:4.5.2' +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + buildFeatures { + dataBinding = true + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-fido' diff --git a/play-services-fido-core/src/main/AndroidManifest.xml b/play-services-fido-core/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..9533cd53e5afbaf392dfd266877b53e4ed02d7ce --- /dev/null +++ b/play-services-fido-core/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/Database.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/Database.kt new file mode 100644 index 0000000000000000000000000000000000000000..f276cf4a2cd49a20d49654382c54347cf0cfbd8a --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/Database.kt @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core + +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE +import android.database.sqlite.SQLiteOpenHelper +import androidx.core.database.getLongOrNull + +class Database(context: Context) : SQLiteOpenHelper(context, "fido.db", null, VERSION) { + + fun isPrivileged(packageName: String, signatureDigest: String): Boolean = readableDatabase.use { + it.count(TABLE_PRIVILEGED_APPS, "$COLUMN_PACKAGE_NAME = ? AND $COLUMN_SIGNATURE_DIGEST = ?", packageName, signatureDigest) > 0 + } + + fun insertPrivileged(packageName: String, signatureDigest: String) = writableDatabase.use { + it.insertWithOnConflict(TABLE_PRIVILEGED_APPS, null, ContentValues().apply { + put(COLUMN_PACKAGE_NAME, packageName) + put(COLUMN_SIGNATURE_DIGEST, signatureDigest) + put(COLUMN_TIMESTAMP, System.currentTimeMillis()) + }, CONFLICT_IGNORE) + } + + override fun onCreate(db: SQLiteDatabase) { + onUpgrade(db, 0, VERSION) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + if (oldVersion < 1) { + db.execSQL("CREATE TABLE $TABLE_PRIVILEGED_APPS($COLUMN_PACKAGE_NAME TEXT, $COLUMN_SIGNATURE_DIGEST TEXT, $COLUMN_TIMESTAMP INT, UNIQUE($COLUMN_PACKAGE_NAME, $COLUMN_SIGNATURE_DIGEST) ON CONFLICT REPLACE);") + } + } + + companion object { + const val VERSION = 1 + private const val TABLE_PRIVILEGED_APPS = "privileged_apps" + private const val COLUMN_PACKAGE_NAME = "package_name" + private const val COLUMN_SIGNATURE_DIGEST = "signature_digest" + private const val COLUMN_TIMESTAMP = "timestamp" + } +} + +fun SQLiteDatabase.count(table: String, selection: String? = null, vararg selectionArgs: String) = + if (selection == null) { + rawQuery("SELECT COUNT(*) FROM $table", null) + } else { + rawQuery("SELECT COUNT(*) FROM $table WHERE $selection", selectionArgs) + }.use { + if (it.moveToFirst()) { + it.getLongOrNull(0) ?: 0 + } else { + 0 + } + } diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt new file mode 100644 index 0000000000000000000000000000000000000000..2f27e9bdc1219eadd10e33dd67f87d817f2caaae --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/RequestHandling.kt @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core + +import android.content.Context +import android.util.Base64 +import com.google.android.gms.fido.fido2.api.common.* +import com.google.android.gms.fido.fido2.api.common.ErrorCode.* +import com.upokecenter.cbor.CBORObject +import org.json.JSONObject +import org.microg.gms.fido.core.RequestOptionsType.REGISTER +import org.microg.gms.fido.core.RequestOptionsType.SIGN +import org.microg.gms.utils.getApplicationLabel +import org.microg.gms.utils.getFirstSignatureDigest +import org.microg.gms.utils.toBase64 +import java.security.MessageDigest + +class RequestHandlingException(val errorCode: ErrorCode, message: String? = null) : Exception(message) + +enum class RequestOptionsType { REGISTER, SIGN } + +val RequestOptions.registerOptions: PublicKeyCredentialCreationOptions + get() = when (this) { + is BrowserPublicKeyCredentialCreationOptions -> publicKeyCredentialCreationOptions + is PublicKeyCredentialCreationOptions -> this + else -> throw RequestHandlingException(DATA_ERR, "The request options are not valid") + } + +val RequestOptions.signOptions: PublicKeyCredentialRequestOptions + get() = when (this) { + is BrowserPublicKeyCredentialRequestOptions -> publicKeyCredentialRequestOptions + is PublicKeyCredentialRequestOptions -> this + else -> throw RequestHandlingException(DATA_ERR, "The request options are not valid") + } + +val RequestOptions.type: RequestOptionsType + get() = when (this) { + is PublicKeyCredentialCreationOptions, is BrowserPublicKeyCredentialCreationOptions -> REGISTER + is PublicKeyCredentialRequestOptions, is BrowserPublicKeyCredentialRequestOptions -> RequestOptionsType.SIGN + else -> throw RequestHandlingException(INVALID_STATE_ERR) + } + +val RequestOptions.webAuthnType: String + get() = when (type) { + REGISTER -> "webauthn.create" + SIGN -> "webauthn.get" + } + +val RequestOptions.challenge: ByteArray + get() = when (type) { + REGISTER -> registerOptions.challenge + SIGN -> signOptions.challenge + } + +val RequestOptions.rpId: String + get() = when (type) { + REGISTER -> registerOptions.rp.id + SIGN -> signOptions.rpId + } + +fun RequestOptions.checkIsValid(context: Context) { + if (type == REGISTER) { + if (registerOptions.authenticatorSelection.requireResidentKey == true) { + throw RequestHandlingException( + NOT_SUPPORTED_ERR, + "Resident credentials or empty 'allowCredentials' lists are not supported at this time." + ) + } + } + if (type == SIGN) { + if (signOptions.allowList.isNullOrEmpty()) { + throw RequestHandlingException(NOT_ALLOWED_ERR, "Request doesn't have a valid list of allowed credentials.") + } + } +} + +fun RequestOptions.getWebAuthnClientData(callingPackage: String, origin: String): ByteArray { + val obj = JSONObject() + .put("type", webAuthnType) + .put("challenge", challenge.toBase64(Base64.NO_PADDING, Base64.NO_WRAP, Base64.URL_SAFE)) + .put("androidPackageName", callingPackage) + .put("tokenBinding", tokenBinding?.toJsonObject()) + .put("origin", origin) + return obj.toString().encodeToByteArray() +} + +fun getApplicationName(context: Context, options: RequestOptions, callingPackage: String): String = when (options) { + is BrowserPublicKeyCredentialCreationOptions, is BrowserPublicKeyCredentialRequestOptions -> options.rpId + else -> context.packageManager.getApplicationLabel(callingPackage).toString() +} + +fun getApkHashOrigin(context: Context, packageName: String): String { + val digest = context.packageManager.getFirstSignatureDigest(packageName, "SHA-256") + ?: throw RequestHandlingException(NOT_ALLOWED_ERR, "Unknown package $packageName") + return "android:apk-key-hash:${digest.toBase64(Base64.NO_PADDING, Base64.NO_WRAP, Base64.URL_SAFE)}" +} + +fun getOrigin(context: Context, options: RequestOptions, callingPackage: String): String = when { + options is BrowserRequestOptions -> { + if (options.origin.scheme == null || options.origin.authority == null) { + throw RequestHandlingException(NOT_ALLOWED_ERR, "Bad url ${options.origin}") + } + "${options.origin.scheme}://${options.origin.authority}" + } + else -> getApkHashOrigin(context, callingPackage) +} + +fun ByteArray.digest(md: String): ByteArray = MessageDigest.getInstance(md).digest(this) + +fun getClientDataAndHash( + context: Context, + options: RequestOptions, + callingPackage: String +): Pair { + val clientData: ByteArray? + var clientDataHash = (options as? BrowserPublicKeyCredentialCreationOptions)?.clientDataHash + if (clientDataHash == null) { + clientData = options.getWebAuthnClientData(callingPackage, getOrigin(context, options, callingPackage)) + clientDataHash = clientData.digest("SHA-256") + } else { + clientData = "".toByteArray() + } + return clientData to clientDataHash +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/privileged/Fido2PrivilegedService.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/privileged/Fido2PrivilegedService.kt new file mode 100644 index 0000000000000000000000000000000000000000..eeb3e049d8874bf53eaabdda608bcfd91ac1bf8e --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/privileged/Fido2PrivilegedService.kt @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.privileged + +import android.app.KeyguardManager +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_IMMUTABLE +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import android.content.Context +import android.content.Context.KEYGUARD_SERVICE +import android.content.Intent +import android.os.Build +import android.os.Parcel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.fido.fido2.api.IBooleanCallback +import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialCreationOptions +import com.google.android.gms.fido.fido2.api.common.BrowserPublicKeyCredentialRequestOptions +import com.google.android.gms.fido.fido2.internal.privileged.IFido2PrivilegedCallbacks +import com.google.android.gms.fido.fido2.internal.privileged.IFido2PrivilegedService +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.common.GmsService.FIDO2_PRIVILEGED +import org.microg.gms.fido.core.ui.AuthenticatorActivity +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.SOURCE_BROWSER +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_SOURCE +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_OPTIONS +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_SERVICE +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_TYPE +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.TYPE_REGISTER +import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.TYPE_SIGN +import org.microg.gms.utils.warnOnTransactionIssues + +const val TAG = "Fido2Privileged" + +class Fido2PrivilegedService : BaseService(TAG, FIDO2_PRIVILEGED) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + callback.onPostInitComplete( + CommonStatusCodes.SUCCESS, + Fido2PrivilegedServiceImpl(this, lifecycle).asBinder(), + null + ); + } +} + +class Fido2PrivilegedServiceImpl(private val context: Context, private val lifecycle: Lifecycle) : + IFido2PrivilegedService.Stub(), LifecycleOwner { + override fun register(callbacks: IFido2PrivilegedCallbacks, options: BrowserPublicKeyCredentialCreationOptions) { + lifecycleScope.launchWhenStarted { + val intent = Intent(context, AuthenticatorActivity::class.java) + .putExtra(KEY_SERVICE, FIDO2_PRIVILEGED.SERVICE_ID) + .putExtra(KEY_SOURCE, SOURCE_BROWSER) + .putExtra(KEY_TYPE, TYPE_REGISTER) + .putExtra(KEY_OPTIONS, options.serializeToBytes()) + + val pendingIntent = + PendingIntent.getActivity(context, options.hashCode(), intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) + callbacks.onPendingIntent(Status.SUCCESS, pendingIntent) + } + } + + override fun sign(callbacks: IFido2PrivilegedCallbacks, options: BrowserPublicKeyCredentialRequestOptions) { + lifecycleScope.launchWhenStarted { + val intent = Intent(context, AuthenticatorActivity::class.java) + .putExtra(KEY_SERVICE, FIDO2_PRIVILEGED.SERVICE_ID) + .putExtra(KEY_SOURCE, SOURCE_BROWSER) + .putExtra(KEY_TYPE, TYPE_SIGN) + .putExtra(KEY_OPTIONS, options.serializeToBytes()) + + val pendingIntent = + PendingIntent.getActivity(context, options.hashCode(), intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) + callbacks.onPendingIntent(Status.SUCCESS, pendingIntent) + } + } + + override fun isUserVerifyingPlatformAuthenticatorAvailable(callbacks: IBooleanCallback) { + lifecycleScope.launchWhenStarted { + if (Build.VERSION.SDK_INT < 24) { + callbacks.onBoolean(false) + } else { + val keyguardManager = context.getSystemService(KEYGUARD_SERVICE) as? KeyguardManager? + callbacks.onBoolean(keyguardManager?.isDeviceSecure == true) + } + } + } + + 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) } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AndroidSafetyNetAttestationObject.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AndroidSafetyNetAttestationObject.kt new file mode 100644 index 0000000000000000000000000000000000000000..5e612a6479d7571b1593831e0db269f98e7b47c7 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AndroidSafetyNetAttestationObject.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol + +import com.upokecenter.cbor.CBORObject + +class AndroidSafetyNetAttestationObject(authData: AuthenticatorData, val ver: String, val response: ByteArray) : + AttestationObject(authData) { + override val fmt: String + get() = "android-safetynet" + override val attStmt: CBORObject + get() = CBORObject.NewMap().apply { + set("ver", ver.encodeAsCbor()) + set("response", response.encodeAsCbor()) + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AttestationObject.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AttestationObject.kt new file mode 100644 index 0000000000000000000000000000000000000000..043521e99bd66aa16aaee3be3a7ab875500125af --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AttestationObject.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol + +import com.upokecenter.cbor.CBORObject + +abstract class AttestationObject(val authData: AuthenticatorData) { + abstract val fmt: String + abstract val attStmt: CBORObject + + fun encode(): ByteArray = CBORObject.NewMap().apply { + set("fmt", fmt.encodeAsCbor()) + set("attStmt", attStmt) + set("authData", authData.toCBOR()) + }.EncodeToBytes() +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AttestedCredentialData.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AttestedCredentialData.kt new file mode 100644 index 0000000000000000000000000000000000000000..54e553e911eb0b88203ad56a5ac06d8e0210309d --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AttestedCredentialData.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol + +import java.nio.ByteBuffer +import java.nio.ByteOrder + +class AttestedCredentialData(val aaguid: ByteArray, val id: ByteArray, val publicKey: ByteArray) { + fun encode() = ByteBuffer.allocate(aaguid.size + 2 + id.size + publicKey.size) + .put(aaguid) + .order(ByteOrder.BIG_ENDIAN).putShort(id.size.toShort()) + .put(id) + .put(publicKey) + .array() +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AuthenticatorData.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AuthenticatorData.kt new file mode 100644 index 0000000000000000000000000000000000000000..a8b75f0785a2edd623fd6bb2672d671098e9d457 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/AuthenticatorData.kt @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol + +import com.upokecenter.cbor.CBORObject +import java.nio.ByteBuffer +import java.nio.ByteOrder +import kotlin.experimental.or + +class AuthenticatorData( + val rpIdHash: ByteArray, + val userPresent: Boolean, + val userVerified: Boolean, + val signCount: Int, + val attestedCredentialData: AttestedCredentialData? = null, + val extensions: ByteArray? = null +) { + fun encode(): ByteArray { + val attestedCredentialData = attestedCredentialData?.encode() ?: ByteArray(0) + val extensions = extensions ?: ByteArray(0) + return ByteBuffer.allocate(rpIdHash.size + 5 + attestedCredentialData.size + extensions.size) + .put(rpIdHash) + .put(buildFlags(userPresent, userVerified, attestedCredentialData.isNotEmpty(), extensions.isNotEmpty())) + .order(ByteOrder.BIG_ENDIAN).putInt(signCount) + .put(attestedCredentialData) + .put(extensions) + .array() + } + + fun toCBOR(): CBORObject = encode().encodeAsCbor() + + companion object { + /** User Present **/ + private const val FLAG_UP: Byte = 1 + + /** User Verified **/ + private const val FLAG_UV: Byte = 4 + + /** Attested credential data included **/ + private const val FLAG_AT: Byte = 64 + + /** Extension data included **/ + private const val FLAG_ED: Byte = -128 + + private fun buildFlags(up: Boolean, uv: Boolean, at: Boolean, ed: Boolean): Byte = + (if (up) FLAG_UP else 0) or (if (uv) FLAG_UV else 0) or (if (at) FLAG_AT else 0) or (if (ed) FLAG_ED else 0) + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/Cbor.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/Cbor.kt new file mode 100644 index 0000000000000000000000000000000000000000..0aaefec68a6ad9d9042c025d61ad630a44fa096c --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/Cbor.kt @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.fido.core.protocol + +import com.google.android.gms.fido.common.Transport +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialParameters +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRpEntity +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialUserEntity +import com.upokecenter.cbor.CBORObject + +fun CBORObject.AsStringSequence(): Iterable = Iterable { + object : Iterator { + var index = 0 + override fun hasNext(): Boolean = size() + 1 < index + override fun next(): String = get(index++).AsString() + } +} + +fun CBORObject.AsInt32Sequence(): Iterable = Iterable { + object : Iterator { + var index = 0 + override fun hasNext(): Boolean = size() + 1 < index + override fun next(): Int = get(index++).AsInt32() + } +} + +fun String.encodeAsCbor() = CBORObject.FromObject(this) +fun ByteArray.encodeAsCbor() = CBORObject.FromObject(this) +fun Int.encodeAsCbor() = CBORObject.FromObject(this) +fun Boolean.encodeAsCbor() = CBORObject.FromObject(this) + +fun PublicKeyCredentialRpEntity.encodeAsCbor() = CBORObject.NewMap().apply { + set("id", id.encodeAsCbor()) + if (name != null) set("name", name.encodeAsCbor()) + if (icon != null) set("icon", icon.encodeAsCbor()) +} + +fun PublicKeyCredentialUserEntity.encodeAsCbor() = CBORObject.NewMap().apply { + set("id", id.encodeAsCbor()) + if (name != null) set("name", name.encodeAsCbor()) + if (icon != null) set("icon", icon.encodeAsCbor()) + if (displayName != null) set("displayName", displayName.encodeAsCbor()) +} + +fun CBORObject.decodeAsPublicKeyCredentialUserEntity() = PublicKeyCredentialUserEntity( + get("id")?.GetByteString(), + get("name")?.AsString(), + get("icon")?.AsString(), + get("displayName")?.AsString() +) + +fun PublicKeyCredentialParameters.encodeAsCbor() = CBORObject.NewMap().apply { + set("alg", algorithmIdAsInteger.encodeAsCbor()) + set("type", typeAsString.encodeAsCbor()) +} + +fun PublicKeyCredentialDescriptor.encodeAsCbor() = CBORObject.NewMap().apply { + set("type", typeAsString.encodeAsCbor()) + set("id", id.encodeAsCbor()) + set("transports", transports.encodeAsCbor { it.toString().encodeAsCbor() }) +} + +fun CBORObject.decodeAsPublicKeyCredentialDescriptor() = PublicKeyCredentialDescriptor( + get("type")?.AsString(), + get("id")?.GetByteString(), + get("transports")?.AsStringSequence()?.map { Transport.fromString(it) } +) + +fun List.encodeAsCbor(f: (T) -> CBORObject) = CBORObject.NewArray().apply { this@encodeAsCbor.forEachIndexed { index, t -> set(index, f(t)) } } +fun Map.encodeAsCbor(f: (T) -> CBORObject) = CBORObject.NewMap().apply { + for (entry in this@encodeAsCbor) { + set(entry.key, f(entry.value)) + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/CoseKey.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/CoseKey.kt new file mode 100644 index 0000000000000000000000000000000000000000..89225e080e0b8c3a4a2ebbc99fc8fb4c0373d2e4 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/CoseKey.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol + +import com.google.android.gms.fido.fido2.api.common.Algorithm +import com.upokecenter.cbor.CBORObject +import java.math.BigInteger + +class CoseKey( + val algorithm: Algorithm, + val x: BigInteger, + val y: BigInteger, + val curveId: Int, + val curvePointSize: Int +) { + fun encode(): ByteArray = CBORObject.NewMap().apply { + set(1, 2.encodeAsCbor()) + set(3, algorithm.algoValue.encodeAsCbor()) + set(-1, curveId.encodeAsCbor()) + set(-2, x.toByteArray(curvePointSize).encodeAsCbor()) + set(-3, y.toByteArray(curvePointSize).encodeAsCbor()) + }.EncodeToBytes() + + companion object { + fun BigInteger.toByteArray(size: Int): ByteArray { + val res = ByteArray(size) + val orig = toByteArray() + if (orig.size > size) { + System.arraycopy(orig, orig.size - size, res, 0, size) + } else { + System.arraycopy(orig, 0, res, size - orig.size, orig.size) + } + return res + } + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/CredentialId.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/CredentialId.kt new file mode 100644 index 0000000000000000000000000000000000000000..445dab191a52a9e6e5facfd21e3bf3b8c1a38dc2 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/CredentialId.kt @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol + +import org.microg.gms.fido.core.digest +import java.nio.ByteBuffer +import java.security.PublicKey + +class CredentialId(val type: Byte, val data: ByteArray, val rpId: String, val publicKey: PublicKey) { + fun encode(): ByteArray = ByteBuffer.allocate(1 + data.size + 32).apply { + put(type) + put(data) + put((rpId.toByteArray() + publicKey.encoded).digest("SHA-256")) + }.array() + + companion object { + fun decodeTypeAndData(bytes: ByteArray): Pair { + val buffer = ByteBuffer.wrap(bytes) + val type = buffer.get() + val data = ByteArray(32) + buffer.get(data) + return type to data + } + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/NoneAttestationObject.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/NoneAttestationObject.kt new file mode 100644 index 0000000000000000000000000000000000000000..c29f3e724c4420a9d64ff14e01e417d151ad3812 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/NoneAttestationObject.kt @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol + +import com.upokecenter.cbor.CBORObject + +class NoneAttestationObject(authData: AuthenticatorData) : AttestationObject(authData) { + override val fmt: String + get() = "none" + override val attStmt: CBORObject + get() = CBORObject.NewMap() +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorGetAssertion.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorGetAssertion.kt new file mode 100644 index 0000000000000000000000000000000000000000..dafc37f37c317ffc078ccf8b4cbe6ba5f8ad2e1f --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorGetAssertion.kt @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol.msgs + +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialUserEntity +import com.upokecenter.cbor.CBORObject +import org.microg.gms.fido.core.protocol.decodeAsPublicKeyCredentialDescriptor +import org.microg.gms.fido.core.protocol.decodeAsPublicKeyCredentialUserEntity +import org.microg.gms.fido.core.protocol.encodeAsCbor + +class AuthenticatorGetAssertionCommand(request: AuthenticatorGetAssertionRequest) : + Ctap2Command(request) { + override fun decodeResponse(obj: CBORObject) = AuthenticatorGetAssertionResponse.decodeFromCbor(obj) +} + +class AuthenticatorGetAssertionRequest( + val rpId: String, + val clientDataHash: ByteArray, + val allowList: List = emptyList(), + val extensions: Map = emptyMap(), + val options: Options? = null, + val pinAuth: ByteArray? = null, + val pinProtocol: Int? = null +) : Ctap2Request(0x03) { + companion object { + class Options( + val userPresence: Boolean = true, + val userVerification: Boolean = false + ) { + fun encodeAsCBOR(): CBORObject = CBORObject.NewMap().apply { + set("up", userPresence.encodeAsCbor()) + set("uv", userVerification.encodeAsCbor()) + } + } + } + + fun encodeAsCBOR() = CBORObject.NewMap().apply { + set(0x01, rpId.encodeAsCbor()) + set(0x02, clientDataHash.encodeAsCbor()) + if (allowList.isNotEmpty()) set(0x03, allowList.encodeAsCbor { it.encodeAsCbor() }) + if (extensions.isNotEmpty()) set(0x04, extensions.encodeAsCbor { it }) + if (options != null) set(0x05, options.encodeAsCBOR()) + if (pinAuth != null) set(0x06, pinAuth.encodeAsCbor()) + if (pinProtocol != null) set(0x07, pinProtocol.encodeAsCbor()) + } +} + +class AuthenticatorGetAssertionResponse( + val credential: PublicKeyCredentialDescriptor?, + val authData: ByteArray, + val signature: ByteArray, + val user: PublicKeyCredentialUserEntity?, + val numberOfCredentials: Int? +) : Ctap2Response { + + companion object { + fun decodeFromCbor(obj: CBORObject) = AuthenticatorGetAssertionResponse( + credential = obj.get(0x01)?.decodeAsPublicKeyCredentialDescriptor(), + authData = obj.get(0x02).GetByteString(), + signature = obj.get(0x03).GetByteString(), + user = obj.get(0x04)?.decodeAsPublicKeyCredentialUserEntity(), + numberOfCredentials = obj.get(0x05)?.AsInt32Value() + ) + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorGetInfo.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorGetInfo.kt new file mode 100644 index 0000000000000000000000000000000000000000..4c78ae1b9ecf177ce08e208561079cb199f9066b --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorGetInfo.kt @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol.msgs + +import com.upokecenter.cbor.CBORObject +import org.microg.gms.fido.core.protocol.AsInt32Sequence +import org.microg.gms.fido.core.protocol.AsStringSequence + +class AuthenticatorGetInfoCommand : Ctap2Command(AuthenticatorGetInfoRequest()) { + override fun decodeResponse(obj: CBORObject) = AuthenticatorGetInfoResponse.decodeFromCbor(obj) +} + +class AuthenticatorGetInfoRequest : Ctap2Request(0x04) + +class AuthenticatorGetInfoResponse( + val versions: List, + val extensions: List, + val aaguid: ByteArray, + val options: Options, + val maxMsgSize: Int?, + val pinProtocols: List +) : Ctap2Response { + + companion object { + class Options( + val platformDevice: Boolean, + val residentKey: Boolean, + val clientPin: Boolean?, + val userPresence: Boolean, + val userVerification: Boolean?, + ) { + companion object { + fun decodeFromCbor(map: CBORObject?) = Options( + platformDevice = map?.get("plat")?.AsBoolean() == true, + residentKey = map?.get("rk")?.AsBoolean() == true, + clientPin = map?.get("clientPin")?.AsBoolean(), + userPresence = map?.get("up")?.AsBoolean() != false, + userVerification = map?.get("uv")?.AsBoolean() + ) + } + + override fun toString(): String { + return "Options(platformDevice=$platformDevice, residentKey=$residentKey, clientPin=$clientPin, userPresence=$userPresence, userVerification=$userVerification)" + } + } + + fun decodeFromCbor(obj: CBORObject) = AuthenticatorGetInfoResponse( + versions = obj.get(1)?.AsStringSequence()?.toList().orEmpty(), + extensions = obj.get(2)?.AsStringSequence()?.toList().orEmpty(), + aaguid = obj.get(3)?.GetByteString() + ?: throw IllegalArgumentException("Not a valid response for authenticatorGetInfo"), + options = Options.decodeFromCbor(obj.get(4)), + maxMsgSize = obj.get(5)?.AsInt32Value(), + pinProtocols = obj.get(6)?.AsInt32Sequence()?.toList().orEmpty() + ) + } + + override fun toString(): String { + return "AuthenticatorGetInfoResponse(versions=$versions, extensions=$extensions, aaguid=${aaguid.contentToString()}, options=$options, maxMsgSize=$maxMsgSize, pinProtocols=$pinProtocols)" + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorMakeCredential.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorMakeCredential.kt new file mode 100644 index 0000000000000000000000000000000000000000..ab0e6462bb424f223699863ba374f350b5d132d3 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/AuthenticatorMakeCredential.kt @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol.msgs + +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialDescriptor +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialParameters +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialRpEntity +import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialUserEntity +import com.upokecenter.cbor.CBORObject +import org.microg.gms.fido.core.protocol.encodeAsCbor + +class AuthenticatorMakeCredentialCommand(request: AuthenticatorMakeCredentialRequest) : + Ctap2Command(request) { + override fun decodeResponse(obj: CBORObject) = AuthenticatorMakeCredentialResponse.decodeFromCbor(obj) +} + +class AuthenticatorMakeCredentialRequest( + val clientDataHash: ByteArray, + val rp: PublicKeyCredentialRpEntity, + val user: PublicKeyCredentialUserEntity, + val pubKeyCredParams: List, + val excludeList: List = emptyList(), + val extensions: Map = emptyMap(), + val options: Options? = null, + val pinAuth: ByteArray? = null, + val pinProtocol: Int? = null +) : Ctap2Request(0x01) { + companion object { + class Options( + val residentKey: Boolean = false, + val userVerification: Boolean = false + ) { + fun encodeAsCBOR() = CBORObject.NewMap().apply { + set("rk", residentKey.encodeAsCbor()) + set("uv", residentKey.encodeAsCbor()) + } + } + } + + fun encodeAsCBOR() = CBORObject.NewMap().apply { + set(0x01, clientDataHash.encodeAsCbor()) + set(0x02, rp.encodeAsCbor()) + set(0x03, user.encodeAsCbor()) + set(0x04, pubKeyCredParams.encodeAsCbor { it.encodeAsCbor() }) + if (excludeList.isNotEmpty()) set(0x05, excludeList.encodeAsCbor { it.encodeAsCbor() }) + if (extensions.isNotEmpty()) set(0x06, extensions.encodeAsCbor { it }) + if (options != null) set(0x07, options.encodeAsCBOR()) + if (pinAuth != null) set(0x08, pinAuth.encodeAsCbor()) + if (pinProtocol != null) set(0x09, pinProtocol.encodeAsCbor()) + } +} + +class AuthenticatorMakeCredentialResponse( + val authData: ByteArray, + val fmt: String, + val attStmt: ByteArray +) : Ctap2Response { + companion object { + fun decodeFromCbor(obj: CBORObject) = AuthenticatorMakeCredentialResponse( + authData = obj.get(0x01).GetByteString(), + fmt = obj.get(0x02).AsString(), + attStmt = obj.get(0x03).GetByteString() + ) + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/Ctap1Command.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/Ctap1Command.kt new file mode 100644 index 0000000000000000000000000000000000000000..73db9e7744054cd1888d197de151536a737c44cf --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/Ctap1Command.kt @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol.msgs + +abstract class Ctap1Command(val request: Q) { + val commandByte: Byte + get() = request.commandByte +} + +abstract class Ctap1Request( + val commandByte: Byte, + val parameter1: Byte = 0, + val parameter2: Byte = 0, + val data: ByteArray +) { + val payload = byteArrayOf( + 0, + commandByte, + parameter1, + parameter2, + (data.size shr 16).toByte(), + (data.size shr 8).toByte(), + data.size.toByte() + ) + data +} + +abstract class Ctap1Response(val statusCode: Byte) { + open fun encode(): ByteArray = throw UnsupportedOperationException() +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/Ctap2Command.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/Ctap2Command.kt new file mode 100644 index 0000000000000000000000000000000000000000..43bd82912d1f9303b5047e29ea90702b6f83adcb --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/Ctap2Command.kt @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol.msgs + +import com.upokecenter.cbor.CBORObject +import java.io.ByteArrayInputStream +import java.io.InputStream + +abstract class Ctap2Command(val request: Q) { + val hasParameters: Boolean + get() = request.parameters != null + fun decodeResponse(bytes: ByteArray, offset: Int): R = + decodeResponse(ByteArrayInputStream(bytes, offset, bytes.size - offset)) + open fun decodeResponse(i: InputStream) = decodeResponse(CBORObject.Read(i)) + abstract fun decodeResponse(obj: CBORObject): R +} + +interface Ctap2Response + +abstract class Ctap2Request(val commandByte: Byte, val parameters: CBORObject? = null) { + val payload: ByteArray = parameters?.EncodeToBytes() ?: ByteArray(0) +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/U2fRegistration.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/U2fRegistration.kt new file mode 100644 index 0000000000000000000000000000000000000000..a9e638bab6026508be1ad9cbc87c1ed6ef5c5895 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/protocol/msgs/U2fRegistration.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.protocol.msgs + +class U2fRegistrationCommand(request: U2fRegistrationRequest) : + Ctap1Command(request) + +class U2fRegistrationRequest(val challenge: ByteArray, val application: ByteArray) : Ctap1Request(0x01, data = challenge + application) + +class U2fRegistrationResponse( + val userPublicKey: ByteArray, + val keyHandle: ByteArray, + val attestationCertificate: ByteArray, + val signature: ByteArray +) : Ctap1Response(0x01) diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/Transport.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/Transport.kt new file mode 100644 index 0000000000000000000000000000000000000000..8cd45f11a2f4b30598b075383d47b427e88fcc65 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/Transport.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport + +enum class Transport { + BLUETOOTH, + NFC, + USB, + SCREEN_LOCK +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..08d02ed80fbed141ade255168d4642acd0976fc7 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport + +import com.google.android.gms.fido.fido2.api.common.AuthenticatorResponse +import com.google.android.gms.fido.fido2.api.common.ErrorCode +import com.google.android.gms.fido.fido2.api.common.RequestOptions +import org.microg.gms.fido.core.RequestHandlingException + +abstract class TransportHandler(val transport: Transport) { + open val isSupported: Boolean + get() = false + open suspend fun start(options: RequestOptions, callerPackage: String): AuthenticatorResponse = + throw RequestHandlingException(ErrorCode.NOT_SUPPORTED_ERR) + open fun shouldBeUsedInstantly(options: RequestOptions): Boolean = false +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/bluetooth/BluetoothTransportHandler.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/bluetooth/BluetoothTransportHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..70205084b63c3c7977947126090238a4062c7d92 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/bluetooth/BluetoothTransportHandler.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.bluetooth + +import android.bluetooth.BluetoothManager +import android.content.Context +import android.os.Build +import androidx.core.content.getSystemService +import org.microg.gms.fido.core.transport.Transport +import org.microg.gms.fido.core.transport.TransportHandler + +class BluetoothTransportHandler(private val context: Context) : TransportHandler(Transport.BLUETOOTH) { + override val isSupported: Boolean + get() = Build.VERSION.SDK_INT >= 18 && context.getSystemService()?.adapter != null +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/nfc/NfcTransportHandler.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/nfc/NfcTransportHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..021b393e761c49daed6b886b294ae836589aabf5 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/nfc/NfcTransportHandler.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.nfc + +import android.content.Context +import android.nfc.NfcAdapter +import org.microg.gms.fido.core.transport.Transport +import org.microg.gms.fido.core.transport.TransportHandler + +class NfcTransportHandler(private val context: Context) : TransportHandler(Transport.NFC) { + override val isSupported: Boolean + get() = NfcAdapter.getDefaultAdapter(context) != null +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockCredentialStore.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockCredentialStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..81f69b95e62ae93159e25aedbae8c12ea01ef3d8 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockCredentialStore.kt @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.screenlock + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import android.util.Log +import androidx.annotation.RequiresApi +import org.microg.gms.utils.toBase64 +import java.security.* +import java.security.spec.ECGenParameterSpec +import kotlin.random.Random + +class ScreenLockCredentialStore(val context: Context) { + private val keyStore by lazy { KeyStore.getInstance("AndroidKeyStore").apply { load(null) } } + + private fun getAlias(rpId: String, keyId: ByteArray): String = + "1." + keyId.toBase64(Base64.NO_PADDING, Base64.NO_WRAP) + "." + rpId + + private fun getPrivateKey(rpId: String, keyId: ByteArray) = keyStore.getKey(getAlias(rpId, keyId), null) as? PrivateKey + + @RequiresApi(23) + fun createKey(rpId: String): ByteArray { + val keyId = Random.nextBytes(32) + val identifier = getAlias(rpId, keyId) + Log.d(TAG, "Creating key for $identifier") + val generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore") + generator.initialize( + KeyGenParameterSpec.Builder(identifier, KeyProperties.PURPOSE_SIGN) + .setDigests(KeyProperties.DIGEST_SHA256) + .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) + .setUserAuthenticationRequired(true) + .build() + ) + generator.generateKeyPair() + return keyId + } + + fun getPublicKey(rpId: String, keyId: ByteArray): PublicKey? = + keyStore.getCertificate(getAlias(rpId, keyId))?.publicKey + + fun getSignature(rpId: String, keyId: ByteArray): Signature? { + val privateKey = getPrivateKey(rpId, keyId) ?: return null + val signature = Signature.getInstance("SHA256withECDSA") + signature.initSign(privateKey) + return signature + } + + fun containsKey(rpId: String, keyId: ByteArray): Boolean = keyStore.containsAlias(getAlias(rpId, keyId)) + + companion object { + const val TAG = "FidoLockStore" + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockTransportHandler.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockTransportHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..75993a9dd2fe2c2e6618d8b0ac72ce93f6b0e12a --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockTransportHandler.kt @@ -0,0 +1,211 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.screenlock + +import android.app.KeyguardManager +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricPrompt +import androidx.core.content.getSystemService +import androidx.fragment.app.FragmentActivity +import com.google.android.gms.fido.fido2.api.common.* +import kotlinx.coroutines.suspendCancellableCoroutine +import org.microg.gms.fido.core.* +import org.microg.gms.fido.core.protocol.* +import org.microg.gms.fido.core.transport.Transport +import org.microg.gms.fido.core.transport.TransportHandler +import java.security.Signature +import java.security.interfaces.ECPublicKey +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +class ScreenLockTransportHandler(private val activity: FragmentActivity): TransportHandler(Transport.SCREEN_LOCK) { + private val store by lazy { ScreenLockCredentialStore(activity) } + + override val isSupported: Boolean + get() = Build.VERSION.SDK_INT >= 23 && activity.getSystemService()?.isDeviceSecure == true + + suspend fun showBiometricPrompt(applicationName: String, signature: Signature?) { + suspendCancellableCoroutine { continuation -> + val prompt = BiometricPrompt(activity, object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + continuation.resume(result) + } + + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + val errorMessage = when (errorCode) { + BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED, BiometricPrompt.ERROR_NEGATIVE_BUTTON -> "User canceled verification" + else -> errString.toString() + } + continuation.resumeWithException(RequestHandlingException(ErrorCode.NOT_ALLOWED_ERR, errorMessage)) + } + }) + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle(activity.getString(R.string.fido_biometric_prompt_title)) + .setDescription( + activity.getString( + R.string.fido_biometric_prompt_body, + applicationName + ) + ) + .setNegativeButtonText(activity.getString(android.R.string.cancel)) + .build() + if (signature != null) { + prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(signature)) + } else { + prompt.authenticate(promptInfo) + } + continuation.invokeOnCancellation { prompt.cancelAuthentication() } + } + } + + suspend fun getActiveSignature( + options: RequestOptions, + callingPackage: String, + keyId: ByteArray + ): Signature { + val signature = + store.getSignature(options.rpId, keyId) ?: throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR) + showBiometricPrompt(getApplicationName(activity, options, callingPackage), signature) + return signature + } + + fun getCredentialData(credentialId: CredentialId, coseKey: CoseKey) = AttestedCredentialData( + ByteArray(16), // 0xb93fd961f2e6462fb12282002247de78 for SafetyNet + credentialId.encode(), + coseKey.encode() + ) + + fun getAuthenticatorData( + rpId: String, + credentialData: AttestedCredentialData, + userPresent: Boolean = true, + userVerified: Boolean = true, + signCount: Int = 0 + ) = AuthenticatorData( + rpId.toByteArray().digest("SHA-256"), + userPresent = userPresent, + userVerified = userVerified, + signCount = signCount, + attestedCredentialData = credentialData + ) + + @RequiresApi(23) + suspend fun register( + options: RequestOptions, + callerPackage: String + ): AuthenticatorAttestationResponse { + if (options.type != RequestOptionsType.REGISTER) throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR) + for (descriptor in options.registerOptions.excludeList.orEmpty()) { + if (store.containsKey(options.rpId, descriptor.id)) { + throw RequestHandlingException( + ErrorCode.NOT_ALLOWED_ERR, + "An excluded credential has already been registered with the device" + ) + } + } + val (clientData, clientDataHash) = getClientDataAndHash(activity, options, callerPackage) + if (options.registerOptions.attestationConveyancePreference in setOf( + AttestationConveyancePreference.NONE, + null + ) + ) { + // No attestation needed + } else { + // TODO: SafetyNet + throw RequestHandlingException(ErrorCode.NOT_SUPPORTED_ERR, "SafetyNet Attestation not yet supported") + } + val keyId = store.createKey(options.rpId) + val publicKey = + store.getPublicKey(options.rpId, keyId) ?: throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR) + + // We're ignoring the signature object as we don't need it for registration + getActiveSignature(options, callerPackage, keyId) + + val (x, y) = (publicKey as ECPublicKey).w.let { it.affineX to it.affineY } + val coseKey = CoseKey(EC2Algorithm.ES256, x, y, 1, 32) + val credentialId = CredentialId(1, keyId, options.rpId, publicKey) + + val credentialData = getCredentialData(credentialId, coseKey) + val authenticatorData = getAuthenticatorData(options.rpId, credentialData) + + return AuthenticatorAttestationResponse( + credentialId.encode(), + clientData, + NoneAttestationObject(authenticatorData).encode() + ) + } + + suspend fun sign( + options: RequestOptions, + callerPackage: String + ): AuthenticatorAssertionResponse { + if (options.type != RequestOptionsType.SIGN) throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR) + val candidates = mutableListOf() + for (descriptor in options.signOptions.allowList) { + try { + val (type, data) = CredentialId.decodeTypeAndData(descriptor.id) + if (type == 1.toByte() && store.containsKey(options.rpId, data)) { + candidates.add(CredentialId(type, data, options.rpId, store.getPublicKey(options.rpId, data)!!)) + } + } catch (e: Exception) { + // Not in store or unknown id + } + } + if (candidates.isEmpty()) { + // Show a biometric prompt even if no matching key to effectively rate-limit + showBiometricPrompt(getApplicationName(activity, options, callerPackage), null) + throw RequestHandlingException( + ErrorCode.NOT_ALLOWED_ERR, + "Cannot find credential in local KeyStore or database" + ) + } + + val (clientData, clientDataHash) = getClientDataAndHash(activity, options, callerPackage) + + val credentialId = candidates.first() + val keyId = credentialId.data + + val (x, y) = (credentialId.publicKey as ECPublicKey).w.let { it.affineX to it.affineY } + val coseKey = CoseKey(EC2Algorithm.ES256, x, y, 1, 32) + + val credentialData = getCredentialData(credentialId, coseKey) + val authenticatorData = getAuthenticatorData(options.rpId, credentialData) + + val signature = getActiveSignature(options, callerPackage, keyId) + signature.update(authenticatorData.encode() + clientDataHash) + val sig = signature.sign() + + return AuthenticatorAssertionResponse( + credentialId.encode(), + clientData, + authenticatorData.encode(), + sig, + null + ) + } + + @RequiresApi(23) + override suspend fun start(options: RequestOptions, callerPackage: String): AuthenticatorResponse = when (options.type) { + RequestOptionsType.REGISTER -> register(options, callerPackage) + RequestOptionsType.SIGN -> sign(options, callerPackage) + } + + override fun shouldBeUsedInstantly(options: RequestOptions): Boolean { + if (options.type != RequestOptionsType.SIGN) return false + for (descriptor in options.signOptions.allowList) { + try { + val (type, data) = CredentialId.decodeTypeAndData(descriptor.id) + if (type == 1.toByte() && store.containsKey(options.rpId, data)) { + return true + } + } catch (e: Exception) { + // Ignore + } + } + return false + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbDevicePermissionManager.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbDevicePermissionManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..29d3b20ae9988839710f20ad0b562c79730b5614 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbDevicePermissionManager.kt @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.usb + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbManager +import kotlinx.coroutines.CompletableDeferred + +private val Context.usbPermissionCallbackAction + get() = "$packageName.USB_PERMISSION_CALLBACK" + +private val receiver = object : BroadcastReceiver() { + private var registered = false + private val pendingRequests = hashMapOf>>() + + fun register(context: Context) = synchronized(this) { + if (!registered) { + context.registerReceiver(this, IntentFilter(context.usbPermissionCallbackAction)) + registered = true + } + } + + fun addDeferred(device: UsbDevice, deferred: CompletableDeferred) = synchronized(this) { + if (pendingRequests.containsKey(device)) { + pendingRequests[device]!!.add(deferred) + false + } else { + pendingRequests[device] = arrayListOf(deferred) + true + } + } + + fun unregister(context: Context) = synchronized(this) { + if (registered) { + context.unregisterReceiver(this) + registered = false + } + } + + override fun onReceive(context: Context?, intent: Intent?) { + if (intent == null || context == null) return + if (intent.action == context.usbPermissionCallbackAction) { + val device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) + if (device != null) { + synchronized(this) { + if (pendingRequests.containsKey(device)) { + val hasPermission = context.usbManager?.hasPermission(device) == true + for (deferred in pendingRequests[device].orEmpty()) { + deferred.complete(hasPermission) + } + pendingRequests.remove(device) + if (pendingRequests.isEmpty()) { + unregister(context) + } + } + } + } + } + } +} + +class UsbDevicePermissionManager(private val context: Context) { + + suspend fun awaitPermission(device: UsbDevice): Boolean { + if (context.usbManager?.hasPermission(device) == true) return true + val res = CompletableDeferred() + if (receiver.addDeferred(device, res)) { + receiver.register(context) + val intent = PendingIntent.getBroadcast(context, 0, Intent(context.usbPermissionCallbackAction), 0) + context.usbManager?.requestPermission(device, intent) + } + return res.await() + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbTransportHandler.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbTransportHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..20b1397776af59f0aad4bba2092f003b671ab2aa --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/UsbTransportHandler.kt @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.usb + +import android.content.Context +import android.hardware.usb.UsbConstants.* +import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbInterface +import android.util.Log +import androidx.annotation.RequiresApi +import com.google.android.gms.fido.fido2.api.common.AuthenticatorResponse +import com.google.android.gms.fido.fido2.api.common.RequestOptions +import kotlinx.coroutines.CancellationException +import org.microg.gms.fido.core.transport.Transport +import org.microg.gms.fido.core.transport.TransportHandler + +@RequiresApi(21) +class UsbTransportHandler(private val context: Context) : TransportHandler(Transport.USB) { + override val isSupported: Boolean + get() = context.packageManager.hasSystemFeature("android.hardware.usb.host") && context.usbManager != null + + private val devicePermissionManager by lazy { UsbDevicePermissionManager(context) } + + private infix fun List.eq(other: List):Boolean = other.size == size && zip(other).all { (x, y) -> x == y } + + suspend fun getCtapHidInterface(device: UsbDevice): UsbInterface? { + for (iface in device.interfaces) { + if (iface.interfaceClass != USB_CLASS_HID) continue + if (iface.interfaceSubclass != 0) continue + if (iface.interfaceProtocol != 0) continue + if (iface.endpointCount != 2) continue + val e0 = iface.getEndpoint(0) + val e1 = iface.getEndpoint(1) + val (i, o) = if (e0.direction == USB_DIR_IN) e0 to e1 else e1 to e0 + if (i.type != USB_ENDPOINT_XFER_INT || o.type != USB_ENDPOINT_XFER_INT) continue + if (i.direction != USB_DIR_IN || o.direction != USB_DIR_OUT) continue + + Log.d(TAG, "${device.productName} has suitable hid interface") + if (!devicePermissionManager.awaitPermission(device)) continue + Log.d(TAG, "${device.productName} has permission") + val match = context.usbManager?.openDevice(device)?.use { connection -> + if (connection.claimInterface(iface, true)) { + val buf = ByteArray(256) + val read = connection.controlTransfer(0x81, 0x06, 0x2200, iface.id, buf, buf.size, 5000) + read >= 5 && buf.slice(0 until 5) eq CTAPHID_SIGNATURE + } else { + false + } + } == true + if (match) { + return iface + } + } + return null + } + + override suspend fun start(options: RequestOptions, callerPackage: String): AuthenticatorResponse { + for (device in context.usbManager?.deviceList?.values.orEmpty()) { + val iface = getCtapHidInterface(device) + if (iface != null) { + context.usbManager?.openDevice(device)?.use { connection -> + if (connection.claimInterface(iface, true)) { + + } + } + } + } + Log.d(TAG, "No suitable device found") + throw CancellationException("No suitable device found") + } + + companion object { + const val TAG = "FidoUsbHandler" + val CTAPHID_SIGNATURE = listOf(0x06, 0xd0.toByte(), 0xf1.toByte(), 0x09, 0x01) + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidMessage.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidMessage.kt new file mode 100644 index 0000000000000000000000000000000000000000..e7f27df57353565bbcaa66e27aae2f15eede4bfc --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidMessage.kt @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.usb.ctaphid + +import kotlin.math.min + +open class CtapHidMessage(val commandId: Byte, val data: ByteArray = ByteArray(0)) { + init { + if (data.size > Short.MAX_VALUE) throw IllegalArgumentException("Request too large") + } + + fun encodePackets(channelIdentifier: Int, packetSize: Int): List { + val packets = arrayListOf() + val initializationDataSize = packetSize - 7 + val continuationDataSize = packetSize - 5 + var position = 0 + var nextPosition = min(data.size, initializationDataSize) + packets.add( + CtapHidInitializationPacket( + channelIdentifier, + commandId, + data.size.toShort(), + data.sliceArray(position until nextPosition), + packetSize + ) + ) + var sequenceNumber: Byte = 0 + while (nextPosition < data.size) { + position = nextPosition + nextPosition = min(data.size, position + continuationDataSize) + packets.add( + CtapHidContinuationPacket( + channelIdentifier, + sequenceNumber++, + data.sliceArray(position until nextPosition), + packetSize + ) + ) + } + return packets + } +} + +class CtapHidKeepAliveMessage(val status: Byte) : CtapHidMessage(0x3b, byteArrayOf(status)) { + companion object { + const val STATUS_PROCESSING: Byte = 1 + const val STATUS_UPNEEDED: Byte = 2 + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidPacket.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidPacket.kt new file mode 100644 index 0000000000000000000000000000000000000000..4dd99a32cedbdd43df73dfccbe07d18386ea51d3 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidPacket.kt @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.usb.ctaphid + +import java.nio.ByteBuffer +import java.nio.ByteOrder + +interface CtapHidPacket { + fun encode(): ByteArray +} + +class CtapHidInitializationPacket( + val channelIdentifier: Int, + val commandId: Byte, + val payloadLength: Short, + val data: ByteArray, + val packetSize: Int +) : CtapHidPacket { + + init { + if (data.size > packetSize - 7) throw IllegalArgumentException("Too much data for packet size") + if (commandId >= 0) throw IllegalArgumentException("7-bit must be set on initialization packet") + } + + override fun encode(): ByteArray = ByteBuffer.allocate(packetSize).apply { + order(ByteOrder.BIG_ENDIAN) + position(0) + putInt(channelIdentifier) + put(commandId) + putShort(payloadLength) + put(data) + }.array() +} + +class CtapHidContinuationPacket( + val channelIdentifier: Int, + val sequenceNumber: Byte, + val data: ByteArray, + val packetSize: Int +) : CtapHidPacket { + init { + if (data.size > packetSize - 5) throw IllegalArgumentException("Too much data for packet size") + if (sequenceNumber < 0) throw IllegalArgumentException("7-bit must not be set on continuation packet") + } + + override fun encode(): ByteArray = ByteBuffer.allocate(packetSize).apply { + order(ByteOrder.BIG_ENDIAN) + position(0) + putInt(channelIdentifier) + put(sequenceNumber) + put(data) + }.array() +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidRequest.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidRequest.kt new file mode 100644 index 0000000000000000000000000000000000000000..f16d2b80478d2e4abd8d4fffb27a8d4de848d80f --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidRequest.kt @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.usb.ctaphid + +import org.microg.gms.fido.core.protocol.msgs.Ctap1Request +import org.microg.gms.fido.core.protocol.msgs.Ctap2Request +import kotlin.math.min +import kotlin.random.Random + +abstract class CtapHidRequest(commandId: Byte, data: ByteArray = ByteArray(0)): CtapHidMessage(commandId, data) + +class CtapHidPingRequest(data: ByteArray) : CtapHidRequest(0x01, data) + +class CtapHidMessageRequest(val request: Ctap1Request) : + CtapHidRequest(0x03, byteArrayOf(request.commandByte) + request.payload) + + +class CtapHidLockRequest(val seconds: Byte) : CtapHidRequest(0x04, byteArrayOf(seconds)) + +class CtapHidInitRequest(val nonce: ByteArray = Random.nextBytes(8)) : + CtapHidRequest(0x06, nonce) { + init { + if (nonce.size != 8) throw IllegalArgumentException("nonce must be 8 bytes") + } +} + +class CtapHidWinkRequest : CtapHidRequest(0x08) + +class CtapHidCborRequest(val request: Ctap2Request) : + CtapHidRequest(0x10, byteArrayOf(request.commandByte) + request.payload) + +class CtapHidCancelRequest : CtapHidRequest(0x11) diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidResponse.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidResponse.kt new file mode 100644 index 0000000000000000000000000000000000000000..8baa323ea4071c6b6d644fd8274be6a21455364d --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/ctaphid/CtapHidResponse.kt @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.usb.ctaphid + +import java.nio.ByteBuffer +import java.nio.ByteOrder + +abstract class CtapHidResponse(commandId: Byte, data: ByteArray) : CtapHidMessage(commandId, data) + +class CtapHidPingResponse(data: ByteArray) : CtapHidResponse(0x01, data) + +class CtapHidMessageResponse(val statusCode: Byte, val payload: ByteArray) : + CtapHidResponse(0x03, byteArrayOf(statusCode) + payload) + +class CtapHidInitResponse( + val nonce: ByteArray, + val channelId: Int, + val protocolVersion: Byte, + val majorDeviceVersion: Byte, + val minorDeviceVersion: Byte, + val buildDeviceVersion: Byte, + val capabilities: Byte +) : CtapHidResponse(0x06, ByteBuffer.allocate(17).apply { + order(ByteOrder.BIG_ENDIAN) + position(0) + put(nonce) + putInt(channelId) + put(protocolVersion) + put(majorDeviceVersion) + put(minorDeviceVersion) + put(buildDeviceVersion) + put(capabilities) +}.array()) { + val deviceVersion: String = "$majorDeviceVersion.$minorDeviceVersion.$buildDeviceVersion" + + companion object { + const val CAPABILITY_WINK: Byte = 0x01 + const val CAPABILITY_CBOR: Byte = 0x04 + const val CAPABILITY_NMSG: Byte = 0x08 + } +} + +class CtapHidCborResponse(val statusCode: Byte, val payload: ByteArray) : + CtapHidResponse(0x10, byteArrayOf(statusCode) + payload) + +class CtapHidErrorResponse(val errorCode: Byte) : CtapHidResponse(0x3f, byteArrayOf(errorCode)) { + companion object { + const val ERR_INVALID_CMD: Byte = 0x01 + const val ERR_INVALID_PAR: Byte = 0x02 + const val ERR_INVALID_LEN: Byte = 0x03 + const val ERR_INVALID_SEQ: Byte = 0x04 + const val ERR_MSG_TIMEOUT: Byte = 0x05 + const val ERR_CHANNEL_BUSY: Byte = 0x06 + const val ERR_LOCK_REQUIRED: Byte = 0x0A + const val ERR_INVALID_CHANNEL: Byte = 0x0B + const val ERR_OTHER: Byte = 0x7F + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/extensions.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/extensions.kt new file mode 100644 index 0000000000000000000000000000000000000000..12453b149d213cd1371880d644ae80633f5692b3 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/transport/usb/extensions.kt @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.transport.usb + +import android.content.Context +import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbDeviceConnection +import android.hardware.usb.UsbInterface +import android.hardware.usb.UsbManager + +val Context.usbManager: UsbManager? + get() = getSystemService(Context.USB_SERVICE) as? UsbManager? + +val UsbDevice.interfaces: Iterable + get() = Iterable { + object : Iterator { + private var index = 0 + override fun hasNext(): Boolean = index + 1 < interfaceCount + override fun next(): UsbInterface = getInterface(index++) + } + } + +fun UsbDeviceConnection.use(block: (UsbDeviceConnection) -> R): R { + var exception: Throwable? = null + try { + return block(this) + } catch (e: Throwable) { + exception = e + throw e + } finally { + when (exception) { + null -> close() + else -> try { + close() + } catch (closeException: Throwable) { + // cause.addSuppressed(closeException) // ignored here + } + } + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..02da1ad984f378cb32df95aa099adaf1392b5001 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt @@ -0,0 +1,213 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.commit +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.NavHostFragment +import com.google.android.gms.fido.fido2.api.common.* +import com.google.android.gms.fido.fido2.api.common.ErrorCode.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import org.microg.gms.common.GmsService +import org.microg.gms.fido.api.FidoConstants.* +import org.microg.gms.fido.core.* +import org.microg.gms.fido.core.transport.Transport +import org.microg.gms.fido.core.transport.Transport.SCREEN_LOCK +import org.microg.gms.fido.core.transport.Transport.USB +import org.microg.gms.fido.core.transport.TransportHandler +import org.microg.gms.fido.core.transport.bluetooth.BluetoothTransportHandler +import org.microg.gms.fido.core.transport.nfc.NfcTransportHandler +import org.microg.gms.fido.core.transport.screenlock.ScreenLockTransportHandler +import org.microg.gms.fido.core.transport.usb.UsbTransportHandler +import org.microg.gms.utils.getApplicationLabel +import org.microg.gms.utils.getFirstSignatureDigest +import org.microg.gms.utils.toBase64 + +const val TAG = "FidoUi" + +class AuthenticatorActivity : AppCompatActivity() { + val options: RequestOptions? + get() = when (intent.getStringExtra(KEY_SOURCE) to intent.getStringExtra(KEY_TYPE)) { + SOURCE_BROWSER to TYPE_REGISTER -> + BrowserPublicKeyCredentialCreationOptions.deserializeFromBytes(intent.getByteArrayExtra(KEY_OPTIONS)) + SOURCE_BROWSER to TYPE_SIGN -> + BrowserPublicKeyCredentialRequestOptions.deserializeFromBytes(intent.getByteArrayExtra(KEY_OPTIONS)) + SOURCE_APP to TYPE_REGISTER -> + PublicKeyCredentialCreationOptions.deserializeFromBytes(intent.getByteArrayExtra(KEY_OPTIONS)) + SOURCE_APP to TYPE_SIGN -> + PublicKeyCredentialRequestOptions.deserializeFromBytes(intent.getByteArrayExtra(KEY_OPTIONS)) + else -> null + } + + private val service: GmsService + get() = GmsService.byServiceId(intent.getIntExtra(KEY_SERVICE, GmsService.UNKNOWN.SERVICE_ID)) + private val database by lazy { Database(this) } + private val transportHandlers by lazy { + setOfNotNull( + BluetoothTransportHandler(this), + NfcTransportHandler(this), + if (Build.VERSION.SDK_INT >= 21) UsbTransportHandler(this) else null, + ScreenLockTransportHandler(this) + ) + } + + private lateinit var callerPackage: String + private lateinit var callerSignature: String + private lateinit var navHostFragment: NavHostFragment + + private inline fun getTransportHandler(): T? = + transportHandlers.filterIsInstance().firstOrNull { it.isSupported } + + fun getTransportHandler(transport: Transport): TransportHandler? = + transportHandlers.firstOrNull { it.transport == transport && it.isSupported } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + try { + + val callerPackage = callingActivity?.packageName ?: return finish() + if (!intent.extras?.keySet().orEmpty().containsAll(REQUIRED_EXTRAS)) { + return finishWithError(UNKNOWN_ERR, "Extra missing from request") + } + if (Build.VERSION.SDK_INT < 24) { + return finishWithError(NOT_SUPPORTED_ERR, "FIDO2 API is not supported on devices below N") + } + val options = options ?: return finishWithError(DATA_ERR, "The request options are not valid") + this.callerPackage = callerPackage + this.callerSignature = packageManager.getFirstSignatureDigest(callerPackage, "SHA-256")?.toBase64() + ?: return finishWithError(UNKNOWN_ERR, "Could not determine signature of app") + + Log.d(TAG, "onCreate caller=$callerPackage options=$options") + + options.checkIsValid(this) + val origin = getOrigin(this, options, callerPackage) + val appName = getApplicationName(this, options, callerPackage) + val callerName = packageManager.getApplicationLabel(callerPackage).toString() + + val requiresPrivilege = + options is BrowserRequestOptions && !database.isPrivileged(callerPackage, callerSignature) + + Log.d(TAG, "origin=$origin, appName=$appName") + + // Check if we can directly open screen lock handling + if (!requiresPrivilege) { + val instantTransport = transportHandlers.firstOrNull { it.isSupported && it.shouldBeUsedInstantly(options) } + if (instantTransport != null && instantTransport.transport in INSTANT_SUPPORTED_TRANSPORTS) { + window.setBackgroundDrawable(ColorDrawable(0)) + window.statusBarColor = Color.TRANSPARENT + setTheme(R.style.Theme_Fido_Translucent) + startTransportHandling(instantTransport.transport) + return + } + } + + setTheme(R.style.Theme_AppCompat_DayNight_NoActionBar) + setContentView(R.layout.fido_authenticator_activity) + val arguments = AuthenticatorActivityFragmentData().apply { + this.appName = appName + this.isFirst = true + this.privilegedCallerName = callerName.takeIf { options is BrowserRequestOptions } + this.requiresPrivilege = requiresPrivilege + this.supportedTransports = transportHandlers.filter { it.isSupported }.map { it.transport }.toSet() + }.arguments + navHostFragment = NavHostFragment.create(R.navigation.nav_fido_authenticator, arguments) + // TODO: Go directly to appropriate fragment for known key + // TODO: If not first usage, skip welcome and go directly to transport selection + //navHostFragment.findNavController().navigate(next, arguments) + supportFragmentManager.commit { + replace(R.id.fragment_container, navHostFragment) + } + } catch (e: RequestHandlingException) { + finishWithError(e.errorCode, e.message ?: e.errorCode.name) + } catch (e: Exception) { + finishWithError(UNKNOWN_ERR, e.message ?: e.javaClass.simpleName) + } + } + + fun finishWithError(errorCode: ErrorCode, errorMessage: String) { + Log.d(TAG, "Finish with error: $errorMessage ($errorCode)") + finishWithCredential( + PublicKeyCredential.Builder().setResponse(AuthenticatorErrorResponse(errorCode, errorMessage)).build() + ) + } + + fun finishWithSuccessResponse(response: AuthenticatorResponse) { + Log.d(TAG, "Finish with success response: $response") + if (options is BrowserRequestOptions) database.insertPrivileged(callerPackage, callerSignature) + finishWithCredential(PublicKeyCredential.Builder().setResponse(response).build()) + } + + private fun finishWithCredential(publicKeyCredential: PublicKeyCredential) { + val intent = Intent() + intent.putExtra(FIDO2_KEY_CREDENTIAL_EXTRA, publicKeyCredential.serializeToBytes()) + val response: AuthenticatorResponse = publicKeyCredential.response + if (response is AuthenticatorErrorResponse) { + intent.putExtra(FIDO2_KEY_ERROR_EXTRA, response.serializeToBytes()) + } else { + intent.putExtra(FIDO2_KEY_RESPONSE_EXTRA, response.serializeToBytes()) + } + setResult(-1, intent) + finish() + } + + fun shouldStartTransportInstantly(transport: Transport): Boolean { + return getTransportHandler(transport)?.shouldBeUsedInstantly(options ?: return false) == true + } + + fun isScreenLockSigner(): Boolean { + return shouldStartTransportInstantly(SCREEN_LOCK) + } + + fun startTransportHandling(transport: Transport): Job = lifecycleScope.launchWhenStarted { + val options = options ?: return@launchWhenStarted + try { + finishWithSuccessResponse( + getTransportHandler(transport)!!.start(options, callerPackage) + ) + } catch (e: CancellationException) { + Log.w(TAG, e) + // Ignoring cancellation here + } catch (e: RequestHandlingException) { + Log.w(TAG, e) + finishWithError(e.errorCode, e.message ?: e.errorCode.name) + } catch (e: Exception) { + Log.w(TAG, e) + finishWithError(UNKNOWN_ERR, e.message ?: e.javaClass.simpleName) + } + } + + fun cancelTransportHandling(transport: Transport) { + // TODO + } + + companion object { + const val KEY_SERVICE = "service" + const val KEY_SOURCE = "source" + const val KEY_TYPE = "type" + const val KEY_OPTIONS = "options" + val REQUIRED_EXTRAS = setOf(KEY_SERVICE, KEY_SOURCE, KEY_TYPE, KEY_OPTIONS) + + const val SOURCE_BROWSER = "browser" + const val SOURCE_APP = "app" + + const val TYPE_REGISTER = "register" + const val TYPE_SIGN = "sign" + + val IMPLEMENTED_TRANSPORTS = setOf(SCREEN_LOCK) + val INSTANT_SUPPORTED_TRANSPORTS = setOf(SCREEN_LOCK) + } +} + + diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragment.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..75903fc9e4bc7550895bfa9537529d7a3384855f --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragment.kt @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.annotation.TargetApi +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.fido.fido2.api.common.ErrorCode +import com.google.android.gms.fido.fido2.api.common.RequestOptions +import org.microg.gms.fido.core.* +import org.microg.gms.fido.core.transport.Transport + +@TargetApi(24) +abstract class AuthenticatorActivityFragment : Fragment() { + val data: AuthenticatorActivityFragmentData + get() = AuthenticatorActivityFragmentData(arguments ?: Bundle.EMPTY) + val authenticatorActivity: AuthenticatorActivity? + get() = activity as? AuthenticatorActivity + val options: RequestOptions? + get() = authenticatorActivity?.options + + fun startTransportHandling(transport: Transport) = authenticatorActivity?.startTransportHandling(transport) + fun cancelTransportHandling(transport: Transport) = authenticatorActivity?.cancelTransportHandling(transport) + fun shouldStartTransportInstantly(transport: Transport) = authenticatorActivity?.shouldStartTransportInstantly(transport) == true + + abstract override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragmentData.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragmentData.kt new file mode 100644 index 0000000000000000000000000000000000000000..8625b74a5b551304a7fe7dde6e1e725fff154ac7 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivityFragmentData.kt @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.os.Bundle +import org.microg.gms.fido.core.transport.Transport +import org.microg.gms.fido.core.ui.AuthenticatorActivityFragmentData.Companion.KEY_IS_FIRST + +class AuthenticatorActivityFragmentData(val arguments: Bundle = Bundle()) { + var appName: String? + get() = arguments.getString(KEY_APP_NAME) + set(value) = arguments.putString(KEY_APP_NAME, value) + + var isFirst: Boolean + get() = arguments.getBoolean(KEY_IS_FIRST, true) + set(value) = arguments.putBoolean(KEY_IS_FIRST, value) + + var supportedTransports: Set + get() = arguments.getStringArrayList(KEY_SUPPORTED_TRANSPORTS)?.map { Transport.valueOf(it) }?.toSet().orEmpty() + set(value) = arguments.putStringArrayList(KEY_SUPPORTED_TRANSPORTS, ArrayList(value.map { it.name })) + + val implementedTransports: Set + get() = AuthenticatorActivity.IMPLEMENTED_TRANSPORTS + + var privilegedCallerName: String? + get() = arguments.getString(KEY_PRIVILEGED_CALLER_NAME) + set(value) = arguments.putString(KEY_PRIVILEGED_CALLER_NAME, value) + + var requiresPrivilege: Boolean + get() = arguments.getBoolean(KEY_REQUIRES_PRIVILEGE) + set(value) = arguments.putBoolean(KEY_REQUIRES_PRIVILEGE, value) + + companion object { + const val KEY_APP_NAME = "appName" + const val KEY_IS_FIRST = "isFirst" + const val KEY_SUPPORTED_TRANSPORTS = "supportedTransports" + const val KEY_REQUIRES_PRIVILEGE = "requiresPrivilege" + const val KEY_PRIVILEGED_CALLER_NAME = "privilegedCallerName" + } +} + +fun Bundle?.withIsFirst(isFirst: Boolean) = Bundle(this ?: Bundle.EMPTY).apply { putBoolean(KEY_IS_FIRST, isFirst) } diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/TransportSelectionFragment.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/TransportSelectionFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..384bd1fd923c17f43402db1514cf068dfa5bdb6a --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/TransportSelectionFragment.kt @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import org.microg.gms.fido.core.R +import org.microg.gms.fido.core.databinding.FidoTransportSelectionFragmentBinding +import org.microg.gms.fido.core.transport.Transport +import org.microg.gms.fido.core.transport.Transport.SCREEN_LOCK + +class TransportSelectionFragment : AuthenticatorActivityFragment() { + private lateinit var binding: FidoTransportSelectionFragmentBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FidoTransportSelectionFragmentBinding.inflate(inflater, container, false) + binding.data = data + binding.setOnBluetoothClick { + findNavController().navigate(R.id.openBluetoothFragment, arguments.withIsFirst(false)) + } + binding.setOnNfcClick { + findNavController().navigate(R.id.openNfcFragment, arguments.withIsFirst(false)) + } + binding.setOnUsbClick { + findNavController().navigate(R.id.openUsbFragment, arguments.withIsFirst(false)) + } + binding.setOnScreenLockClick { + startTransportHandling(SCREEN_LOCK) + } + return binding.root + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/UsbTransportFragment.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/UsbTransportFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..d86adc1985fcc5ef443f0178250af61ae0f4c0b5 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/UsbTransportFragment.kt @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import org.microg.gms.fido.core.databinding.FidoUsbTransportFragmentBinding +import org.microg.gms.fido.core.transport.Transport + +class UsbTransportFragment : AuthenticatorActivityFragment() { + private lateinit var binding: FidoUsbTransportFragmentBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FidoUsbTransportFragmentBinding.inflate(inflater, container, false) + binding.data = data + return binding.root + } + + override fun onResume() { + super.onResume() + startTransportHandling(Transport.USB) + } + + override fun onPause() { + cancelTransportHandling(Transport.USB) + super.onPause() + } +} diff --git a/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/WelcomeFragment.kt b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/WelcomeFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..af15b910f5931ebb8252f49d7a6b3e75d5c59189 --- /dev/null +++ b/play-services-fido-core/src/main/kotlin/org/microg/gms/fido/core/ui/WelcomeFragment.kt @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.fido.core.ui + +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.findNavController +import org.microg.gms.fido.core.R +import org.microg.gms.fido.core.transport.Transport +import org.microg.gms.fido.core.databinding.FidoWelcomeFragmentBinding + +class WelcomeFragment : AuthenticatorActivityFragment() { + private lateinit var binding: FidoWelcomeFragmentBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FidoWelcomeFragmentBinding.inflate(inflater, container, false) + binding.data = data + binding.onGetStartedClick = View.OnClickListener { + for (transport in data.supportedTransports) { + if (shouldStartTransportInstantly(transport)) { + startTransportHandling(transport) + return@OnClickListener + } + } + val next = data.supportedTransports.singleOrNull()?.let { + when (it) { + Transport.BLUETOOTH -> R.id.openBluetoothFragmentDirect + Transport.NFC -> R.id.openNfcFragmentDirect + Transport.USB -> R.id.openUsbFragmentDirect + Transport.SCREEN_LOCK -> { + startTransportHandling(Transport.SCREEN_LOCK) + return@OnClickListener + } + } + } ?: R.id.openTransportSelectionFragment + findNavController().navigate(next, arguments.withIsFirst(false)) + } + return binding.root + } +} diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_bluetooth.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_bluetooth.xml new file mode 100644 index 0000000000000000000000000000000000000000..d1b5b9246f586d8825e415b667ad627d58e81273 --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_bluetooth.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_fingerprint.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_fingerprint.xml new file mode 100644 index 0000000000000000000000000000000000000000..eba76e4e38124dca0a0a7ccda7bf46acdf7c31a6 --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_fingerprint.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_key.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_key.xml new file mode 100644 index 0000000000000000000000000000000000000000..67b8a97fc5c3c077f5b3dd755305617fb68ce6dd --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_key.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_nfc.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_nfc.xml new file mode 100644 index 0000000000000000000000000000000000000000..8c9d471702e5d1a534b36e59d11e16ea2069e310 --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_nfc.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/drawable/ic_fido_usb.xml b/play-services-fido-core/src/main/res/drawable/ic_fido_usb.xml new file mode 100644 index 0000000000000000000000000000000000000000..e1772cd5fe6fe52d237efddd3f31b86b1c40934b --- /dev/null +++ b/play-services-fido-core/src/main/res/drawable/ic_fido_usb.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-fido-core/src/main/res/layout/fido_authenticator_activity.xml b/play-services-fido-core/src/main/res/layout/fido_authenticator_activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..cfbf3edfe780c179a8266a2e4d8a08a1820045ef --- /dev/null +++ b/play-services-fido-core/src/main/res/layout/fido_authenticator_activity.xml @@ -0,0 +1,11 @@ + + + + diff --git a/play-services-fido-core/src/main/res/layout/fido_transport_selection_fragment.xml b/play-services-fido-core/src/main/res/layout/fido_transport_selection_fragment.xml new file mode 100644 index 0000000000000000000000000000000000000000..e335503b77a86f1c8887ad518eef11c28785d7fa --- /dev/null +++ b/play-services-fido-core/src/main/res/layout/fido_transport_selection_fragment.xml @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-fido-core/src/main/res/layout/fido_usb_transport_fragment.xml b/play-services-fido-core/src/main/res/layout/fido_usb_transport_fragment.xml new file mode 100644 index 0000000000000000000000000000000000000000..4bc838c314aefdbf6736cb03a34a7518c37bc9e6 --- /dev/null +++ b/play-services-fido-core/src/main/res/layout/fido_usb_transport_fragment.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-fido-core/src/main/res/layout/fido_welcome_fragment.xml b/play-services-fido-core/src/main/res/layout/fido_welcome_fragment.xml new file mode 100644 index 0000000000000000000000000000000000000000..0b68167bad4e7f0006087af471a5d75654718622 --- /dev/null +++ b/play-services-fido-core/src/main/res/layout/fido_welcome_fragment.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +