Deep Java refactoring to match the C API logic and clean up some OO oddities.
This commit is contained in:
@@ -24,11 +24,11 @@ import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @see MasterKey.Version
|
||||
* @see MPMasterKey.Version
|
||||
*/
|
||||
public interface MasterKeyAlgorithm extends Serializable {
|
||||
public interface MPAlgorithm extends Serializable {
|
||||
|
||||
MasterKey.Version getAlgorithmVersion();
|
||||
MPMasterKey.Version getAlgorithmVersion();
|
||||
|
||||
byte[] deriveKey(String fullName, char[] masterPassword);
|
||||
|
||||
@@ -38,11 +38,11 @@ import javax.crypto.IllegalBlockSizeException;
|
||||
|
||||
|
||||
/**
|
||||
* @see MasterKey.Version#V0
|
||||
* @see MPMasterKey.Version#V0
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MasterKeyV0 implements MasterKeyAlgorithm {
|
||||
public class MPAlgorithmV0 implements MPAlgorithm {
|
||||
|
||||
/**
|
||||
* mpw: validity for the time-based rolling counter.
|
||||
@@ -84,9 +84,9 @@ public class MasterKeyV0 implements MasterKeyAlgorithm {
|
||||
protected final Logger logger = Logger.get( getClass() );
|
||||
|
||||
@Override
|
||||
public MasterKey.Version getAlgorithmVersion() {
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
|
||||
return MasterKey.Version.V0;
|
||||
return MPMasterKey.Version.V0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,16 +23,16 @@ import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @see MasterKey.Version#V1
|
||||
* @see MPMasterKey.Version#V1
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MasterKeyV1 extends MasterKeyV0 {
|
||||
public class MPAlgorithmV1 extends MPAlgorithmV0 {
|
||||
|
||||
@Override
|
||||
public MasterKey.Version getAlgorithmVersion() {
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
|
||||
return MasterKey.Version.V1;
|
||||
return MPMasterKey.Version.V1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -28,16 +28,16 @@ import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @see MasterKey.Version#V2
|
||||
* @see MPMasterKey.Version#V2
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MasterKeyV2 extends MasterKeyV1 {
|
||||
public class MPAlgorithmV2 extends MPAlgorithmV1 {
|
||||
|
||||
@Override
|
||||
public MasterKey.Version getAlgorithmVersion() {
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
|
||||
return MasterKey.Version.V2;
|
||||
return MPMasterKey.Version.V2;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,25 +56,25 @@ public class MasterKeyV2 extends MasterKeyV1 {
|
||||
|
||||
// OTP counter value.
|
||||
if (siteCounter.longValue() == 0)
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MasterKeyV0.mpw_otp_window * 1000)) * MasterKeyV0.mpw_otp_window );
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPAlgorithmV0.mpw_otp_window * 1000)) * MPAlgorithmV0.mpw_otp_window );
|
||||
|
||||
// Calculate the site seed.
|
||||
byte[] siteNameBytes = siteName.getBytes( MasterKeyV0.mpw_charset );
|
||||
byte[] siteNameBytes = siteName.getBytes( MPAlgorithmV0.mpw_charset );
|
||||
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
|
||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
||||
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( MasterKeyV0.mpw_charset );
|
||||
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( MPAlgorithmV0.mpw_charset );
|
||||
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length );
|
||||
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
|
||||
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
|
||||
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( MasterKeyV0.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( MPAlgorithmV0.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (keyContextBytes != null)
|
||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
|
||||
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
|
||||
|
||||
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) );
|
||||
byte[] sitePasswordSeedBytes = MasterKeyV0.mpw_digest.of( masterKey, sitePasswordInfo );
|
||||
byte[] sitePasswordSeedBytes = MPAlgorithmV0.mpw_digest.of( masterKey, sitePasswordInfo );
|
||||
logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) );
|
||||
|
||||
return sitePasswordSeedBytes;
|
||||
@@ -29,25 +29,25 @@ import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @see MasterKey.Version#V3
|
||||
* @see MPMasterKey.Version#V3
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MasterKeyV3 extends MasterKeyV2 {
|
||||
public class MPAlgorithmV3 extends MPAlgorithmV2 {
|
||||
|
||||
@Override
|
||||
public MasterKey.Version getAlgorithmVersion() {
|
||||
public MPMasterKey.Version getAlgorithmVersion() {
|
||||
|
||||
return MasterKey.Version.V3;
|
||||
return MPMasterKey.Version.V3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] deriveKey(final String fullName, final char[] masterPassword) {
|
||||
Preconditions.checkArgument( masterPassword.length > 0 );
|
||||
|
||||
byte[] fullNameBytes = fullName.getBytes( MasterKeyV0.mpw_charset );
|
||||
byte[] fullNameBytes = fullName.getBytes( MPAlgorithmV0.mpw_charset );
|
||||
byte[] fullNameLengthBytes = MPUtils.bytesForInt( fullNameBytes.length );
|
||||
ByteBuffer mpBytesBuf = MasterKeyV0.mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
|
||||
ByteBuffer mpBytesBuf = MPAlgorithmV0.mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
|
||||
|
||||
logger.trc( "-- mpw_masterKey (algorithm: %u)", getAlgorithmVersion().toInt() );
|
||||
logger.trc( "fullName: %s", fullName );
|
||||
@@ -59,12 +59,12 @@ public class MasterKeyV3 extends MasterKeyV2 {
|
||||
// Calculate the master key salt.
|
||||
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
|
||||
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
|
||||
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( MasterKeyV0.mpw_charset ), fullNameLengthBytes, fullNameBytes );
|
||||
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( MPAlgorithmV0.mpw_charset ), fullNameLengthBytes, fullNameBytes );
|
||||
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
|
||||
|
||||
// Calculate the master key.
|
||||
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )",
|
||||
MasterKeyV0.scrypt_N, MasterKeyV0.scrypt_r, MasterKeyV0.scrypt_p );
|
||||
MPAlgorithmV0.scrypt_N, MPAlgorithmV0.scrypt_r, MPAlgorithmV0.scrypt_p );
|
||||
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
|
||||
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
|
||||
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
|
||||
@@ -0,0 +1,25 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password 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.
|
||||
//
|
||||
// Master Password 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 can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-21
|
||||
*/
|
||||
public class MPInvalidatedException extends Exception {
|
||||
}
|
||||
@@ -20,9 +20,10 @@ package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.masterpassword.MPUtils.idForBytes;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -30,26 +31,28 @@ import javax.annotation.Nullable;
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MasterKey {
|
||||
public class MPMasterKey {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MasterKey.class );
|
||||
private static final Logger logger = Logger.get( MPMasterKey.class );
|
||||
|
||||
private final EnumMap<Version, byte[]> keyByVersion = new EnumMap<>( Version.class );
|
||||
private final String fullName;
|
||||
private final char[] masterPassword;
|
||||
|
||||
private boolean invalidated;
|
||||
|
||||
/**
|
||||
* @param masterPassword The characters of the user's master password. Note: this array is held by reference and its contents
|
||||
* invalidated on {@link #invalidate()}.
|
||||
*/
|
||||
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
|
||||
public MasterKey(final String fullName, final char[] masterPassword) {
|
||||
public MPMasterKey(final String fullName, final char[] masterPassword) {
|
||||
|
||||
this.fullName = fullName;
|
||||
this.masterPassword = masterPassword;
|
||||
}
|
||||
|
||||
private byte[] getKey(final Version algorithmVersion) {
|
||||
// TODO: Cache keys.
|
||||
return algorithmVersion.getAlgorithm().deriveKey( fullName, masterPassword );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a site result token.
|
||||
*
|
||||
@@ -60,10 +63,13 @@ public class MasterKey {
|
||||
* @param resultType The type of result to generate.
|
||||
* @param resultParam A parameter for the resultType. For stateful result types, the output of
|
||||
* {@link #siteState(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
|
||||
*
|
||||
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
|
||||
final Version algorithmVersion) {
|
||||
final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
return algorithmVersion.getAlgorithm().siteResult(
|
||||
getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
}
|
||||
@@ -78,10 +84,13 @@ public class MasterKey {
|
||||
* @param resultType The type of result token to encrypt.
|
||||
* @param resultParam The result token desired from
|
||||
* {@link #siteResult(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
|
||||
*
|
||||
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
|
||||
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
|
||||
final Version algorithmVersion) {
|
||||
final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
return algorithmVersion.getAlgorithm().siteState(
|
||||
getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
}
|
||||
@@ -92,45 +101,81 @@ public class MasterKey {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public byte[] getKeyID(final Version algorithmVersion) {
|
||||
/**
|
||||
* Calculate an identifier for the master key.
|
||||
*
|
||||
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public byte[] getKeyID(final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
|
||||
return idForBytes( getKey( algorithmVersion ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe this key's secrets from memory, making the object permanently unusable.
|
||||
*/
|
||||
public void invalidate() {
|
||||
|
||||
invalidated = true;
|
||||
for (final byte[] key : keyByVersion.values())
|
||||
Arrays.fill( key, (byte) 0 );
|
||||
Arrays.fill( masterPassword, (char) 0 );
|
||||
}
|
||||
|
||||
private byte[] getKey(final Version algorithmVersion)
|
||||
throws MPInvalidatedException {
|
||||
if (invalidated)
|
||||
throw new MPInvalidatedException();
|
||||
|
||||
byte[] key = keyByVersion.get( algorithmVersion );
|
||||
if (key == null)
|
||||
keyByVersion.put( algorithmVersion, key = algorithmVersion.getAlgorithm().deriveKey( fullName, masterPassword ) );
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* The algorithm iterations.
|
||||
*/
|
||||
public enum Version {
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - does math with chars whose signedness was platform-dependent.
|
||||
* - miscounted the byte-length for multi-byte site names.
|
||||
* - miscounted the byte-length for multi-byte full names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V0( new MasterKeyV0() ),
|
||||
V0( new MPAlgorithmV0() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length for multi-byte site names.
|
||||
* - miscounted the byte-length for multi-byte full names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V1( new MasterKeyV1() ),
|
||||
V1( new MPAlgorithmV1() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length for multi-byte full names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V2( new MasterKeyV2() ),
|
||||
V2( new MPAlgorithmV2() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - no known issues.
|
||||
*/
|
||||
V3( new MasterKeyV3() );
|
||||
V3( new MPAlgorithmV3() );
|
||||
|
||||
public static final Version CURRENT = V3;
|
||||
|
||||
private final MasterKeyAlgorithm algorithm;
|
||||
private final MPAlgorithm algorithm;
|
||||
|
||||
Version(final MasterKeyAlgorithm algorithm) {
|
||||
Version(final MPAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public MasterKeyAlgorithm getAlgorithm() {
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ import java.nio.ByteBuffer;
|
||||
public final class MPUtils {
|
||||
|
||||
public static byte[] bytesForInt(final int number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MasterKeyV0.mpw_byteOrder ).putInt( number ).array();
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithmV0.mpw_byteOrder ).putInt( number ).array();
|
||||
}
|
||||
|
||||
public static byte[] bytesForInt(final UnsignedInteger number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MasterKeyV0.mpw_byteOrder ).putInt( number.intValue() ).array();
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithmV0.mpw_byteOrder ).putInt( number.intValue() ).array();
|
||||
}
|
||||
|
||||
public static byte[] idForBytes(final byte[] bytes) {
|
||||
return MasterKeyV0.mpw_hash.of( bytes );
|
||||
return MPAlgorithmV0.mpw_hash.of( bytes );
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user