Move identicon and toID to mpw native.
Clean out all unused Java MPAlgorithm stuff. Fix master password entries stuck in memory.
This commit is contained in:
@@ -87,6 +87,29 @@ public interface MPAlgorithm {
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
MPResultType resultType, String resultParam);
|
||||
|
||||
/**
|
||||
* Derive an identicon that represents the user's identity in a visually recognizable way.
|
||||
*
|
||||
* @param fullName The name of the user whose identity is described by the key.
|
||||
* @param masterPassword The user's secret that authenticates his access to the identity.
|
||||
*/
|
||||
MPIdenticon identicon(final String fullName, final char[] masterPassword);
|
||||
|
||||
/**
|
||||
* Encode a fingerprint for a message.
|
||||
*/
|
||||
String toID(final String string);
|
||||
|
||||
/**
|
||||
* Encode a fingerprint for a char buffer.
|
||||
*/
|
||||
String toID(final char[] message);
|
||||
|
||||
/**
|
||||
* Encode a fingerprint for a byte buffer.
|
||||
*/
|
||||
String toID(final byte[] buffer);
|
||||
|
||||
// Configuration
|
||||
|
||||
/**
|
||||
@@ -125,69 +148,6 @@ public interface MPAlgorithm {
|
||||
@Nonnull
|
||||
Charset mpw_charset();
|
||||
|
||||
/**
|
||||
* mpw: Platform-agnostic byte order.
|
||||
*/
|
||||
@Nonnull
|
||||
ByteOrder mpw_byteOrder();
|
||||
|
||||
/**
|
||||
* mpw: Key ID hash.
|
||||
*/
|
||||
@Nonnull
|
||||
MessageDigests mpw_hash();
|
||||
|
||||
/**
|
||||
* mpw: Site digest.
|
||||
*/
|
||||
@Nonnull
|
||||
MessageAuthenticationDigests mpw_digest();
|
||||
|
||||
/**
|
||||
* mpw: Master key size (byte).
|
||||
*/
|
||||
int mpw_dkLen();
|
||||
|
||||
/**
|
||||
* mpw: Minimum size for derived keys (bit).
|
||||
*/
|
||||
int mpw_keySize_min();
|
||||
|
||||
/**
|
||||
* mpw: Maximum size for derived keys (bit).
|
||||
*/
|
||||
int mpw_keySize_max();
|
||||
|
||||
/**
|
||||
* mpw: validity for the time-based rolling counter (s).
|
||||
*/
|
||||
long mpw_otp_window();
|
||||
|
||||
/**
|
||||
* scrypt: CPU cost parameter.
|
||||
*/
|
||||
int scrypt_N();
|
||||
|
||||
/**
|
||||
* scrypt: Memory cost parameter.
|
||||
*/
|
||||
int scrypt_r();
|
||||
|
||||
/**
|
||||
* scrypt: Parallelization parameter.
|
||||
*/
|
||||
int scrypt_p();
|
||||
|
||||
// Utilities
|
||||
|
||||
byte[] toBytes(final int number);
|
||||
|
||||
byte[] toBytes(final UnsignedInteger number);
|
||||
|
||||
byte[] toBytes(final char[] characters);
|
||||
|
||||
byte[] toID(final byte[] bytes);
|
||||
|
||||
/**
|
||||
* The algorithm iterations.
|
||||
*/
|
||||
@@ -222,10 +182,6 @@ public interface MPAlgorithm {
|
||||
|
||||
public static final Version CURRENT = V3;
|
||||
|
||||
@SuppressWarnings("HardcodedFileSeparator")
|
||||
private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
||||
private static final int AES_BLOCKSIZE = 128 /* bit */;
|
||||
|
||||
static {
|
||||
if (!Native.load( MPAlgorithm.class, "mpw" ))
|
||||
Logger.get( MPAlgorithm.class ).err( "Native mpw library unavailable." );
|
||||
@@ -321,6 +277,70 @@ public interface MPAlgorithm {
|
||||
final int keyPurpose, @Nullable final String keyContext,
|
||||
final int resultType, final String resultParam, final int algorithmVersion);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MPIdenticon identicon(final String fullName, final char[] masterPassword) {
|
||||
|
||||
// Create a memory-safe NUL-terminated UTF-8 C-string byte array variant of masterPassword.
|
||||
CharsetEncoder encoder = mpw_charset().newEncoder();
|
||||
byte[] masterPasswordBytes = new byte[(int) (masterPassword.length * (double) encoder.maxBytesPerChar()) + 1];
|
||||
try {
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
ByteBuffer masterPasswordBuffer = ByteBuffer.wrap( masterPasswordBytes );
|
||||
|
||||
CoderResult result = encoder.encode( CharBuffer.wrap( masterPassword ), masterPasswordBuffer, true );
|
||||
if (result.isError())
|
||||
throw new IllegalStateException( result.toString() );
|
||||
result = encoder.flush( masterPasswordBuffer );
|
||||
if (result.isError())
|
||||
throw new IllegalStateException( result.toString() );
|
||||
|
||||
return _identicon( fullName, masterPasswordBytes );
|
||||
}
|
||||
finally {
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected native MPIdenticon _identicon(final String fullName, final byte[] masterPassword);
|
||||
|
||||
@Override
|
||||
public String toID(final String message) {
|
||||
return toID( message.toCharArray() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toID(final char[] message) {
|
||||
// Create a memory-safe NUL-terminated UTF-8 C-string byte array variant of masterPassword.
|
||||
CharsetEncoder encoder = mpw_charset().newEncoder();
|
||||
byte[] messageBytes = new byte[(int) (message.length * (double) encoder.maxBytesPerChar()) + 1];
|
||||
try {
|
||||
Arrays.fill( messageBytes, (byte) 0 );
|
||||
ByteBuffer messageBuffer = ByteBuffer.wrap( messageBytes );
|
||||
|
||||
CoderResult result = encoder.encode( CharBuffer.wrap( message ), messageBuffer, true );
|
||||
if (result.isError())
|
||||
throw new IllegalStateException( result.toString() );
|
||||
result = encoder.flush( messageBuffer );
|
||||
if (result.isError())
|
||||
throw new IllegalStateException( result.toString() );
|
||||
|
||||
return toID( messageBytes );
|
||||
}
|
||||
finally {
|
||||
Arrays.fill( messageBytes, (byte) 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toID(final byte[] buffer) {
|
||||
return _toID( buffer );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected native String _toID(final byte[] buffer);
|
||||
|
||||
// Configuration
|
||||
|
||||
@Nonnull
|
||||
@@ -358,93 +378,5 @@ public interface MPAlgorithm {
|
||||
public Charset mpw_charset() {
|
||||
return Charsets.UTF_8;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ByteOrder mpw_byteOrder() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MessageDigests mpw_hash() {
|
||||
return MessageDigests.SHA256;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MessageAuthenticationDigests mpw_digest() {
|
||||
return MessageAuthenticationDigests.HmacSHA256;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int mpw_dkLen() {
|
||||
return 64;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int mpw_keySize_min() {
|
||||
return 128;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int mpw_keySize_max() {
|
||||
return 512;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public long mpw_otp_window() {
|
||||
return 5 * 60 /* s */;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int scrypt_N() {
|
||||
return 32768;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int scrypt_r() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int scrypt_p() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
@Nonnull
|
||||
public byte[] toBytes(final int number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public byte[] toBytes(final UnsignedInteger number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public byte[] toBytes(final char[] characters) {
|
||||
ByteBuffer byteBuffer = mpw_charset().encode( CharBuffer.wrap( characters ) );
|
||||
|
||||
byte[] bytes = new byte[byteBuffer.remaining()];
|
||||
byteBuffer.get( bytes );
|
||||
|
||||
Arrays.fill( byteBuffer.array(), (byte) 0 );
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public byte[] toID(final byte[] bytes) {
|
||||
return mpw_hash().of( bytes );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,14 +20,8 @@ package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.primitives.UnsignedBytes;
|
||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
@@ -39,43 +33,23 @@ public class MPIdenticon {
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPIdenticon.class );
|
||||
|
||||
private static final Charset charset = Charsets.UTF_8;
|
||||
private static final Color[] colors = {
|
||||
Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
|
||||
private static final char[] leftArm = { '╔', '╚', '╰', '═' };
|
||||
private static final char[] rightArm = { '╗', '╝', '╯', '═' };
|
||||
private static final char[] body = { '█', '░', '▒', '▓', '☺', '☻' };
|
||||
private static final char[] accessory = {
|
||||
'◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
|
||||
'⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
|
||||
'⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
|
||||
|
||||
private final String fullName;
|
||||
private final String leftArm;
|
||||
private final String body;
|
||||
private final String rightArm;
|
||||
private final String accessory;
|
||||
private final Color color;
|
||||
private final String text;
|
||||
|
||||
public MPIdenticon(final String fullName, final String masterPassword) {
|
||||
this( fullName, masterPassword.toCharArray() );
|
||||
}
|
||||
|
||||
@SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
|
||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
||||
public MPIdenticon(final String fullName, final char[] masterPassword) {
|
||||
public MPIdenticon(final String fullName, final String leftArm, final String body, final String rightArm, final String accessory,
|
||||
final Color color) {
|
||||
this.fullName = fullName;
|
||||
|
||||
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
|
||||
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
|
||||
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
|
||||
IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
|
||||
while (identiconSeedBytes.hasRemaining())
|
||||
identiconSeedBuffer.put( UnsignedBytes.toInt( identiconSeedBytes.get() ) );
|
||||
int[] identiconSeed = identiconSeedBuffer.array();
|
||||
|
||||
color = colors[identiconSeed[4] % colors.length];
|
||||
text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
|
||||
rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
|
||||
this.leftArm = leftArm;
|
||||
this.body = body;
|
||||
this.rightArm = rightArm;
|
||||
this.accessory = accessory;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
@@ -83,11 +57,11 @@ public class MPIdenticon {
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
return strf( "%s%s%s%s", this.leftArm, this.body, this.rightArm, this.accessory );
|
||||
}
|
||||
|
||||
public String getHTML() {
|
||||
return strf( "<span style='color: %s'>%s</span>", color.getCSS(), text );
|
||||
return strf( "<span style='color: %s'>%s</span>", color.getCSS(), getText() );
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
@@ -95,6 +69,12 @@ public class MPIdenticon {
|
||||
}
|
||||
|
||||
public enum Color {
|
||||
UNSET {
|
||||
@Override
|
||||
public String getCSS() {
|
||||
return "inherit";
|
||||
}
|
||||
},
|
||||
RED,
|
||||
GREEN,
|
||||
YELLOW,
|
||||
|
@@ -55,6 +55,18 @@ public class MPMasterKey {
|
||||
Arrays.fill( masterPassword, (char) 0 );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize()
|
||||
throws Throwable {
|
||||
|
||||
if (isValid()) {
|
||||
logger.wrn( "A master key for %s was abandoned without being invalidated.", getFullName() );
|
||||
invalidate();
|
||||
}
|
||||
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getFullName() {
|
||||
|
||||
@@ -67,7 +79,7 @@ public class MPMasterKey {
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
@Nonnull
|
||||
public byte[] getKeyID(final MPAlgorithm algorithm)
|
||||
public String getKeyID(final MPAlgorithm algorithm)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return algorithm.toID( masterKey( algorithm ) );
|
||||
@@ -98,11 +110,6 @@ public class MPMasterKey {
|
||||
|
||||
byte[] masterKey = keyByVersion.get( algorithm.version() );
|
||||
if (masterKey == null) {
|
||||
logger.trc( "-- mpw_masterKey (algorithm: %s)", algorithm );
|
||||
logger.trc( "fullName: %s", fullName );
|
||||
logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex(
|
||||
algorithm.toID( algorithm.toBytes( masterPassword ) ) ) );
|
||||
|
||||
keyByVersion.put( algorithm.version(), masterKey = algorithm.masterKey( fullName, masterPassword ) );
|
||||
}
|
||||
if (masterKey == null)
|
||||
@@ -118,13 +125,6 @@ public class MPMasterKey {
|
||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
|
||||
logger.trc( "-- mpw_siteKey (algorithm: %s)", algorithm );
|
||||
logger.trc( "siteName: %s", siteName );
|
||||
logger.trc( "siteCounter: %s", siteCounter );
|
||||
logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() );
|
||||
logger.trc( "keyContext: %s", keyContext );
|
||||
|
||||
byte[] siteKey = algorithm.siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
if (siteKey == null)
|
||||
throw new MPAlgorithmException( "Could not derive site key." );
|
||||
@@ -161,10 +161,6 @@ public class MPMasterKey {
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
|
||||
|
||||
logger.trc( "-- mpw_siteResult (algorithm: %s)", algorithm );
|
||||
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
|
||||
logger.trc( "resultParam: %s", resultParam );
|
||||
|
||||
String siteResult = algorithm.siteResult(
|
||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
if (siteResult == null)
|
||||
@@ -199,10 +195,6 @@ public class MPMasterKey {
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
|
||||
|
||||
logger.trc( "-- mpw_siteState (algorithm: %s)", algorithm );
|
||||
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
|
||||
logger.trc( "resultParam: %d bytes = %s", resultParam.getBytes( algorithm.mpw_charset() ).length, resultParam );
|
||||
|
||||
String siteState = algorithm.siteState(
|
||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
if (siteState == null)
|
||||
|
Reference in New Issue
Block a user