diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
index 55b3107f616ba28a906790bd20e8feef339c4096..df7f430bbe48c42f64406e73b64ed417d2750c51 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt
@@ -25,6 +25,7 @@ import de.rki.coronawarnapp.storage.TracingSettings
 import de.rki.coronawarnapp.submission.SubmissionRepository
 import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.ui.presencetracing.TraceLocationPreferences
+import de.rki.coronawarnapp.vaccination.core.repository.ValueSetsRepository
 import de.rki.coronawarnapp.vaccination.core.VaccinationPreferences
 import de.rki.coronawarnapp.vaccination.core.repository.VaccinationRepository
 import kotlinx.coroutines.sync.Mutex
@@ -63,6 +64,7 @@ class DataReset @Inject constructor(
     private val traceWarningRepository: TraceWarningRepository,
     private val coronaTestRepository: CoronaTestRepository,
     private val ratProfileSettings: RATProfileSettings,
+    private val valueSetsRepository: ValueSetsRepository,
     private val vaccinationPreferences: VaccinationPreferences,
     private val vaccinationRepository: VaccinationRepository,
 ) {
@@ -88,7 +90,6 @@ class DataReset @Inject constructor(
         riskLevelStorage.clear()
         contactDiaryPreferences.clear()
         traceLocationPreferences.clear()
-
         cwaSettings.clear()
         surveySettings.clear()
         analyticsSettings.clear()
@@ -110,6 +111,7 @@ class DataReset @Inject constructor(
         coronaTestRepository.clear()
         ratProfileSettings.deleteProfile()
 
+        valueSetsRepository.clear()
         vaccinationRepository.clear()
         vaccinationPreferences.clear()
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/ValueSetsRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/ValueSetsRepository.kt
index e3497c090778dcdf8079163b72f2bd4082338107..ca5f244a11c0785cd63b06bede6576f00c49579a 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/ValueSetsRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/ValueSetsRepository.kt
@@ -1,17 +1,77 @@
 package de.rki.coronawarnapp.vaccination.core.repository
 
-import de.rki.coronawarnapp.submission.server.SubmissionServer
+import dagger.Reusable
+import de.rki.coronawarnapp.util.coroutine.AppScope
+import de.rki.coronawarnapp.vaccination.core.repository.storage.ValueSetsStorage
+import de.rki.coronawarnapp.vaccination.core.server.valueset.DefaultVaccinationValueSet
+import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationServer
 import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet
-
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import timber.log.Timber
+import java.util.Locale
 import javax.inject.Inject
-import javax.inject.Singleton
 
-@Singleton
+@Reusable
 class ValueSetsRepository @Inject constructor(
-    private val submissionServer: SubmissionServer
+    private val vaccinationServer: VaccinationServer,
+    private val valueSetsStorage: ValueSetsStorage,
+    @AppScope appScope: CoroutineScope
 ) {
 
-    val latestValueSet: Flow<VaccinationValueSet?> = flowOf(null)
+    private val internalFlow = MutableStateFlow<Locale?>(null)
+
+    val latestValueSet: Flow<VaccinationValueSet?> = internalFlow
+        .filterNotNull()
+        .map {
+            fromServer(it) ?: fromLocalStorage() ?: fromServer(Locale.ENGLISH) ?: createEmptyValueSet(it)
+            // Return empty value set as last resort
+        }
+        .stateIn(
+            scope = appScope,
+            started = SharingStarted.Lazily,
+            initialValue = createEmptyValueSet(Locale.ENGLISH)
+        )
+
+    fun reloadValueSet(languageCode: Locale) {
+        Timber.d("reloadValueSet(languageCode=%s)", languageCode)
+        internalFlow.value = languageCode
+    }
+
+    private suspend fun fromServer(languageCode: Locale): VaccinationValueSet? = try {
+        Timber.d("fromServer(languageCode=%s)", languageCode)
+        vaccinationServer.getVaccinationValueSets(languageCode = languageCode)?.also {
+            Timber.d("Saving new value sets %s", it)
+            valueSetsStorage.vaccinationValueSet = it
+        }
+    } catch (e: Exception) {
+        Timber.w(e, "fromServer(): %s", e.message)
+        null
+    }
+
+    private fun fromLocalStorage(): VaccinationValueSet? = try {
+        Timber.d("fromLocalStorage()")
+        valueSetsStorage.vaccinationValueSet
+    } catch (e: Exception) {
+        Timber.w(e, "fromLocalStorage(): %s", e.message)
+        null
+    }
+
+    private fun createEmptyValueSet(languageCode: Locale) = DefaultVaccinationValueSet(
+        languageCode = languageCode,
+        vp = DefaultVaccinationValueSet.DefaultValueSet(items = emptyList()),
+        mp = DefaultVaccinationValueSet.DefaultValueSet(items = emptyList()),
+        ma = DefaultVaccinationValueSet.DefaultValueSet(items = emptyList())
+    )
+
+    fun clear() {
+        Timber.d("Clearing value sets")
+        vaccinationServer.clear()
+        valueSetsStorage.clear()
+    }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainer.kt
index 76ca4e4b5c2840800c9f0c0c19be965abad73ba9..d9320fb3136e8f1ea406c7f620cc605153b3a077 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainer.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainer.kt
@@ -13,6 +13,7 @@ import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData
 import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateQRCode
 import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationQRCodeExtractor
 import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet
+import de.rki.coronawarnapp.vaccination.core.server.valueset.getDisplayText
 import org.joda.time.Instant
 import org.joda.time.LocalDate
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/ValueSetsStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/ValueSetsStorage.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e9d91b475b72a8d521cbf6e649407bc7f7c08f4d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/repository/storage/ValueSetsStorage.kt
@@ -0,0 +1,98 @@
+package de.rki.coronawarnapp.vaccination.core.repository.storage
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.annotation.Keep
+import androidx.core.content.edit
+import com.google.gson.Gson
+import com.google.gson.annotations.SerializedName
+import dagger.Reusable
+import de.rki.coronawarnapp.util.di.AppContext
+import de.rki.coronawarnapp.util.preferences.clearAndNotify
+import de.rki.coronawarnapp.util.serialization.BaseGson
+import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet
+import timber.log.Timber
+import java.util.Locale
+import javax.inject.Inject
+
+@Reusable
+class ValueSetsStorage @Inject constructor(
+    @AppContext private val context: Context,
+    @BaseGson private val gson: Gson
+) {
+
+    private val prefs: SharedPreferences by lazy {
+        context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
+    }
+
+    var vaccinationValueSet: VaccinationValueSet?
+        get() = getValueSet()
+        set(value) = setValueSet(value)
+
+    private fun getValueSet(): VaccinationValueSet? {
+        Timber.d("Loading value set")
+        return prefs.getString(PKEY_VALUE_SETS_PREFIX, null)?.let {
+            gson.fromJson(it, StoredVaccinationValueSet::class.java).also { loaded -> Timber.d("Loaded %s", loaded) }
+        }.also { Timber.d("Returning %s", it) }
+    }
+
+    private fun setValueSet(value: VaccinationValueSet?) {
+        Timber.d("Saving %s", value)
+        value?.let {
+            prefs.edit {
+                val storeValue = it.toStoredVaccinationValueSet()
+                val json = gson.toJson(storeValue, StoredVaccinationValueSet::class.java)
+                Timber.d("String %s", json)
+                putString(PKEY_VALUE_SETS_PREFIX, json)
+            }
+        }
+    }
+
+    fun clear() {
+        Timber.d("Clearing local storage")
+        prefs.clearAndNotify()
+    }
+
+    @Keep
+    private data class StoredVaccinationValueSet(
+        @SerializedName("languageCode") override val languageCode: Locale,
+        @SerializedName("vp") override val vp: StoredValueSet,
+        @SerializedName("mp") override val mp: StoredValueSet,
+        @SerializedName("ma") override val ma: StoredValueSet
+    ) : VaccinationValueSet {
+
+        @Keep
+        data class StoredValueSet(
+            @SerializedName("items") override val items: List<StoredItem>
+        ) : VaccinationValueSet.ValueSet {
+
+            @Keep
+            data class StoredItem(
+                @SerializedName("key") override val key: String,
+                @SerializedName("displayText") override val displayText: String
+            ) : VaccinationValueSet.ValueSet.Item
+        }
+    }
+
+    private fun VaccinationValueSet.toStoredVaccinationValueSet(): StoredVaccinationValueSet =
+        StoredVaccinationValueSet(
+            languageCode = languageCode,
+            vp = vp.toStoredValueSet(),
+            mp = mp.toStoredValueSet(),
+            ma = ma.toStoredValueSet()
+        )
+
+    private fun VaccinationValueSet.ValueSet.toStoredValueSet(): StoredVaccinationValueSet.StoredValueSet =
+        StoredVaccinationValueSet.StoredValueSet(
+            items = items.map { it.toStoredItem() }
+        )
+
+    private fun VaccinationValueSet.ValueSet.Item.toStoredItem(): StoredVaccinationValueSet.StoredValueSet.StoredItem =
+        StoredVaccinationValueSet.StoredValueSet.StoredItem(
+            key = key,
+            displayText = displayText
+        )
+}
+
+private const val PREF_NAME = "valuesets_local"
+private const val PKEY_VALUE_SETS_PREFIX = "valuesets"
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/DefaultVaccinationValueSet.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/DefaultVaccinationValueSet.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1962cf330dbe26a192497bc4eeaeeb0e052b00d9
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/DefaultVaccinationValueSet.kt
@@ -0,0 +1,21 @@
+package de.rki.coronawarnapp.vaccination.core.server.valueset
+
+import java.util.Locale
+
+data class DefaultVaccinationValueSet(
+    override val languageCode: Locale,
+    override val vp: VaccinationValueSet.ValueSet,
+    override val mp: VaccinationValueSet.ValueSet,
+    override val ma: VaccinationValueSet.ValueSet
+) : VaccinationValueSet {
+
+    data class DefaultValueSet(
+        override val items: List<VaccinationValueSet.ValueSet.Item>
+    ) : VaccinationValueSet.ValueSet {
+
+        data class DefaultItem(
+            override val key: String,
+            override val displayText: String
+        ) : VaccinationValueSet.ValueSet.Item
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationServer.kt
index cb64715a67d263d703c095a9e64ad11c925e006c..0d108cd2aac3b3b439cf8a679a82e029eb8d788e 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationServer.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationServer.kt
@@ -1,7 +1,19 @@
 package de.rki.coronawarnapp.vaccination.core.server.valueset
 
+import dagger.Lazy
 import dagger.Reusable
+import de.rki.coronawarnapp.server.protocols.internal.dgc.ValueSetsOuterClass
+import de.rki.coronawarnapp.util.ZipHelper.readIntoMap
+import de.rki.coronawarnapp.util.ZipHelper.unzip
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.security.SignatureValidation
+import de.rki.coronawarnapp.vaccination.core.server.valueset.internal.ValueSetInvalidSignatureException
+import de.rki.coronawarnapp.vaccination.core.server.valueset.internal.toVaccinationValueSet
+import kotlinx.coroutines.withContext
 import okhttp3.Cache
+import okhttp3.ResponseBody
+import retrofit2.HttpException
+import retrofit2.Response
 import timber.log.Timber
 import java.util.Locale
 import javax.inject.Inject
@@ -11,11 +23,52 @@ import javax.inject.Inject
  */
 @Reusable
 class VaccinationServer @Inject constructor(
-    @VaccinationValueSetHttpClient private val cache: Cache
+    @ValueSet private val cache: Cache,
+    private val apiV1: Lazy<VaccinationValueSetApiV1>,
+    private val dispatcherProvider: DispatcherProvider,
+    private val signatureValidation: SignatureValidation
 ) {
 
-    suspend fun getVaccinationValueSets(languageCode: Locale): VaccinationValueSet {
-        throw NotImplementedError()
+    suspend fun getVaccinationValueSets(languageCode: Locale): VaccinationValueSet? =
+        withContext(dispatcherProvider.Default) {
+            return@withContext try {
+                val response = requestValueSets(languageCode.language)
+                if (!response.isSuccessful) throw HttpException(response)
+
+                val body = requireNotNull(response.body()) { "Body of response was null" }
+                val valueSetsProtobuf = body.parseBody()
+                valueSetsProtobuf.toVaccinationValueSet(languageCode = languageCode)
+            } catch (e: Exception) {
+                Timber.e(e, "Getting vaccination value sets from server failed cause: ${e.message}")
+                null
+            }
+        }
+
+    private suspend fun requestValueSets(languageCode: String): Response<ResponseBody> =
+        withContext(dispatcherProvider.IO) {
+            Timber.d("Requesting value sets for language $languageCode from server")
+            apiV1.get().getValueSets(languageCode = languageCode)
+        }
+
+    private fun ResponseBody.parseBody(): ValueSetsOuterClass.ValueSets {
+        val fileMap = this.byteStream().unzip().readIntoMap()
+
+        val exportBinary = fileMap[EXPORT_BINARY_FILE_NAME]
+        val exportSignature = fileMap[EXPORT_SIGNATURE_FILE_NAME]
+
+        if (exportBinary == null || exportSignature == null)
+            throw ValueSetInvalidSignatureException(msg = "Unknown files ${fileMap.entries}")
+
+        val hasValidSignature = signatureValidation.hasValidSignature(
+            toVerify = exportBinary,
+            signatureList = SignatureValidation.parseTEKStyleSignature(exportSignature)
+        )
+
+        if (!hasValidSignature) {
+            throw ValueSetInvalidSignatureException(msg = "Signature of value sets did not match")
+        }
+
+        return ValueSetsOuterClass.ValueSets.parseFrom(exportBinary)
     }
 
     fun clear() {
@@ -24,3 +77,6 @@ class VaccinationServer @Inject constructor(
         cache.evictAll()
     }
 }
+
+private const val EXPORT_BINARY_FILE_NAME = "export.bin"
+private const val EXPORT_SIGNATURE_FILE_NAME = "export.sig"
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSet.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSet.kt
index 6bace45c474e8760501732e684653ab8c7360ea8..2ebc6e08a1f3ce0f9de7d1f0deeaa12ff9acebf2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSet.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSet.kt
@@ -1,9 +1,27 @@
 package de.rki.coronawarnapp.vaccination.core.server.valueset
 
+import androidx.annotation.Keep
 import java.util.Locale
 
+@Keep
 interface VaccinationValueSet {
     val languageCode: Locale
+    val vp: ValueSet
+    val mp: ValueSet
+    val ma: ValueSet
 
-    fun getDisplayText(key: String): String?
+    interface ValueSet {
+        val items: List<Item>
+
+        // Use custom item instead of map to allow for future extensions
+        interface Item {
+            val key: String
+            val displayText: String
+        }
+    }
 }
+
+fun VaccinationValueSet.getDisplayText(key: String): String? =
+    vp.getDisplayText(key) ?: mp.getDisplayText(key) ?: ma.getDisplayText(key)
+
+fun VaccinationValueSet.ValueSet.getDisplayText(key: String): String? = items.find { key == it.key }?.displayText
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSetModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSetModule.kt
index 1a4d73746910ef349635ffe3e5e95f0c1a0f0807..0fd80cda3bf80f09da0109692a134f4995a9ff1f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSetModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSetModule.kt
@@ -4,48 +4,67 @@ import android.content.Context
 import dagger.Module
 import dagger.Provides
 import dagger.Reusable
-import de.rki.coronawarnapp.environment.vaccination.VaccinationCertificateCDNUrl
-import de.rki.coronawarnapp.http.HttpClientDefault
+import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient
+import de.rki.coronawarnapp.environment.download.DownloadCDNServerUrl
 import de.rki.coronawarnapp.util.di.AppContext
 import okhttp3.Cache
+import okhttp3.CacheControl
+import okhttp3.Interceptor
 import okhttp3.OkHttpClient
+import okhttp3.Response
 import retrofit2.Retrofit
 import java.io.File
+import java.util.concurrent.TimeUnit
 
 @Module
 class VaccinationValueSetModule {
 
     @Reusable
-    @VaccinationValueSetHttpClient
+    @ValueSet
     @Provides
     fun cache(
         @AppContext context: Context
     ): Cache {
         val cacheDir = File(context.cacheDir, "vaccination_value")
-        val cacheFile = File(cacheDir, "http_cache")
-        return Cache(cacheFile, CACHE_SIZE_5MB)
+        return Cache(cacheDir, CACHE_SIZE_5MB)
     }
 
-    @Reusable
-    @VaccinationValueSetHttpClient
-    @Provides
-    fun httpClient(
-        @HttpClientDefault defaultHttpClient: OkHttpClient,
-        @VaccinationValueSetHttpClient cache: Cache
-    ): OkHttpClient = defaultHttpClient.newBuilder()
-        .cache(cache)
-        .build()
-
     @Reusable
     @Provides
     fun api(
-        @VaccinationValueSetHttpClient httpClient: OkHttpClient,
-        @VaccinationCertificateCDNUrl url: String
-    ): VaccinationValueSetApiV1 = Retrofit.Builder()
-        .client(httpClient)
-        .baseUrl(url)
-        .build()
-        .create(VaccinationValueSetApiV1::class.java)
+        @DownloadCDNHttpClient httpClient: OkHttpClient,
+        @DownloadCDNServerUrl url: String,
+        @ValueSet cache: Cache
+    ): VaccinationValueSetApiV1 {
+        val client = httpClient.newBuilder()
+            .addNetworkInterceptor(CacheInterceptor())
+            .cache(cache)
+            .build()
+
+        return Retrofit.Builder()
+            .client(client)
+            .baseUrl(url)
+            .build()
+            .create(VaccinationValueSetApiV1::class.java)
+    }
+
+    private class CacheInterceptor : Interceptor {
+        override fun intercept(chain: Interceptor.Chain): Response {
+            val response = chain.proceed(chain.request())
+
+            val cacheControl = CacheControl.Builder()
+                .maxAge(300, TimeUnit.SECONDS)
+                .build()
+
+            // We cache as we please
+            val cacheHeader = "Cache-Control"
+            return response.newBuilder()
+                .removeHeader("Pragma")
+                .removeHeader(cacheHeader)
+                .addHeader(cacheHeader, cacheControl.toString())
+                .build()
+        }
+    }
 
     companion object {
         private const val CACHE_SIZE_5MB = 5 * 1024 * 1024L // 5MB
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSetHttpClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/ValueSet.kt
similarity index 77%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSetHttpClient.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/ValueSet.kt
index 1650bcb5f61039656a1cc3e51485dacf03e5317b..1300c045c0d8f448781ddcaa327bb7f2959fe09f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/VaccinationValueSetHttpClient.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/ValueSet.kt
@@ -5,4 +5,4 @@ import javax.inject.Qualifier
 @Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
-annotation class VaccinationValueSetHttpClient
+annotation class ValueSet
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/internal/VaccinationValueSetMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/internal/VaccinationValueSetMapper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ad13f40aace7a84a4d73ac99b0d5b836fa8a2185
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/internal/VaccinationValueSetMapper.kt
@@ -0,0 +1,28 @@
+package de.rki.coronawarnapp.vaccination.core.server.valueset.internal
+
+import de.rki.coronawarnapp.server.protocols.internal.dgc.ValueSetsOuterClass
+import de.rki.coronawarnapp.vaccination.core.server.valueset.DefaultVaccinationValueSet
+import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet
+import timber.log.Timber
+import java.util.Locale
+
+internal fun ValueSetsOuterClass.ValueSets.toVaccinationValueSet(languageCode: Locale): VaccinationValueSet {
+    Timber.d("toVaccinationValueSet(valueSets=%s, languageCode=%s)", this, languageCode)
+    return DefaultVaccinationValueSet(
+        languageCode = languageCode,
+        vp = vp.toValueSet(),
+        mp = mp.toValueSet(),
+        ma = ma.toValueSet()
+    ).also { Timber.tag(TAG).d("Created %s", it) }
+}
+
+internal fun ValueSetsOuterClass.ValueSet.toValueSet(): VaccinationValueSet.ValueSet =
+    DefaultVaccinationValueSet.DefaultValueSet(items = itemsList.map { it.toItem() })
+
+internal fun ValueSetsOuterClass.ValueSetItem.toItem(): VaccinationValueSet.ValueSet.Item =
+    DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
+        key = key,
+        displayText = displayText
+    )
+
+private const val TAG: String = "ValueSetMapper"
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/internal/ValueSetInvalidSignatureException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/internal/ValueSetInvalidSignatureException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6de24adafb228da191c3f7d076ef1729b8ad3540
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/core/server/valueset/internal/ValueSetInvalidSignatureException.kt
@@ -0,0 +1,9 @@
+package de.rki.coronawarnapp.vaccination.core.server.valueset.internal
+
+import de.rki.coronawarnapp.exception.reporting.ErrorCodes
+import de.rki.coronawarnapp.util.security.InvalidSignatureException
+
+class ValueSetInvalidSignatureException(msg: String) : InvalidSignatureException(
+    code = ErrorCodes.APPLICATION_CONFIGURATION_INVALID.code,
+    message = msg
+)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListViewModel.kt
index 0ed0550a991257e869ab11c699eacc6153f71707..a8dbcba0f7b644b09a507dda3bf45869e9b8ea71 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/vaccination/ui/list/VaccinationListViewModel.kt
@@ -1,17 +1,21 @@
 package de.rki.coronawarnapp.vaccination.ui.list
 
+import android.content.Context
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.asLiveData
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.contactdiary.util.getLocale
 import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat
+import de.rki.coronawarnapp.util.di.AppContext
 import de.rki.coronawarnapp.util.ui.SingleLiveEvent
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson
 import de.rki.coronawarnapp.vaccination.core.VaccinatedPerson.Status.COMPLETE
 import de.rki.coronawarnapp.vaccination.core.repository.VaccinationRepository
+import de.rki.coronawarnapp.vaccination.core.repository.ValueSetsRepository
 import de.rki.coronawarnapp.vaccination.ui.list.adapter.VaccinationListItem
 import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListIncompleteTopCardItemVH.VaccinationListIncompleteTopCardItem
 import de.rki.coronawarnapp.vaccination.ui.list.adapter.viewholder.VaccinationListNameCardItemVH.VaccinationListNameCardItem
@@ -21,9 +25,15 @@ import kotlinx.coroutines.flow.map
 
 class VaccinationListViewModel @AssistedInject constructor(
     vaccinationRepository: VaccinationRepository,
+    valueSetsRepository: ValueSetsRepository,
+    @AppContext context: Context,
     @Assisted private val personIdentifierCodeSha256: String
 ) : CWAViewModel() {
 
+    init {
+        valueSetsRepository.reloadValueSet(languageCode = context.getLocale())
+    }
+
     val events = SingleLiveEvent<Event>()
 
     private val vaccinatedPersonFlow = vaccinationRepository.vaccinationInfos.map { vaccinatedPersonSet ->
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt
index 1dfedac57403a304eb753e1193f923402fbab5b2..5577560dc72f94ae8b79716c375227c270df1e73 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt
@@ -26,6 +26,7 @@ import de.rki.coronawarnapp.submission.SubmissionSettings
 import de.rki.coronawarnapp.ui.presencetracing.TraceLocationPreferences
 import de.rki.coronawarnapp.vaccination.core.repository.VaccinationRepository
 import de.rki.coronawarnapp.vaccination.core.VaccinationPreferences
+import de.rki.coronawarnapp.vaccination.core.repository.ValueSetsRepository
 import io.mockk.MockKAnnotations
 import io.mockk.coVerify
 import io.mockk.impl.annotations.MockK
@@ -62,6 +63,7 @@ internal class DataResetTest : BaseTest() {
     @MockK lateinit var ratProfileSettings: RATProfileSettings
     @MockK lateinit var vaccinationRepository: VaccinationRepository
     @MockK lateinit var vaccinationPreferences: VaccinationPreferences
+    @MockK lateinit var valueSetsRepository: ValueSetsRepository
 
     @BeforeEach
     fun setUp() {
@@ -95,6 +97,7 @@ internal class DataResetTest : BaseTest() {
         ratProfileSettings = ratProfileSettings,
         vaccinationPreferences = vaccinationPreferences,
         vaccinationRepository = vaccinationRepository,
+        valueSetsRepository = valueSetsRepository
     )
 
     @Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/ValueSetsRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/ValueSetsRepositoryTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3edc082661868d7de4748dcb8356778d988835a2
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/ValueSetsRepositoryTest.kt
@@ -0,0 +1,213 @@
+package de.rki.coronawarnapp.vaccination.core.repository
+
+import de.rki.coronawarnapp.vaccination.core.repository.storage.ValueSetsStorage
+import de.rki.coronawarnapp.vaccination.core.server.valueset.DefaultVaccinationValueSet
+import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationServer
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.runs
+import io.mockk.verify
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+import java.util.Locale
+
+class ValueSetsRepositoryTest : BaseTest() {
+
+    @MockK lateinit var vaccinationServer: VaccinationServer
+    @MockK lateinit var valueSetsStorage: ValueSetsStorage
+
+    private val testScope = TestCoroutineScope()
+
+    private val emptyValueSetEN = createValueSet(languageCode = Locale.ENGLISH)
+    private val emptyValueSetDE = createValueSet(languageCode = Locale.GERMAN)
+
+    private val valueSetEN = createValueSet(
+        languageCode = Locale.ENGLISH,
+        vpItems = listOf(
+            DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
+                key = "1119305005",
+                displayText = "Vaccine-Name"
+            )
+        ),
+        mpItems = listOf(
+            DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
+                key = "EU/1/21/1529",
+                displayText = "MedicalProduct-Name"
+            )
+        ),
+        maItems = listOf(
+            DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
+                key = "ORG-100001699",
+                displayText = "Manufactorer-Name"
+            )
+        )
+    )
+
+    private val valueSetDE = createValueSet(
+        languageCode = Locale.GERMAN,
+        vpItems = listOf(
+            DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
+                key = "1119305005",
+                displayText = "Impfstoff-Name"
+            )
+        ),
+        mpItems = listOf(
+            DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
+                key = "EU/1/21/1529",
+                displayText = "Arzneimittel-Name"
+            )
+        ),
+        maItems = listOf(
+            DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
+                key = "ORG-100001699",
+                displayText = "Hersteller-Name"
+            )
+        )
+    )
+
+    private fun createValueSet(
+        languageCode: Locale,
+        vpItems: List<DefaultVaccinationValueSet.DefaultValueSet.DefaultItem> = emptyList(),
+        mpItems: List<DefaultVaccinationValueSet.DefaultValueSet.DefaultItem> = emptyList(),
+        maItems: List<DefaultVaccinationValueSet.DefaultValueSet.DefaultItem> = emptyList()
+    ) = DefaultVaccinationValueSet(
+        languageCode = languageCode,
+        vp = DefaultVaccinationValueSet.DefaultValueSet(items = vpItems),
+        mp = DefaultVaccinationValueSet.DefaultValueSet(items = mpItems),
+        ma = DefaultVaccinationValueSet.DefaultValueSet(items = maItems)
+    )
+
+    private fun createInstance() = ValueSetsRepository(
+        vaccinationServer = vaccinationServer,
+        valueSetsStorage = valueSetsStorage,
+        appScope = testScope
+    )
+
+    @BeforeEach
+    fun setUp() {
+        MockKAnnotations.init(this)
+        coEvery { vaccinationServer.getVaccinationValueSets(any()) } returns null
+        every { vaccinationServer.clear() } just runs
+        every { valueSetsStorage.vaccinationValueSet } returns null
+        every { valueSetsStorage.vaccinationValueSet = any() } just runs
+        every { valueSetsStorage.clear() } just runs
+    }
+
+    @Test
+    fun `default value is an empty value set EN`() = runBlockingTest {
+        createInstance().run {
+            latestValueSet.first() shouldBe emptyValueSetEN
+
+            coVerify(exactly = 0) {
+                vaccinationServer.getVaccinationValueSets(any())
+                valueSetsStorage.vaccinationValueSet
+            }
+        }
+    }
+
+    @Test
+    fun `falls back to empty value set with specified language code`() = runBlockingTest {
+        createInstance().run {
+            reloadValueSet(languageCode = Locale.GERMAN)
+            latestValueSet.first() shouldBe emptyValueSetDE
+
+            coVerify(exactly = 1) {
+                valueSetsStorage.vaccinationValueSet
+                vaccinationServer.getVaccinationValueSets(Locale.GERMAN)
+                vaccinationServer.getVaccinationValueSets(Locale.ENGLISH)
+            }
+        }
+    }
+
+    @Test
+    fun `returns value set for specified language from server`() = runBlockingTest {
+        coEvery { vaccinationServer.getVaccinationValueSets(Locale.GERMAN) } returns valueSetDE
+        coEvery { vaccinationServer.getVaccinationValueSets(Locale.ENGLISH) } returns valueSetEN
+
+        createInstance().run {
+            reloadValueSet(Locale.GERMAN)
+            latestValueSet.first() shouldBe valueSetDE
+
+            reloadValueSet(Locale.ENGLISH)
+            latestValueSet.first() shouldBe valueSetEN
+        }
+
+        verify(exactly = 0) {
+            valueSetsStorage.vaccinationValueSet
+        }
+
+        coVerify(exactly = 1) {
+            vaccinationServer.getVaccinationValueSets(Locale.GERMAN)
+            vaccinationServer.getVaccinationValueSets(Locale.ENGLISH)
+        }
+    }
+
+    @Test
+    fun `if no value set is available for specified language fall back to EN`() = runBlockingTest {
+        coEvery { vaccinationServer.getVaccinationValueSets(Locale.ENGLISH) } returns valueSetEN
+
+        createInstance().run {
+            reloadValueSet(Locale.GERMAN)
+            latestValueSet.first() shouldBe valueSetEN
+
+            coVerify(exactly = 1) {
+                vaccinationServer.getVaccinationValueSets(Locale.GERMAN)
+                vaccinationServer.getVaccinationValueSets(Locale.ENGLISH)
+            }
+        }
+    }
+
+    @Test
+    fun `use local storage if server returns nothing`() = runBlockingTest {
+        every { valueSetsStorage.vaccinationValueSet } returns valueSetDE
+
+        createInstance().run {
+            reloadValueSet(Locale.GERMAN)
+            latestValueSet.first() shouldBe valueSetDE
+
+            coVerify(exactly = 1) {
+                valueSetsStorage.vaccinationValueSet
+                vaccinationServer.getVaccinationValueSets(any())
+            }
+        }
+    }
+
+    @Test
+    fun `user errors will not crash the app`() = runBlockingTest {
+        val userError = Exception("User error")
+        coEvery { vaccinationServer.getVaccinationValueSets(any()) } throws userError
+        every { valueSetsStorage.vaccinationValueSet } throws userError
+
+        createInstance().run {
+            reloadValueSet(Locale.GERMAN)
+            latestValueSet.first() shouldBe emptyValueSetDE
+
+            coVerify(exactly = 1) {
+                valueSetsStorage.vaccinationValueSet
+                vaccinationServer.getVaccinationValueSets(Locale.GERMAN)
+                vaccinationServer.getVaccinationValueSets(Locale.ENGLISH)
+            }
+        }
+    }
+
+    @Test
+    fun `clear() clears server and local storage`() {
+        createInstance().run {
+            clear()
+
+            coVerify(exactly = 1) {
+                vaccinationServer.clear()
+                valueSetsStorage.clear()
+            }
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainerTest.kt
index ec211990b19279cdc68d54302cbcbab0f21d022b..6aaf73ff21566a7612d6f288eb44178045e764f8 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainerTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/vaccination/core/repository/storage/VaccinationContainerTest.kt
@@ -83,11 +83,31 @@ class VaccinationContainerTest : BaseTest() {
 
     @Test
     fun `mapping to user facing data - with valueset`() {
+        val vpItem = mockk<VaccinationValueSet.ValueSet.Item> {
+            every { key } returns "1119305005"
+            every { displayText } returns "Vaccine-Name"
+        }
+
+        val mpItem = mockk<VaccinationValueSet.ValueSet.Item> {
+            every { key } returns "EU/1/21/1529"
+            every { displayText } returns "MedicalProduct-Name"
+        }
+
+        val maItem = mockk<VaccinationValueSet.ValueSet.Item> {
+            every { key } returns "ORG-100001699"
+            every { displayText } returns "Manufactorer-Name"
+        }
+
+        val vpMockk = mockk<VaccinationValueSet.ValueSet> {
+            every { items } returns listOf(vpItem, mpItem, maItem)
+        }
+
         val valueSet = mockk<VaccinationValueSet> {
-            every { getDisplayText("ORG-100001699") } returns "Manufactorer-Name"
-            every { getDisplayText("EU/1/21/1529") } returns "MedicalProduct-Name"
-            every { getDisplayText("1119305005") } returns "Vaccine-Name"
+            every { vp } returns vpMockk
+            every { mp } returns vpMockk
+            every { ma } returns vpMockk
         }
+
         testData.personAVac1Container.toVaccinationCertificate(valueSet).apply {
             firstName shouldBe "Andreas"
             lastName shouldBe "Astrá Eins"