214 lines
8.3 KiB
Java
214 lines
8.3 KiB
Java
/*
|
|
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
|
*
|
|
* 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 org.sufficientlysecure.keychain.securitytoken;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import android.os.Parcelable;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import com.google.auto.value.AutoValue;
|
|
import org.bouncycastle.util.encoders.Hex;
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
|
|
|
|
|
@AutoValue
|
|
public abstract class SecurityTokenInfo implements Parcelable {
|
|
private static final byte[] EMPTY_ARRAY = new byte[20];
|
|
private static final Pattern GNUK_VERSION_PATTERN = Pattern.compile("FSIJ-(\\d\\.\\d\\.\\d)-.+");
|
|
|
|
public abstract TransportType getTransportType();
|
|
public abstract TokenType getTokenType();
|
|
|
|
public abstract List<byte[]> getFingerprints();
|
|
@Nullable
|
|
@SuppressWarnings("mutable")
|
|
public abstract byte[] getAid();
|
|
@Nullable
|
|
public abstract String getUserId();
|
|
@Nullable
|
|
public abstract String getUrl();
|
|
public abstract int getVerifyRetries();
|
|
public abstract int getVerifyAdminRetries();
|
|
public abstract boolean hasLifeCycleManagement();
|
|
|
|
public boolean isEmpty() {
|
|
return getFingerprints().isEmpty();
|
|
}
|
|
|
|
public static SecurityTokenInfo create(TransportType transportType, TokenType tokenType, byte[][] fingerprints,
|
|
byte[] aid, String userId, String url,
|
|
int verifyRetries, int verifyAdminRetries,
|
|
boolean hasLifeCycleSupport) {
|
|
ArrayList<byte[]> fingerprintList = new ArrayList<>(fingerprints.length);
|
|
for (byte[] fingerprint : fingerprints) {
|
|
if (!Arrays.equals(EMPTY_ARRAY, fingerprint)) {
|
|
fingerprintList.add(fingerprint);
|
|
}
|
|
}
|
|
return new AutoValue_SecurityTokenInfo(
|
|
transportType, tokenType, fingerprintList, aid, userId, url, verifyRetries, verifyAdminRetries, hasLifeCycleSupport);
|
|
}
|
|
|
|
public static SecurityTokenInfo newInstanceDebugKeyserver() {
|
|
if (!Constants.DEBUG) {
|
|
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
|
}
|
|
return SecurityTokenInfo.create(TransportType.NFC, TokenType.UNKNOWN,
|
|
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("1efdb4845ca242ca6977fddb1f788094fd3b430a") },
|
|
Hex.decode("010203040506"), "yubinu2@mugenguild.com", null, 3, 3, true);
|
|
}
|
|
|
|
public static SecurityTokenInfo newInstanceDebugUri() {
|
|
if (!Constants.DEBUG) {
|
|
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
|
}
|
|
return SecurityTokenInfo.create(TransportType.NFC, TokenType.UNKNOWN,
|
|
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
|
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 3, 3, true);
|
|
}
|
|
|
|
public static SecurityTokenInfo newInstanceDebugLocked() {
|
|
if (!Constants.DEBUG) {
|
|
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
|
}
|
|
return SecurityTokenInfo.create(TransportType.NFC, TokenType.UNKNOWN,
|
|
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
|
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 0, 3, true);
|
|
}
|
|
|
|
public static SecurityTokenInfo newInstanceDebugLockedHard() {
|
|
if (!Constants.DEBUG) {
|
|
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
|
}
|
|
return SecurityTokenInfo.create(TransportType.NFC, TokenType.UNKNOWN,
|
|
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
|
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 0, 0, true);
|
|
}
|
|
|
|
public enum TransportType {
|
|
NFC, USB
|
|
}
|
|
|
|
public enum TokenType {
|
|
YUBIKEY_NEO, YUBIKEY_4_5, FIDESMO, NITROKEY_PRO, NITROKEY_STORAGE, NITROKEY_START_OLD,
|
|
NITROKEY_START_1_25_AND_NEWER, GNUK_OLD, GNUK_1_25_AND_NEWER, LEDGER_NANO_S, SECALOT, UNKNOWN
|
|
}
|
|
|
|
public static final Set<TokenType> SUPPORTED_USB_TOKENS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
|
TokenType.YUBIKEY_NEO,
|
|
TokenType.YUBIKEY_4_5,
|
|
TokenType.NITROKEY_PRO,
|
|
TokenType.NITROKEY_STORAGE,
|
|
TokenType.NITROKEY_START_OLD,
|
|
TokenType.NITROKEY_START_1_25_AND_NEWER,
|
|
TokenType.GNUK_OLD,
|
|
TokenType.GNUK_1_25_AND_NEWER,
|
|
TokenType.LEDGER_NANO_S,
|
|
TokenType.SECALOT
|
|
)));
|
|
|
|
private static final Set<TokenType> SUPPORTED_USB_SETUP = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
|
TokenType.YUBIKEY_NEO,
|
|
TokenType.YUBIKEY_4_5,
|
|
TokenType.NITROKEY_PRO,
|
|
TokenType.NITROKEY_STORAGE,
|
|
TokenType.NITROKEY_START_1_25_AND_NEWER,
|
|
TokenType.GNUK_1_25_AND_NEWER,
|
|
TokenType.SECALOT
|
|
)));
|
|
|
|
public boolean isPutKeySupported() {
|
|
boolean isKnownSupported = SUPPORTED_USB_SETUP.contains(getTokenType());
|
|
boolean isNfcTransport = getTransportType() == TransportType.NFC;
|
|
|
|
return isKnownSupported || isNfcTransport;
|
|
}
|
|
|
|
public boolean isResetSupported() {
|
|
boolean isKnownSupported = SUPPORTED_USB_SETUP.contains(getTokenType());
|
|
boolean isNfcTransport = getTransportType() == TransportType.NFC;
|
|
boolean hasLifeCycleManagement = hasLifeCycleManagement();
|
|
|
|
return (isKnownSupported || isNfcTransport) && hasLifeCycleManagement;
|
|
}
|
|
|
|
public static Version parseGnukVersionString(String serialNo) {
|
|
if (serialNo == null) {
|
|
return null;
|
|
}
|
|
|
|
Matcher matcher = GNUK_VERSION_PATTERN.matcher(serialNo);
|
|
if (!matcher.matches()) {
|
|
return null;
|
|
}
|
|
return Version.create(matcher.group(1));
|
|
}
|
|
|
|
public double getOpenPgpVersion() {
|
|
byte[] aid = getAid();
|
|
float minv = aid[7];
|
|
while (minv > 0) minv /= 10.0;
|
|
return aid[6] + minv;
|
|
}
|
|
|
|
@AutoValue
|
|
public static abstract class Version implements Comparable<Version> {
|
|
|
|
abstract String getVersion();
|
|
|
|
public static Version create(@NonNull String version) {
|
|
if (!version.matches("[0-9]+(\\.[0-9]+)*")) {
|
|
throw new IllegalArgumentException("Invalid version format");
|
|
}
|
|
return new AutoValue_SecurityTokenInfo_Version(version);
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(@NonNull Version that) {
|
|
String[] thisParts = this.getVersion().split("\\.");
|
|
String[] thatParts = that.getVersion().split("\\.");
|
|
int length = Math.max(thisParts.length, thatParts.length);
|
|
for (int i = 0; i < length; i++) {
|
|
int thisPart = i < thisParts.length ?
|
|
Integer.parseInt(thisParts[i]) : 0;
|
|
int thatPart = i < thatParts.length ?
|
|
Integer.parseInt(thatParts[i]) : 0;
|
|
if (thisPart < thatPart) {
|
|
return -1;
|
|
}
|
|
if (thisPart > thatPart) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
}
|