Skip to content

MASTG-DEMO-0075: Uses of Explicit Security Providers in Cryptographic APIs with semgrep

Download MASTG-DEMO-0075 APK Open MASTG-DEMO-0075 Folder Build MASTG-DEMO-0075 APK

Sample

The code snippet below shows sample code that demonstrates both insecure and secure usage of security providers in getInstance calls.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package org.owasp.mastestapp

import android.content.Context
import java.security.KeyStore
import java.security.MessageDigest
import java.security.Provider
import java.security.SecureRandom
import java.security.Security
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

import javax.crypto.CipherSpi
import java.security.Key
import java.security.AlgorithmParameters


class CustomProvider : Provider(
    "CustomProvider",
    1.0,
    "OWASP MAS Demo provider"
) {
    init {
        put("Cipher.AES", CustomAesCipherSpi::class.java.name)
    }
}

class CustomAesCipherSpi : CipherSpi() {

    private var mode: Int = 0
    private var key: Key? = null

    override fun engineSetMode(mode: String?) {
        // ignore for demo
    }

    override fun engineSetPadding(padding: String?) {
        // ignore for demo
    }

    override fun engineGetBlockSize(): Int = 16

    override fun engineGetOutputSize(inputLen: Int): Int = inputLen

    override fun engineGetIV(): ByteArray = ByteArray(16)
    override fun engineGetParameters(): AlgorithmParameters? {
        TODO("Not yet implemented")
    }

    override fun engineInit(opmode: Int, key: Key?, params: AlgorithmParameters?, random: SecureRandom?) {
        this.mode = opmode
        this.key = key
    }

    override fun engineInit(opmode: Int, key: Key?, params: java.security.spec.AlgorithmParameterSpec?, random: SecureRandom?) {
        this.mode = opmode
        this.key = key
    }

    override fun engineInit(opmode: Int, key: Key?, random: SecureRandom?) {
        this.mode = opmode
        this.key = key
    }

    override fun engineUpdate(input: ByteArray?, inputOffset: Int, inputLen: Int): ByteArray {
        return ByteArray(inputLen)
    }

    override fun engineUpdate(input: ByteArray?, inputOffset: Int, inputLen: Int, output: ByteArray?, outputOffset: Int): Int {
        if (output == null || input == null) return inputLen
        for (i in 0 until inputLen) {
            output[outputOffset + i] = 0
        }
        return inputLen
    }

    override fun engineDoFinal(input: ByteArray?, inputOffset: Int, inputLen: Int): ByteArray {
        return ByteArray(inputLen)
    }

    override fun engineDoFinal(input: ByteArray?, inputOffset: Int, inputLen: Int, output: ByteArray?, outputOffset: Int): Int {
        if (output == null || input == null) return inputLen
        for (i in 0 until inputLen) {
            output[outputOffset + i] = 0
        }
        return inputLen
    }
}

class MastgTest (private val context: Context){

    private fun ByteArray.toHex(): String {
        val sb = StringBuilder(size * 2)
        for (b in this) {
            val v = b.toInt() and 0xff
            if (v < 16) sb.append('0')
            sb.append(v.toString(16))
        }
        return sb.toString()
    }

    fun mastgTest(): String {
        val results = StringBuilder()

        Security.addProvider(CustomProvider())

        // PASS: AndroidKeyStore is allowed and MUST be there as provider for KeyStore
        val keyStore = KeyStore.getInstance("AndroidKeyStore")
        keyStore.load(null)
        results.append("KeyStore provider: ${keyStore.provider.name}\n")


        // PASS: Using default provider for KeyGenerator
        val keyGen = KeyGenerator.getInstance("AES")
        results.append("Default KeyGenerator provider: ${keyGen.provider.name}\n")

        // PASS: Using default provider for MessageDigest
        val digest = MessageDigest.getInstance("SHA-256")
        results.append("Default MessageDigest provider: ${digest.provider.name}\n")

        // PASS: Using default provider (AndroidOpenSSL/Conscrypt) - no provider specified
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        results.append("Default Cipher provider: ${cipher.provider.name}\n")

        // FAIL: Explicitly specifying BouncyCastle provider
        val cipherBC = Cipher.getInstance("AES/GCM/NoPadding", "BC")
        results.append("Default Cipher provider: ${cipherBC.provider.name}\n")

        val keyBytes = ByteArray(16)
        SecureRandom().nextBytes(keyBytes)
        val key = SecretKeySpec(keyBytes, "AES")

        val iv = ByteArray(12)
        SecureRandom().nextBytes(iv)
        val gcmSpec = GCMParameterSpec(128, iv)

        cipherBC.init(Cipher.ENCRYPT_MODE, key, gcmSpec)
        val plaintext = "Hello MAS".toByteArray(Charsets.UTF_8)
        val ciphertext = cipherBC.doFinal(plaintext)
        results.append("\n\nCiphertext: ${ciphertext.toHex()}\n")

        // FAIL: Explicitly specifying a custom provider
        val cipherCustom = Cipher.getInstance("AES/CBC/PKCS5Padding", "CustomProvider")

        cipherCustom.init(Cipher.ENCRYPT_MODE, key, gcmSpec)
        val plaintext2 = "Hello MAS".toByteArray(Charsets.UTF_8)
        val ciphertext2 = cipherCustom.doFinal(plaintext2)
        results.append("\n\nCiphertext: ${ciphertext2.toHex()}\n\n---------------\n")

        val builder = java.lang.StringBuilder()
        for (provider in Security.getProviders()) {
            builder.append("provider: ${provider.name} ${provider.version} (${provider.info})\n")
        }
        val providers = builder.toString()

        results.append("\n\nProvider list: ${providers}\n")

        return results.toString()
    }

}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
package org.owasp.mastestapp;

import android.content.Context;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateException;
import java.util.Iterator;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import kotlin.Metadata;
import kotlin.UByte;
import kotlin.jvm.internal.ArrayIteratorKt;
import kotlin.jvm.internal.Intrinsics;
import kotlin.text.CharsKt;
import kotlin.text.Charsets;

/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000\u001e\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0002\u0010\u0012\n\u0002\b\u0002\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\f\u0010\u0006\u001a\u00020\u0007*\u00020\bH\u0002J\u0006\u0010\t\u001a\u00020\u0007R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\n"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "toHex", "", "", "mastgTest", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
/* loaded from: classes3.dex */
public final class MastgTest {
    public static final int $stable = 8;
    private final Context context;

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
    }

    private final String toHex(byte[] $this$toHex) {
        StringBuilder sb = new StringBuilder($this$toHex.length * 2);
        for (byte b : $this$toHex) {
            int v = b & UByte.MAX_VALUE;
            if (v < 16) {
                sb.append('0');
            }
            String string = Integer.toString(v, CharsKt.checkRadix(16));
            Intrinsics.checkNotNullExpressionValue(string, "toString(...)");
            sb.append(string);
        }
        String string2 = sb.toString();
        Intrinsics.checkNotNullExpressionValue(string2, "toString(...)");
        return string2;
    }

    public final String mastgTest() throws BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, IOException, InvalidKeyException, KeyStoreException, CertificateException, NoSuchProviderException, InvalidAlgorithmParameterException {
        StringBuilder results = new StringBuilder();
        Security.addProvider(new CustomProvider());
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        results.append("KeyStore provider: " + keyStore.getProvider().getName() + "\n");
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        results.append("Default KeyGenerator provider: " + keyGen.getProvider().getName() + "\n");
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        results.append("Default MessageDigest provider: " + digest.getProvider().getName() + "\n");
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        results.append("Default Cipher provider: " + cipher.getProvider().getName() + "\n");
        Cipher cipherBC = Cipher.getInstance("AES/GCM/NoPadding", "BC");
        results.append("Default Cipher provider: " + cipherBC.getProvider().getName() + "\n");
        byte[] keyBytes = new byte[16];
        new SecureRandom().nextBytes(keyBytes);
        SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
        byte[] iv = new byte[12];
        new SecureRandom().nextBytes(iv);
        GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
        cipherBC.init(1, key, gcmSpec);
        byte[] plaintext = "Hello MAS".getBytes(Charsets.UTF_8);
        Intrinsics.checkNotNullExpressionValue(plaintext, "getBytes(...)");
        byte[] ciphertext = cipherBC.doFinal(plaintext);
        Intrinsics.checkNotNull(ciphertext);
        results.append("\n\nCiphertext: " + toHex(ciphertext) + "\n");
        Cipher cipherCustom = Cipher.getInstance("AES/CBC/PKCS5Padding", "CustomProvider");
        cipherCustom.init(1, key, gcmSpec);
        byte[] plaintext2 = "Hello MAS".getBytes(Charsets.UTF_8);
        Intrinsics.checkNotNullExpressionValue(plaintext2, "getBytes(...)");
        byte[] ciphertext2 = cipherCustom.doFinal(plaintext2);
        Intrinsics.checkNotNull(ciphertext2);
        results.append("\n\nCiphertext: " + toHex(ciphertext2) + "\n\n---------------\n");
        StringBuilder builder = new StringBuilder();
        for (Iterator it = ArrayIteratorKt.iterator(Security.getProviders()); it.hasNext(); it = it) {
            Provider provider = (Provider) it.next();
            Cipher cipherCustom2 = cipherCustom;
            builder.append("provider: " + provider.getName() + " " + provider.getVersion() + " (" + provider.getInfo() + ")\n");
            cipherCustom = cipherCustom2;
            plaintext2 = plaintext2;
            ciphertext2 = ciphertext2;
        }
        String providers = builder.toString();
        Intrinsics.checkNotNullExpressionValue(providers, "toString(...)");
        results.append("\n\nProvider list: " + providers + "\n");
        String string = results.toString();
        Intrinsics.checkNotNullExpressionValue(string, "toString(...)");
        return string;
    }
}

Steps

Let's run our semgrep rule against the sample code.

../../../../rules/mastg-android-hardcoded-security-provider.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
rules:
  - id: mastg-android-hardcoded-security-provider
    languages:
      - java
    severity: WARNING
    metadata:
      summary: This rule looks for explicitly specified security providers in getInstance calls.
    message: "[MASVS-CRYPTO-1] Explicitly specified security provider found. Do not hardcode a provider unless using AndroidKeyStore."
    patterns:
      - pattern-either:
          - pattern: $X.getInstance($ALGO, $PROVIDER)
          - pattern: $X.getInstance($ALGO, (String $PROVIDER))
          - pattern: $X.getInstance($ALGO, (java.security.Provider $PROVIDER))
      - pattern-not: KeyStore.getInstance("AndroidKeyStore")
      - metavariable-regex:
          metavariable: $X
          regex: (Cipher|KeyGenerator|KeyPairGenerator|MessageDigest|Signature|Mac|SecretKeyFactory|KeyFactory|SecureRandom|KeyAgreement|AlgorithmParameters|AlgorithmParameterGenerator|CertificateFactory|CertPathBuilder|CertPathValidator|CertStore|KeyStore)
run.sh
1
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-hardcoded-security-provider.yaml ./MastgTest_reversed.java > output.txt

Observation

The rule has identified instances in the code file where a security provider is explicitly specified. The specified line numbers can be located in the reverse-engineered code for further investigation and remediation.

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
┌─────────────────┐
 2 Code Findings 
└─────────────────┘

    MastgTest_reversed.java
    ❯❱ rules.mastg-android-hardcoded-security-provider
          [MASVS-CRYPTO-1] Explicitly specified security provider found. Do not hardcode a provider unless
          using AndroidKeyStore.                                                                          

           71 Cipher cipherBC = Cipher.getInstance("AES/GCM/NoPadding", "BC");
            ⋮┆----------------------------------------
           85 Cipher cipherCustom = Cipher.getInstance("AES/CBC/PKCS5Padding", "CustomProvider");

Evaluation

Review each of the reported instances:

  • Line 71 uses the deprecated "BC" (BouncyCastle) provider with Cipher.getInstance. This is deprecated since Android 9 and removed in Android 12.
  • Line 85 uses a custom provider "CustomProvider" which may not be regularly updated or patched.

All other cases are correctly handled and do not trigger the rule.