Skip to content

Commit 977a574

Browse files
committed
Don't use unqualified generic types for validator
1 parent 5cb8d96 commit 977a574

4 files changed

Lines changed: 47 additions & 84 deletions

File tree

ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/di/AdBlockingExtensionModule.kt

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.duckduckgo.adblocking.impl.di
1818

19-
import com.duckduckgo.adblocking.impl.domain.PublicKeyProvider
2019
import com.duckduckgo.adblocking.impl.remoteconfig.AdBlockingExtensionSettings
2120
import com.duckduckgo.adblocking.impl.remoteconfig.DomainJsonAdapter
2221
import com.duckduckgo.adblocking.impl.remoteconfig.ScriptletsSettings
@@ -34,12 +33,6 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
3433
import dagger.Module
3534
import dagger.Provides
3635
import dagger.SingleInstanceIn
37-
import java.nio.charset.CharsetDecoder
38-
import java.nio.charset.StandardCharsets
39-
import java.security.KeyFactory
40-
import java.security.PublicKey
41-
import java.security.Signature
42-
import java.util.Base64
4336

4437
@Module
4538
@ContributesTo(AppScope::class)
@@ -62,29 +55,6 @@ object AdBlockingExtensionModule {
6255
fun provideAdBlockingExtensionDao(database: AdBlockingExtensionDatabase): AdBlockingExtensionDao =
6356
database.adBlockingExtensionDao()
6457

65-
@SingleInstanceIn(AppScope::class)
66-
@Provides
67-
fun provideBase64Decoder(): Base64.Decoder =
68-
Base64.getDecoder()
69-
70-
@SingleInstanceIn(AppScope::class)
71-
@Provides
72-
fun provideKeyFactory(): KeyFactory =
73-
KeyFactory.getInstance("EC")
74-
75-
@SingleInstanceIn(AppScope::class)
76-
@Provides
77-
fun provideAdBlockingExtensionPublicKey(publicKeyProvider: PublicKeyProvider): PublicKey =
78-
publicKeyProvider.publicKey
79-
80-
@Provides
81-
fun provideUTF8CharsetDecoder(): CharsetDecoder =
82-
StandardCharsets.UTF_8.newDecoder()
83-
84-
@Provides
85-
fun provideSignature(): Signature =
86-
Signature.getInstance("SHA256withECDSA")
87-
8858
@SingleInstanceIn(AppScope::class)
8959
@Provides
9060
fun provideAdBlockingExtensionSettingsAdapter(): JsonAdapter<AdBlockingExtensionSettings> =

ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/domain/PublicKeyProvider.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,10 @@ private const val KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuqrhn2ztPnPt8QybIGs
3333

3434
@ContributesBinding(AppScope::class)
3535
@SingleInstanceIn(AppScope::class)
36-
class RealPublicKeyProvider @Inject constructor(
37-
private val keyFactory: KeyFactory,
38-
private val decoder: Base64.Decoder,
39-
) : PublicKeyProvider {
36+
class RealPublicKeyProvider @Inject constructor() : PublicKeyProvider {
4037

4138
override val publicKey: PublicKey by lazy {
42-
keyFactory.generatePublic(X509EncodedKeySpec(decoder.decode(KEY)))
39+
KeyFactory.getInstance("EC")
40+
.generatePublic(X509EncodedKeySpec(Base64.getDecoder().decode(KEY)))
4341
}
4442
}

ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/domain/ScriptletSignatureValidator.kt

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@ package com.duckduckgo.adblocking.impl.domain
1919
import com.duckduckgo.di.scopes.AppScope
2020
import com.squareup.anvil.annotations.ContributesBinding
2121
import dagger.SingleInstanceIn
22-
import kotlinx.coroutines.sync.Mutex
23-
import kotlinx.coroutines.sync.withLock
2422
import logcat.LogPriority.WARN
2523
import logcat.asLog
2624
import logcat.logcat
2725
import java.nio.ByteBuffer
2826
import java.nio.charset.CharacterCodingException
29-
import java.nio.charset.CharsetDecoder
27+
import java.nio.charset.StandardCharsets
3028
import java.security.PublicKey
3129
import java.security.Signature
3230
import java.util.Base64
@@ -42,35 +40,32 @@ sealed interface ScriptletValidationResult {
4240
}
4341

4442
interface ScriptletSignatureValidator {
45-
suspend fun validate(content: ByteArray, signatureBase64: String): ScriptletValidationResult
43+
fun validate(content: ByteArray, signatureBase64: String): ScriptletValidationResult
4644
}
4745

4846
@ContributesBinding(AppScope::class)
4947
@SingleInstanceIn(AppScope::class)
5048
class RealScriptletSignatureValidator @Inject constructor(
51-
private val publicKey: PublicKey,
52-
private val signature: Signature,
53-
private val charsetDecoder: CharsetDecoder,
54-
private val base64Decoder: Base64.Decoder,
49+
publicKeyProvider: PublicKeyProvider,
5550
) : ScriptletSignatureValidator {
5651

57-
private val mutex = Mutex()
52+
private val publicKey: PublicKey = publicKeyProvider.publicKey
5853

59-
override suspend fun validate(content: ByteArray, signatureBase64: String): ScriptletValidationResult = mutex.withLock {
54+
override fun validate(content: ByteArray, signatureBase64: String): ScriptletValidationResult {
6055
try {
61-
charsetDecoder.decode(ByteBuffer.wrap(content))
56+
StandardCharsets.UTF_8.newDecoder().decode(ByteBuffer.wrap(content))
6257
} catch (e: CharacterCodingException) {
6358
logcat(WARN) { "Validating scriptlet failed with invalid encoding: ${e.message}" }
64-
return@withLock ScriptletValidationResult.Invalid.Encoding
59+
return ScriptletValidationResult.Invalid.Encoding
6560
}
6661
val signatureBytes = try {
67-
base64Decoder.decode(signatureBase64)
62+
Base64.getDecoder().decode(signatureBase64)
6863
} catch (e: IllegalArgumentException) {
6964
logcat(WARN) { "Validating scriptlet failed with invalid signature format: ${e.message}" }
70-
return@withLock ScriptletValidationResult.Invalid.SignatureFormat
65+
return ScriptletValidationResult.Invalid.SignatureFormat
7166
}
72-
try {
73-
val verified = signature.run {
67+
return try {
68+
val verified = Signature.getInstance(SIGNATURE_ALGORITHM).run {
7469
initVerify(publicKey)
7570
update(content)
7671
verify(signatureBytes)
@@ -87,4 +82,8 @@ class RealScriptletSignatureValidator @Inject constructor(
8782
ScriptletValidationResult.Invalid.SignatureVerificationFailed
8883
}
8984
}
85+
86+
companion object {
87+
const val SIGNATURE_ALGORITHM = "SHA256withECDSA"
88+
}
9089
}

ad-blocking/ad-blocking-impl/src/test/java/com/duckduckgo/adblocking/impl/domain/ScriptletSignatureValidatorTest.kt

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717
package com.duckduckgo.adblocking.impl.domain
1818

1919
import androidx.test.ext.junit.runners.AndroidJUnit4
20-
import kotlinx.coroutines.test.runTest
2120
import org.junit.Assert
2221
import org.junit.Before
2322
import org.junit.Test
2423
import org.junit.runner.RunWith
25-
import java.nio.charset.StandardCharsets
2624
import java.security.KeyPair
2725
import java.security.KeyPairGenerator
2826
import java.security.PrivateKey
27+
import java.security.PublicKey
2928
import java.security.Signature
3029
import java.security.spec.ECGenParameterSpec
3130
import java.util.Base64
@@ -42,23 +41,22 @@ class ScriptletSignatureValidatorTest {
4241
initialize(ECGenParameterSpec("secp256r1"))
4342
}.generateKeyPair()
4443
validator = RealScriptletSignatureValidator(
45-
publicKey = keyPair.public,
46-
signature = Signature.getInstance("SHA256withECDSA"),
47-
charsetDecoder = StandardCharsets.UTF_8.newDecoder(),
48-
base64Decoder = Base64.getDecoder(),
44+
publicKeyProvider = object : PublicKeyProvider {
45+
override val publicKey: PublicKey = keyPair.public
46+
},
4947
)
5048
}
5149

5250
@Test
53-
fun whenContentAndSignatureMatchThenValidateReturnsValid() = runTest {
51+
fun whenContentAndSignatureMatchThenValidateReturnsValid() {
5452
val content = "console.log('hello')".toByteArray()
5553
val signature = sign(content, keyPair.private)
5654

5755
Assert.assertEquals(ScriptletValidationResult.Valid, validator.validate(content, signature))
5856
}
5957

6058
@Test
61-
fun whenContentIsTamperedThenValidateReturnsSignatureVerificationFailed() = runTest {
59+
fun whenContentIsTamperedThenValidateReturnsSignatureVerificationFailed() {
6260
val original = "original content".toByteArray()
6361
val signature = sign(original, keyPair.private)
6462
val tampered = "tampered content".toByteArray()
@@ -70,22 +68,21 @@ class ScriptletSignatureValidatorTest {
7068
}
7169

7270
@Test
73-
fun whenSignatureIsFromDifferentKeyThenValidateReturnsSignatureVerificationFailed() =
74-
runTest {
75-
val content = "content".toByteArray()
76-
val otherKeyPair = KeyPairGenerator.getInstance("EC").apply {
77-
initialize(ECGenParameterSpec("secp256r1"))
78-
}.generateKeyPair()
79-
val signature = sign(content, otherKeyPair.private)
80-
81-
Assert.assertEquals(
82-
ScriptletValidationResult.Invalid.SignatureVerificationFailed,
83-
validator.validate(content, signature),
84-
)
85-
}
71+
fun whenSignatureIsFromDifferentKeyThenValidateReturnsSignatureVerificationFailed() {
72+
val content = "content".toByteArray()
73+
val otherKeyPair = KeyPairGenerator.getInstance("EC").apply {
74+
initialize(ECGenParameterSpec("secp256r1"))
75+
}.generateKeyPair()
76+
val signature = sign(content, otherKeyPair.private)
77+
78+
Assert.assertEquals(
79+
ScriptletValidationResult.Invalid.SignatureVerificationFailed,
80+
validator.validate(content, signature),
81+
)
82+
}
8683

8784
@Test
88-
fun whenSignatureIsMalformedBase64ThenValidateReturnsInvalidSignatureFormat() = runTest {
85+
fun whenSignatureIsMalformedBase64ThenValidateReturnsInvalidSignatureFormat() {
8986
val content = "content".toByteArray()
9087

9188
Assert.assertEquals(
@@ -95,19 +92,18 @@ class ScriptletSignatureValidatorTest {
9592
}
9693

9794
@Test
98-
fun whenSignatureBytesAreNotValidEcdsaSignatureThenValidateReturnsSignatureVerificationFailed() =
99-
runTest {
100-
val content = "content".toByteArray()
101-
val garbage = Base64.getEncoder().encodeToString(ByteArray(64) { 0x42 })
102-
103-
Assert.assertEquals(
104-
ScriptletValidationResult.Invalid.SignatureVerificationFailed,
105-
validator.validate(content, garbage),
106-
)
107-
}
95+
fun whenSignatureBytesAreNotValidEcdsaSignatureThenValidateReturnsSignatureVerificationFailed() {
96+
val content = "content".toByteArray()
97+
val garbage = Base64.getEncoder().encodeToString(ByteArray(64) { 0x42 })
98+
99+
Assert.assertEquals(
100+
ScriptletValidationResult.Invalid.SignatureVerificationFailed,
101+
validator.validate(content, garbage),
102+
)
103+
}
108104

109105
@Test
110-
fun whenContentIsNotValidUtf8ThenValidateReturnsInvalidEncoding() = runTest {
106+
fun whenContentIsNotValidUtf8ThenValidateReturnsInvalidEncoding() {
111107
val invalidUtf8 = byteArrayOf(0xFF.toByte(), 0xFE.toByte(), 0x80.toByte(), 0x81.toByte())
112108
val signature = sign(invalidUtf8, keyPair.private)
113109

0 commit comments

Comments
 (0)