Support converting IkeAuthDigitalSignConfig to/from PersistableBundle

Bug: 163604823
Test: FrameworksVcnTests(add new tests)
Change-Id: I62cdf4cb0297a394e0c97973e621b5c051ab0192
This commit is contained in:
Yan Yan
2021-02-24 15:41:16 -08:00
parent 2f0f6b575c
commit 848568a3ce
5 changed files with 229 additions and 1 deletions

View File

@@ -18,18 +18,24 @@ package android.net.vcn.persistablebundleutils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Objects;
/**
* CertUtils provides utility methods for constructing Certificate.
* CertUtils provides utility methods for constructing Certificate and PrivateKey.
*
* @hide
*/
public class CertUtils {
private static final String CERT_TYPE_X509 = "X.509";
private static final String PRIVATE_KEY_TYPE_RSA = "RSA";
/** Decodes an ASN.1 DER encoded Certificate */
public static X509Certificate certificateFromByteArray(byte[] derEncoded) {
@@ -43,4 +49,18 @@ public class CertUtils {
throw new IllegalArgumentException("Fail to decode certificate", e);
}
}
/** Decodes a PKCS#8 encoded RSA private key */
public static RSAPrivateKey privateKeyFromByteArray(byte[] pkcs8Encoded) {
Objects.requireNonNull(pkcs8Encoded, "pkcs8Encoded was null");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8Encoded);
try {
KeyFactory keyFactory = KeyFactory.getInstance(PRIVATE_KEY_TYPE_RSA);
return (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalArgumentException("Fail to decode PrivateKey", e);
}
}
}

View File

@@ -22,12 +22,18 @@ import android.annotation.NonNull;
import android.net.ipsec.ike.IkeSaProposal;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig;
import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignLocalConfig;
import android.net.ipsec.ike.IkeSessionParams.IkeAuthDigitalSignRemoteConfig;
import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig;
import android.os.PersistableBundle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.vcn.util.PersistableBundleUtils;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -146,6 +152,14 @@ public final class IkeSessionParamsUtils {
IkeAuthPskConfig config = (IkeAuthPskConfig) authConfig;
return IkeAuthPskConfigUtils.toPersistableBundle(
config, createPersistableBundle(IKE_AUTH_METHOD_PSK));
} else if (authConfig instanceof IkeAuthDigitalSignLocalConfig) {
IkeAuthDigitalSignLocalConfig config = (IkeAuthDigitalSignLocalConfig) authConfig;
return IkeAuthDigitalSignConfigUtils.toPersistableBundle(
config, createPersistableBundle(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE));
} else if (authConfig instanceof IkeAuthDigitalSignRemoteConfig) {
IkeAuthDigitalSignRemoteConfig config = (IkeAuthDigitalSignRemoteConfig) authConfig;
return IkeAuthDigitalSignConfigUtils.toPersistableBundle(
config, createPersistableBundle(IKE_AUTH_METHOD_PUB_KEY_SIGNATURE));
} else {
throw new IllegalStateException("Invalid IkeAuthConfig subclass");
}
@@ -178,6 +192,15 @@ public final class IkeSessionParamsUtils {
IkeAuthPskConfigUtils.setBuilderByReadingPersistableBundle(
localAuthBundle, remoteAuthBundle, builder);
break;
case IKE_AUTH_METHOD_PUB_KEY_SIGNATURE:
if (remoteMethodType != IKE_AUTH_METHOD_PUB_KEY_SIGNATURE) {
throw new IllegalArgumentException(
"Expect remote auth method to be digital signature based, but was "
+ remoteMethodType);
}
IkeAuthDigitalSignConfigUtils.setBuilderByReadingPersistableBundle(
localAuthBundle, remoteAuthBundle, builder);
break;
default:
throw new IllegalArgumentException(
"Invalid EAP method type " + localMethodType);
@@ -218,4 +241,102 @@ public final class IkeSessionParamsUtils {
builder.setAuthPsk(localPsk);
}
}
private static class IkeAuthDigitalSignConfigUtils {
private static final String END_CERT_KEY = "END_CERT_KEY";
private static final String INTERMEDIATE_CERTS_KEY = "INTERMEDIATE_CERTS_KEY";
private static final String PRIVATE_KEY_KEY = "PRIVATE_KEY_KEY";
private static final String TRUST_CERT_KEY = "TRUST_CERT_KEY";
@NonNull
public static PersistableBundle toPersistableBundle(
@NonNull IkeAuthDigitalSignLocalConfig config, @NonNull PersistableBundle result) {
try {
result.putPersistableBundle(
END_CERT_KEY,
PersistableBundleUtils.fromByteArray(
config.getClientEndCertificate().getEncoded()));
final List<X509Certificate> certList = config.getIntermediateCertificates();
final List<byte[]> encodedCertList = new ArrayList<>(certList.size());
for (X509Certificate cert : certList) {
encodedCertList.add(cert.getEncoded());
}
final PersistableBundle certsBundle =
PersistableBundleUtils.fromList(
encodedCertList, PersistableBundleUtils::fromByteArray);
result.putPersistableBundle(INTERMEDIATE_CERTS_KEY, certsBundle);
} catch (CertificateEncodingException e) {
throw new IllegalArgumentException("Fail to encode certificate");
}
// TODO: b/170670506 Consider putting PrivateKey in Android KeyStore
result.putPersistableBundle(
PRIVATE_KEY_KEY,
PersistableBundleUtils.fromByteArray(config.getPrivateKey().getEncoded()));
return result;
}
@NonNull
public static PersistableBundle toPersistableBundle(
@NonNull IkeAuthDigitalSignRemoteConfig config, @NonNull PersistableBundle result) {
try {
X509Certificate caCert = config.getRemoteCaCert();
if (caCert != null) {
result.putPersistableBundle(
TRUST_CERT_KEY,
PersistableBundleUtils.fromByteArray(caCert.getEncoded()));
}
} catch (CertificateEncodingException e) {
throw new IllegalArgumentException("Fail to encode the certificate");
}
return result;
}
public static void setBuilderByReadingPersistableBundle(
@NonNull PersistableBundle localAuthBundle,
@NonNull PersistableBundle remoteAuthBundle,
@NonNull IkeSessionParams.Builder builder) {
Objects.requireNonNull(localAuthBundle, "localAuthBundle was null");
Objects.requireNonNull(remoteAuthBundle, "remoteAuthBundle was null");
// Deserialize localAuth
final PersistableBundle endCertBundle =
localAuthBundle.getPersistableBundle(END_CERT_KEY);
Objects.requireNonNull(endCertBundle, "End cert was null");
final byte[] encodedCert = PersistableBundleUtils.toByteArray(endCertBundle);
final X509Certificate endCert = CertUtils.certificateFromByteArray(encodedCert);
final PersistableBundle certsBundle =
localAuthBundle.getPersistableBundle(INTERMEDIATE_CERTS_KEY);
Objects.requireNonNull(certsBundle, "Intermediate certs was null");
final List<byte[]> encodedCertList =
PersistableBundleUtils.toList(certsBundle, PersistableBundleUtils::toByteArray);
final List<X509Certificate> certList = new ArrayList<>(encodedCertList.size());
for (byte[] encoded : encodedCertList) {
certList.add(CertUtils.certificateFromByteArray(encoded));
}
final PersistableBundle privateKeyBundle =
localAuthBundle.getPersistableBundle(PRIVATE_KEY_KEY);
Objects.requireNonNull(privateKeyBundle, "PrivateKey bundle was null");
final PrivateKey privateKey =
CertUtils.privateKeyFromByteArray(
PersistableBundleUtils.toByteArray(privateKeyBundle));
// Deserialize remoteAuth
final PersistableBundle trustCertBundle =
remoteAuthBundle.getPersistableBundle(TRUST_CERT_KEY);
X509Certificate caCert = null;
if (trustCertBundle != null) {
final byte[] encodedCaCert = PersistableBundleUtils.toByteArray(trustCertBundle);
caCert = CertUtils.certificateFromByteArray(encodedCaCert);
}
builder.setAuthDigitalSignature(caCert, endCert, certList, privateKey);
}
}
}

View File

@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDaDCCAlCgAwIBAgIIcorRI3n29E4wDQYJKoZIhvcNAQELBQAwQTELMAkGA1UE
BhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxIDAeBgNVBAMTF3R3by5jYS50ZXN0LmFu
ZHJvaWQubmV0MB4XDTIwMDQxNDA1MDM0OVoXDTIzMDQxNDA1MDM0OVowRTELMAkG
A1UEBhMCVVMxEDAOBgNVBAoTB0FuZHJvaWQxJDAiBgNVBAMTG2NsaWVudC50ZXN0
LmlrZS5hbmRyb2lkLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AK/cK+sIaiQlJYvy5+Dq70sJbgR7PO1uS2qkLRP7Wb3z5SNvz94nQvZRrFn1AFIE
CpfESh5kUF6gJe7t7NR3mpQ98iEosCRBMDJT8qB+EeHiL4wkrmCE9sYMTyvaApRc
6Qzozn/9kKma7Qpj/25AvoPluTERqhZ6AQ77BJeb6FNOAoO1Aoe9GJuB1xmRxjRw
D0mwusL+ciQ/7uKlsFP5VO5XqACcohXSerzO8jcD9necBvka3SDepqqzn1K0NPRC
25fMmS5kSjddKtKOif7w2NI3OpVsmP3kHv66If73VURsy0lgXPYyKkq8lAMrtmXG
R7svFGPbEl+Swkpr3b+dzF8CAwEAAaNgMF4wHwYDVR0jBBgwFoAUcqSu1uRYT/DL
bLoDNUz38nGvCKQwJgYDVR0RBB8wHYIbY2xpZW50LnRlc3QuaWtlLmFuZHJvaWQu
bmV0MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQCa53tK
I9RM9/MutZ5KNG2Gfs2cqaPyv8ZRhs90HDWZhkFVu7prywJAxOd2hxxHPsvgurio
4bKAxnT4EXevgz5YoCbj2TPIL9TdFYh59zZ97XXMxk+SRdypgF70M6ETqKPs3hDP
ZRMMoHvvYaqaPvp4StSBX9A44gSyjHxVYJkrjDZ0uffKg5lFL5IPvqfdmSRSpGab
SyGTP4OLTy0QiNV3pBsJGdl0h5BzuTPR9OTl4xgeqqBQy2bDjmfJBuiYyCSCkPi7
T3ohDYCymhuSkuktHPNG1aKllUJaw0tuZuNydlgdAveXPYfM36uvK0sfd9qr9pAy
rmkYV2MAWguFeckh
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCv3CvrCGokJSWL
8ufg6u9LCW4EezztbktqpC0T+1m98+Ujb8/eJ0L2UaxZ9QBSBAqXxEoeZFBeoCXu
7ezUd5qUPfIhKLAkQTAyU/KgfhHh4i+MJK5ghPbGDE8r2gKUXOkM6M5//ZCpmu0K
Y/9uQL6D5bkxEaoWegEO+wSXm+hTTgKDtQKHvRibgdcZkcY0cA9JsLrC/nIkP+7i
pbBT+VTuV6gAnKIV0nq8zvI3A/Z3nAb5Gt0g3qaqs59StDT0QtuXzJkuZEo3XSrS
jon+8NjSNzqVbJj95B7+uiH+91VEbMtJYFz2MipKvJQDK7Zlxke7LxRj2xJfksJK
a92/ncxfAgMBAAECggEAQztaMvW5lm35J8LKsWs/5qEJRX9T8LWs8W0oqq36Riub
G2wgvR6ndAIPcSjAYZqX7iOl7m6NZ0+0kN63HxdGqovwKIskpAekBGmhpYftED1n
zh0r6UyMB3UnQ22KdOv8UOokIDxxdNX8728BdUYdT9Ggdkj5jLRB+VcwD0IUlNvo
zzTpURV9HEd87uiLqd4AAHXSI0lIHI5U43z24HI/J6/YbYHT3Rlh6CIa/LuwO6vL
gFkgqg0/oy6yJtjrHtzNVA67F0UaH62hR4YFgbC0d955SJnDidWOv/0j2DMpfdCc
9kFAcPwUSyykvUSLnGIKWSG4D+6gzIeAeUx4oO7kMQKBgQDVNRkX8AGTHyLg+NXf
spUWWcodwVioXl30Q7h6+4bt8OI61UbhQ7wX61wvJ1cySpa2KOYa2UdagQVhGhhL
ADu363R77uXF/jZgzVfmjjyJ2nfDqRgHWRTlSkuq/jCOQCz7VIPHRZg5WL/9D4ms
TAqMjpzqeMfFZI+w4/+xpcJIuQKBgQDTKBy+ZuerWrVT9icWKvLU58o5EVj/2yFy
GJvKm+wRAAX2WzjNnR4HVd4DmMREVz1BPYby0j5gqjvtDsxYYu39+NT7JvMioLLK
QPj+7k5geYgNqVgCxB1vP89RhY2X1RLrN9sTXOodgFPeXOQWNYITkGp3eQpx4nTJ
+K/al3oB1wKBgAjnc8nVIyuyxDEjE0OJYMKTM2a0uXAmqMPXxC+Wq5bqVXhhidlE
i+lv0eTCPtkB1nN7F8kNQ/aaps/cWCFhvBy9P5shagUvzbOTP9WIIS0cq53HRRKh
fMbqqGhWv05hjb9dUzeSR341n6cA7B3++v3Nwu3j52vt/DZF/1q68nc5AoGAS0SU
ImbKE/GsizZGLoe2sZ/CHN+LKwCwhlwxRGKaHmE0vuE7eUeVSaYZEo0lAPtb8WJ+
NRYueASWgeTxgFwbW5mUScZTirdfo+rPFwhZVdhcYApKPgosN9i2DOgfVcz1BnWN
mPRY25U/0BaqkyQVruWeneG+kGPZn5kPDktKiVcCgYEAkzwU9vCGhm7ZVALvx/zR
wARz2zsL9ImBc0P4DK1ld8g90FEnHrEgeI9JEwz0zFHOCMLwlk7kG0Xev7vfjZ7G
xSqtQYOH33Qp6rtBOgdt8hSyDFvakvDl6bqhAw52gelO3MTpAB1+ZsfZ5gFx13Jf
idNFcaIrC52PtZIH7QCzdDY=
-----END PRIVATE KEY-----

View File

@@ -23,13 +23,22 @@ import android.net.ipsec.ike.IkeFqdnIdentification;
import android.net.ipsec.ike.IkeSessionParams;
import android.os.PersistableBundle;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.org.bouncycastle.util.io.pem.PemObject;
import com.android.internal.org.bouncycastle.util.io.pem.PemReader;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -95,4 +104,33 @@ public class IkeSessionParamsUtilsTest {
final IkeSessionParams params = createBuilderMinimum().setAuthPsk("psk".getBytes()).build();
verifyPersistableBundleEncodeDecodeIsLossless(params);
}
private static InputStream openAssetsFile(String fileName) throws Exception {
return InstrumentationRegistry.getContext().getResources().getAssets().open(fileName);
}
private static X509Certificate createCertFromPemFile(String fileName) throws Exception {
final CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(openAssetsFile(fileName));
}
private static RSAPrivateKey createRsaPrivateKeyFromKeyFile(String fileName) throws Exception {
final PemObject pemObject =
new PemReader(new InputStreamReader(openAssetsFile(fileName))).readPemObject();
return (RSAPrivateKey) CertUtils.privateKeyFromByteArray(pemObject.getContent());
}
@Test
public void testEncodeRecodeParamsWithDigitalSignAuth() throws Exception {
final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
final X509Certificate clientEndCert = createCertFromPemFile("client-end-cert.pem");
final RSAPrivateKey clientPrivateKey =
createRsaPrivateKeyFromKeyFile("client-private-key.key");
final IkeSessionParams params =
createBuilderMinimum()
.setAuthDigitalSignature(serverCaCert, clientEndCert, clientPrivateKey)
.build();
verifyPersistableBundleEncodeDecodeIsLossless(params);
}
}