/*- * Copyright 2010 Google Inc. * * 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.google.authenticator.blackberry; import java.util.Hashtable; import java.util.Vector; import com.google.authenticator.blackberry.resource.AuthenticatorResource; import net.rim.device.api.i18n.ResourceBundle; import net.rim.device.api.system.CodeModuleManager; import net.rim.device.api.system.CodeSigningKey; import net.rim.device.api.system.ControlledAccess; import net.rim.device.api.system.PersistentObject; import net.rim.device.api.system.PersistentStore; /** * BlackBerry port of {@code AccountDb}. */ public class AccountDb { private static final String TABLE_NAME = "accounts"; static final String ID_COLUMN = "_id"; static final String EMAIL_COLUMN = "email"; static final String SECRET_COLUMN = "secret"; static final String COUNTER_COLUMN = "counter"; static final String TYPE_COLUMN = "type"; static final Integer TYPE_LEGACY_TOTP = new Integer(-1); // TODO: remove April 2010 private static final long PERSISTENT_STORE_KEY = 0x9f1343901e600bf7L; static Hashtable sPreferences; static PersistentObject sPersistentObject; /** * Types of secret keys. */ public static class OtpType { private static ResourceBundle sResources = ResourceBundle.getBundle( AuthenticatorResource.BUNDLE_ID, AuthenticatorResource.BUNDLE_NAME); public static final OtpType TOTP = new OtpType(0); // time based public static final OtpType HOTP = new OtpType(1); // counter based private static final OtpType[] values = { TOTP, HOTP }; public final Integer value; // value as stored in database OtpType(int value) { this.value = new Integer(value); } public static OtpType getEnum(Integer i) { for (int index = 0; index < values.length; index++) { OtpType type = values[index]; if (type.value.intValue() == i.intValue()) { return type; } } return null; } public static OtpType[] values() { return values; } /** * {@inheritDoc} */ public String toString() { if (this == TOTP) { return sResources.getString(AuthenticatorResource.TOTP); } else if (this == HOTP) { return sResources.getString(AuthenticatorResource.HOTP); } else { return super.toString(); } } } private AccountDb() { // Don't new me } static { sPersistentObject = PersistentStore.getPersistentObject(PERSISTENT_STORE_KEY); sPreferences = (Hashtable) sPersistentObject.getContents(); if (sPreferences == null) { sPreferences = new Hashtable(); } // Use an instance of a class owned by this application // to easily get the appropriate CodeSigningKey: Object appObject = new FieldUtils(); // Get the public code signing key CodeSigningKey codeSigningKey = CodeSigningKey.get(appObject); if (codeSigningKey == null) { throw new SecurityException("Code not protected by a signing key"); } // Ensure that the code has been signed with the corresponding private key int moduleHandle = CodeModuleManager.getModuleHandleForObject(appObject); if (!ControlledAccess.verifyCodeModuleSignature(moduleHandle, codeSigningKey)) { String signerId = codeSigningKey.getSignerId(); throw new SecurityException("Code not signed by " + signerId + " key"); } Object contents = sPreferences; // Only allow signed applications to access user data contents = new ControlledAccess(contents, codeSigningKey); sPersistentObject.setContents(contents); sPersistentObject.commit(); } private static Vector getAccounts() { Vector accounts = (Vector) sPreferences.get(TABLE_NAME); if (accounts == null) { accounts = new Vector(10); sPreferences.put(TABLE_NAME, accounts); sPersistentObject.commit(); } return accounts; } private static Hashtable getAccount(String email) { if (email == null) { throw new NullPointerException(); } Vector accounts = getAccounts(); for (int i = 0, n = accounts.size(); i < n; i++) { Hashtable account = (Hashtable) accounts.elementAt(i); if (email.equals(account.get(EMAIL_COLUMN))) { return account; } } return null; } static String[] getNames() { Vector accounts = getAccounts(); int size = accounts.size(); String[] names = new String[size]; for (int i = 0; i < size; i++) { Hashtable account = (Hashtable) accounts.elementAt(i); names[i] = (String) account.get(EMAIL_COLUMN); } return names; } static boolean nameExists(String email) { Hashtable account = getAccount(email); return account != null; } static String getSecret(String email) { Hashtable account = getAccount(email); return account != null ? (String) account.get(SECRET_COLUMN) : null; } static Integer getCounter(String email) { Hashtable account = getAccount(email); return account != null ? (Integer) account.get(COUNTER_COLUMN) : null; } static void incrementCounter(String email) { Hashtable account = getAccount(email); if (account != null) { Integer counter = (Integer) account.get(COUNTER_COLUMN); counter = new Integer(counter.intValue() + 1); account.put(COUNTER_COLUMN, counter); sPersistentObject.commit(); } } static OtpType getType(String user) { Hashtable account = getAccount(user); if (account != null) { Integer value = (Integer) account.get(TYPE_COLUMN); return OtpType.getEnum(value); } else { return null; } } static void delete(String email) { Vector accounts = getAccounts(); boolean modified = false; for (int index = 0; index < accounts.size();) { Hashtable account = (Hashtable) accounts.elementAt(index); if (email.equals(account.get(EMAIL_COLUMN))) { accounts.removeElementAt(index); modified = true; } else { index++; } } if (modified) { sPersistentObject.commit(); } } /** * Save key to database, creating a new user entry if necessary. * @param email the user email address. When editing, the new user email. * @param secret the secret key. * @param oldEmail If editing, the original user email, otherwise null. */ static void update(String email, String secret, String oldEmail, AccountDb.OtpType type) { Hashtable account = oldEmail != null ? getAccount(oldEmail) : null; if (account == null) { account = new Hashtable(10); Vector accounts = getAccounts(); accounts.addElement(account); } account.put(EMAIL_COLUMN, email); account.put(SECRET_COLUMN, secret); account.put(TYPE_COLUMN, type.value); if (!account.containsKey(COUNTER_COLUMN)) { account.put(COUNTER_COLUMN, new Integer(0)); } sPersistentObject.commit(); } }