| /* |
| * 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 com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.tools.lint.detector.api.Category; |
| import com.android.tools.lint.detector.api.ClassContext; |
| import com.android.tools.lint.detector.api.ClassScanner; |
| import com.android.tools.lint.detector.api.Context; |
| 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.Lint; |
| 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 com.google.common.annotations.VisibleForTesting; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| 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; |
| |
| /** Checks for pseudo random number generator initialization issues */ |
| public class SecureRandomGeneratorDetector extends Detector implements ClassScanner { |
| private static final String OWNER_SECURE_RANDOM = "java/security/SecureRandom"; |
| |
| @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", |
| "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"; |
| private static final String UNWRAP = "unwrap"; |
| private static final String INIT = "init"; |
| private static final String INIT_SIGN = "initSign"; |
| private static final String GET_INSTANCE = "getInstance"; |
| private static final String FOR_NAME = "forName"; |
| private static final String JAVA_LANG_CLASS = "java/lang/Class"; |
| 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<String> 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<String> 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 = Lint.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 = Lint.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 afterCheckRootProject(@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); |
| } |
| } |
| } |