/* * 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 . */ 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 android.support.annotation.NonNull; import android.support.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 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 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 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 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 { 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; } } }