diff options
Diffstat (limited to 'app/src')
6 files changed, 248 insertions, 37 deletions
diff --git a/app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl b/app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl index c6db965b..1f77b15f 100644 --- a/app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl +++ b/app/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl @@ -1,16 +1,16 @@ // ExternalCertificateProvider.aidl package de.blinkt.openvpn.api; - /* * This is very simple interface that is specialised to have only the minimal set of crypto * operation that are needed for OpenVPN to authenticate with an external certificate */ interface ExternalCertificateProvider { /** + * @deprecated use {@link #getSignedDataWithExtra} instead * Requests signing the data with RSA/ECB/PKCS1PADDING * for RSA certficate and with NONEwithECDSA for EC certificates - * @parm alias the parameter that + * @param alias user certificate identifier */ byte[] getSignedData(in String alias, in byte[] data); @@ -36,4 +36,33 @@ interface ExternalCertificateProvider { * */ Bundle getCertificateMetaData(in String alias); + + /** + * Requests signing the data with RSA/ECB/nopadding, RSA/ECB/PKCS1PADDING or PKCS1PSSPADDING + * for RSA certficate and with NONEwithECDSA for EC certificates + * @param alias user certificate identifier + * @param data the data to be signed + * @param extra additional information. + * Should contain the following keys: + * <ul> + * <li>int key "de.blinkt.openvpn.api.RSA_PADDING_TYPE", may be set as: + * <ul> + * <li>0 - for RSA/ECB/nopadding + * <li>1 - for RSA/ECB/PKCS1PADDING + * <li>2 - for PKCS1PSSPADDING + * </ul> + * <li>string key "de.blinkt.openvpn.api.SALTLEN", may be set as: + * <ul> + * <li>"digest" - use the same salt size as the hash to sign + * <li>"max" - use maximum possible saltlen which is '(nbits-1)/8 - hlen - 2'. Here + * 'nbits' is the number of bits in the key modulus and 'hlen' is the size in octets of + * the hash. See: RFC 8017 sec 8.1.1 and 9.1.1. + * </ul> + * <li>boolean key "de.blinkt.openvpn.api.NEEDS_DIGEST", indicating that the data should be + * hashed before signing or not + * <li>string key "de.blinkt.openvpn.api.DIGEST", the short common digest algorithm name to + * use (such as SHA256, SHA224, etc.) + * </ul> + */ + byte[] getSignedDataWithExtra(in String alias, in byte[] data, in Bundle extra); } diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index 9baac195..9f722dfe 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -15,6 +15,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; +import android.os.Bundle; import android.preference.PreferenceManager; import android.security.KeyChain; import android.security.KeyChainException; @@ -37,7 +38,9 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Serializable; import java.io.StringWriter; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; @@ -45,6 +48,9 @@ import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PSSParameterSpec; import java.util.Collection; import java.util.HashSet; import java.util.Locale; @@ -58,6 +64,7 @@ import javax.crypto.NoSuchPaddingException; import de.blinkt.openvpn.core.ExtAuthHelper; import de.blinkt.openvpn.core.NativeUtils; +import de.blinkt.openvpn.core.OpenVPNManagement; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.OrbotHelper; import de.blinkt.openvpn.core.PasswordCache; @@ -104,6 +111,10 @@ public class VpnProfile implements Serializable, Cloneable { private static final long serialVersionUID = 7085688938959334563L; private static final int AUTH_RETRY_NONE_KEEP = 1; private static final int AUTH_RETRY_INTERACT = 3; + private static final String EXTRA_RSA_PADDING_TYPE = "de.blinkt.openvpn.api.RSA_PADDING_TYPE"; + private static final String EXTRA_SALTLEN = "de.blinkt.openvpn.api.SALTLEN"; + private static final String EXTRA_NEEDS_DIGEST = "de.blinkt.openvpn.api.NEEDS_DIGEST"; + private static final String EXTRA_DIGEST = "de.blinkt.openvpn.api.DIGEST"; public static String DEFAULT_DNS1 = "8.8.8.8"; public static String DEFAULT_DNS2 = "8.8.4.4"; // variable named wrong and should haven beeen transient @@ -458,7 +469,7 @@ public class VpnProfile implements Serializable, Cloneable { if (!TextUtils.isEmpty(ks[1])) cfg.append("<extra-certs>\n").append(ks[1]).append("\n</extra-certs>\n"); cfg.append("<cert>\n").append(ks[2]).append("\n</cert>\n"); - cfg.append("management-external-key nopadding\n"); + cfg.append("management-external-key nopadding pkcs1 pss digest\n"); } else { cfg.append(context.getString(R.string.keychain_access)).append("\n"); } @@ -1148,13 +1159,14 @@ public class VpnProfile implements Serializable, Cloneable { } @Nullable - public String getSignedData(Context c, String b64data, boolean pkcs1padding) { + public String getSignedData(Context c, String b64data, OpenVPNManagement.SignaturePadding padding, String saltlen, String hashalg, boolean needDigest) { byte[] data = Base64.decode(b64data, Base64.DEFAULT); byte[] signed_bytes; - if (mAuthenticationType == TYPE_EXTERNAL_APP) - signed_bytes = getExtAppSignedData(c, data); - else - signed_bytes = getKeyChainSignedData(data, pkcs1padding); + if (mAuthenticationType == TYPE_EXTERNAL_APP) { + signed_bytes = getExtAppSignedData(c, data, padding, saltlen, hashalg, needDigest); + } else { + signed_bytes = getKeyChainSignedData(data, padding, saltlen, hashalg, needDigest); + } if (signed_bytes != null) return Base64.encodeToString(signed_bytes, Base64.NO_WRAP); @@ -1162,19 +1174,41 @@ public class VpnProfile implements Serializable, Cloneable { return null; } - private byte[] getExtAppSignedData(Context c, byte[] data) { + private byte[] getExtAppSignedData(Context c, byte[] data, OpenVPNManagement.SignaturePadding padding, String saltlen, String hashalg, boolean needDigest) + { + + Bundle extra = new Bundle(); + RsaPaddingType paddingType; + switch (padding) { + case RSA_PKCS1_PADDING: + paddingType = RsaPaddingType.PKCS1_PADDING; + break; + case NO_PADDING: + paddingType = RsaPaddingType.NO_PADDING; + break; + case RSA_PKCS1_PSS_PADDING: + paddingType = RsaPaddingType.RSAPSS_PADDING; + break; + default: + paddingType = RsaPaddingType.NO_PADDING; + } + + extra.putInt(EXTRA_RSA_PADDING_TYPE, paddingType.ordinal()); + extra.putString(EXTRA_SALTLEN, saltlen); + extra.putString(EXTRA_DIGEST, hashalg); + extra.putBoolean(EXTRA_NEEDS_DIGEST, needDigest); + if (TextUtils.isEmpty(mExternalAuthenticator)) return null; try { - return ExtAuthHelper.signData(c, mExternalAuthenticator, mAlias, data); + return ExtAuthHelper.signData(c, mExternalAuthenticator, mAlias, data, extra); } catch (KeyChainException | InterruptedException e) { VpnStatus.logError(R.string.error_extapp_sign, mExternalAuthenticator, e.getClass().toString(), e.getLocalizedMessage()); return null; } } - private byte[] getKeyChainSignedData(byte[] data, boolean pkcs1padding) { - + private byte[] getKeyChainSignedData(byte[] data, OpenVPNManagement.SignaturePadding padding, String saltlen, String hashalg, boolean needDigest) { PrivateKey privkey = getKeystoreKey(); try { @@ -1182,36 +1216,121 @@ public class VpnProfile implements Serializable, Cloneable { String keyalgorithm = privkey.getAlgorithm(); byte[] signed_bytes; - if (keyalgorithm.equals("EC")) { - Signature signer = Signature.getInstance("NONEwithECDSA"); - - signer.initSign(privkey); - signer.update(data); - signed_bytes = signer.sign(); - + if (needDigest || keyalgorithm.equals("EC")) { + return doDigestSign(privkey, data, padding, hashalg, saltlen); } else { - /* ECB is perfectly fine in this special case, since we are using it for - the public/private part in the TLS exchange - */ - Cipher signer; - if (pkcs1padding) - signer = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); - else - signer = Cipher.getInstance("RSA/ECB/NoPadding"); - + /* ECB is perfectly fine in this special case, since we are using it for + the public/private part in the TLS exchange */ + Cipher signer = null; + switch (padding) { + case RSA_PKCS1_PADDING: + signer = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); + break; + case NO_PADDING: + signer = Cipher.getInstance("RSA/ECB/NoPadding"); + break; + case RSA_PKCS1_PSS_PADDING: + throw new NoSuchPaddingException("Cannot do PKCS1 PSS padding without also doing the digest"); + } signer.init(Cipher.ENCRYPT_MODE, privkey); signed_bytes = signer.doFinal(data); + + return signed_bytes; } - return signed_bytes; - } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException - | BadPaddingException | NoSuchPaddingException | SignatureException e) { + } catch + (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | SignatureException | InvalidAlgorithmParameterException + e) { VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage()); return null; } } + private byte[] addPSSPadding(PrivateKey privkey, String digest, byte[] data) throws NoSuchAlgorithmException { + /* For < API 23, add padding ourselves */ + int hashtype = getHashtype(digest); + + MessageDigest msgDigest = MessageDigest.getInstance(digest); + byte[] hash = msgDigest.digest(data); + + /* MSBits = (BN_num_bits(rsa->n) - 1) & 0x7; */ + int numbits = ((RSAPrivateKey) privkey).getModulus().bitLength(); + + int MSBits = (numbits - 1) & 0x7; + + return NativeUtils.addRssPssPadding(hashtype, MSBits, numbits/8, hash); + } + + private int getHashtype(String digest) throws NoSuchAlgorithmException { + int hashtype = 0; + switch (digest) { + case "SHA1": + hashtype = 1; + break; + case "SHA224": + hashtype = 2; + break; + case "SHA256": + hashtype = 3; + break; + case "SHA384": + hashtype = 4; + break; + case "SHA512": + hashtype = 5; + break; + default: + throw new NoSuchAlgorithmException("Unknown digest algorithm: " + digest); + } + return hashtype; + } + + private byte[] doDigestSign(PrivateKey privkey, byte[] data, OpenVPNManagement.SignaturePadding padding, String hashalg, String saltlen) throws SignatureException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { + /* RSA */ + Signature sig = null; + + if (privkey.getAlgorithm().equals("EC")) { + if (hashalg.equals("")) + hashalg = "NONE"; + /* e.g. SHA512withECDSA */ + hashalg = hashalg + "withECDSA"; + sig = Signature.getInstance(hashalg.toUpperCase(Locale.ROOT)); + } else if (padding == OpenVPNManagement.SignaturePadding.RSA_PKCS1_PSS_PADDING) { + /* https://developer.android.com/training/articles/keystore#SupportedSignatures */ + if (!"digest".equals(saltlen)) + throw new SignatureException("PSS signing requires saltlen=digest"); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + data = addPSSPadding(privkey, hashalg, data); + return getKeyChainSignedData(data, OpenVPNManagement.SignaturePadding.NO_PADDING, "none", "none", false); + } + + sig = Signature.getInstance(hashalg + "withRSA/PSS"); + + PSSParameterSpec pssspec = null; + switch (hashalg) { + case "SHA256": + pssspec = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1); + break; + case "SHA512": + pssspec = new PSSParameterSpec("SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 64, 1); + break; + case "SHA384": + pssspec = new PSSParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, 48, 1); + break; + } + sig.setParameter(pssspec); + } else if (padding == OpenVPNManagement.SignaturePadding.RSA_PKCS1_PADDING) { + sig = Signature.getInstance(hashalg + "withRSA"); + } + + sig.initSign(privkey); + sig.update(data); + return sig.sign(); + } + + private boolean usesExtraProxyOptions() { if (mUseCustomConfig && mCustomConfigOptions != null && mCustomConfigOptions.contains("http-proxy-option ")) return true; @@ -1222,6 +1341,16 @@ public class VpnProfile implements Serializable, Cloneable { return false; } + /** + * The order of elements is important! + */ + private enum RsaPaddingType { + NO_PADDING, + PKCS1_PADDING, + RSAPSS_PADDING + } + + class NoCertReturnedException extends Exception { public NoCertReturnedException(String msg) { super(msg); diff --git a/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java b/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java index a62a4c62..d102dce2 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java @@ -108,15 +108,23 @@ public class ExtAuthHelper { public static byte[] signData(@NonNull Context context, @NonNull String extAuthPackageName, @NonNull String alias, - @NonNull byte[] data + @NonNull byte[] data, + @NonNull Bundle extra ) throws KeyChainException, InterruptedException { - try (ExternalAuthProviderConnection authProviderConnection = bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) { + try (ExternalAuthProviderConnection authProviderConnection = + bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) { ExternalCertificateProvider externalAuthProvider = authProviderConnection.getService(); - return externalAuthProvider.getSignedData(alias, data); + + byte[] result = externalAuthProvider.getSignedDataWithExtra(alias, data, extra); + // When the desired method is not implemented, a default implementation is called, returning null + if (result == null) + result = externalAuthProvider.getSignedData(alias, data); + + return result; } catch (RemoteException e) { throw new KeyChainException(e); diff --git a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java index f769b38e..818564c7 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java +++ b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java @@ -29,6 +29,21 @@ public class NativeUtils { private static native String getJNIAPI(); + static boolean rsspssloaded = false; + + public static byte[] addRssPssPadding(int hashtype, int MSBits, int rsa_size, byte[] from) + { + if (!rsspssloaded) { + rsspssloaded = true; + System.loadLibrary("rsapss"); + } + + return rsapss(hashtype, MSBits, rsa_size, from); + } + + private static native byte[] rsapss(int hashtype, int MSBits, int rsa_size, byte[] from); + + public final static int[] openSSLlengths = { 16, 64, 256, 1024, 8 * 1024, 16 * 1024 }; diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java index ef17e98b..02e4eca9 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java @@ -16,6 +16,12 @@ public interface OpenVPNManagement { screenOff, } + enum SignaturePadding { + RSA_PKCS1_PSS_PADDING, + RSA_PKCS1_PADDING, + NO_PADDING + } + int mBytecountInterval = 2; void reconnect(); diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java index a02e7e27..88b933eb 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -194,7 +194,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { // Closing one of the two sockets also closes the other //mServerSocketLocal.close(); - managmentCommand("version 2\n"); + managmentCommand("version 3\n"); while (true) { @@ -730,9 +730,33 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { releaseHold(); } - private void processSignCommand(String b64data) { + private void processSignCommand(String argument) { - String signed_string = mProfile.getSignedData(mOpenVPNService, b64data, false); + String[] arguments = argument.split(","); + + // NC9t8IkYrjAQcCzc85zN0H5TvwfAUDwYkR4j2ga6fGw=,RSA_PKCS1_PSS_PADDING,hashalg=SHA256,saltlen=digest + + + SignaturePadding padding = SignaturePadding.NO_PADDING; + String saltlen=""; + String hashalg=""; + boolean needsDigest = false; + + for (int i=1;i < arguments.length;i++) { + String arg = arguments[i]; + if(arg.equals("RSA_PKCS1_PADDING")) + padding = SignaturePadding.RSA_PKCS1_PADDING; + else if (arg.equals("RSA_PKCS1_PSS_PADDING")) + padding = SignaturePadding.RSA_PKCS1_PSS_PADDING; + else if (arg.startsWith("saltlen=")) + saltlen= arg.substring(8); + else if (arg.startsWith("hashalg=")) + hashalg = arg.substring(8); + else if (arg.equals("data=message")) + needsDigest = true; + } + + String signed_string = mProfile.getSignedData(mOpenVPNService, arguments[0], padding, saltlen, hashalg, needsDigest); if (signed_string == null) { managmentCommand("pk-sig\n"); |