Showing posts with label Android Certificate. Show all posts
Showing posts with label Android Certificate. Show all posts

Monday, August 5, 2013

Android - APK Signature check programmatically in runtime

I've been at this for some time... I've tried to make my application distinct between debug version, release version, in some cases staging version, free, paid premium... and the list goes on and on...

So... I've asked the pro's, it was hard for me to describe the question, but I think it is right. I got no answer so I've went and searched a bit, and found an article explaining how this things work, I'm not going to go by all the explanation, I'll just lay it down.

First we define the certificates:

private enum SignedCertificate {
 Debug("...", null, true, true),
 Staging("...", Environments.Staging, false, true),
 Production("...", Environments.Production, false, false),
 Unknown("There is no such a certificate", Environments.Production, false, false), ;

 private final String md5;

 private final Environments environment;

 private final boolean debugMode;

 private final boolean showsLogs;

 private SignedCertificate(String md5, Environments environment, boolean debugMode, boolean showsLogs) {
  this.md5 = md5;
  this.environment = environment;
  this.debugMode = debugMode;
  this.showsLogs = showsLogs;
 }

 public static final SignedCertificate getCertificate(String md5) {
  md5 = md5.toLowerCase();
  for (SignedCertificate certificate : values()) {
   if (certificate.md5.toLowerCase().equals(md5))
    return certificate;
  }
  return Unknown;
 } 
}

The MD5 for each certificate can be copied from the export dialog in the final step of exporting a signed application in Eclipse, for debug md5 sign it with you debug keystore.

In order to retrieve the current certificate in runtime:

private void checkCertificate() {
 try {
  PackageManager pm = application.getPackageManager();
  Signature sig = pm.getPackageInfo(application.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
  String md5Fingerprint = doFingerprint(sig.toByteArray(), "MD5");
  certificate = SignedCertificate.getCertificate(md5Fingerprint);
 } catch (Exception e) {
  logInfo("Error getting certificate, assuming release version...", e);
  certificate = SignedCertificate.Production;
 } finally {
  logInfo("Found Certificate: " + certificate.name());
  Log.setShowLogs(certificate.showsLogs);
 }
}

protected static String doFingerprint(byte[] certificateBytes, String algorithm)
  throws Exception {
 MessageDigest md = MessageDigest.getInstance(algorithm);
 md.update(certificateBytes);
 byte[] digest = md.digest();
 
 String toRet = "";
 for (int i = 0; i < digest.length; i++) {
  if (i != 0)
   toRet += ":";
  int b = digest[i] & 0xff;
  String hex = Integer.toHexString(b);
  if (hex.length() == 1)
   toRet += "0";
  toRet += hex;
 }
 return toRet;
}

After retrieving the certificate, implement any logic you may want according to the certificate, I've used the enum to define logs or debug mode, working environment and so on...

The nice thing about this implementation, is that it does not care about the application package name Google uses... if you have shared code between free and paid version of the application, and you would like to distinct them programmatically, I think this is a very nice solution...

Leave your comments below... :)

-- UPDATE --

I've released Cyborg not too long ago, and this Certificate Validation is build in and optimized further in terms of API and how much code you need to write to get it to work, You can find it here.

Thursday, August 2, 2012

How to determine if your application runs in debug...

Sometimes there are actions your Android application should perform while it runs in release, but not in debug, and vice verse.

The issue is that Android methods of checking whether the application is debuggable or not, are not reliable, and there are three of them...

This is highly frustrating.

The best solution I've found, which served me well, is to determine in runtime whether the apk which runs the application is signed with a debug certificate or not. I've tested this on tens of phones ranging from version 2.1 - 4.0.4.

Here is the code sample:

PackageManager pm = context.getPackageManager();
Signature sig = packageManager.getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(sig.toByteArray()));
String dn = cert.getIssuerDN().getName();
boolean debuggable=dn.contains(myDebugCertificateName);

On most development environment the default debug certificate name is:
myDebugCertificateName = "Android Debug"



The problem I noticed people having is that they don't even understand this is what they really need... they just solve it in the build, and add a flag during CI to the manifest and later extract the value at runtime, I find it ridicules!