Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 848568a3 authored by Yan Yan's avatar Yan Yan
Browse files

Support converting IkeAuthDigitalSignConfig to/from PersistableBundle

Bug: 163604823
Test: FrameworksVcnTests(add new tests)
Change-Id: I62cdf4cb0297a394e0c97973e621b5c051ab0192
parent 2f0f6b57
Loading
Loading
Loading
Loading
+21 −1
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+121 −0
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+21 −0
Original line number Diff line number Diff line
-----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-----
 No newline at end of file
+28 −0
Original line number Diff line number Diff line
-----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-----
 No newline at end of file
+38 −0
Original line number Diff line number Diff line
@@ -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);
    }
}