Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.content.Context;

import com.auth0.jwt.exceptions.JWTVerificationException;
import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.JWEObject;

Expand All @@ -18,12 +19,15 @@
import org.cryptomator.domain.exception.hub.HubVaultAccessForbiddenException;
import org.cryptomator.domain.exception.hub.HubVaultIsArchivedException;
import org.cryptomator.domain.repository.HubRepository;
import org.cryptomator.domain.usecases.AndroidLicenseVerifier;
import org.cryptomator.util.crypto.HubDeviceCryptor;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.text.ParseException;
import java.time.Instant;

Expand All @@ -35,6 +39,7 @@
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;

@Singleton
Expand Down Expand Up @@ -65,9 +70,7 @@ public HubRepository.VaultAccess getVaultAccess(UnverifiedHubVaultConfig unverif
switch (response.code()) {
case HttpURLConnection.HTTP_OK:
if (response.body() != null) {
String subscriptionHeader = response.header("Hub-Subscription-State");
HubRepository.SubscriptionState state = "ACTIVE".equalsIgnoreCase(subscriptionHeader) ? HubRepository.SubscriptionState.ACTIVE : HubRepository.SubscriptionState.INACTIVE;
return new HubRepository.VaultAccess(response.body().string(), state);
return new HubRepository.VaultAccess(response.body().string(), resolveSubscriptionState(response));
} else {
throw new FatalBackendException("Failed to load JWE, response code good but no body");
}
Expand All @@ -87,6 +90,28 @@ public HubRepository.VaultAccess getVaultAccess(UnverifiedHubVaultConfig unverif
}
}

private HubRepository.SubscriptionState resolveSubscriptionState(Response response) {
String androidLicense = response.header("Hub-Android-License");
if (androidLicense == null || androidLicense.isEmpty()) {
return subscriptionStateFromHeader(response.header("Hub-Subscription-State"));
}
return isAndroidLicenseValid(androidLicense) ? HubRepository.SubscriptionState.ACTIVE : HubRepository.SubscriptionState.INACTIVE;
}

private boolean isAndroidLicenseValid(String androidLicense) {
try {
AndroidLicenseVerifier.verify(androidLicense, AndroidLicenseVerifier.ANDROID_PUB_KEY);
return true;
} catch (JWTVerificationException | NoSuchAlgorithmException | InvalidKeySpecException | FatalBackendException e) {
Timber.tag("HubRepositoryImpl").e(e, "Failed to validate Android license retrieved from Hub");
return false;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private HubRepository.SubscriptionState subscriptionStateFromHeader(String subscriptionHeader) {
return "ACTIVE".equalsIgnoreCase(subscriptionHeader) ? HubRepository.SubscriptionState.ACTIVE : HubRepository.SubscriptionState.INACTIVE;
}

@Override
public UserDto getUser(UnverifiedHubVaultConfig unverifiedHubVaultConfig, String accessToken) throws FatalBackendException {
var request = new Request.Builder().get() //
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.cryptomator.domain.usecases;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.google.common.io.BaseEncoding;

import org.cryptomator.domain.exception.FatalBackendException;

import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

public final class AndroidLicenseVerifier {

public static final String ANDROID_PUB_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBcnb81CfNeL3qBVFMx/yRfm1Y1yib" + //
"ajIJkV1s82AQt+mOl4+Kub64wq1OCgBVwWUlKwqgnyF39nmkoXEjakRPFngBzg2J" + //
"zo4UR0B7OYmn0uGf3K+zQfxKnNMxGVPtlzE8j9Nqz/dm2YvYLLVwvTSDQX/GaxoP" + //
"/EH84Hupw2wuU7qAaFU=";

private AndroidLicenseVerifier() {
}

public static DecodedJWT verify(String license, String base64PublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
Algorithm algorithm = Algorithm.ECDSA512(getPublicKey(base64PublicKey), null);
JWTVerifier verifier = JWT.require(algorithm).build();
return verifier.verify(license);
}

private static ECPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(BaseEncoding.base64().decode(publicKey));
Key key = KeyFactory.getInstance("EC").generatePublic(keySpec);
if (key instanceof ECPublicKey) {
return (ECPublicKey) key;
} else {
throw new FatalBackendException("Key not an EC public key.");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package org.cryptomator.domain.usecases;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.google.common.io.BaseEncoding;

import org.cryptomator.domain.exception.BackendException;
import org.cryptomator.domain.exception.FatalBackendException;
Expand All @@ -17,20 +13,12 @@
import org.cryptomator.generator.UseCase;
import org.cryptomator.util.SharedPreferencesHandler;

import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

@UseCase
public class DoLicenseCheck {

private static final String ANDROID_PUB_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBcnb81CfNeL3qBVFMx/yRfm1Y1yib" + //
"ajIJkV1s82AQt+mOl4+Kub64wq1OCgBVwWUlKwqgnyF39nmkoXEjakRPFngBzg2J" + //
"zo4UR0B7OYmn0uGf3K+zQfxKnNMxGVPtlzE8j9Nqz/dm2YvYLLVwvTSDQX/GaxoP" + //
"/EH84Hupw2wuU7qAaFU=";
private static final String DESKTOP_SUPPORTER_CERTIFICATE_PUB_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW" + //
"fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV" + //
"gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE" + //
Expand All @@ -46,9 +34,7 @@ public class DoLicenseCheck {
public LicenseCheck execute() throws BackendException {
license = useLicenseOrRetrieveFromPreferences(license);
try {
Algorithm algorithm = Algorithm.ECDSA512(getPublicKey(ANDROID_PUB_KEY), null);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(license);
DecodedJWT jwt = AndroidLicenseVerifier.verify(license, AndroidLicenseVerifier.ANDROID_PUB_KEY);
sharedPreferencesHandler.setLicenseToken(license);
return jwt::getSubject;
} catch (SignatureVerificationException | JWTDecodeException | FatalBackendException e) {
Expand All @@ -72,21 +58,9 @@ private String useLicenseOrRetrieveFromPreferences(String license) throws NoLice
return stored;
}

private ECPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(BaseEncoding.base64().decode(publicKey));
Key key = KeyFactory.getInstance("EC").generatePublic(keySpec);
if (key instanceof ECPublicKey) {
return (ECPublicKey) key;
} else {
throw new FatalBackendException("Key not an EC public key.");
}
}

private boolean isDesktopSupporterCertificate(String license) {
try {
Algorithm algorithm = Algorithm.ECDSA512(getPublicKey(DESKTOP_SUPPORTER_CERTIFICATE_PUB_KEY), null);
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(license);
AndroidLicenseVerifier.verify(license, DESKTOP_SUPPORTER_CERTIFICATE_PUB_KEY);
return true;
} catch (SignatureVerificationException | NoSuchAlgorithmException | InvalidKeySpecException e) {
return false;
Expand Down
Loading