Skip to content

Commit

Permalink
Merge branch 'tim/bad_gateway' into 'master'
Browse files Browse the repository at this point in the history
TEP 502: E2E passphrase

See merge request TankerHQ/sdk-android!260
  • Loading branch information
tux3 committed Jun 27, 2022
2 parents af3d484 + e671883 commit b8ae641
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 26 deletions.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
159 changes: 138 additions & 21 deletions tanker-bindings/src/androidTest/kotlin/io/tanker/api/Unlock.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import io.tanker.api.admin.TankerAppUpdateOptions
import io.tanker.api.errors.InvalidArgument
import io.tanker.api.errors.InvalidVerification
import io.tanker.api.errors.PreconditionFailed
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
Expand All @@ -21,11 +23,22 @@ class UnlockTests : TankerSpec() {
@Before
fun beforeTest() {
identity = tc.createIdentity()
tanker1 = Tanker(options.setPersistentPath(createTmpDir().toString()).setCachePath(createTmpDir().toString()))
tanker2 = Tanker(options.setPersistentPath(createTmpDir().toString()).setCachePath(createTmpDir().toString()))
tanker1 = Tanker(
options.setPersistentPath(createTmpDir().toString())
.setCachePath(createTmpDir().toString())
)
tanker2 = Tanker(
options.setPersistentPath(createTmpDir().toString())
.setCachePath(createTmpDir().toString())
)
}

private fun checkSessionToken(publicIdentity: String, token: String, allowedMethod: String, value: String? = null): String {
private fun checkSessionToken(
publicIdentity: String,
token: String,
allowedMethod: String,
value: String? = null
): String {
val jsonMapper = ObjectMapper()
val jsonAllowedMethod = jsonMapper.createObjectNode()
jsonAllowedMethod.put("type", allowedMethod)
Expand All @@ -48,12 +61,12 @@ class UnlockTests : TankerSpec() {

val url = tc.trustchaindUrl()
val request = Request.Builder()
.url("$url/verification/session-token")
.post(jsonBody.toRequestBody(HttpClient.JSON))
.build()
.url("$url/verification/session-token")
.post(jsonBody.toRequestBody(HttpClient.JSON))
.build()
val response = OkHttpClient().newCall(request).execute()
if (!response.isSuccessful)
throw RuntimeException("Check session token request failed: "+response.body?.string())
throw RuntimeException("Check session token request failed: " + response.body?.string())
val jsonResponse = jsonMapper.readTree(response.body?.string())
return jsonResponse.get("verification_method").asText()
}
Expand Down Expand Up @@ -147,7 +160,13 @@ class UnlockTests : TankerSpec() {
val verificationCode = tc.getSMSVerificationCode(phoneNumber)
tanker1.registerIdentity(PhoneNumberVerification(phoneNumber, verificationCode)).get()
val methods = tanker1.getVerificationMethods().get()
assertThat(methods).containsExactlyInAnyOrderElementsOf(listOf(PhoneNumberVerificationMethod(phoneNumber)))
assertThat(methods).containsExactlyInAnyOrderElementsOf(
listOf(
PhoneNumberVerificationMethod(
phoneNumber
)
)
)
tanker1.stop().get()
}

Expand Down Expand Up @@ -210,8 +229,8 @@ class UnlockTests : TankerSpec() {
val martineIdentity = tc.createIdentity(martineConfig.email)

val appOptions = TankerAppUpdateOptions()
.setOidcClientId(oidcConfig.clientId)
.setOidcClientProvider(oidcConfig.provider)
.setOidcClientId(oidcConfig.clientId)
.setOidcClientProvider(oidcConfig.provider)
tc.admin.appUpdate(tc.id(), appOptions)

// Get a fresh OIDC ID token from GOOG
Expand All @@ -224,9 +243,9 @@ class UnlockTests : TankerSpec() {
val jsonBody = jsonMapper.writeValueAsString(jsonObj)

val request = Request.Builder()
.url("https://www.googleapis.com/oauth2/v4/token")
.post(jsonBody.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()!!))
.build()
.url("https://www.googleapis.com/oauth2/v4/token")
.post(jsonBody.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()!!))
.build()
val response = OkHttpClient().newCall(request).execute()
if (!response.isSuccessful)
throw java.lang.RuntimeException("Google OAuth test request failed!")
Expand Down Expand Up @@ -320,7 +339,8 @@ class UnlockTests : TankerSpec() {
assertThat(notToken).isNull()

var verificationCode = tc.getEmailVerificationCode(email)
val token = tanker1.setVerificationMethod(EmailVerification(email, verificationCode), options).get()
val token =
tanker1.setVerificationMethod(EmailVerification(email, verificationCode), options).get()
assertThat(token).isNotBlank

val publicIdentity = Identity.getPublicIdentity(identity)
Expand All @@ -342,7 +362,10 @@ class UnlockTests : TankerSpec() {
assertThat(notToken).isNull()

var verificationCode = tc.getSMSVerificationCode(phoneNumber)
val token = tanker1.setVerificationMethod(PhoneNumberVerification(phoneNumber, verificationCode), options).get()
val token = tanker1.setVerificationMethod(
PhoneNumberVerification(phoneNumber, verificationCode),
options
).get()
assertThat(token).isNotBlank

val publicIdentity = Identity.getPublicIdentity(identity)
Expand Down Expand Up @@ -432,12 +455,12 @@ class UnlockTests : TankerSpec() {
tanker1.start(identity).get()
tanker1.registerIdentity(PassphraseVerification(pass)).get()
assertThat(tanker1.getVerificationMethods().get()).containsExactly(
PassphraseVerificationMethod
PassphraseVerificationMethod
)

tanker1.setVerificationMethod(PreverifiedEmailVerification(email)).get()
assertThat(tanker1.getVerificationMethods().get()).containsExactlyInAnyOrder(
PreverifiedEmailVerificationMethod(email), PassphraseVerificationMethod
PreverifiedEmailVerificationMethod(email), PassphraseVerificationMethod
)

tanker2.start(identity).get()
Expand All @@ -446,7 +469,7 @@ class UnlockTests : TankerSpec() {
assertThat(tanker2.getStatus()).isEqualTo(Status.READY)

assertThat(tanker1.getVerificationMethods().get()).containsExactlyInAnyOrder(
EmailVerificationMethod(email), PassphraseVerificationMethod
EmailVerificationMethod(email), PassphraseVerificationMethod
)

tanker1.stop().get()
Expand All @@ -464,12 +487,12 @@ class UnlockTests : TankerSpec() {
tanker1.start(identity).get()
tanker1.registerIdentity(PassphraseVerification(pass)).get()
assertThat(tanker1.getVerificationMethods().get()).containsExactly(
PassphraseVerificationMethod
PassphraseVerificationMethod
)

tanker1.setVerificationMethod(PreverifiedPhoneNumberVerification(phoneNumber)).get()
assertThat(tanker1.getVerificationMethods().get()).containsExactlyInAnyOrder(
PreverifiedPhoneNumberVerificationMethod(phoneNumber), PassphraseVerificationMethod
PreverifiedPhoneNumberVerificationMethod(phoneNumber), PassphraseVerificationMethod
)

tanker2.start(identity).get()
Expand All @@ -478,10 +501,104 @@ class UnlockTests : TankerSpec() {
assertThat(tanker2.getStatus()).isEqualTo(Status.READY)

assertThat(tanker1.getVerificationMethods().get()).containsExactlyInAnyOrder(
PhoneNumberVerificationMethod(phoneNumber), PassphraseVerificationMethod
PhoneNumberVerificationMethod(phoneNumber), PassphraseVerificationMethod
)

tanker1.stop().get()
tanker2.stop().get()
}

@Test
fun test_register_e2e_passphrase() {
val passphrase = "mangerbouger.fr"
tanker1.start(identity).get()
tanker1.registerIdentity(E2ePassphraseVerification(passphrase)).get()

tanker2.start(identity).get()
tanker2.verifyIdentity(E2ePassphraseVerification(passphrase)).get()
assertThat(tanker2.getStatus()).isEqualTo(Status.READY)

tanker1.stop().get()
tanker2.stop().get()
}

@Test
fun test_update_e2e_passphrase() {
val oldPassphrase = "alkalosis"
val newPassphrase = "acidosis"
tanker1.start(identity).get()
tanker1.registerIdentity(E2ePassphraseVerification(oldPassphrase)).get()
tanker1.setVerificationMethod(E2ePassphraseVerification(newPassphrase)).get()
tanker1.stop().get()

tanker2.start(identity).get()
val e = shouldThrow<TankerFutureException> {
tanker2.verifyIdentity(E2ePassphraseVerification(oldPassphrase)).get()
}
assertThat(e.cause).hasCauseInstanceOf(InvalidVerification::class.java)

tanker2.verifyIdentity(E2ePassphraseVerification(newPassphrase)).get()
assertThat(tanker2.getStatus()).isEqualTo(Status.READY)
tanker2.stop().get()
}

@Test
fun test_switch_to_e2e_passphrase() {
val oldPassphrase = "alkalosis"
val newPassphrase = "acidosis"
tanker1.start(identity).get()
tanker1.registerIdentity(PassphraseVerification(oldPassphrase)).get()
tanker1.setVerificationMethod(
E2ePassphraseVerification(newPassphrase),
VerificationOptions().allowE2eMethodSwitch(true)
).get()
tanker1.stop().get()

tanker2.start(identity).get()
val e = shouldThrow<TankerFutureException> {
tanker2.verifyIdentity(PassphraseVerification(oldPassphrase)).get()
}
assertThat(e.cause).hasCauseInstanceOf(PreconditionFailed::class.java)

tanker2.verifyIdentity(E2ePassphraseVerification(newPassphrase)).get()
assertThat(tanker2.getStatus()).isEqualTo(Status.READY)
tanker2.stop().get()
}

fun test_switch_from_e2e_passphrase() {
val oldPassphrase = "alkalosis"
val newPassphrase = "acidosis"
tanker1.start(identity).get()
tanker1.registerIdentity(E2ePassphraseVerification(oldPassphrase)).get()
tanker1.setVerificationMethod(
PassphraseVerification(newPassphrase),
VerificationOptions().allowE2eMethodSwitch(true)
).get()
tanker1.stop().get()

tanker2.start(identity).get()
val e = shouldThrow<TankerFutureException> {
tanker2.verifyIdentity(E2ePassphraseVerification(oldPassphrase)).get()
}
assertThat(e.cause).hasCauseInstanceOf(PreconditionFailed::class.java)

tanker2.verifyIdentity(PassphraseVerification(newPassphrase)).get()
assertThat(tanker2.getStatus()).isEqualTo(Status.READY)
tanker2.stop().get()
}

@Test
fun test_cannot_switch_to_e2e_passphrase_without_allow_e2e_switch_flag() {
val oldPassphrase = "alkalosis"
val newPassphrase = "acidosis"
tanker1.start(identity).get()
tanker1.registerIdentity(PassphraseVerification(oldPassphrase)).get()
val e = shouldThrow<TankerFutureException> {
tanker1.setVerificationMethod(
E2ePassphraseVerification(newPassphrase),
).get()
}
assertThat(e.cause).hasCauseInstanceOf(InvalidArgument::class.java)
tanker1.stop().get()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ data class OIDCIDTokenVerification(val oidcIDToken: String) : Verification()
data class PhoneNumberVerification(val phoneNumber: String, val verificationCode: String) : Verification()
data class PreverifiedEmailVerification(val preverifiedEmail: String) : Verification()
data class PreverifiedPhoneNumberVerification(val preverifiedPhoneNumber: String) : Verification()
data class E2ePassphraseVerification(val e2ePassphrase: String) : Verification()
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ object OIDCIDTokenVerificationMethod : VerificationMethod()
data class PhoneNumberVerificationMethod(val phoneNumber: String) : VerificationMethod()
data class PreverifiedEmailVerificationMethod(val preverifiedEmail: String) : VerificationMethod()
data class PreverifiedPhoneNumberVerificationMethod(val preverifiedPhoneNumber: String): VerificationMethod()
object E2ePassphraseVerificationMethod : VerificationMethod()

fun verificationMethodFromCVerification(method: TankerVerificationMethod) =
when (method.type) {
Expand All @@ -21,5 +22,6 @@ fun verificationMethodFromCVerification(method: TankerVerificationMethod) =
TankerVerification.TypePhoneNumber -> PhoneNumberVerificationMethod(method.value!!)
TankerVerification.TypePreverifiedEmail -> PreverifiedEmailVerificationMethod(method.value!!)
TankerVerification.TypePreverifiedPhoneNumber -> PreverifiedPhoneNumberVerificationMethod(method.value!!)
TankerVerification.TypeE2ePassphrase -> E2ePassphraseVerificationMethod
else -> throw RuntimeException("unknown verification method type: ${method.type}")
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import com.sun.jna.Structure
*/
open class VerificationOptions: Structure() {
// NOTE: Remember to keep the version in sync w/ the c++!
@JvmField var version: Byte = 1
@JvmField var version: Byte = 2
@JvmField var withSessionToken: Byte = 0
@JvmField var allowE2eMethodSwitch: Byte = 0

/**
* Requests to create a Session Token on verification
Expand All @@ -18,7 +19,15 @@ open class VerificationOptions: Structure() {
return this
}

/**
* Allow switching to and from E2E verification methods
*/
fun allowE2eMethodSwitch(allowE2eMethodSwitch: Boolean): VerificationOptions {
this.allowE2eMethodSwitch = if (allowE2eMethodSwitch) 1 else 0
return this
}

override fun getFieldOrder(): List<String> {
return listOf("version", "withSessionToken")
return listOf("version", "withSessionToken", "allowE2eMethodSwitch")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ class TankerVerification : Structure() {
const val TypePhoneNumber: Byte = 5
const val TypePreverifiedEmail: Byte = 6
const val TypePreverifiedPhoneNumber: Byte = 7
const val TypeE2ePassphrase: Byte = 8
}

// NOTE: Remember to keep the version in sync w/ the c++!
@JvmField
var version: Byte = 5
var version: Byte = 6
@JvmField
var type: Byte = 0
@JvmField
Expand All @@ -26,6 +27,8 @@ class TankerVerification : Structure() {
@JvmField
var passphrase: String? = null
@JvmField
var e2ePassphrase: String? = null
@JvmField
var oidcIDToken: String? = null
@JvmField
var phoneNumberVerification: TankerPhoneNumberVerification? = null
Expand All @@ -35,7 +38,7 @@ class TankerVerification : Structure() {
var preverifiedPhoneNumber: String? = null

override fun getFieldOrder(): List<String> {
return listOf("version", "type", "verificationKey", "emailVerification", "passphrase", "oidcIDToken", "phoneNumberVerification", "preverifiedEmail", "preverifiedPhoneNumber")
return listOf("version", "type", "verificationKey", "emailVerification", "passphrase", "e2ePassphrase", "oidcIDToken", "phoneNumberVerification", "preverifiedEmail", "preverifiedPhoneNumber")
}
}

Expand Down Expand Up @@ -74,6 +77,10 @@ fun Verification.toCVerification(): TankerVerification {
out.type = TankerVerification.TypePreverifiedPhoneNumber
out.preverifiedPhoneNumber = this.preverifiedPhoneNumber
}
is E2ePassphraseVerification -> {
out.type = TankerVerification.TypeE2ePassphrase
out.e2ePassphrase = this.e2ePassphrase
}
}
return out
}

0 comments on commit b8ae641

Please sign in to comment.