/**
* @author Dirk Bergstrom
*
* Keyring for webOS - Easy password management on your phone.
* Copyright (C) 2009-2010, Dirk Bergstrom, keyring@otisbean.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.otisbean.keyring;
import java.security.GeneralSecurityException;
import org.json.simple.JSONAware;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
/**
* A single item on a Keyring.
*
* @author Dirk Bergstrom
*/
public class Item implements JSONAware, Comparable<Item> {
// ENCRYPTED_ATTRS: ['username', 'pass', 'url', 'notes'],
// PLAINTEXT_ATTRS: ['title', 'category', 'created', 'viewed', 'changed'],
private Ring ring;
private String username;
private String pass;
private String url;
private String notes;
private String title;
private int category;
private long created;
private long viewed;
private long changed;
private String encryptedData;
private boolean locked;
/**
* Create an Item given all the values.
*/
public Item(Ring ring, String username, String pass, String url, String notes,
String title, String categoryName, long created, long viewed, long changed)
throws GeneralSecurityException, KeyringException {
super();
this.ring = ring;
this.username = username;
this.pass = pass;
this.url = url;
this.notes = notes;
this.title = title;
this.category = ring.categoryIdForName(categoryName);
this.created = created;
this.viewed = viewed;
this.changed = changed;
lock();
}
/**
* Create a new Item, setting created/viewed/changed to now.
*/
public Item(Ring ring, String username, String pass, String url, String notes,
String title, int categoryId)
throws GeneralSecurityException, KeyringException {
super();
this.ring = ring;
this.username = username;
this.pass = pass;
this.url = url;
this.notes = notes;
this.title = title;
this.category = categoryId;
this.created = this.viewed = this.changed = System.currentTimeMillis();
lock();
}
/**
* Create an item from a JSONObject sourced from a backup file.
*
* The Ring need not have an active password, since the encrypted_data
* blob is used as-is.
*
* XXX??? Since the encrypted blob isn't decrypted, it could be corrupt.
*/
public Item(Ring ring, JSONObject rawItem) {
super();
this.ring = ring;
encryptedData = (String) rawItem.get("encrypted_data");
title = (String) rawItem.get("title");
Object tmp = rawItem.get("category");
category = (int) (null == tmp ? 0 : (Long) tmp);
tmp = rawItem.get("created");
created = null == tmp ? 0 : (Long) tmp;
tmp = rawItem.get("viewed");
viewed = null == tmp ? 0 : (Long) tmp;
tmp = rawItem.get("changed");
changed = null == tmp ? 0 : (Long) tmp;
// TODO Unlock and re-lock the item to validate that the encrypted data is valid?
//unlock();
//lock();
locked = true;
}
@SuppressWarnings("unchecked")
@Override
public String toJSONString() {
if (! locked) {
try {
lock();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (KeyringException e) {
throw new RuntimeException(e);
}
}
JSONObject itemJson = new JSONObject();
itemJson.put("title", title);
itemJson.put("category", category);
// Dates are stored as an empty string if undefined
itemJson.put("created", created == 0 ? "" : created);
itemJson.put("viewed", viewed == 0 ? "" : viewed);
itemJson.put("changed", changed == 0 ? "" : changed);
itemJson.put("encrypted_data", encryptedData);
return itemJson.toJSONString();
}
@SuppressWarnings("unchecked")
public void lock() throws GeneralSecurityException, KeyringException {
if (locked) {
throw new KeyringException("Locking an already locked record is wrong");
}
JSONObject crypted = new JSONObject();
crypted.put("username", username);
crypted.put("pass", pass);
crypted.put("url", url);
crypted.put("notes", notes);
encryptedData = ring.encrypt(crypted.toJSONString(), Ring.ITEM_SALT_LENGTH);
username = pass = url = notes = "";
locked = true;
}
public void unlock() throws GeneralSecurityException, KeyringException {
String decryptedData;
decryptedData = ring.decrypt(encryptedData);
JSONObject obj;
try {
obj = (JSONObject) ring.parser.parse(decryptedData);
} catch (ParseException e) {
// ParseException's toString() method returns a good error message
throw new KeyringException("Unparseable JSON data: " + e);
}
username = (String) obj.get("username");
pass = (String) obj.get("pass");
url = (String) obj.get("url");
notes = (String) obj.get("notes");
locked = false;
}
public String getUsername() throws GeneralSecurityException, KeyringException {
if (locked) {
unlock();
}
return username;
}
public void setUsername(String username) throws GeneralSecurityException, KeyringException {
if (locked) {
unlock();
}
this.username = username;
}
public String getPass() throws GeneralSecurityException, KeyringException {
if (locked) {
unlock();
}
return pass;
}
public void setPass(String pass) throws GeneralSecurityException, KeyringException {
if (locked) {
unlock();
}
this.pass = pass;
}
public String getUrl() throws GeneralSecurityException, KeyringException {
if (locked) {
unlock();
}
return url;
}
public void setUrl(String url) throws GeneralSecurityException, KeyringException {
if (locked) {
unlock();
}
this.url = url;
}
public String getNotes() throws GeneralSecurityException, KeyringException {
if (locked) {
unlock();
}
return notes;
}
public void setNotes(String notes) throws GeneralSecurityException, KeyringException {
if (locked) {
unlock();
}
this.notes = notes;
}
public String getTitle() {
return title;
}
public String getEncryptedData() {
return encryptedData;
}
public void setEncryptedData(String encryptedData) {
this.encryptedData = encryptedData;
}
public void setTitle(String title) {
this.title = title;
}
public long getCreated() {
return created;
}
public void setCreated(long created) {
this.created = created;
}
public long getViewed() {
return viewed;
}
public void setViewed(long viewed) {
this.viewed = viewed;
}
public long getChanged() {
return changed;
}
public void setChanged(long changed) {
this.changed = changed;
}
public int getCategoryId() {
return category;
}
public void setCategoryId(int cat) {
this.category = cat;
}
public String getCategory() {
return ring.categoryNameForId(category);
}
public void setCategory(String categoryName) {
this.category = ring.categoryIdForName(categoryName);
}
/**
* @return the ring
*/
public Ring getRing() {
return ring;
}
/**
* @param ring the ring to set
*/
public void setRing(Ring ring) {
this.ring = ring;
}
@Override
public String toString() {
return title;
}
@Override
public int compareTo(Item other) {
return title.compareTo(other.title);
}
@Override
public boolean equals(Object other) {
if (other instanceof Item) {
return title.equals(((Item) other).title);
} else {
return false;
}
}
@Override
public int hashCode() {
return title.hashCode();
}
}