2
0

Deep Java refactoring to match the C API logic and clean up some OO oddities.

This commit is contained in:
Maarten Billemont
2017-09-22 19:03:50 -04:00
parent dc7089c38c
commit 5d1be43b65
42 changed files with 823 additions and 1157 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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 );

View File

@@ -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 {
}

View File

@@ -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;
}

View File

@@ -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 );
}
}