blob: 7833b6648e5b5d7a3c3ea95f54537b705097bc77 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.lint.checks
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.ConstantEvaluator
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.TypeEvaluator
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
import org.jetbrains.uast.tryResolve
/** Checks for hardcoded seeds with random numbers. */
class SecureRandomDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> {
return listOf(SET_SEED)
}
override fun visitMethodCall(
context: JavaContext,
node: UCallExpression,
method: PsiMethod
) {
val arguments = node.valueArguments
if (arguments.isEmpty()) {
return
}
val seedArgument = arguments[0]
val evaluator = context.evaluator
if (evaluator.isMemberInClass(method, JAVA_SECURITY_SECURE_RANDOM) ||
evaluator.isMemberInSubClassOf(method, JAVA_UTIL_RANDOM, false) &&
isSecureRandomReceiver(node)
) {
// Called with a fixed seed?
val seed = ConstantEvaluator.evaluate(context, seedArgument)
if (seed != null) {
context.report(
ISSUE, node, context.getLocation(node),
"Do not call `setSeed()` on a `SecureRandom` with a fixed seed: " +
"it is not secure. Use `getSeed()`."
)
} else {
// Called with a simple System.currentTimeMillis() seed or something like that?
val seedMethod = seedArgument.tryResolve()
if (seedMethod is PsiMethod) {
val methodName = seedMethod.name
if (methodName == "currentTimeMillis" || methodName == "nanoTime") {
context.report(
ISSUE, node, context.getLocation(node),
"It is dangerous to seed `SecureRandom` with the current " +
"time because that value is more predictable to " +
"an attacker than the default seed"
)
}
}
}
}
}
/**
* Returns true if the given invocation is assigned a SecureRandom
* type.
*/
private fun isSecureRandomReceiver(
call: UCallExpression
): Boolean {
val operand = call.receiver
return operand != null && isSecureRandomType(operand)
}
/**
* Returns true if the node evaluates to an instance of type
* SecureRandom.
*/
private fun isSecureRandomType(
node: UElement
): Boolean {
return JAVA_SECURITY_SECURE_RANDOM == TypeEvaluator.evaluate(node)?.canonicalText
}
companion object {
/** Unregistered activities and services. */
@JvmField
val ISSUE = Issue.create(
id = "SecureRandom",
briefDescription = "Using a fixed seed with `SecureRandom`",
explanation = """
Specifying a fixed seed will cause the instance to return a predictable \
sequence of numbers. This may be useful for testing but it is not appropriate \
for secure use.
""",
moreInfo = "https://developer.android.com/reference/java/security/SecureRandom.html",
category = Category.SECURITY,
priority = 9,
severity = Severity.WARNING,
implementation = Implementation(
SecureRandomDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
)
const val JAVA_SECURITY_SECURE_RANDOM = "java.security.SecureRandom"
const val JAVA_UTIL_RANDOM = "java.util.Random"
private const val SET_SEED = "setSeed"
}
}