com.android.tools.lint.checks.SecureRandomGeneratorDetector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lint-checks Show documentation
Show all versions of lint-checks Show documentation
A packaging of the IntelliJ Community Edition lint-checks library.
This is release number 1 of trunk branch 142.
The newest version!
/*
* Copyright (C) 2013 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 static com.android.SdkConstants.CONSTRUCTOR_NAME;
import static com.android.tools.lint.checks.SecureRandomDetector.OWNER_SECURE_RANDOM;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Detector.ClassScanner;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Checks for pseudo random number generator initialization issues
*/
public class SecureRandomGeneratorDetector extends Detector implements ClassScanner {
@SuppressWarnings("SpellCheckingInspection")
private static final String BLOG_URL
= "https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html";
/** Whether the random number generator is initialized correctly */
public static final Issue ISSUE = Issue.create(
"TrulyRandom", //$NON-NLS-1$
"Weak RNG",
"Key generation, signing, encryption, and random number generation may not " +
"receive cryptographically strong values due to improper initialization of " +
"the underlying PRNG on Android 4.3 and below.\n" +
"\n" +
"If your application relies on cryptographically secure random number generation " +
"you should apply the workaround described in " + BLOG_URL + " .\n" +
"\n" +
"This lint rule is mostly informational; it does not accurately detect whether " +
"cryptographically secure RNG is required, or whether the workaround has already " +
"been applied. After reading the blog entry and updating your code if necessary, " +
"you can disable this lint issue.",
Category.SECURITY,
9,
Severity.WARNING,
new Implementation(
SecureRandomGeneratorDetector.class,
Scope.CLASS_FILE_SCOPE))
.addMoreInfo(BLOG_URL);
private static final String WRAP = "wrap"; //$NON-NLS-1$
private static final String UNWRAP = "unwrap"; //$NON-NLS-1$
private static final String INIT = "init"; //$NON-NLS-1$
private static final String INIT_SIGN = "initSign"; //$NON-NLS-1$
private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$
private static final String FOR_NAME = "forName"; //$NON-NLS-1$
private static final String JAVA_LANG_CLASS = "java/lang/Class"; //$NON-NLS-1$
private static final String JAVAX_CRYPTO_KEY_GENERATOR = "javax/crypto/KeyGenerator";
private static final String JAVAX_CRYPTO_KEY_AGREEMENT = "javax/crypto/KeyAgreement";
private static final String JAVA_SECURITY_KEY_PAIR_GENERATOR =
"java/security/KeyPairGenerator";
private static final String JAVAX_CRYPTO_SIGNATURE = "javax/crypto/Signature";
private static final String JAVAX_CRYPTO_CIPHER = "javax/crypto/Cipher";
private static final String JAVAX_NET_SSL_SSLENGINE = "javax/net/ssl/SSLEngine";
/** Constructs a new {@link SecureRandomGeneratorDetector} */
public SecureRandomGeneratorDetector() {
}
// ---- Implements ClassScanner ----
@Nullable
@Override
public List getApplicableCallOwners() {
return Arrays.asList(
JAVAX_CRYPTO_KEY_GENERATOR,
JAVA_SECURITY_KEY_PAIR_GENERATOR,
JAVAX_CRYPTO_KEY_AGREEMENT,
OWNER_SECURE_RANDOM,
JAVAX_NET_SSL_SSLENGINE,
JAVAX_CRYPTO_SIGNATURE,
JAVAX_CRYPTO_CIPHER
);
}
@Nullable
@Override
public List getApplicableCallNames() {
return Collections.singletonList(FOR_NAME);
}
/** Location of first call to key generator (etc), if any */
private Location mLocation;
/** Whether the issue should be ignored (because we have a workaround, or because
* we're only targeting correct implementations, etc */
private boolean mIgnore;
@Override
public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
@NonNull MethodNode method, @NonNull MethodInsnNode call) {
if (mIgnore) {
return;
}
String owner = call.owner;
String name = call.name;
// Look for the workaround code: if we see a Class.forName on the harmony NativeCrypto,
// we'll consider that a sign.
if (name.equals(FOR_NAME)) {
if (call.getOpcode() != Opcodes.INVOKESTATIC ||
!owner.equals(JAVA_LANG_CLASS)) {
return;
}
AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
if (prev instanceof LdcInsnNode) {
Object cst = ((LdcInsnNode)prev).cst;
//noinspection SpellCheckingInspection
if (cst instanceof String &&
"org.apache.harmony.xnet.provider.jsse.NativeCrypto".equals(cst)) {
mIgnore = true;
}
}
return;
}
// Look for calls that probably require a properly initialized random number generator.
assert owner.equals(JAVAX_CRYPTO_KEY_GENERATOR)
|| owner.equals(JAVA_SECURITY_KEY_PAIR_GENERATOR)
|| owner.equals(JAVAX_CRYPTO_KEY_AGREEMENT)
|| owner.equals(OWNER_SECURE_RANDOM)
|| owner.equals(JAVAX_CRYPTO_CIPHER)
|| owner.equals(JAVAX_CRYPTO_SIGNATURE)
|| owner.equals(JAVAX_NET_SSL_SSLENGINE) : owner;
boolean warn = false;
if (owner.equals(JAVAX_CRYPTO_SIGNATURE)) {
warn = name.equals(INIT_SIGN);
} else if (owner.equals(JAVAX_CRYPTO_CIPHER)) {
if (name.equals(INIT)) {
int arity = getDescArity(call.desc);
AbstractInsnNode node = call;
for (int i = 0; i < arity; i++) {
node = LintUtils.getPrevInstruction(node);
if (node == null) {
break;
}
}
if (node != null) {
int opcode = node.getOpcode();
if (opcode == Opcodes.ICONST_3 || // Cipher.WRAP_MODE
opcode == Opcodes.ICONST_1) { // Cipher.ENCRYPT_MODE
warn = true;
}
}
}
} else if (name.equals(GET_INSTANCE) || name.equals(CONSTRUCTOR_NAME)
|| name.equals(WRAP) || name.equals(UNWRAP)) { // For SSLEngine
warn = true;
}
if (warn) {
if (mLocation != null) {
return;
}
if (context.getMainProject().getMinSdk() > 18) {
// Fix no longer needed
mIgnore = true;
return;
}
if (context.getDriver().isSuppressed(ISSUE, classNode, method, call)) {
mIgnore = true;
} else {
mLocation = context.getLocation(call);
}
}
}
@VisibleForTesting
static int getDescArity(String desc) {
int arity = 0;
// For example, (ILjava/security/Key;)V => 2
for (int i = 1, max = desc.length(); i < max; i++) {
char c = desc.charAt(i);
if (c == ')') {
break;
} else if (c == 'L') {
arity++;
i = desc.indexOf(';', i);
assert i != -1 : desc;
} else {
arity++;
}
}
return arity;
}
@Override
public void afterCheckProject(@NonNull Context context) {
if (mLocation != null && !mIgnore) {
String message = "Potentially insecure random numbers on Android 4.3 and older. "
+ "Read " + BLOG_URL + " for more info.";
context.report(ISSUE, mLocation, message);
}
}
}