--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.ietfng.ns.android.vcpass"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name">
+ <activity android:name="VCPass"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name="VCPassActivity">
+ <intent-filter>
+ <action android:name="org.ietfng.ns.android.vcpass.CHAL_PRESENT" />
+ <action android:name="org.ietfng.ns.android.vcpass.CHAL_CREATE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name="VCPassImport">
+ <intent-filter>
+ <action android:name="org.ietfng.ns.android.vcpass.SEED_IMPORT" />
+ <action android:name="org.ietfng.ns.android.vcpass.IMPORT_AND_CREATE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
--- /dev/null
+# This file is used to override default values used by the Ant build system.
+#
+# This file must be checked in Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+# 'source.dir' for the location of your java source folder and
+# 'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+# 'key.store' for the location of your keystore and
+# 'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="VCPass" default="help">
+
+ <!-- The local.properties file is created and updated by the 'android' tool.
+ It contain the path to the SDK. It should *NOT* be checked in in Version
+ Control Systems. -->
+ <property file="local.properties"/>
+
+ <!-- The build.properties file can be created by you and is never touched
+ by the 'android' tool. This is the place to change some of the default property values
+ used by the Ant rules.
+ Here are some properties you may want to change/update:
+
+ application-package
+ the name of your application package as defined in the manifest. Used by the
+ 'uninstall' rule.
+ source-folder
+ the name of the source folder. Default is 'src'.
+ out-folder
+ the name of the output folder. Default is 'bin'.
+
+ Properties related to the SDK location or the project target should be updated
+ using the 'android' tool with the 'update' action.
+
+ This file is an integral part of the build system for your application and
+ should be checked in in Version Control Systems.
+
+ -->
+ <property file="build.properties"/>
+
+ <!-- The default.properties file is created and updated by the 'android' tool, as well
+ as ADT.
+ This file is an integral part of the build system for your application and
+ should be checked in in Version Control Systems. -->
+ <property file="default.properties"/>
+
+ <!-- Custom Android task to deal with the project target, and import the proper rules.
+ This requires ant 1.6.0 or above. -->
+ <path id="android.antlibs">
+ <pathelement path="${sdk-location}/tools/lib/anttasks.jar" />
+ <pathelement path="${sdk-location}/tools/lib/sdklib.jar" />
+ <pathelement path="${sdk-location}/tools/lib/androidprefs.jar" />
+ <pathelement path="${sdk-location}/tools/lib/apkbuilder.jar" />
+ <pathelement path="${sdk-location}/tools/lib/jarutils.jar" />
+ </path>
+
+ <taskdef name="setup"
+ classname="com.android.ant.SetupTask"
+ classpathref="android.antlibs"/>
+
+ <!-- Execute the Android Setup task that will setup some properties specific to the target,
+ and import the rules files.
+ To customize the rules, copy/paste them below the task, and disable import by setting
+ the import attribute to false:
+ <setup import="false" />
+
+ This will ensure that the properties are setup correctly but that your customized
+ targets are used.
+ -->
+ <setup />
+
+</project>
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-4
--- /dev/null
+/** Host-side slide generator.
+ *
+ * Required classpath entries:
+ * /usr/share/java/bcprov.jar
+ * /usr/share/java/commons-codec.jar
+ * /usr/share/java/commons-cli.jar
+ * $HOME/src/zxing/core/core.jar
+ *
+ * Build with:
+ * javac -d bin/classes -cp bin/classes:... \
+ * host/org/ietfng/ns/android/vcpass/VCSlideGen.java
+ *
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.io.FileWriter;
+import java.security.SecureRandom;
+import java.security.Provider;
+import java.security.Security;
+import javax.crypto.SecretKey;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.ByteMatrix;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+import org.apache.commons.codec.binary.Base64;
+
+public final class VCSlideGen {
+ static final private int SEED_SIZE = 16;
+
+ private static final int QR_SIZEX = 400;
+ private static final int QR_SIZEY = 400;
+
+ static final private Options cliopts = new Options();
+ static final private String OPT_SS_HELP = "h";
+ static final private String OPT_SS_QRFILE = "b";
+ static final private String OPT_SS_SECRET = "S";
+ static final private String OPT_SS_SLIDEF = "s";
+ static final private String OPT_SS_VOCABS = "v";
+
+ private static final int crpix = VCParameters.DISP_X
+ / VCParameters.GRID_X;
+ private static final int ccpix = VCParameters.DISP_Y
+ / VCParameters.GRID_Y;
+
+ private static final void
+ printPBMHeader(FileWriter f, int v, int x, int y)
+ throws java.io.IOException
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("P");
+ sb.append(v);
+ sb.append(" ");
+ sb.append(x);
+ sb.append(" ");
+ sb.append(y);
+ sb.append("\n");
+ f.write(sb.toString(), 0, sb.length());
+ }
+
+ static {
+ cliopts.addOption(OPT_SS_HELP, "help", false, "Show this help");
+ cliopts.addOption(OPT_SS_QRFILE, "qrfile", true,
+ "Barcode PNM file basename");
+ cliopts.addOption(OPT_SS_SECRET, "secretseed", true,
+ "Secret seed (for development)");
+ cliopts.addOption(OPT_SS_SLIDEF, "slidefile", true,
+ "Slide PNM file basename");
+ cliopts.addOption(OPT_SS_VOCABS, "vocabfile", true,
+ "Vocabulary PNM file basename");
+ }
+
+ public static void main(String[] args) throws Exception {
+ CommandLineParser parser = new PosixParser();
+ CommandLine cmd = parser.parse(cliopts, args);
+
+ if(cmd.hasOption(OPT_SS_HELP)) {
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp( "VCSlideGen" , cliopts );
+ return;
+ }
+
+ Security.addProvider(VCParameters.CSPROV);
+
+/* XXX */
+ char[] useed = "foo".toCharArray();
+ char[] vseed = "bar".toCharArray();
+
+/* XXX
+ byte[] useed = new byte[SEED_SIZE];
+ byte[] vseed = new byte[SEED_SIZE];
+
+ {
+ SecureRandom seedsr = SecureRandom.getInstance(
+ VCParameters.CSPRNG
+ );
+ if(cmd.hasOption(OPT_SS_SECRET)) {
+ System.err.println("WARN: Using given seed.");
+ seedsr.setSeed(cmd.getOptionValue(OPT_SS_SECRET)
+ .getBytes("UTF-8"));
+ } else {
+ // seedsr will initialize from the system's RNG when
+ // we first pull some data out of it.
+ }
+ seedsr.nextBytes(useed);
+ seedsr.nextBytes(vseed);
+ }
+*/
+
+ if(cmd.hasOption(OPT_SS_QRFILE)) {
+ String encodedseeds = null;
+ {
+ StringBuilder esb = new StringBuilder();
+ Base64 b64 = new Base64(80,new byte[0]);
+ // XXX esb.append(b64.encodeToString(useed));
+ esb.append(" ");
+ // XXX esb.append(b64.encodeToString(vseed));
+ encodedseeds = esb.toString();
+ System.out.printf("QRSTR: %s\n", encodedseeds);
+ }
+ FileWriter qrout = new FileWriter(
+ cmd.getOptionValue(OPT_SS_QRFILE)
+ +".pbm"
+ );
+ printPBMHeader(qrout, 1, QR_SIZEX, QR_SIZEY);
+ ByteMatrix qrbm = new MultiFormatWriter().encode(
+ encodedseeds,
+ BarcodeFormat.QR_CODE,
+ QR_SIZEX, QR_SIZEY);
+ byte[][] qr = qrbm.getArray();
+ for(int x = 0; x < qr.length; x++) {
+ for(int y = 0; y < qr[x].length; y++) {
+ qrout.write('1' - (qr[x][y] & 0x1));
+ }
+ qrout.write('\n');
+ }
+ qrout.flush();
+ qrout.close();
+ }
+
+ if(cmd.hasOption(OPT_SS_SLIDEF)) {
+ FileWriter sout = new FileWriter(
+ cmd.getOptionValue(OPT_SS_SLIDEF)
+ +".pbm"
+ );
+ printPBMHeader(sout, 1, VCParameters.DISP_X,
+ VCParameters.DISP_Y);
+
+ Integer[][] slide = VCGenerator.generateSlide(useed, null);
+
+ int[][] pixels = VCGenerator.vcArrayToPixels(slide);
+ for(int r = 0; r < pixels.length; r++) {
+ for(int c = 0; c < pixels[r].length; c++) {
+ sout.write(pixels[r][c] == VCParameters.white ?
+ '0' : '1');
+ }
+ sout.write('\n');
+ }
+ sout.flush();
+ sout.close();
+ }
+
+ if(cmd.hasOption(OPT_SS_VOCABS)) {
+ int[] plain = new int[VCParameters.GRID_X
+ *VCParameters.GRID_Y];
+ for(int i = 0; i < VCParameters.VCVOC_SIZE; i++) {
+ FileWriter vout = new FileWriter(
+ cmd.getOptionValue(OPT_SS_VOCABS)
+ +"-"
+ +Integer.toString(i)
+ +".pbm"
+ );
+ printPBMHeader(vout, 1, VCParameters.DISP_X,
+ VCParameters.DISP_Y);
+
+ for(int j = 0; j < plain.length; j++) {
+ plain[j] = i;
+ }
+
+ Integer[][] vocab = VCGenerator.generateChallenge(
+ vseed, useed, plain, null
+ );
+ int[][] pixels = VCGenerator.vcArrayToPixels(vocab);
+ for(int r = 0; r < pixels.length; r++) {
+ for(int c = 0; c < pixels[r].length; c++) {
+ vout.write(pixels[r][c] == VCParameters.white ?
+ '1' : '0');
+ }
+ vout.write('\n');
+ }
+ vout.flush();
+ vout.close();
+ }
+ }
+ }
+}
--- /dev/null
+/** Host-side slide generator.
+ *
+ * Required classpath entries:
+ * /usr/share/java/bcprov.jar
+ * /usr/share/java/commons-codec.jar
+ * /usr/share/java/commons-cli.jar
+ * $HOME/src/zxing/core/core.jar
+ *
+ * Build with:
+ * javac -d bin/classes -cp bin/classes:... \
+ * host/org/ietfng/ns/android/vcpass/VCTestVocGen.java
+ *
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.io.FileWriter;
+import java.security.SecureRandom;
+import java.security.Provider;
+import java.security.Security;
+import javax.crypto.SecretKey;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.ByteMatrix;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+import org.apache.commons.codec.binary.Base64;
+
+public final class VCTestVocGen {
+ static final private int SEED_SIZE = 16;
+
+ private static final int QR_SIZEX = 400;
+ private static final int QR_SIZEY = 400;
+
+ static final private Options cliopts = new Options();
+ static final private String OPT_SS_HELP = "h";
+ static final private String OPT_SS_SECRET = "S";
+ static final private String OPT_SS_VOCSEC = "V";
+ static final private String OPT_SS_GENEXC = "c";
+ static final private String OPT_SS_GENEXV = "v";
+
+ private static final int crpix = VCParameters.DISP_X
+ / VCParameters.GRID_X;
+ private static final int ccpix = VCParameters.DISP_Y
+ / VCParameters.GRID_Y;
+
+ private static final void
+ printPBMHeader(FileWriter f, int v, int x, int y)
+ throws java.io.IOException
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("P");
+ sb.append(v);
+ sb.append(" ");
+ sb.append(x);
+ sb.append(" ");
+ sb.append(y);
+ sb.append("\n");
+ f.write(sb.toString(), 0, sb.length());
+ }
+
+ static {
+ cliopts.addOption(OPT_SS_HELP, "help", false, "Show this help");
+ cliopts.addOption(OPT_SS_SECRET, "secretseed", true,
+ "Secret seed (for development)");
+ cliopts.addOption(OPT_SS_SECRET, "slideseed", true,
+ "Slide seed (for development)");
+ cliopts.addOption(OPT_SS_VOCSEC, "vocseed", true,
+ "Vocabulary seed (for development)");
+ cliopts.addOption(OPT_SS_GENEXC, "chalfile", true,
+ "Example challenge file name");
+ cliopts.addOption(OPT_SS_GENEXV, "vocfile", true,
+ "Example vocabulary file name");
+ }
+
+ public static void main(String[] args) throws Exception {
+ CommandLineParser parser = new PosixParser();
+ CommandLine cmd = parser.parse(cliopts, args);
+
+ if(cmd.hasOption(OPT_SS_HELP)) {
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp( "VCSlideGen" , cliopts );
+ return;
+ }
+
+ Security.addProvider(VCParameters.CSPROV);
+
+ char[] useed;
+ if(cmd.hasOption(OPT_SS_SECRET)) {
+ useed = cmd.getOptionValue(OPT_SS_SECRET).toCharArray();
+ } else {
+ // XXX
+ useed = "foo".toCharArray();
+ }
+
+ char[] vseed;
+ if(cmd.hasOption(OPT_SS_VOCSEC)) {
+ vseed = cmd.getOptionValue(OPT_SS_VOCSEC).toCharArray();
+ } else {
+ // XXX
+ vseed = "bar".toCharArray();
+ }
+
+ if(cmd.hasOption(OPT_SS_GENEXV)) {
+ Integer[][] vocab = new Integer[VCParameters.VCVOC_SIZE][];
+ Integer[][] vslide = new Integer[VCParameters.VCVOC_SIZE][];
+
+ {
+ Integer[][] slide = VCGenerator.generateSlide( useed, null );
+ for(int i = 0; i < vslide.length; i++) {
+ vslide[i] = slide[0];
+ }
+ }
+
+ for(int i = 0; i < vocab.length; i++) {
+ int[] plain = new int[VCParameters.GRID_X
+ *VCParameters.GRID_Y];
+ plain[0] = i;
+
+ Integer[][] vocabi = VCGenerator.generateChallenge(
+ vseed, useed, plain, null
+ );
+
+ vocab[i] = vocabi[0];
+ }
+
+ FileWriter vout = new FileWriter(
+ cmd.getOptionValue(OPT_SS_GENEXV)
+ );
+ printPBMHeader(vout, 1, VCParameters.DISP_X,
+ VCParameters.DISP_Y);
+
+ /* XXX */
+ int[][] spixels = VCGenerator.vcArrayToPixels(vslide);
+ int[][] vpixels = VCGenerator.vcArrayToPixels(vocab);
+
+ for(int i = crpix-1; i < VCParameters.DISP_X-1; i += crpix){
+ for(int r = 0; r < vpixels.length; r++) {
+ vpixels[i][r] = VCParameters.black;
+ vpixels[i+1][r] = VCParameters.black;
+ vpixels[r][i] = VCParameters.black;
+ vpixels[r][i+1] = VCParameters.black;
+ }}
+
+ for(int r = 0; r < vpixels.length; r++) {
+ for(int c = 0; c < vpixels[r].length; c++) {
+ if(spixels[r][c] == VCParameters.black) {
+ vout.write('1');
+ } else {
+ vout.write(vpixels[r][c] == VCParameters.white ?
+ '0' : '1');
+ }
+ }
+ vout.write('\n');
+ }
+ vout.flush();
+ vout.close();
+ }
+
+ if(cmd.hasOption(OPT_SS_GENEXC)) {
+ int[] plain = new int[VCParameters.GRID_X
+ *VCParameters.GRID_Y];
+ for(int i = 0; i < plain.length; i++ ){
+ plain[i] = (i*7+8) % VCParameters.VCVOC_SIZE;
+ }
+ FileWriter vout = new FileWriter(
+ cmd.getOptionValue(OPT_SS_GENEXC)
+ );
+ printPBMHeader(vout, 1, VCParameters.DISP_X,
+ VCParameters.DISP_Y);
+
+ Integer[][] vocab = VCGenerator.generateChallenge(
+ vseed, useed, plain, null
+ );
+ int[][] vpixels = VCGenerator.vcArrayToPixels(vocab);
+
+ Integer[][] slide = VCGenerator.generateSlide( useed, null );
+ int[][] spixels = VCGenerator.vcArrayToPixels(slide);
+
+ for(int i = crpix-1; i < VCParameters.DISP_X-1; i += crpix){
+ for(int r = 0; r < vpixels.length; r++) {
+ vpixels[i][r] = VCParameters.black;
+ vpixels[i+1][r] = VCParameters.black;
+ vpixels[r][i] = VCParameters.black;
+ vpixels[r][i+1] = VCParameters.black;
+ }}
+
+ for(int r = 0; r < vpixels.length; r++) {
+ for(int c = 0; c < vpixels[r].length; c++) {
+ if(spixels[r][c] == VCParameters.black) {
+ vout.write('1');
+ } else {
+ vout.write(vpixels[r][c] == VCParameters.white ?
+ '0' : '1');
+ }
+ }
+ vout.write('\n');
+ }
+ vout.flush();
+ vout.close();
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello World, VCPass"
+ />
+</LinearLayout>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical"
+ android:paddingTop="4px">
+ <ImageView android:id="@+id/image"
+ android:layout_width="@dimen/chalX"
+ android:layout_height="@dimen/chalY" />
+ <Button android:id="@+id/done"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/image"
+ android:layout_alignParentLeft="true"
+ android:paddingTop="4px"
+ android:text="Done" />
+ <Button android:id="@+id/reset"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/image"
+ android:layout_alignParentRight="true"
+ android:paddingTop="4px"
+ android:text="Reset" />
+ <TextView android:id="@+id/prompttext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="4px"
+ android:layout_below="@id/done"
+ />
+</RelativeLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical">
+<LinearLayout
+ android:orientation="vertical" android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <TextView android:id="@+id/entry_header"
+ android:paddingBottom="10px"
+ android:paddingTop="10px"
+ android:textSize="24px"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enter VC seed secret:"
+ />
+ <EditText android:id="@+id/password"
+ android:inputType="textPassword"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+ <Button android:id="@+id/impdone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/password"
+ android:layout_alignParentLeft="true"
+ android:paddingTop="4px"
+ android:text="Done" />
+</LinearLayout>
+</ScrollView>
--- /dev/null
+<resources>
+ <dimen name="chalX">320px</dimen>
+ <dimen name="chalY">320px</dimen>
+</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">VCPass</string>
+ <string name="import_test">
+ To ensure that you have correctly imported the seeds,
+ please answer the above challenge. The back button
+ will take you back to importing.
+ </string>
+</resources>
--- /dev/null
+package org.ietfng.ns.android.vcpass;
+
+import java.util.List;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+public class Utils {
+ /** From http://developer.android.com/resources/articles/can-i-use-this-intent.html */
+ public final static boolean isIntentAvailable(Context context, Intent intent) {
+ final PackageManager packageManager = context.getPackageManager();
+ List<ResolveInfo> list =
+ packageManager.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ return list.size() > 0;
+ }
+
+ /*
+ * DJB Netstrings-esque encoding functions for storing
+ * our two seeds in one string.
+ */
+ public static String encode_seeds(char[] useed, char[] vseed) {
+ StringBuilder seed = new StringBuilder();
+ seed.append(Integer.toString(useed.length));
+ seed.append(':');
+ seed.append(useed);
+ seed.append(Integer.toString(vseed.length));
+ seed.append(':');
+ seed.append(vseed);
+ return seed.toString();
+ }
+
+ public static char[][] decode_seeds(String s) {
+ StringBuilder sb = new StringBuilder(s);
+ int ulenend = sb.indexOf(":");
+ int ulen = Integer.parseInt(sb.substring(0,ulenend));
+ int vlenend = sb.indexOf(":",ulenend+ulen+1);
+ int vlen = Integer.parseInt(sb.substring(ulenend+ulen+1,vlenend));
+ char[][] ret = new char[2][];
+ ret[0] = sb.substring(ulenend+1,ulenend+ulen+1).toCharArray();
+ ret[1] = sb.substring(vlenend+1).toCharArray();
+ return ret;
+ }
+
+}
--- /dev/null
+/** Code for generating visual cryptography slides and challenges.
+ * @author Nathaniel Filardo
+ * @license AGPLv3.
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.security.GeneralSecurityException;
+import java.security.Provider;
+import java.security.ProviderException;
+
+import java.util.Stack;
+import java.util.Vector;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+public final class VCGenerator {
+
+ public interface ProgCallback {
+ public void progress(int x);
+ }
+
+ private static final int cells = VCParameters.GRID_X
+ * VCParameters.GRID_Y;
+ private static final int cpix = VCParameters.DISP_X
+ * VCParameters.DISP_Y
+ / VCParameters.GRID_X
+ / VCParameters.GRID_Y;
+ private static final int crvpix = VCParameters.DISP_X
+ / VCParameters.GRID_X
+ / VCParameters.PR_X;
+ private static final int ccvpix = VCParameters.DISP_Y
+ / VCParameters.GRID_Y
+ / VCParameters.PR_Y;
+ private static final int crpix = VCParameters.DISP_X
+ / VCParameters.GRID_X;
+ private static final int ccpix = VCParameters.DISP_Y
+ / VCParameters.GRID_Y;
+
+ private static final void
+ _bitsToVCPixels(Stack<Integer> r,
+ Integer c1,
+ Integer c2,
+ byte[] bits) {
+
+ for(int i = 0; i < crvpix/8; i++) {
+ byte by = bits[i];
+ for (int b = 0; b < 8; b++) {
+ boolean bi = !((by & 0x80) == 0);
+ by <<= 1;
+
+ for(int px = 0; px < VCParameters.PR_X; px++) {
+ if(bi) { r.push(c1); }
+ else { r.push(c2); }
+ Integer ct; ct = c1; c1 = c2; c2 = ct;
+ }
+ }
+ }
+
+ byte by = bits[(crvpix+7)/8 - 1];
+ for (int b = 0; b < crvpix%8; b++) {
+ boolean bi = !((by & 0x80) == 0);
+ by <<= 1;
+
+ for(int px = 0; px < VCParameters.PR_X; px++) {
+ if(bi) { r.push(c1); }
+ else { r.push(c2); }
+ Integer ct; ct = c1; c1 = c2; c2 = ct;
+ }
+ }
+ }
+
+ private static final Vector<Integer>
+ bitsToVCPixels(byte[] bits) {
+ Stack<Integer> r = new Stack<Integer>();
+ int py;
+ for(py = 0; py < VCParameters.PR_Y/2; py++) {
+ _bitsToVCPixels(r, VCParameters.black, VCParameters.white, bits);
+ }
+ for(; py < VCParameters.PR_Y; py++) {
+ _bitsToVCPixels(r, VCParameters.white, VCParameters.black, bits);
+ }
+
+ return r;
+ }
+
+/*
+ private static final void
+ mapXor(byte[] a, byte[] b) {
+ assert(a.length == b.length);
+
+ for (int i = 0; i < a.length; i++) {
+ a[i] ^= b[i];
+ }
+ }
+*/
+
+ /*
+ * The CSPRNGs are clocked as follows:
+ * For each cell,
+ * For each row,
+ * For each vocabulary entry,
+ * generate (CPIXX+7)/8 octets
+ *
+ * The byte array is consulted in ascending index order and
+ * bits within bytes are mapped to pixels LSB to MSB as left
+ * to right.
+ *
+ * Note that CSPRNG output bits may be lost if there are
+ * not an integer multiple of 8 vcpixels per row.
+ *
+ * Note that the user's slide does not have the "for each
+ * vocabulary entry" iteration.
+ *
+ * Even the distinguished vocabulary elements' owned pixels
+ * are clocked out of the CSPRNGs just for simplicity.
+ *
+ * The total number of bits read out for the user's slide is
+ * GRID_X*GRID_Y*CPIXY*CPIXX
+ * or
+ * 4*4*160*160 bits = 409600 bits = 51200 bytes
+ * and for the challenge is
+ * VCVOC_SIZE*...
+ * or
+ * 16*4*4*160*160 bits = 6553600 bits = 819200 bytes
+ */
+
+ public final static Integer[][]
+ generateSlide(
+ final char[] useed,
+ final ProgCallback pcb
+ ) throws
+ ProviderException, GeneralSecurityException
+ {
+ Cipher slidec = seedToCipher(useed);
+
+ Integer[][] cella = new Integer[cells][];
+
+ for(int i = 0; i < cells; i++) {
+ Vector<Integer> cell = new Vector<Integer>();
+ for(int j = 0; j < ccvpix; j++) {
+ byte[] srow = new byte[(crvpix+7)/8];
+ srow = slidec.update(srow);
+ assert(srow.length == (crvpix+7)/8);
+ Vector<Integer> srowvc = bitsToVCPixels(srow);
+ cell.addAll(srowvc);
+ } /* Row */
+ cella[i] = new Integer[cell.size()];
+ cell.toArray(cella[i]);
+ } /* Cell */
+
+ return cella;
+ }
+
+ public final static Integer[][] generateChallenge(
+ final char[] vseed,
+ final char[] useed,
+ final int[] plain,
+ final ProgCallback pcb
+ ) throws
+ ProviderException, GeneralSecurityException
+ {
+ Cipher slidec = seedToCipher(useed);
+ Cipher cellc = seedToCipher(vseed);
+
+ Integer[][] cella = new Integer[cells][];
+
+ for(int i = 0; i < cells; i++) {
+ if(Thread.interrupted()) {
+ return null;
+ }
+ assert(plain[i] < VCParameters.VCVOC_SIZE);
+
+ Vector<Integer> cell = new Vector<Integer>();
+ for(int j = 0; j < ccvpix; j++) {
+ byte[] srow = new byte[(crvpix+7)/8];
+ srow = slidec.update(srow);
+ assert(srow.length == (crvpix+7)/8);
+
+ byte[] vrow = null;
+ /* Get the right bits into vrow */
+ for(int k = 0; k <= plain[i]; k++) {
+ vrow = new byte[(crvpix+7)/8];
+ vrow = cellc.update(vrow);
+ assert(vrow.length == (crvpix+7)/8);
+ }
+
+ if(plain[i] <= VCParameters.VCVOC_DISTINGUISHED) {
+ /* Set the owned pixels in this row to
+ * match those of srow; mutates vrow in place.
+ */
+
+ /* This design works for square cells */
+ assert(crvpix == ccvpix);
+
+ final int lix = j/8;
+ final int lbm
+ = (j%8 == 0)
+ ? 0
+ : ((byte)0x80) >> ((j%8)-1);
+
+ final int rix = (ccvpix+7-j)/8-1;
+ final int rbm
+ = ((ccvpix-j)%8 == 0)
+ ? 0
+ : ~(((byte)0x80) >> ((ccvpix+7-j)%8));
+
+ switch(plain[i]) {
+ case VCParameters.VCVOC_DISTING_DOWN:
+ if (j < ccvpix/2) {
+ if(lix == rix) {
+ vrow[lix] &= lbm | rbm;
+ vrow[lix] |= srow[lix] & ~(lbm | rbm);
+ } else {
+ vrow[lix] &= lbm;
+ vrow[lix] |= srow[lix] & ~lbm;
+ vrow[rix] &= rbm;
+ vrow[rix] |= srow[rix] & ~rbm;
+ }
+ }
+ for(int k = lix+1; k < rix; k++)
+ vrow[k] = srow[k];
+ break;
+ case VCParameters.VCVOC_DISTING_UP:
+ if (j > ccvpix/2) {
+ if(lix == rix) {
+ vrow[lix] &= ~lbm | ~rbm;
+ vrow[lix] |= srow[lix] & (lbm & rbm);
+ } else {
+ vrow[lix] &= ~lbm;
+ vrow[lix] |= srow[lix] & lbm;
+ vrow[rix] &= ~rbm;
+ vrow[rix] |= srow[rix] & rbm;
+ }
+ }
+ for(int k = rix+1; k < lix; k++)
+ vrow[k] = srow[k];
+ break;
+ case VCParameters.VCVOC_DISTING_RIGHT:
+ if (j < ccvpix/2) {
+ vrow[lix] &= ~lbm;
+ vrow[lix] |= srow[lix] & lbm;
+ for(int k = 0; k < lix; k++)
+ vrow[k] = srow[k];
+ } else {
+ vrow[rix] &= rbm;
+ vrow[rix] |= srow[rix] & ~rbm;
+ for(int k = 0; k < rix; k++)
+ vrow[k] = srow[k];
+ }
+ break;
+ case VCParameters.VCVOC_DISTING_LEFT:
+ if (j < ccvpix/2) {
+ vrow[rix] &= ~rbm;
+ vrow[rix] |= srow[rix] & rbm;
+ for(int k = rix+1; k < (ccvpix+7)/8; k++)
+ vrow[k] = srow[k];
+ } else {
+ vrow[lix] &= lbm;
+ vrow[lix] |= srow[lix] & ~lbm;
+ for(int k = lix+1; k < (ccvpix+7)/8; k++)
+ vrow[k] = srow[k];
+ }
+ break;
+ }
+ }
+
+ /* Set pixels in resulting image */
+ Vector<Integer> vrowvc = bitsToVCPixels(vrow);
+ cell.addAll(vrowvc);
+
+ /* Drain the remaining bits from the CSPRNG */
+ for(int k = plain[i] + 1;
+ k < VCParameters.VCVOC_SIZE;
+ k++) {
+ vrow = new byte[(crvpix+7)/8];
+ vrow = cellc.update(vrow);
+ assert(vrow.length == (crvpix+7)/8);
+ }
+ } /* Row */
+ cella[i] = new Integer[cell.size()];
+ cell.toArray(cella[i]);
+
+ if (pcb != null) { pcb.progress(i); }
+ } /* Cell */
+
+ return cella;
+ }
+
+ public final static int[][] vcArrayToPixels(Integer[][] p) {
+ assert(p.length == cells);
+ for(int i = 0; i < cells; i++) {
+ assert(p[i].length == crpix*ccpix);
+ }
+
+ int[][] res = new int[VCParameters.DISP_Y][];
+ for(int r = 0; r < VCParameters.DISP_Y; r++) {
+ res[r] = new int[VCParameters.DISP_X];
+
+ for(int c = 0; c < VCParameters.DISP_X; c++) {
+ final int cell =
+ r/crpix*VCParameters.GRID_X + (c/ccpix);
+ final int pixoff =
+ (r%crpix)*ccpix + c%ccpix;
+ res[r][c] = p[cell][pixoff];
+ }
+ }
+
+ return res;
+ }
+
+ private static final Cipher
+ seedToCipher(final char[] seed)
+ throws ProviderException, GeneralSecurityException
+ {
+ SecretKeyFactory skf =
+ SecretKeyFactory.getInstance(VCParameters.CSKEYFACT,
+ VCParameters.CSPROV);
+
+ byte[] salt = { 0x22, 0x24 };
+ PBEKeySpec pks = new PBEKeySpec(seed, salt, 1024);
+ SecretKey sk = skf.generateSecret(pks);
+
+ Cipher c = Cipher.getInstance(VCParameters.CSPRNG,
+ VCParameters.CSPROV);
+ c.init(Cipher.ENCRYPT_MODE, sk);
+
+ return c;
+ }
+}
+
--- /dev/null
+package org.ietfng.ns.android.vcpass;
+
+import java.security.Provider;
+import javax.crypto.spec.PBEKeySpec;
+
+public class VCParameters {
+ /* CSPRNG Class name */
+ static final Provider CSPROV =
+ new org.bouncycastle.jce.provider.BouncyCastleProvider();
+ static final String CSPRNG = "AES/CFB8/NoPadding";
+ static final String CSKEYFACT = "PBEWithSHAAnd128BitAES-CBC-BC";
+
+ /* Challenge grid size */
+ static final int GRID_X = 4;
+ static final int GRID_Y = 4;
+
+ /* Real pixels per VC Pixel */
+ static final int PR_X = 2;
+ static final int PR_Y = 2;
+
+ /* Display size, in real pixels */
+ static final int DISP_X = 320;
+ static final int DISP_Y = 320;
+
+ /** Number of entries in the per-cell vocabulary */
+ static final int VCVOC_SIZE = 6;
+ /** Last index of distinguished entries in the per-cell vocabulary.
+ * @see VCPassActivity.VCPassTouchHandler.onTouch
+ * @see VCGenerator.createChallenge
+ */
+ static final int VCVOC_DISTINGUISHED = 3;
+
+ static final int VCVOC_DISTING_UP = 0;
+ static final int VCVOC_DISTING_DOWN = 1;
+ static final int VCVOC_DISTING_LEFT = 2;
+ static final int VCVOC_DISTING_RIGHT = 3;
+
+ /** Pixel values */
+ static final Integer black = 0xFF000000;
+ static final Integer white = 0xFFFFFFFF;
+};
--- /dev/null
+/* Visual Cryptography Passcodes
+ * (C) 2009 Nathaniel Filardo
+ *
+ * This code is available under the GPLv3 license.
+ *
+ * Much credit is owed to the excellent documentation and example code at
+ * http://developer.android.com.
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.io.Serializable;
+import java.security.Provider;
+import java.util.Formatter;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.util.Log;
+
+import dalvik.system.PathClassLoader;
+
+
+// drawing
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+// import com.google.zxing.client.android.Intents;
+
+public class VCPass extends Activity
+{
+ private boolean canceled = false;
+ private boolean inited = false;
+ private char[] useed;
+ private char[] vseed;
+ private String secret;
+ private Bitmap chal;
+
+ private static final int REQ_IMPORT = 0;
+ private static final int REQ_CREATE = 1;
+ private static final int REQ_PRESENT = 2;
+
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ Log.i("VCPass", "CREATE");
+
+
+ if(0) {
+ PackageManager pm = getPackageManager();
+ Intent intent = new Intent("org.ietfng.ns.android.vcpass.CHAL_PRESENT");
+ ResolveInfo ri = pm.resolveActivity(intent, 0);
+ PathClassLoader pcl = new PathClassLoader
+ (ri.activityInfo.applicationInfo.publicSourceDir
+ ,getClassLoader()
+ );
+
+ Class c;
+ try{
+ c = pcl.loadClass(ri.activityInfo.name);
+ Log.i("VCPass:CREATE:c", c.toString());
+ } catch (ClassNotFoundException cnfe) {
+ Log.i("VCPass:CREATE:c", "CNFE" + cnfe.toString());
+ return;
+ }
+
+ /* This loop is dumb because Java clearly knows better than we do
+ * but specifying the parameter types is a pain. If something goes
+ * wrong, it'll get caught by reflection later.
+ */
+ Method m = null;
+ for(Method mc : c.getMethods()) {
+ if(mc.getName().equals("do_createChallenge")) { m = mc; break; }
+ }
+ if(m == null) {
+ Log.i("VCPass:CREATE:m", "NOT FOUND");
+ return;
+ } else {
+ Log.i("VCPass:CREATE:m", m.toString());
+ }
+
+ Object r;
+ try {
+ r = m.invoke(null, "foo".toCharArray(),
+ "bar".toCharArray(),
+ new Integer(0),
+ null);
+ Log.i("VCPass:CREATE:r", r.toString());
+ } catch (IllegalAccessException iae) {
+ Log.i("VCPass:CREATE:r", "IAE" + iae.toString());
+ return;
+ } catch (java.lang.reflect.InvocationTargetException ite) {
+ Log.i("VCPass:CREATE:r", "ITE" + ite.toString());
+ return;
+ }
+
+ try {
+ Field fe = r.getClass().getField("error");
+ String e = (String) fe.get(r);
+ Field fp = r.getClass().getField("plain");
+ String p = (String) fp.get(r);
+ Field fb = r.getClass().getField("bm");
+ Bitmap b = (Bitmap) fb.get(r);
+
+ if(e != null)
+ Log.i("VCPass:CREATE:e", e);
+ else {
+ Log.i("VCPass:CREATE:b", b.toString());
+ Log.i("VCPass:CREATE:p", p);
+ }
+ } catch (IllegalAccessException iae) {
+ Log.i("VCPass:CREATE:_", "IAE" + iae.toString());
+ return;
+ } catch (NoSuchFieldException nsfe) {
+ Log.i("VCPass:CREATE:_", "NSFE" + nsfe.toString());
+ return;
+ }
+ }
+
+/*
+ for (String s : Security.getAlgorithms("cipher")) {
+ Log.d ("VCPassCrypto", s);
+ }
+ try {
+ Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ } catch (NoSuchAlgorithmException e) {
+ Log.d("VCPassCrypto", "Argh NSA");
+ } catch (NoSuchPaddingException e) {
+ Log.d("VCPassCrypto", "Argh NSP");
+ }
+*/
+
+ }
+
+
+ private void launchImporter() {
+ Intent intent = new Intent(this, VCPassImport.class);
+ intent.setAction(VCPassImport.ACTION_IMPORT_SEED);
+ startActivityForResult(intent, REQ_IMPORT);
+ }
+
+ private void launchGenerator() {
+ Intent intent = new Intent(this, VCPassActivity.class);
+ intent.setAction(VCPassActivity.ACTION_CREATE_CHALLENGE);
+ intent.putExtra(VCPassActivity.EXTRA_USER_SLIDE_SEED,useed);
+ intent.putExtra(VCPassActivity.EXTRA_VOCABULARY_SEED,vseed);
+ // intent.putExtra(VCPassActivity.EXTRA_QUIET_OPERATION,true);
+ Log.d("VCPass", intent.toString());
+ startActivityForResult(intent, REQ_CREATE);
+ }
+
+ private void launchChallenger() {
+ Intent intent = new Intent(this, VCPassActivity.class);
+ intent.setAction(VCPassActivity.ACTION_PRESENT_CHALLENGE);
+ intent.putExtra(VCPassActivity.EXTRA_CHALLENGE,chal);
+ intent.putExtra(VCPassActivity.EXTRA_PROMPT_TEXT,"Try this one...");
+ Log.d("VCPass", intent.toString());
+ startActivityForResult(intent, REQ_PRESENT);
+ }
+
+ public void onResume()
+ {
+ super.onResume();
+
+ Log.i("VCPass", "RESUME");
+
+ if(!inited) {
+ inited = true;
+ launchImporter();
+ }
+ }
+
+ public void onActivityResult(int req, int res, Intent data)
+ {
+ Log.i("VCPass", "OAR");
+ Log.i("VCPass", Integer.toString(req));
+
+ if(data != null)
+ Log.i("VCPass", data.getAction());
+
+ if(res == RESULT_CANCELED) {
+ Log.d("VCPass", "CANCELED");
+ canceled = true;
+ String err = null;
+ if(data != null)
+ err = data.getStringExtra(VCPassActivity.EXTRA_ERROR);
+ if(err != null)
+ Log.i("VCPass", err);
+ finish();
+ } else {
+ if (req == REQ_IMPORT) {
+ useed = data.getCharArrayExtra(VCPassActivity.EXTRA_USER_SLIDE_SEED);
+ vseed = data.getCharArrayExtra(VCPassActivity.EXTRA_VOCABULARY_SEED);
+
+ launchGenerator();
+ } else if(req == REQ_CREATE) {
+ chal = (Bitmap) data.getParcelableExtra(VCPassActivity.EXTRA_CHALLENGE);
+ secret = data.getStringExtra(VCPassActivity.EXTRA_SECRET);
+ Log.i("VCPass", "CREATECHAL: " + secret);
+
+ launchChallenger();
+ } else if (req == REQ_PRESENT) {
+ String s = data.getStringExtra(VCPassActivity.EXTRA_SECRET);
+ if(s.equals(secret)) {
+ Log.i("VCPass","correct");
+ launchGenerator();
+ } else {
+ launchChallenger();
+ }
+ } else {
+ throw new RuntimeException("Invalid request!");
+ }
+ }
+ }
+}
--- /dev/null
+/* Visual Cryptography Passcodes
+ * (C) 2009 Nathaniel Filardo
+ *
+ * This code is available under the GPLv3 license.
+ *
+ * Much credit is owed to the excellent documentation and example code at
+ * http://developer.android.com.
+ */
+
+package org.ietfng.ns.android.vcpass;
+
+import java.io.Serializable;
+import java.security.ProviderException;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Formatter;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Parcelable;
+import android.util.Log;
+// import android.view.Menu;
+// import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public final class
+VCPassActivity
+extends Activity
+{
+ /* * * * * * Protocol parameters * * * * * */
+
+ private static final String DBGN = "VCPassAct";
+
+ private static final String SAVED_STATE_KEY_RESPONSE
+ = "SEQ";
+
+ /** Intent ACTION name for challenging the user */
+ public static final String ACTION_PRESENT_CHALLENGE
+ = "org.ietfng.ns.android.vcpass.CHAL_PRESENT";
+ /** Intent ACTION name for creating a challenge */
+ public static final String ACTION_CREATE_CHALLENGE
+ = "org.ietfng.ns.android.vcpass.CHAL_CREATE";
+
+ /** Intent EXTRA name for error messages.
+ *
+ * Type: java.lang.String
+ *
+ * Used VCPA to caller for ACTION_*
+ */
+ public static final String EXTRA_ERROR
+ = "ERR";
+ /** Intent EXTRA name for prompt text.
+ *
+ * Type: java.lang.String
+ *
+ * Not used for ACTION_CREATE_CHALLENGE
+ * Used caller to VCPA for ACTION_PRESENT_CHALLENGE
+ */
+ public static final String EXTRA_PROMPT_TEXT
+ = "PT";
+ /** Intent EXTRA name for plain-text response.
+ *
+ * Type: java.lang.String
+ *
+ * Used VCPA to caller for ACTION_CREATE_CHALLENGE
+ * Used VCPA to caller for ACTION_PRESENT_CHALLENGE
+ */
+ public static final String EXTRA_SECRET
+ = "SEQ";
+ /** Intent EXTRA name for Bitmap challenge.
+ *
+ * Type: android.graphics.Bitmap
+ *
+ * Used VCPA to caller for ACTION_CREATE_CHALLENGE.
+ * Used caller to VCPA for ACTION_PRESENT_CHALLENGE.
+ */
+ public static final String EXTRA_CHALLENGE
+ = "CHAL";
+ /** Intent EXTRA name for user slide seed.
+ *
+ * Type: [char
+ *
+ * Used caller to VCPA for ACTION_CREATE_CHALLENGE.
+ * Not used for ACTION_PRESENT_CHALLENGE;
+ * ideally, the contents would be unknown to the caller.
+ * Used VCPA to caller for ACTION_IMPORT_SEED
+ */
+ public static final String EXTRA_USER_SLIDE_SEED
+ = "USEED";
+ /** Intent EXTRA name for vocabulary seed.
+ *
+ * Type: [char
+ *
+ * Used caller to VCPA for ACTION_CREATE_CHALLENGE.
+ * Not used for ACTION_PRESENT_CHALLENGE;
+ * ideally, the contents would be unknown to the caller.
+ * Used VCPA to caller for ACTION_IMPORT_SEED
+ */
+ public static final String EXTRA_VOCABULARY_SEED
+ = "VSEED";
+
+ /** Intent EXTRA name for quiet operation.
+ *
+ * Type: void
+ *
+ * Used caller to VCPA for ACTION_CREATE_CHALLENGE
+ * Not used for ACTION_PRESENT_CHALLENGE
+ */
+ public static final String EXTRA_QUIET_OPERATION = "QUIET";
+
+ /** Intent EXTRA name for user slide seed.
+ *
+ * Type: int
+ *
+ * Used caller to VCPA for ACTION_CREATE_CHALLENGE.
+ * Not used for ACTION_PRESENT_CHALLENGE
+ */
+ public static final String EXTRA_MINIMUM_EVENTS = "MINEVT";
+
+ /* * * * * * Private constants * * * * * */
+
+ private static final int cells = VCParameters.GRID_X
+ * VCParameters.GRID_Y;
+ private static final int crpix = VCParameters.DISP_X
+ / VCParameters.GRID_X;
+ private static final int ccpix = VCParameters.DISP_Y
+ / VCParameters.GRID_Y;
+
+ private static final int REQ_PRESENT_TEST = 2;
+
+ /* * * * * * Private state * * * * * */
+
+ private Resources res; // Android
+ private int[] response; // For state saving
+ private Thread calcThread;
+
+ /* * * * * * Private utility functions * * * * * */
+
+ private final void
+ yieldError(Intent s, String e) {
+ s.putExtra(EXTRA_ERROR, e);
+ setResult(RESULT_CANCELED, s);
+ finish();
+ }
+
+ private final void
+ postYieldError(final Intent s, final String e) {
+ runOnUiThread(new Runnable(){
+ public final void run() {
+ yieldError(s,e);
+ }
+ });
+ }
+
+ /** Prepare an int array for use as a response store */
+ private static final void
+ resetResponse(int[] chal) {
+ assert(chal.length == VCParameters.GRID_X*VCParameters.GRID_Y);
+
+ for(int i = 0;
+ i < VCParameters.GRID_X*VCParameters.GRID_Y;
+ i++) {
+ chal[i] = -1;
+ }
+ }
+
+ /**
+ * Render a challenge grid as plain text.
+ *
+ * Each cell of the array contains an index into the
+ * vocabulary; the values [0,VCVOC_DISTINGUISHED] are
+ * rendered into the resulting string. The remainder
+ * are ignored.
+ */
+ private static final StringBuilder
+ encodeResponse(int[] chal) {
+ assert(chal.length == VCParameters.GRID_X*VCParameters.GRID_Y);
+
+ StringBuilder enc = new StringBuilder();
+
+ for(int i = 0;
+ i < VCParameters.GRID_X*VCParameters.GRID_Y;
+ i++) {
+ assert(chal[i] >= -1);
+ assert(chal[i] <= VCParameters.VCVOC_SIZE);
+ if(chal[i] >= 0
+ && chal[i] <= VCParameters.VCVOC_DISTINGUISHED) {
+ // enc.append(Integer.toString(i));
+ enc.append(Integer.toString(chal[i]));
+ } else {
+ enc.append("_");
+ }
+ }
+
+ return enc;
+ }
+
+ /* * * * * * Presenting a challenge to the user * * * * * */
+
+ private final class
+ VCPassPreDrawListener
+ implements ViewTreeObserver.OnPreDrawListener {
+ private Intent i;
+ private ImageView v;
+ private Bitmap o;
+
+ public VCPassPreDrawListener(Intent i, ImageView v, Bitmap o) {
+ this.i = i;
+ this.v = v;
+ this.o = o;
+ }
+
+ public boolean onPreDraw() {
+ // If the challenge will not fit, bail out.
+ if (v.getWidth() < o.getWidth()
+ || v.getHeight() < o.getHeight()) {
+ Log.d("AAAA", Integer.toString(v.getWidth()));
+ Log.d("AAAA", Integer.toString(o.getWidth()));
+ Log.d("AAAA", Integer.toString(v.getHeight()));
+ Log.d("AAAA", Integer.toString(o.getHeight()));
+ yieldError(i, "Challenge of incorrect size");
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private final class
+ VCPassTouchHandler
+ implements View.OnTouchListener {
+ private int[] r;
+ private Canvas c;
+ private Paint p;
+
+ public VCPassTouchHandler(Canvas c, int[] r) {
+ this.r = r;
+ this.c = c;
+ p = new Paint();
+ p.setARGB(127,0,255,0);
+ }
+
+ private int expected_motion = MotionEvent.ACTION_DOWN;
+ private float last_down_xc = -1;
+ private float last_down_yc = -1;
+ private int last_down_x = -1;
+ private int last_down_y = -1;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ int action = event.getAction();
+ float w = v.getWidth();
+ float h = v.getHeight();
+ float xc = event.getX();
+ float yc = event.getY();
+
+ if (action != expected_motion)
+ return true;
+
+ int x = (int)(xc / w * VCParameters.GRID_X);
+ int y = (int)(yc / h * VCParameters.GRID_Y);
+
+ if( x < 0 || y < 0 )
+ return true;
+
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ last_down_xc = xc;
+ last_down_yc = yc;
+ last_down_x = x;
+ last_down_y = y;
+ expected_motion = MotionEvent.ACTION_UP;
+ } else if (action == MotionEvent.ACTION_UP) {
+ float deltaX = xc - last_down_xc;
+ float deltaY = yc - last_down_yc;
+
+ int label;
+ assert(VCParameters.VCVOC_DISTINGUISHED == 3);
+ if ( Math.abs(deltaX) > Math.abs(deltaY) ) {
+ if ( deltaX > 0 ) {
+ label = VCParameters.VCVOC_DISTING_RIGHT;
+ } else {
+ label = VCParameters.VCVOC_DISTING_LEFT;
+ }
+ } else {
+ if ( deltaY > 0 ) {
+ label = VCParameters.VCVOC_DISTING_DOWN;
+ } else {
+ label = VCParameters.VCVOC_DISTING_UP;
+ }
+ }
+ r[VCParameters.GRID_X*last_down_y
+ + last_down_x] = label;
+
+ expected_motion = MotionEvent.ACTION_DOWN;
+
+ Rect sq = new Rect((int)(last_down_x*w
+ /VCParameters.GRID_X
+ ),
+ (int)(last_down_y*h
+ /VCParameters.GRID_Y
+ ),
+ (int)((last_down_x+1)*w
+ /VCParameters.GRID_X
+ ),
+ (int)((last_down_y+1)*h
+ /VCParameters.GRID_Y
+ ));
+ Log.d("VCPTH", encodeResponse(r).toString());
+ c.drawRect(sq, p);
+ v.invalidate();
+ } else {
+ throw new RuntimeException("Unexpected action:" + action + "\n");
+ }
+
+ return true;
+ }
+ }
+
+ private static final class
+ VCPassResetHandler
+ implements View.OnClickListener {
+ private ImageView v;
+ private Canvas c;
+ private Bitmap o;
+ private int[] r;
+
+ VCPassResetHandler(Canvas c, ImageView v, Bitmap o, int[] r)
+ {
+ this.c = c;
+ this.v = v;
+ this.o = o;
+ this.r = r;
+ }
+
+ @Override
+ public void onClick(View bv) {
+ Bitmap nb = o.copy(o.getConfig(), true);
+ // s.delete(0,s.length());
+ resetResponse(r);
+ c.setBitmap(nb);
+ v.setImageBitmap(nb);
+ }
+ }
+
+ private final class
+ VCPassCompletionHandler
+ implements View.OnClickListener {
+ private Intent i;
+ private int[] r;
+
+ VCPassCompletionHandler(Intent i, int[] r)
+ {
+ this.i = i;
+ this.r = r;
+ }
+
+ @Override
+ public void onClick(View bv) {
+ i.putExtra(EXTRA_SECRET, encodeResponse(r).toString());
+ setResult(RESULT_OK, i);
+ finish();
+ }
+ }
+
+
+ private final void
+ presentChallenge(Intent spawner, Bundle sis) {
+ response = null;
+ if (sis != null) {
+ response = sis.getIntArray(SAVED_STATE_KEY_RESPONSE);
+ }
+ if(response == null) {
+ response = new int[VCParameters.GRID_X
+ *VCParameters.GRID_Y];
+ resetResponse(response);
+ }
+
+
+ Bitmap origchal = (Bitmap)spawner.getParcelableExtra(EXTRA_CHALLENGE);
+ if (null == origchal) {
+ yieldError(spawner, "No Challenge Given");
+ return;
+ }
+
+ /*
+ * XXX Do we have to draw blanks here? Does our graphic state
+ * get saved for us?
+ */
+
+ Bitmap chal = origchal.copy(origchal.getConfig(), true);
+ Canvas c = new Canvas(chal);
+
+ // Switch off the titlebar
+ // requestWindowFeature(Window.FEATURE_NO_TITLE);
+ // Lock orientation
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ // Load layout
+ setContentView(R.layout.vcpact);
+
+ ImageView imgview = (ImageView) findViewById(R.id.image);
+ imgview.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ imgview.setImageBitmap(chal);
+ imgview.setOnTouchListener(new VCPassTouchHandler(c, response));
+ imgview.getViewTreeObserver()
+ .addOnPreDrawListener(
+ new VCPassPreDrawListener(spawner, imgview, origchal));
+
+ Button resetbtn = (Button) findViewById(R.id.reset);
+ resetbtn.setOnClickListener(
+ new VCPassResetHandler(c, imgview, origchal, response));
+ Button donebtn = (Button) findViewById(R.id.done);
+ donebtn.setOnClickListener(
+ new VCPassCompletionHandler(spawner, response));
+
+ String pt = spawner.getStringExtra(EXTRA_PROMPT_TEXT);
+ if(pt != null) {
+ TextView pttv = (TextView) findViewById(R.id.prompttext);
+ pttv.setText(pt);
+ }
+ }
+
+ /* * * * * * Creating a challenge * * * * * */
+
+ public static class CreatedChallenge {
+ /* Have I mentioned recently how much I hate Java?
+ * This is as close to error+plain*bm as is trivial
+ * to make here.
+ */
+ public String plain;
+ public Bitmap bm;
+ public String error;
+ }
+
+ public static CreatedChallenge
+ do_createChallenge(
+ char[] useed,
+ char[] vseed,
+ int minevt,
+ VCGenerator.ProgCallback pcb
+ ) {
+ CreatedChallenge res = new CreatedChallenge();
+ int[] plain = new int[cells];
+
+ try {
+ // Let this one initialize from the system's random source
+ SecureRandom chalsr = new SecureRandom();
+ int evtc = 0;
+ do {
+ evtc = 0;
+ for(int i = 0; i < cells; i++) {
+ plain[i] = chalsr.nextInt(VCParameters.VCVOC_SIZE+1);
+ if(plain[i] <= VCParameters.VCVOC_DISTINGUISHED) {
+ evtc++;
+ }
+ }
+ } while (evtc < minevt);
+
+ res.plain = encodeResponse(plain).toString();
+
+ Integer[][] cella = VCGenerator.generateChallenge(
+ vseed, useed,
+ plain, pcb
+ );
+ if(cella == null) {
+ res.error = "Null return from generator";
+ return res;
+ }
+
+ assert(cella.length == cells);
+ assert(cella[0].length == crpix*ccpix);
+
+ res.bm = Bitmap.createBitmap(
+ VCParameters.DISP_X,
+ VCParameters.DISP_Y,
+ Bitmap.Config.RGB_565);
+
+ for(int i = 0; i < cells; i++) {
+ int[] cellai = new int[cella[i].length];
+ for(int j = 0; j < cella[i].length; j++) {
+ cellai[j] = cella[i][j];
+ }
+
+ res.bm.setPixels(cellai, 0,
+ crpix,
+ (i%VCParameters.GRID_X)*crpix,
+ (i/VCParameters.GRID_Y)*ccpix,
+ crpix, ccpix
+ );
+ }
+
+/* Alternative, row-by-row formulation:
+ int[][] pixels = VCGenerator.vcArrayToPixels(cella);
+ for(int i = 0; i < pixels.length; i++) {
+ res.bm.setPixels(pixels[i], 0, pixels[i].length, 0, i, pixels[i].length, 1);
+ }
+*/
+
+ Paint p = new Paint();
+ p.setColor(0xC0FFFF00);
+
+ Canvas c = new Canvas(res.bm);
+ for(int i = crpix-1; i < VCParameters.DISP_X-1; i += crpix){
+ c.drawLine(i,0,i,VCParameters.DISP_Y,p);
+ c.drawLine(i+1,0,i+1,VCParameters.DISP_Y,p);
+ }
+ for(int i = ccpix-1; i < VCParameters.DISP_Y-1; i += ccpix){
+ c.drawLine(0,i,VCParameters.DISP_X,i,p);
+ c.drawLine(0,i+1,VCParameters.DISP_X,i+1,p);
+ }
+
+ } catch (ProviderException pe) {
+ res.error = pe.toString();
+ return res;
+ } catch (GeneralSecurityException gse) {
+ res.error = gse.toString();
+ return res;
+ }
+
+/*
+ Paint p = new Paint();
+ Canvas c = new Canvas(bm);
+ p.setTextSize(20);
+
+ StringBuilder sb = new StringBuilder();
+ Formatter f = new Formatter(sb);
+ f.format("%f", p.getFontMetrics().top);
+ Log.d("VCPassPDH", sb.toString());
+
+ c.drawText("1", 1.0f, 1.0f, p);
+ c.drawText("30", 30.0f, 30.0f, p);
+ c.drawText("70", 70.0f, 70.0f, p);
+*/
+
+ return res;
+
+ }
+
+ private final void
+ _intent_createChallenge(final Intent spawner,
+ boolean quiet) {
+ char[] useed = spawner.getCharArrayExtra(EXTRA_USER_SLIDE_SEED );
+ char[] vseed = spawner.getCharArrayExtra(EXTRA_VOCABULARY_SEED );
+ int minevt = spawner.getIntExtra (EXTRA_MINIMUM_EVENTS, -1);
+
+ if(useed == null || vseed == null) {
+ postYieldError(spawner, "Null seed");
+ return;
+ }
+
+ final VCGenerator.ProgCallback pcb
+ = quiet ? null : new VCGenerator.ProgCallback() {
+ public void progress(final int x) {
+ runOnUiThread(new Runnable() {
+ public final void run() {
+ getWindow().setFeatureInt(
+ Window.FEATURE_PROGRESS,
+ x*9000
+ /VCParameters.GRID_X
+ /VCParameters.GRID_Y);
+ }
+ });
+ }
+ };
+
+ CreatedChallenge cc = do_createChallenge(useed, vseed, minevt, pcb);
+ if(cc.error != null) {
+ postYieldError(spawner, cc.error);
+ return;
+ }
+
+ spawner.putExtra(EXTRA_CHALLENGE, (Parcelable)cc.bm);
+ spawner.putExtra(EXTRA_SECRET, cc.plain);
+
+ calcThread = null;
+
+ runOnUiThread(new Runnable() {
+ final public void run() {
+ setResult(RESULT_OK, spawner);
+ finish();
+ }
+ });
+ }
+
+ private final void
+ createChallenge(final Intent spawner, Bundle sis) {
+ final boolean quiet = spawner.hasExtra(EXTRA_QUIET_OPERATION);
+
+ if(!quiet) {
+ requestWindowFeature(Window.FEATURE_PROGRESS);
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS, 0);
+
+ TextView tv = new TextView(this);
+ tv.setText("Please wait while I do some math...");
+ setContentView(tv);
+ }
+
+ calcThread = new Thread(new Runnable() {
+ public final void run() {
+ _intent_createChallenge(spawner, quiet);
+ }
+ });
+
+ calcThread.start();
+ }
+
+ /* * * * * * Android interface core * * * * * */
+
+ @Override
+ public void onCreate(Bundle sis)
+ {
+ super.onCreate(sis);
+
+ Log.i(DBGN, "CREATE");
+
+ res = getResources();
+
+ Intent spawner = getIntent();
+ String act = spawner.getAction();
+ Log.d(DBGN, act);
+
+ if(act.equals(ACTION_PRESENT_CHALLENGE)) {
+ presentChallenge(spawner, sis);
+ } else if (act.equals(ACTION_CREATE_CHALLENGE)) {
+ createChallenge(spawner, sis);
+ } else {
+ setResult(RESULT_CANCELED, null);
+ finish();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ if(isFinishing()) {
+ if(calcThread != null) { calcThread.interrupt(); }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ /* If we were presenting a challenge this time around,
+ * store our secret so that we can come back to it
+ */
+ if(getIntent().getAction().equals(ACTION_PRESENT_CHALLENGE)) {
+ outState.putIntArray(SAVED_STATE_KEY_RESPONSE, response);
+ }
+ }
+}
--- /dev/null
+package org.ietfng.ns.android.vcpass;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+public final class
+VCPassImport
+extends Activity
+{
+ private static final boolean TRY_QRCODE = false;
+
+ /* * * * * * Protocol parameters * * * * * */
+
+ private static final String DBGN = "VCPassImp";
+
+ private static final String SAVED_SPAWNER = "SPAWN";
+
+ public static final String ACTION_IMPORT_SEED
+ = "org.ietfng.ns.android.vcpass.SEED_IMPORT";
+ public static final String ACTION_IMPORT_AND_CREATE
+ = "org.ietfng.ns.android.vcpass.IMPORT_AND_CREATE";
+
+ private static final int REQ_IMPORT = 0;
+ private static final int REQ_CREATE_FOR_PRESENT = 1;
+ private static final int REQ_PRESENT = 2;
+ private static final int REQ_CREATE = 3;
+
+ /* EXTRAs are as in VCPassActivity */
+
+ /* * * * * * Private state * * * * * */
+
+ char[] useed;
+ char[] vseed;
+ Bitmap cfpc;
+ String cfps;
+
+ final String SAVED_STATE_USEED = "USEED";
+ final String SAVED_STATE_VSEED = "VSEED";
+ final String SAVED_STATE_CFPC = "CFPC";
+ final String SAVED_STATE_CFPS = "CFPS";
+
+ /* * * * * * Importing Seeds * * * * * */
+
+ private void
+ launchGenerator(int res, int min) {
+ Log.d(DBGN, "Launching generator...");
+ Intent intent = new Intent(this, VCPassActivity.class);
+ intent.setAction(VCPassActivity.ACTION_CREATE_CHALLENGE);
+ intent.putExtra(VCPassActivity.EXTRA_USER_SLIDE_SEED,useed);
+ intent.putExtra(VCPassActivity.EXTRA_VOCABULARY_SEED,vseed);
+ if(min > 0) {
+ intent.putExtra(VCPassActivity.EXTRA_MINIMUM_EVENTS,min);
+ }
+ startActivityForResult(intent, res);
+ }
+
+ private void launchChallenger() {
+ Log.d(DBGN, "Launching challenger...");
+ Intent intent = new Intent(this, VCPassActivity.class);
+ intent.setAction(VCPassActivity.ACTION_PRESENT_CHALLENGE);
+ intent.putExtra(VCPassActivity.EXTRA_CHALLENGE,cfpc);
+ intent.putExtra(VCPassActivity.EXTRA_PROMPT_TEXT,
+ getString(R.string.import_test));
+ Log.d("VCPass", intent.toString());
+ startActivityForResult(intent, REQ_PRESENT);
+ }
+
+ private static final class
+ VCPassImpDoneHandler
+ implements View.OnClickListener {
+ private EditText et;
+ private VCPassImport self;
+ VCPassImpDoneHandler(EditText et, VCPassImport self) {
+ this.et = et;
+ this.self = self;
+ }
+
+ @Override
+ public void onClick(View bv) {
+ self.haveImportedSeed(et.getText().toString());
+ }
+ }
+
+ // XXX
+ private String zxingscan = "com.google.zxing.client.android.SCAN";
+ private final void
+ importSeed() {
+ Log.d(DBGN, "Import seed...");
+
+ Intent intent = new Intent(zxingscan);
+ boolean isAvail = Utils.isIntentAvailable(this, intent);
+ if(TRY_QRCODE && isAvail) {
+ startActivityForResult(intent, REQ_IMPORT);
+ } else {
+ setContentView(R.layout.vcpimp);
+ EditText set = (EditText) findViewById(R.id.password);
+ Button donebtn = (Button) findViewById(R.id.impdone);
+ donebtn.setOnClickListener(
+ new VCPassImpDoneHandler(set, this));
+ }
+ }
+
+ private final boolean
+ haveImportedSeed(String seed) {
+ Log.d(DBGN, "Have imported seed...");
+
+ char[][] ds;
+ try {
+ ds = Utils.decode_seeds(seed);
+ if(ds[0] == null
+ || ds[1] == null
+ || ds[0].length == 0
+ || ds[1].length == 0)
+ return false;
+ } catch (Exception e) {
+ return false;
+ }
+
+ this.useed = ds[0];
+ this.vseed = ds[1];
+
+ if(getIntent().hasExtra(VCPassActivity.EXTRA_QUIET_OPERATION)) {
+ finishImportedSeed();
+ } else {
+ launchGenerator(REQ_CREATE_FOR_PRESENT, 1);
+ }
+
+ return true;
+ }
+
+ private final void
+ finishImportedSeed() {
+ Log.d(DBGN, "Finish imported seed...");
+ if( getIntent().getAction().equals(ACTION_IMPORT_AND_CREATE) ) {
+ launchGenerator(REQ_CREATE, 0);
+ } else {
+ finalResult(null,null);
+ }
+ }
+
+ private final void
+ finalResult(Parcelable chal, String secret) {
+ Log.d(DBGN, "Final result...");
+
+ Intent result = getIntent();
+
+ result.putExtra(VCPassActivity.EXTRA_USER_SLIDE_SEED, useed );
+ result.putExtra(VCPassActivity.EXTRA_VOCABULARY_SEED, vseed );
+ if(chal != null)
+ result.putExtra(VCPassActivity.EXTRA_CHALLENGE , chal );
+ if(secret != null)
+ result.putExtra(VCPassActivity.EXTRA_SECRET , secret );
+ setResult(RESULT_OK, result);
+ finish();
+ }
+
+ public void onActivityResult(int req, int res, Intent data) {
+ if(res == RESULT_CANCELED && req != REQ_PRESENT) {
+ setResult(RESULT_CANCELED, getIntent());
+ }
+ if(data == null) {
+ // being canceled; report failure upstream
+ setResult(RESULT_CANCELED, null);
+ finish();
+ return;
+ }
+
+ switch(req) {
+ case REQ_IMPORT:
+ Log.d(DBGN, "Result IMPORT ...");
+ if(!haveImportedSeed(data.getStringExtra("SCAN_RESULT"))){
+ // Something didn't work out; try again
+ importSeed();
+ }
+ break;
+ case REQ_CREATE_FOR_PRESENT:
+ Log.d(DBGN, "Result CFP ...");
+ cfpc = (Bitmap) data.getParcelableExtra(
+ VCPassActivity.EXTRA_CHALLENGE);
+ cfps = data.getStringExtra(VCPassActivity.EXTRA_SECRET);
+ launchChallenger();
+ break;
+ case REQ_PRESENT:
+ Log.d(DBGN, "Result P ...");
+ // go back and redo import; the user can cancel that
+ // if they really want to cancel.
+ if ( res == RESULT_CANCELED ) {
+ importSeed();
+ } else {
+ String sec = data.getStringExtra(
+ VCPassActivity.EXTRA_SECRET);
+ if(cfps.equals(sec)) {
+ finishImportedSeed();
+ } else {
+ launchChallenger();
+ }
+ }
+ break;
+ case REQ_CREATE:
+ Log.d(DBGN, "Result C ...");
+ finalResult(data.getParcelableExtra(
+ VCPassActivity.EXTRA_CHALLENGE),
+ data.getStringExtra(
+ VCPassActivity.EXTRA_SECRET));
+ break;
+ default:
+ throw new RuntimeException("Impossible req!");
+ }
+ }
+
+ /* * * * * * Android interface core * * * * * */
+
+ @Override
+ public void onCreate(Bundle sis)
+ {
+ super.onCreate(sis);
+
+ Log.i(DBGN, "CREATE");
+
+ if(sis != null) {
+ useed = sis.getCharArray (SAVED_STATE_USEED);
+ vseed = sis.getCharArray (SAVED_STATE_VSEED);
+ cfpc = (Bitmap) sis.getParcelable(SAVED_STATE_CFPC );
+ cfps = sis.getString (SAVED_STATE_CFPS );
+ }
+
+ String act = getIntent().getAction();
+ Log.d(DBGN, act);
+
+ if (act.equals(ACTION_IMPORT_SEED)
+ || act.equals(ACTION_IMPORT_AND_CREATE)) {
+ importSeed();
+ } else {
+ setResult(RESULT_CANCELED, null);
+ finish();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putCharArray (SAVED_STATE_USEED, useed);
+ outState.putCharArray (SAVED_STATE_VSEED, vseed);
+
+ outState.putParcelable(SAVED_STATE_CFPC , cfpc );
+ outState.putString (SAVED_STATE_CFPS , cfps );
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- package name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.ietfng.ns.android.vcpass.tests"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <!-- We add an application tag here just so that we can indicate that
+ this package needs to link against the android.test library,
+ which is needed when building test cases. -->
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+ <!--
+ This declares that this application uses the instrumentation test runner targeting
+ the package of org.ietfng.ns.android.vcpass. To run the tests use the command:
+ "adb shell am instrument -w org.ietfng.ns.android.vcpass.tests/android.test.InstrumentationTestRunner"
+ -->
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="org.ietfng.ns.android.vcpass"
+ android:label="Tests for VCPass"/>
+</manifest>
--- /dev/null
+# This file is used to override default values used by the Ant build system.
+#
+# This file must be checked in Version Control Systems, as it is
+# integral to the build system of your project.
+
+# The name of your application package as defined in the manifest.
+# Used by the 'uninstall' rule.
+#application-package=com.example.myproject
+
+# The name of the source folder.
+#source-folder=src
+
+# The name of the output folder.
+#out-folder=bin
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="VCPass" default="help">
+
+ <!-- The local.properties file is created and updated by the 'android' tool.
+ It contain the path to the SDK. It should *NOT* be checked in in Version
+ Control Systems. -->
+ <property file="local.properties"/>
+
+ <!-- The build.properties file can be created by you and is never touched
+ by the 'android' tool. This is the place to change some of the default property values
+ used by the Ant rules.
+ Here are some properties you may want to change/update:
+
+ application-package
+ the name of your application package as defined in the manifest. Used by the
+ 'uninstall' rule.
+ source-folder
+ the name of the source folder. Default is 'src'.
+ out-folder
+ the name of the output folder. Default is 'bin'.
+
+ Properties related to the SDK location or the project target should be updated
+ using the 'android' tool with the 'update' action.
+
+ This file is an integral part of the build system for your application and
+ should be checked in in Version Control Systems.
+
+ -->
+ <property file="build.properties"/>
+
+ <!-- The default.properties file is created and updated by the 'android' tool, as well
+ as ADT.
+ This file is an integral part of the build system for your application and
+ should be checked in in Version Control Systems. -->
+ <property file="default.properties"/>
+
+ <!-- Custom Android task to deal with the project target, and import the proper rules.
+ This requires ant 1.6.0 or above. -->
+ <path id="android.antlibs">
+ <pathelement path="${sdk-location}/tools/lib/anttasks.jar" />
+ <pathelement path="${sdk-location}/tools/lib/sdklib.jar" />
+ <pathelement path="${sdk-location}/tools/lib/androidprefs.jar" />
+ <pathelement path="${sdk-location}/tools/lib/apkbuilder.jar" />
+ <pathelement path="${sdk-location}/tools/lib/jarutils.jar" />
+ </path>
+
+ <taskdef name="setup"
+ classname="com.android.ant.SetupTask"
+ classpathref="android.antlibs"/>
+
+ <!-- Execute the Android Setup task that will setup some properties specific to the target,
+ and import the rules files.
+ To customize the rules, copy/paste them below the task, and disable import by setting
+ the import attribute to false:
+ <setup import="false" />
+
+ This will ensure that the properties are setup correctly but that your customized
+ targets are used.
+ -->
+ <setup />
+</project>
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-3
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must *NOT* be checked in Version Control Systems,
+# as it contains information specific to your local configuration.
+
+sdk-location=/home/nwf/src/android/android-sdk-linux_x86-1.5_r3
+# location of the SDK. This is only used by Ant
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=/home/nwf/src/android/android-sdk-linux_86
--- /dev/null
+package org.ietfng.ns.android.vcpass;
+
+import android.test.ActivityInstrumentationTestCase;
+
+/**
+ * This is a simple framework for a test of an Application. See
+ * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
+ * how to write and extend Application tests.
+ * <p/>
+ * To run this test, you can type:
+ * adb shell am instrument -w \
+ * -e class org.ietfng.ns.android.vcpass.VCPassTest \
+ * org.ietfng.ns.android.vcpass.tests/android.test.InstrumentationTestRunner
+ */
+public class VCPassTest extends ActivityInstrumentationTestCase<VCPass> {
+
+ public VCPassTest() {
+ super("org.ietfng.ns.android.vcpass", VCPass.class);
+ }
+
+}