Merge branch 'android-eap-tls'
This adds support for EAP-TLS authentication on Android. EAP-only authentication is currently not allowed because the AAA identity is not configurable, so to prevent anyone with a valid certificate from impersonating the AAA server and thus the gateway, we authenticate the gateway (like we do with other authentication methods). Also, it's currently not possible to select a specific CA certificate to authenticate the AAA server certificate, so it either must be issued by the same CA as that of the gateway or automatic CA certificate selection must be used.
This commit is contained in:
@@ -7,7 +7,7 @@ strongswan_USE_BYOD := true
|
||||
|
||||
strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \
|
||||
pkcs1 pkcs8 pem xcbc hmac socket-default kernel-netlink \
|
||||
eap-identity eap-mschapv2 eap-md5 eap-gtc
|
||||
eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls
|
||||
|
||||
ifneq ($(strongswan_USE_BYOD),)
|
||||
strongswan_BYOD_PLUGINS := eap-ttls eap-tnc tnc-imc tnc-tnccs tnccs-20
|
||||
|
||||
@@ -92,7 +92,7 @@ METHOD(credential_set_t, create_cert_enumerator, enumerator_t*,
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
|
||||
if (!trusted || (cert != CERT_ANY && cert != CERT_X509))
|
||||
if (cert != CERT_ANY && cert != CERT_X509)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ METHOD(private_key_t, sign, bool,
|
||||
case KEY_RSA:
|
||||
switch (scheme)
|
||||
{
|
||||
case SIGN_RSA_EMSA_PKCS1_NULL:
|
||||
method = "NONEwithRSA";
|
||||
break;
|
||||
case SIGN_RSA_EMSA_PKCS1_MD5:
|
||||
method = "MD5withRSA";
|
||||
break;
|
||||
@@ -93,12 +96,18 @@ METHOD(private_key_t, sign, bool,
|
||||
case KEY_ECDSA:
|
||||
switch (scheme)
|
||||
{
|
||||
case SIGN_ECDSA_WITH_SHA1_DER:
|
||||
method = "SHA1withECDSA";
|
||||
break;
|
||||
case SIGN_ECDSA_WITH_SHA256_DER:
|
||||
case SIGN_ECDSA_256:
|
||||
method = "SHA256withECDSA";
|
||||
break;
|
||||
case SIGN_ECDSA_WITH_SHA384_DER:
|
||||
case SIGN_ECDSA_384:
|
||||
method = "SHA384withECDSA";
|
||||
break;
|
||||
case SIGN_ECDSA_WITH_SHA512_DER:
|
||||
case SIGN_ECDSA_521:
|
||||
method = "SHA512withECDSA";
|
||||
break;
|
||||
@@ -189,30 +198,36 @@ METHOD(private_key_t, sign, bool,
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* we get an ASN.1 encoded sequence of integers r and s */
|
||||
parse = encoded = chunk_from_byte_array(env, jsigarray);
|
||||
if (asn1_unwrap(&parse, &parse) != ASN1_SEQUENCE ||
|
||||
asn1_unwrap(&parse, &r) != ASN1_INTEGER ||
|
||||
asn1_unwrap(&parse, &s) != ASN1_INTEGER)
|
||||
if (len)
|
||||
{
|
||||
chunk_free(&encoded);
|
||||
goto failed;
|
||||
}
|
||||
r = chunk_skip_zero(r);
|
||||
s = chunk_skip_zero(s);
|
||||
if (r.len > len || s.len > len)
|
||||
{
|
||||
chunk_free(&encoded);
|
||||
goto failed;
|
||||
}
|
||||
/* we get an ASN.1 encoded sequence of integers r and s */
|
||||
parse = encoded = chunk_from_byte_array(env, jsigarray);
|
||||
if (asn1_unwrap(&parse, &parse) != ASN1_SEQUENCE ||
|
||||
asn1_unwrap(&parse, &r) != ASN1_INTEGER ||
|
||||
asn1_unwrap(&parse, &s) != ASN1_INTEGER)
|
||||
{
|
||||
chunk_free(&encoded);
|
||||
goto failed;
|
||||
}
|
||||
r = chunk_skip_zero(r);
|
||||
s = chunk_skip_zero(s);
|
||||
if (r.len > len || s.len > len)
|
||||
{
|
||||
chunk_free(&encoded);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* concatenate r and s (forced to the defined length) */
|
||||
*signature = chunk_alloc(2*len);
|
||||
memset(signature->ptr, 0, signature->len);
|
||||
memcpy(signature->ptr + (len - r.len), r.ptr, r.len);
|
||||
memcpy(signature->ptr + len + (len - s.len), s.ptr, s.len);
|
||||
chunk_free(&encoded);
|
||||
/* concatenate r and s (forced to the defined length) */
|
||||
*signature = chunk_alloc(2*len);
|
||||
memset(signature->ptr, 0, signature->len);
|
||||
memcpy(signature->ptr + (len - r.len), r.ptr, r.len);
|
||||
memcpy(signature->ptr + len + (len - s.len), s.ptr, s.len);
|
||||
chunk_free(&encoded);
|
||||
}
|
||||
else
|
||||
{
|
||||
*signature = chunk_from_byte_array(env, jsigarray);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -617,8 +617,8 @@ METHOD(listener_t, ike_reestablish_post, bool,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void add_auth_cfg_eap(private_android_service_t *this,
|
||||
peer_cfg_t *peer_cfg, bool byod)
|
||||
static void add_auth_cfg_pw(private_android_service_t *this,
|
||||
peer_cfg_t *peer_cfg, bool byod)
|
||||
{
|
||||
identification_t *user;
|
||||
auth_cfg_t *auth;
|
||||
@@ -653,7 +653,17 @@ static bool add_auth_cfg_cert(private_android_service_t *this,
|
||||
}
|
||||
|
||||
auth = auth_cfg_create();
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
|
||||
if (strpfx("ikev2-eap-tls", this->type))
|
||||
{
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
|
||||
auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TLS);
|
||||
id = identification_create_from_string("%any");
|
||||
auth->add(auth, AUTH_RULE_AAA_IDENTITY, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
|
||||
}
|
||||
auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert);
|
||||
|
||||
id = cert->get_subject(cert);
|
||||
@@ -698,7 +708,8 @@ static job_requeue_t initiate(private_android_service_t *this)
|
||||
|
||||
/* local auth config */
|
||||
if (streq("ikev2-cert", this->type) ||
|
||||
streq("ikev2-cert-eap", this->type))
|
||||
streq("ikev2-cert-eap", this->type) ||
|
||||
streq("ikev2-eap-tls", this->type))
|
||||
{
|
||||
if (!add_auth_cfg_cert(this, peer_cfg))
|
||||
{
|
||||
@@ -712,15 +723,15 @@ static job_requeue_t initiate(private_android_service_t *this)
|
||||
streq("ikev2-cert-eap", this->type) ||
|
||||
streq("ikev2-byod-eap", this->type))
|
||||
{
|
||||
add_auth_cfg_eap(this, peer_cfg, strpfx(this->type, "ikev2-byod"));
|
||||
add_auth_cfg_pw(this, peer_cfg, strpfx(this->type, "ikev2-byod"));
|
||||
}
|
||||
|
||||
/* remote auth config */
|
||||
auth = auth_cfg_create();
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
|
||||
gateway = identification_create_from_string(this->gateway);
|
||||
auth->add(auth, AUTH_RULE_IDENTITY, gateway);
|
||||
auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE);
|
||||
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
|
||||
peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE);
|
||||
|
||||
child_cfg = child_cfg_create("android", &lifetime, NULL, TRUE, MODE_TUNNEL,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<item>IKEv2 EAP (Benutzername/Passwort)</item>
|
||||
<item>IKEv2 Zertifikat</item>
|
||||
<item>IKEv2 Zertifikat + EAP (Benutzername/Passwort)</item>
|
||||
<item>IKEv2 EAP-TLS (Zertifikat)</item>
|
||||
<item>IKEv2 EAP-TNC (Benutzername/Passwort)</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -19,6 +19,7 @@
|
||||
<item>IKEv2 EAP (użytkownik/hasło)</item>
|
||||
<item>IKEv2 certyfikat</item>
|
||||
<item>IKEv2 certyfikat + EAP (użytkownik/hasło)</item>
|
||||
<item>IKEv2 EAP-TLS (certyfikat)</item>
|
||||
<item>IKEv2 EAP-TNC (użytkownik/hasło)</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -18,6 +18,7 @@
|
||||
<item>IKEv2 EAP (Логин/Пароль)</item>
|
||||
<item>IKEv2 Сертификат</item>
|
||||
<item>IKEv2 Сертификат + EAP (Логин/Пароль)</item>
|
||||
<item>IKEv2 EAP-TLS (Сертификат)</item>
|
||||
<item>IKEv2 EAP-TNC (Логин/Пароль)</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<item>IKEv2 EAP (Логін/Пароль)</item>
|
||||
<item>IKEv2 Сертифікати</item>
|
||||
<item>IKEv2 Сертифікати + EAP (Логін/Пароль)</item>
|
||||
<item>IKEv2 EAP-TLS (Сертифікати)</item>
|
||||
<item>IKEv2 EAP-TNC (Логін/Пароль)</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2012-2013 Tobias Brunner
|
||||
Copyright (C) 2012-2014 Tobias Brunner
|
||||
Hochschule fuer Technik Rapperswil
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
@@ -19,6 +19,7 @@
|
||||
<item>IKEv2 EAP (Username/Password)</item>
|
||||
<item>IKEv2 Certificate</item>
|
||||
<item>IKEv2 Certificate + EAP (Username/Password)</item>
|
||||
<item>IKEv2 EAP-TLS (Certificate)</item>
|
||||
<item>IKEv2 EAP-TNC (Username/Password)</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Tobias Brunner
|
||||
* Copyright (C) 2012-2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
@@ -15,45 +15,44 @@
|
||||
|
||||
package org.strongswan.android.data;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public enum VpnType
|
||||
{
|
||||
/* the order here must match the items in R.array.vpn_types */
|
||||
IKEV2_EAP("ikev2-eap", true, false),
|
||||
IKEV2_CERT("ikev2-cert", false, true),
|
||||
IKEV2_CERT_EAP("ikev2-cert-eap", true, true),
|
||||
IKEV2_BYOD_EAP("ikev2-byod-eap", true, false, true);
|
||||
|
||||
private String mIdentifier;
|
||||
private boolean mCertificate;
|
||||
private boolean mUsernamePassword;
|
||||
private boolean mBYOD;
|
||||
IKEV2_EAP("ikev2-eap", EnumSet.of(VpnTypeFeature.USER_PASS)),
|
||||
IKEV2_CERT("ikev2-cert", EnumSet.of(VpnTypeFeature.CERTIFICATE)),
|
||||
IKEV2_CERT_EAP("ikev2-cert-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)),
|
||||
IKEV2_EAP_TLS("ikev2-eap-tls", EnumSet.of(VpnTypeFeature.CERTIFICATE)),
|
||||
IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE, VpnTypeFeature.BYOD));
|
||||
|
||||
/**
|
||||
* Enum which provides additional information about the supported VPN types.
|
||||
*
|
||||
* @param id identifier used to store and transmit this specific type
|
||||
* @param userpass true if username and password are required
|
||||
* @param certificate true if a client certificate is required
|
||||
* Features of a VPN type.
|
||||
*/
|
||||
VpnType(String id, boolean userpass, boolean certificate)
|
||||
public enum VpnTypeFeature
|
||||
{
|
||||
this(id, userpass, certificate, false);
|
||||
/** client certificate is required */
|
||||
CERTIFICATE,
|
||||
/** username and password are required */
|
||||
USER_PASS,
|
||||
/** enable BYOD features */
|
||||
BYOD;
|
||||
}
|
||||
|
||||
private String mIdentifier;
|
||||
private EnumSet<VpnTypeFeature> mFeatures;
|
||||
|
||||
/**
|
||||
* Enum which provides additional information about the supported VPN types.
|
||||
*
|
||||
* @param id identifier used to store and transmit this specific type
|
||||
* @param userpass true if username and password are required
|
||||
* @param features of the given VPN type
|
||||
* @param certificate true if a client certificate is required
|
||||
* @param byod true to enable BYOD features
|
||||
*/
|
||||
VpnType(String id, boolean userpass, boolean certificate, boolean byod)
|
||||
VpnType(String id, EnumSet<VpnTypeFeature> features)
|
||||
{
|
||||
mIdentifier = id;
|
||||
mUsernamePassword = userpass;
|
||||
mCertificate = certificate;
|
||||
mBYOD = byod;
|
||||
mFeatures = features;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,33 +65,13 @@ public enum VpnType
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether username and password are required for this type of VPN.
|
||||
* Checks whether a feature is supported/required by this type of VPN.
|
||||
*
|
||||
* @return true if username and password are required
|
||||
* @return true if the feature is supported/required
|
||||
*/
|
||||
public boolean getRequiresUsernamePassword()
|
||||
public boolean has(VpnTypeFeature feature)
|
||||
{
|
||||
return mUsernamePassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a certificate is required for this type of VPN.
|
||||
*
|
||||
* @return true if a certificate is required
|
||||
*/
|
||||
public boolean getRequiresCertificate()
|
||||
{
|
||||
return mCertificate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether BYOD features should be enabled.
|
||||
*
|
||||
* @return true if BYOD features are to be enabled
|
||||
*/
|
||||
public boolean getEnableBYOD()
|
||||
{
|
||||
return mBYOD;
|
||||
return mFeatures.contains(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.data.VpnType.VpnTypeFeature;
|
||||
import org.strongswan.android.logic.VpnStateService.ErrorState;
|
||||
import org.strongswan.android.logic.VpnStateService.State;
|
||||
import org.strongswan.android.logic.imc.ImcState;
|
||||
@@ -211,7 +212,7 @@ public class CharonVpnService extends VpnService implements Runnable
|
||||
mIsDisconnecting = false;
|
||||
|
||||
BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName());
|
||||
if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().getEnableBYOD()))
|
||||
if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
|
||||
{
|
||||
Log.i(TAG, "charon started");
|
||||
initiate(mCurrentProfile.getVpnType().getIdentifier(),
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.strongswan.android.ui;
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.data.VpnType.VpnTypeFeature;
|
||||
import org.strongswan.android.logic.CharonVpnService;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
import org.strongswan.android.logic.VpnStateService;
|
||||
@@ -219,7 +220,7 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
|
||||
profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId());
|
||||
profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername());
|
||||
profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword());
|
||||
profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().getRequiresUsernamePassword());
|
||||
profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS));
|
||||
profileInfo.putString(PROFILE_NAME, profile.getName());
|
||||
|
||||
removeFragmentByTag(DIALOG_TAG);
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.data.VpnType;
|
||||
import org.strongswan.android.data.VpnType.VpnTypeFeature;
|
||||
import org.strongswan.android.logic.TrustedCertificateManager;
|
||||
import org.strongswan.android.security.TrustedCertificateEntry;
|
||||
|
||||
@@ -240,11 +241,11 @@ public class VpnProfileDetailActivity extends Activity
|
||||
*/
|
||||
private void updateCredentialView()
|
||||
{
|
||||
mUsernamePassword.setVisibility(mVpnType.getRequiresUsernamePassword() ? View.VISIBLE : View.GONE);
|
||||
mUserCertificate.setVisibility(mVpnType.getRequiresCertificate() ? View.VISIBLE : View.GONE);
|
||||
mTncNotice.setVisibility(mVpnType.getEnableBYOD() ? View.VISIBLE : View.GONE);
|
||||
mUsernamePassword.setVisibility(mVpnType.has(VpnTypeFeature.USER_PASS) ? View.VISIBLE : View.GONE);
|
||||
mUserCertificate.setVisibility(mVpnType.has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE);
|
||||
mTncNotice.setVisibility(mVpnType.has(VpnTypeFeature.BYOD) ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (mVpnType.getRequiresCertificate())
|
||||
if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
|
||||
{
|
||||
if (mUserCertLoading != null)
|
||||
{
|
||||
@@ -349,7 +350,7 @@ public class VpnProfileDetailActivity extends Activity
|
||||
mGateway.setError(getString(R.string.alert_text_no_input_gateway));
|
||||
valid = false;
|
||||
}
|
||||
if (mVpnType.getRequiresUsernamePassword())
|
||||
if (mVpnType.has(VpnTypeFeature.USER_PASS))
|
||||
{
|
||||
if (mUsername.getText().toString().trim().isEmpty())
|
||||
{
|
||||
@@ -357,7 +358,7 @@ public class VpnProfileDetailActivity extends Activity
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
if (mVpnType.getRequiresCertificate() && mUserCertEntry == null)
|
||||
if (mVpnType.has(VpnTypeFeature.CERTIFICATE) && mUserCertEntry == null)
|
||||
{ /* let's show an error icon */
|
||||
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
|
||||
valid = false;
|
||||
@@ -381,14 +382,14 @@ public class VpnProfileDetailActivity extends Activity
|
||||
mProfile.setName(name.isEmpty() ? gateway : name);
|
||||
mProfile.setGateway(gateway);
|
||||
mProfile.setVpnType(mVpnType);
|
||||
if (mVpnType.getRequiresUsernamePassword())
|
||||
if (mVpnType.has(VpnTypeFeature.USER_PASS))
|
||||
{
|
||||
mProfile.setUsername(mUsername.getText().toString().trim());
|
||||
String password = mPassword.getText().toString().trim();
|
||||
password = password.isEmpty() ? null : password;
|
||||
mProfile.setPassword(password);
|
||||
}
|
||||
if (mVpnType.getRequiresCertificate())
|
||||
if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
|
||||
{
|
||||
mProfile.setUserCertificateAlias(mUserCertEntry.getAlias());
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
import org.strongswan.android.data.VpnType.VpnTypeFeature;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -64,7 +65,7 @@ public class VpnProfileAdapter extends ArrayAdapter<VpnProfile>
|
||||
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway);
|
||||
tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway());
|
||||
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username);
|
||||
if (profile.getVpnType().getRequiresUsernamePassword())
|
||||
if (profile.getVpnType().has(VpnTypeFeature.USER_PASS))
|
||||
{ /* if the view is reused we make sure it is visible */
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername());
|
||||
@@ -74,7 +75,7 @@ public class VpnProfileAdapter extends ArrayAdapter<VpnProfile>
|
||||
tv.setVisibility(View.GONE);
|
||||
}
|
||||
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate);
|
||||
if (profile.getVpnType().getRequiresCertificate())
|
||||
if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE))
|
||||
{
|
||||
tv.setText(getContext().getString(R.string.profile_user_certificate_label) + " " + profile.getUserCertificateAlias());
|
||||
tv.setVisibility(View.VISIBLE);
|
||||
|
||||
Reference in New Issue
Block a user