diff --git a/bramble-android/build.gradle b/bramble-android/build.gradle
index e1db103c333431ef793cbb2b70a37fc1345c35e8..d2a4b9eb4c0a4e01d5ba0b0e75ce36584a53f830 100644
--- a/bramble-android/build.gradle
+++ b/bramble-android/build.gradle
@@ -42,8 +42,10 @@ configurations {
 
 dependencies {
 	implementation project(path: ':bramble-core', configuration: 'default')
+	implementation 'androidx.annotation:annotation:1.5.0'
 	tor "org.briarproject:tor-android:$tor_version"
 	tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
+	tor "org.briarproject:snowflake-android:$snowflake_version"
 
 	annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
 
diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java
index d8933d10174e48be88231ec851573973ed2d0ad3..2acd6705673a5fb6d8b834bf11accff4d6797114 100644
--- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java
+++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java
@@ -31,6 +31,8 @@ import java.util.zip.ZipInputStream;
 
 import javax.net.SocketFactory;
 
+import androidx.annotation.ChecksSdkIntAtLeast;
+
 import static android.os.Build.VERSION.SDK_INT;
 import static java.util.Arrays.asList;
 import static java.util.logging.Level.INFO;
@@ -45,13 +47,14 @@ class AndroidTorPlugin extends TorPlugin {
 
 	private static final String TOR_LIB_NAME = "libtor.so";
 	private static final String OBFS4_LIB_NAME = "libobfs4proxy.so";
+	private static final String SNOWFLAKE_LIB_NAME = "libsnowflake.so";
 
 	private static final Logger LOG =
 			getLogger(AndroidTorPlugin.class.getName());
 
 	private final Application app;
 	private final AndroidWakeLock wakeLock;
-	private final File torLib, obfs4Lib;
+	private final File torLib, obfs4Lib, snowflakeLib;
 
 	AndroidTorPlugin(Executor ioExecutor,
 			Executor wakefulIoExecutor,
@@ -83,6 +86,7 @@ class AndroidTorPlugin extends TorPlugin {
 		String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;
 		torLib = new File(nativeLibDir, TOR_LIB_NAME);
 		obfs4Lib = new File(nativeLibDir, OBFS4_LIB_NAME);
+		snowflakeLib = new File(nativeLibDir, SNOWFLAKE_LIB_NAME);
 	}
 
 	@Override
@@ -108,6 +112,12 @@ class AndroidTorPlugin extends TorPlugin {
 		if (!enable) wakeLock.release();
 	}
 
+	@Override
+	@ChecksSdkIntAtLeast(api = 25)
+	protected boolean canVerifyLetsEncryptCerts() {
+		return SDK_INT >= 25;
+	}
+
 	@Override
 	public void stop() {
 		super.stop();
@@ -124,39 +134,43 @@ class AndroidTorPlugin extends TorPlugin {
 		return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile();
 	}
 
+	@Override
+	protected File getSnowflakeExecutableFile() {
+		return snowflakeLib.exists()
+				? snowflakeLib : super.getSnowflakeExecutableFile();
+	}
+
 	@Override
 	protected void installTorExecutable() throws IOException {
-		File extracted = super.getTorExecutableFile();
-		if (torLib.exists()) {
-			// If an older version left behind a Tor binary, delete it
-			if (extracted.exists()) {
-				if (extracted.delete()) LOG.info("Deleted Tor binary");
-				else LOG.info("Failed to delete Tor binary");
-			}
-		} else if (SDK_INT < 29) {
-			// The binary wasn't extracted at install time. Try to extract it
-			extractLibraryFromApk(TOR_LIB_NAME, extracted);
-		} else {
-			// No point extracting the binary, we won't be allowed to execute it
-			throw new FileNotFoundException(torLib.getAbsolutePath());
-		}
+		installExecutable(super.getTorExecutableFile(), torLib, TOR_LIB_NAME);
 	}
 
 	@Override
 	protected void installObfs4Executable() throws IOException {
-		File extracted = super.getObfs4ExecutableFile();
-		if (obfs4Lib.exists()) {
-			// If an older version left behind an obfs4 binary, delete it
+		installExecutable(super.getObfs4ExecutableFile(), obfs4Lib,
+				OBFS4_LIB_NAME);
+	}
+
+	@Override
+	protected void installSnowflakeExecutable() throws IOException {
+		installExecutable(super.getSnowflakeExecutableFile(), snowflakeLib,
+				SNOWFLAKE_LIB_NAME);
+	}
+
+	private void installExecutable(File extracted, File lib, String libName)
+			throws IOException {
+		if (lib.exists()) {
+			// If an older version left behind a binary, delete it
 			if (extracted.exists()) {
-				if (extracted.delete()) LOG.info("Deleted obfs4 binary");
-				else LOG.info("Failed to delete obfs4 binary");
+				if (extracted.delete()) LOG.info("Deleted old binary");
+				else LOG.info("Failed to delete old binary");
 			}
 		} else if (SDK_INT < 29) {
 			// The binary wasn't extracted at install time. Try to extract it
-			extractLibraryFromApk(OBFS4_LIB_NAME, extracted);
+			extractLibraryFromApk(libName, extracted);
 		} else {
 			// No point extracting the binary, we won't be allowed to execute it
-			throw new FileNotFoundException(obfs4Lib.getAbsolutePath());
+			throw new FileNotFoundException(lib.getAbsolutePath());
 		}
 	}
 
diff --git a/bramble-android/witness.gradle b/bramble-android/witness.gradle
index 046a450992d36217ab81f0355d3ee7948a034828..90146f99f759d9f52da10493145a29eea1010365 100644
--- a/bramble-android/witness.gradle
+++ b/bramble-android/witness.gradle
@@ -1,5 +1,6 @@
 dependencyVerification {
     verify = [
+        'androidx.annotation:annotation:1.5.0:annotation-1.5.0.jar:261fb7c0210858500bab66d34354972a75166ab4182add283780b05513d6ec4a',
         'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
         'com.android.tools.analytics-library:protos:30.0.3:protos-30.0.3.jar:f62b89dcd9de719c6a7b7e15fb1dd20e45b57222e675cf633607bd0ed6bca7e7',
         'com.android.tools.analytics-library:shared:30.0.3:shared-30.0.3.jar:05aa9ba3cc890354108521fdf99802565aae5dd6ca44a6ac8bb8d594d1c1cd15',
@@ -88,6 +89,7 @@ dependencyVerification {
         'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
         'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
         'org.briarproject:obfs4proxy-android:0.0.14:obfs4proxy-android-0.0.14.jar:ad9b1ee4757b05867a19e993147bbb018bddd1f26ce3da746d5f037d5991a8c8',
+        'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293',
         'org.briarproject:tor-android:0.4.5.14:tor-android-0.4.5.14.jar:7cf1beaa6c1db51fc8fac263aba9624ef289c3db29772509efcbc59f7057330a',
         'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
         'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
@@ -129,10 +131,12 @@ dependencyVerification {
         'org.jetbrains.kotlin:kotlin-reflect:1.4.32:kotlin-reflect-1.4.32.jar:dbf19e9cdaa9c3c170f3f6f6ce3922f38dfc1d7fa1cab5b7c23a19da8b5eec5b',
         'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
         'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32:kotlin-stdlib-common-1.4.32.jar:e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145',
+        'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
         'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32:kotlin-stdlib-jdk7-1.4.32.jar:5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6',
         'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:kotlin-stdlib-jdk8-1.4.32.jar:adc43e54757b106e0cd7b3b7aa257dff471b61efdabe067fc02b2f57e2396262',
         'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
         'org.jetbrains.kotlin:kotlin-stdlib:1.4.32:kotlin-stdlib-1.4.32.jar:13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba',
+        'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
         'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
         'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
         'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java
index ff5033bf1d3c404752afb3bc49636a0ebd2ab3ad..7e1c4c83bbb475aff8e96eb8aced66487d5c36ee 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProvider.java
@@ -12,7 +12,8 @@ public interface CircumventionProvider {
 		DEFAULT_OBFS4,
 		NON_DEFAULT_OBFS4,
 		VANILLA,
-		MEEK
+		MEEK,
+		SNOWFLAKE
 	}
 
 	/**
@@ -41,13 +42,14 @@ public interface CircumventionProvider {
 	 * Countries where non-default obfs4 or vanilla bridges are likely to work.
 	 * Should be a subset of {@link #BRIDGES}.
 	 */
-	String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
+	String[] NON_DEFAULT_BRIDGES = {"BY", "RU"};
 
 	/**
 	 * Countries where vanilla bridges are blocked via DPI but non-default
-	 * obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}.
+	 * obfs4 bridges, meek and snowflake may work. Should be a subset of
+	 * {@link #BRIDGES}.
 	 */
-	String[] DPI_BRIDGES = {"CN", "IR"};
+	String[] DPI_BRIDGES = {"CN", "IR", "TM"};
 
 	/**
 	 * Returns true if vanilla Tor connections are blocked in the given country.
@@ -68,6 +70,6 @@ public interface CircumventionProvider {
 	List<BridgeType> getSuitableBridgeTypes(String countryCode);
 
 	@IoExecutor
-	List<String> getBridges(BridgeType type);
-
+	List<String> getBridges(BridgeType type, String countryCode,
+			boolean letsEncrypt);
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java
index 461ce5c31e5f4ff1a8b884bfe2dd0355a07dcb18..ac7d77af67438e5803a1fb9516574d9bdfb6e6c9 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/CircumventionProviderImpl.java
@@ -7,8 +7,10 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Scanner;
 import java.util.Set;
+import java.util.TreeMap;
 
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
@@ -18,6 +20,7 @@ import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
 
 @Immutable
@@ -25,6 +28,8 @@ import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeTy
 class CircumventionProviderImpl implements CircumventionProvider {
 
 	private final static String BRIDGE_FILE_NAME = "bridges";
+	private final static String SNOWFLAKE_PARAMS_FILE_NAME = "snowflake-params";
+	private final static String DEFAULT_COUNTRY_CODE = "ZZ";
 
 	private static final Set<String> BLOCKED_IN_COUNTRIES =
 			new HashSet<>(asList(BLOCKED));
@@ -58,7 +63,7 @@ class CircumventionProviderImpl implements CircumventionProvider {
 		} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
 			return asList(NON_DEFAULT_OBFS4, VANILLA);
 		} else if (DPI_COUNTRIES.contains(countryCode)) {
-			return asList(NON_DEFAULT_OBFS4, MEEK);
+			return asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE);
 		} else {
 			return asList(DEFAULT_OBFS4, VANILLA);
 		}
@@ -66,7 +71,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
 
 	@Override
 	@IoExecutor
-	public List<String> getBridges(BridgeType type) {
+	public List<String> getBridges(BridgeType type, String countryCode,
+			boolean letsEncrypt) {
 		InputStream is = requireNonNull(getClass().getClassLoader()
 				.getResourceAsStream(BRIDGE_FILE_NAME));
 		Scanner scanner = new Scanner(is);
@@ -79,10 +85,43 @@ class CircumventionProviderImpl implements CircumventionProvider {
 					(type == VANILLA && line.startsWith("v ")) ||
 					(type == MEEK && line.startsWith("m "))) {
 				bridges.add(line.substring(2));
+			} else if (type == SNOWFLAKE && line.startsWith("s ")) {
+				String params = getSnowflakeParams(countryCode, letsEncrypt);
+				bridges.add(line.substring(2) + " " + params);
 			}
 		}
 		scanner.close();
 		return bridges;
 	}
 
+	private String getSnowflakeParams(String countryCode, boolean letsEncrypt) {
+		Map<String, String> params = loadSnowflakeParams();
+		if (countryCode.isEmpty()) countryCode = DEFAULT_COUNTRY_CODE;
+		// If we have parameters for this country code, return them
+		String value = params.get(makeKey(countryCode, letsEncrypt));
+		if (value != null) return value;
+		// Return the default parameters
+		value = params.get(makeKey(DEFAULT_COUNTRY_CODE, letsEncrypt));
+		return requireNonNull(value);
+	}
+
+	private Map<String, String> loadSnowflakeParams() {
+		InputStream is = requireNonNull(getClass().getClassLoader()
+				.getResourceAsStream(SNOWFLAKE_PARAMS_FILE_NAME));
+		Scanner scanner = new Scanner(is);
+		Map<String, String> params = new TreeMap<>();
+		while (scanner.hasNextLine()) {
+			String line = scanner.nextLine();
+			if (line.length() < 5) continue;
+			String key = line.substring(0, 4); // Country code, space, digit
+			String value = line.substring(5);
+			params.put(key, value);
+		}
+		scanner.close();
+		return params;
+	}
+
+	private String makeKey(String countryCode, boolean oldAndroid) {
+		return countryCode + " " + (oldAndroid ? "1" : "0");
+	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
index 1b51547a9c7317cc4d3a941aacc03cc4dccc721c..792cf43d5303b39138db2fdebe9f28956dbbfeeb 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java
@@ -95,6 +95,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
 import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
 import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
+import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
 import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
 import static org.briarproject.bramble.util.IoUtils.copyAndClose;
 import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -212,6 +213,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		return new File(torDirectory, "obfs4proxy");
 	}
 
+	protected File getSnowflakeExecutableFile() {
+		return new File(torDirectory, "snowflake");
+	}
+
 	@Override
 	public TransportId getId() {
 		return TorConstants.ID;
@@ -338,6 +343,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		geoIpFile.delete();
 		installTorExecutable();
 		installObfs4Executable();
+		installSnowflakeExecutable();
 		if (!doneFile.createNewFile())
 			LOG.warning("Failed to create done file");
 	}
@@ -363,17 +369,29 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		if (!obfs4File.setExecutable(true, true)) throw new IOException();
 	}
 
+	protected void installSnowflakeExecutable() throws IOException {
+		if (LOG.isLoggable(INFO))
+			LOG.info("Installing snowflake binary for " + architecture);
+		File snowflakeFile = getSnowflakeExecutableFile();
+		extract(getSnowflakeInputStream(), snowflakeFile);
+		if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
+	}
+
 	private InputStream getTorInputStream() throws IOException {
-		InputStream in = resourceProvider
-				.getResourceInputStream("tor_" + architecture, ".zip");
-		ZipInputStream zin = new ZipInputStream(in);
-		if (zin.getNextEntry() == null) throw new IOException();
-		return zin;
+		return getZipInputStream("tor");
 	}
 
 	private InputStream getObfs4InputStream() throws IOException {
+		return getZipInputStream("obfs4proxy");
+	}
+
+	private InputStream getSnowflakeInputStream() throws IOException {
+		return getZipInputStream("snowflake");
+	}
+
+	private InputStream getZipInputStream(String basename) throws IOException {
 		InputStream in = resourceProvider
-				.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
+				.getResourceInputStream(basename + "_" + architecture, ".zip");
 		ZipInputStream zin = new ZipInputStream(in);
 		if (zin.getNextEntry() == null) throw new IOException();
 		return zin;
@@ -402,6 +420,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
 		append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
 		append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
+		String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
+		append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
 		//noinspection CharsetObjectCanBeUsed
 		return new ByteArrayInputStream(
 				strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -559,7 +579,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		}
 	}
 
-	private void enableBridges(List<BridgeType> bridgeTypes)
+	private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
 			throws IOException {
 		if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
 		try {
@@ -569,8 +589,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 			} else {
 				Collection<String> conf = new ArrayList<>();
 				conf.add("UseBridges 1");
+				boolean letsEncrypt = canVerifyLetsEncryptCerts();
 				for (BridgeType bridgeType : bridgeTypes) {
-					conf.addAll(circumventionProvider.getBridges(bridgeType));
+					conf.addAll(circumventionProvider
+							.getBridges(bridgeType, countryCode, letsEncrypt));
 				}
 				controlConnection.setConf(conf);
 			}
@@ -579,6 +601,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 		}
 	}
 
+	/**
+	 * Returns true if this device can verify Let's Encrypt certificates signed
+	 * with the IdentTrust DST Root X3 certificate, which expired at the end of
+	 * September 2021.
+	 */
+	protected boolean canVerifyLetsEncryptCerts() {
+		return true;
+	}
+
 	@Override
 	public void stop() {
 		ServerSocket ss = state.setStopped();
@@ -944,7 +975,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 					if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
 							(automatic && bridgesWork)) {
 						if (ipv6Only) {
-							bridgeTypes = singletonList(MEEK);
+							bridgeTypes = asList(MEEK, SNOWFLAKE);
 						} else {
 							bridgeTypes = circumventionProvider
 									.getSuitableBridgeTypes(country);
@@ -968,7 +999,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
 
 			try {
 				if (enableNetwork) {
-					enableBridges(bridgeTypes);
+					enableBridges(bridgeTypes, country);
 					enableConnectionPadding(enableConnectionPadding);
 					enableIpv6(ipv6Only);
 				}
diff --git a/bramble-core/src/main/resources/bridges b/bramble-core/src/main/resources/bridges
index 1123d36294d75a943804ebdb844941431d7f9470..458165af7f1b4ab4fd1bfda8f8f3d43eb7ab6502 100644
--- a/bramble-core/src/main/resources/bridges
+++ b/bramble-core/src/main/resources/bridges
@@ -33,3 +33,4 @@ v Bridge 185.189.195.124:8199 A1F3EE78F9C2343668E68FEB84358A4C742831A5
 v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB
 v Bridge 75.100.92.154:22815 465E990FA8A752DDE861EDF31E42454ACC037AB4
 m Bridge meek_lite 192.0.2.2:80 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
+s Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72
diff --git a/bramble-core/src/main/resources/snowflake-params b/bramble-core/src/main/resources/snowflake-params
new file mode 100644
index 0000000000000000000000000000000000000000..9192ec246dd461879be9725badbd282ba594a7d8
--- /dev/null
+++ b/bramble-core/src/main/resources/snowflake-params
@@ -0,0 +1,4 @@
+ZZ 1 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478
+ZZ 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478
+TM 1 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479
+TM 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/CircumventionProviderTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/CircumventionProviderTest.java
index 7dd380d68f6c3965a5e1090323e92853e8f3ce00..310ec6eb0074a770e3685777f3359c0d31d17df9 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/CircumventionProviderTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/tor/CircumventionProviderTest.java
@@ -12,6 +12,7 @@ import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_BRIDGES;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DPI_BRIDGES;
@@ -56,7 +57,7 @@ public class CircumventionProviderTest extends BrambleTestCase {
 					provider.getSuitableBridgeTypes(country));
 		}
 		for (String country : DPI_BRIDGES) {
-			assertEquals(asList(NON_DEFAULT_OBFS4, MEEK),
+			assertEquals(asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE),
 					provider.getSuitableBridgeTypes(country));
 		}
 		assertEquals(asList(DEFAULT_OBFS4, VANILLA),
diff --git a/bramble-java/build.gradle b/bramble-java/build.gradle
index fb3414d7df74adbfffeedbb4fedf52210cd4aacb..82b11c4a4b278fafbaa37c1bb26ea80dda013b1b 100644
--- a/bramble-java/build.gradle
+++ b/bramble-java/build.gradle
@@ -21,6 +21,8 @@ dependencies {
 	tor "org.briarproject:tor-windows:$tor_version"
 	tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
 	tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
+	tor "org.briarproject:snowflake-linux:$snowflake_version"
+	tor "org.briarproject:snowflake-windows:$snowflake_version"
 
 	annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
 
diff --git a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java
index 79818a3498d258cd47d73833e250de98b2335013..54575469cac2af18290388260c3cf519a54fa817 100644
--- a/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java
+++ b/bramble-java/src/test/java/org/briarproject/bramble/plugin/tor/BridgeTest.java
@@ -47,6 +47,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
+import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
 import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
 import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
 import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
@@ -58,6 +59,8 @@ import static org.junit.Assume.assumeTrue;
 @RunWith(Parameterized.class)
 public class BridgeTest extends BrambleTestCase {
 
+	private static final String[] SNOWFLAKE_COUNTRY_CODES = {"TM", "ZZ"};
+
 	@Parameters
 	public static Iterable<Params> data() {
 		BrambleJavaIntegrationTestComponent component =
@@ -69,23 +72,36 @@ public class BridgeTest extends BrambleTestCase {
 		CircumventionProvider provider = component.getCircumventionProvider();
 		List<Params> states = new ArrayList<>();
 		for (int i = 0; i < ATTEMPTS_PER_BRIDGE; i++) {
-			for (String bridge : provider.getBridges(DEFAULT_OBFS4)) {
+			for (String bridge :
+					provider.getBridges(DEFAULT_OBFS4, "", true)) {
 				states.add(new Params(bridge, DEFAULT_OBFS4, stats, false));
 			}
-			for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) {
-				states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats, false));
+			for (String bridge :
+					provider.getBridges(NON_DEFAULT_OBFS4, "", true)) {
+				states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats,
+						false));
 			}
-			for (String bridge : provider.getBridges(VANILLA)) {
+			for (String bridge : provider.getBridges(VANILLA, "", true)) {
 				states.add(new Params(bridge, VANILLA, stats, false));
 			}
-			for (String bridge : provider.getBridges(MEEK)) {
+			for (String bridge : provider.getBridges(MEEK, "", true)) {
 				states.add(new Params(bridge, MEEK, stats, true));
 			}
+			for (String countryCode : SNOWFLAKE_COUNTRY_CODES) {
+				for (String bridge :
+						provider.getBridges(SNOWFLAKE, countryCode, true)) {
+					states.add(new Params(bridge, SNOWFLAKE, stats, true));
+				}
+				for (String bridge :
+						provider.getBridges(SNOWFLAKE, countryCode, false)) {
+					states.add(new Params(bridge, SNOWFLAKE, stats, true));
+				}
+			}
 		}
 		return states;
 	}
 
-	private final static long OBFS4_TIMEOUT = MINUTES.toMillis(2);
+	private final static long TIMEOUT = MINUTES.toMillis(2);
 	private final static long MEEK_TIMEOUT = MINUTES.toMillis(6);
 	private final static int UNREACHABLE_BRIDGES_ALLOWED = 6;
 	private final static int ATTEMPTS_PER_BRIDGE = 5;
@@ -163,7 +179,8 @@ public class BridgeTest extends BrambleTestCase {
 			}
 
 			@Override
-			public List<String> getBridges(BridgeType bridgeType) {
+			public List<String> getBridges(BridgeType bridgeType,
+					String countryCode, boolean letsEncrypt) {
 				return singletonList(params.bridge);
 			}
 		};
@@ -190,8 +207,7 @@ public class BridgeTest extends BrambleTestCase {
 		try {
 			plugin.start();
 			long start = clock.currentTimeMillis();
-			long timeout = params.bridgeType == MEEK
-					? MEEK_TIMEOUT : OBFS4_TIMEOUT;
+			long timeout = params.bridgeType == MEEK ? MEEK_TIMEOUT : TIMEOUT;
 			while (clock.currentTimeMillis() - start < timeout) {
 				if (plugin.getState() == ACTIVE) return;
 				clock.sleep(500);
diff --git a/bramble-java/witness.gradle b/bramble-java/witness.gradle
index 97577bb1a8acf9f1adf18dfed6d3120be9827c39..b9cb4a8126c8423fec83e145941cbf673d2c2368 100644
--- a/bramble-java/witness.gradle
+++ b/bramble-java/witness.gradle
@@ -26,6 +26,8 @@ dependencyVerification {
         'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
         'org.briarproject:obfs4proxy-linux:0.0.14:obfs4proxy-linux-0.0.14.jar:6391d323d45a279362236c7c62e21b903d07d4f31f5e0c8d49d009769b720cc6',
         'org.briarproject:obfs4proxy-windows:0.0.14:obfs4proxy-windows-0.0.14.jar:801d48525f52583a470a1671026b87992176d4432b299774989387cb87bc8ba3',
+        'org.briarproject:snowflake-linux:2.3.1:snowflake-linux-2.3.1.jar:99ecf4546d8f79eb8408168c09380fec596558ac934554bf7d4247ea7ef2c9f3',
+        'org.briarproject:snowflake-windows:2.3.1:snowflake-windows-2.3.1.jar:d011f1a72c00a221f56380c19aad8ff11db8c2bb1adb0784125572d80b4d275a',
         'org.briarproject:tor-linux:0.4.5.14:tor-linux-0.4.5.14.jar:1844e54cf6df0c85cec219381a3364c759ae444a6b63f7558b757becb7d41d08',
         'org.briarproject:tor-windows:0.4.5.14:tor-windows-0.4.5.14.jar:d337afa1043f0cfa7e6e8c2473d682a5663a2c8052bb97a770450893c78c9b4f',
         'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
diff --git a/briar-android/witness.gradle b/briar-android/witness.gradle
index 3893f1f72b2a64dca56b2a3e429497f3707513a2..d2a4e23194ea45d9fb6c41c350c25a2596157996 100644
--- a/briar-android/witness.gradle
+++ b/briar-android/witness.gradle
@@ -4,7 +4,7 @@ dependencyVerification {
         'androidx.activity:activity:1.2.2:activity-1.2.2.aar:e165fb20f006b77894d349572cc3acd2760baa8416ae4d33cb8de6a84dd6730c',
         'androidx.activity:activity:1.2.4:activity-1.2.4.aar:ae8e9c7de57e387d2ad90e73f3a5a5dfd502bd4f034c1dccfdb3506d1d2df81a',
         'androidx.annotation:annotation-experimental:1.0.0:annotation-experimental-1.0.0.aar:b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11',
-        'androidx.annotation:annotation:1.1.0:annotation-1.1.0.jar:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692',
+        'androidx.annotation:annotation:1.5.0:annotation-1.5.0.jar:261fb7c0210858500bab66d34354972a75166ab4182add283780b05513d6ec4a',
         'androidx.appcompat:appcompat-resources:1.2.0:appcompat-resources-1.2.0.aar:c470297c03ff3de1c3d15dacf0be0cae63abc10b52f021dd07ae28daa3100fe5',
         'androidx.appcompat:appcompat:1.2.0:appcompat-1.2.0.aar:3d2131a55a61a777322e2126e0018011efa6339e53b44153eb651b16020cca70',
         'androidx.arch.core:core-common:2.1.0:core-common-2.1.0.jar:fe1237bf029d063e7f29fe39aeaf73ef74c8b0a3658486fc29d3c54326653889',
@@ -227,13 +227,13 @@ dependencyVerification {
         'org.jetbrains.kotlin:kotlin-reflect:1.4.32:kotlin-reflect-1.4.32.jar:dbf19e9cdaa9c3c170f3f6f6ce3922f38dfc1d7fa1cab5b7c23a19da8b5eec5b',
         'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
         'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32:kotlin-stdlib-common-1.4.32.jar:e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145',
-        'org.jetbrains.kotlin:kotlin-stdlib-common:1.6.20:kotlin-stdlib-common-1.6.20.jar:8da40a2520d30dcb1012176fe93d24e82d08a3e346c37e0343b0fb6f64f6be01',
+        'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
         'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32:kotlin-stdlib-jdk7-1.4.32.jar:5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6',
         'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.20:kotlin-stdlib-jdk7-1.6.20.jar:aa2fa2e81355c4d98dd97da2169bf401f842261378f5b1cbea1aa11855d67620',
         'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:kotlin-stdlib-jdk8-1.4.32.jar:adc43e54757b106e0cd7b3b7aa257dff471b61efdabe067fc02b2f57e2396262',
         'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
         'org.jetbrains.kotlin:kotlin-stdlib:1.4.32:kotlin-stdlib-1.4.32.jar:13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba',
-        'org.jetbrains.kotlin:kotlin-stdlib:1.6.20:kotlin-stdlib-1.6.20.jar:eeb51c2b67b26233fd81d0bc4f8044ec849718890905763ceffd84a31e2cb799',
+        'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
         'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1:kotlinx-coroutines-android-1.4.1.jar:d4cadb673b2101f1ee5fbc147956ac78b1cfd9cc255fb53d3aeb88dff11d99ca',
         'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.1:kotlinx-coroutines-core-jvm-1.4.1.jar:6d2f87764b6638f27aff12ed380db4b63c9d46ba55dc32683a650598fa5a3e22',
         'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
diff --git a/build.gradle b/build.gradle
index 5d8a71affb3d56d8a2c367999ee9d052cdfbd2fc..f0de0c7809488bbe48c6df2c6fd774ee674e0480 100644
--- a/build.gradle
+++ b/build.gradle
@@ -40,6 +40,7 @@ buildscript {
 		jackson_version = "2.13.0"
 		tor_version = "0.4.5.14"
 		obfs4proxy_version = "0.0.14"
+		snowflake_version = "2.3.1"
 		junit_version = "4.13.2"
 		jmock_version = '2.12.0'
 	}