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

Commit e4095a80 authored by Jeff Tinker's avatar Jeff Tinker
Browse files

Support CAST V2 Authentication in MediaDrm

Java API version

Update frameworks to enable support for CAST
V2 Authentication in the DRM Plugin.

Change-Id: I23cfbbbc89c1226b7a3968ce8bc1e2d4bd41014a
related-to-bug: 12702350
parent d27f82f9
Loading
Loading
Loading
Loading
+147 −6
Original line number Diff line number Diff line
@@ -21,12 +21,15 @@ import java.lang.ref.WeakReference;
import java.util.UUID;
import java.util.HashMap;
import java.util.List;
import android.os.Binder;
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;
import android.content.Context;

/**
 * MediaDrm can be used to obtain keys for decrypting protected media streams, in
@@ -102,6 +105,20 @@ public final class MediaDrm {

    private long mNativeContext;

    /**
     * Specify no certificate type
     *
     * @hide - not part of the public API at this time
     */
    public static final int CERTIFICATE_TYPE_NONE = 0;

    /**
     * Specify X.509 certificate type
     *
     * @hide - not part of the public API at this time
     */
    public static final int CERTIFICATE_TYPE_X509 = 1;

    /**
     * Query if the given scheme identified by its UUID is supported on
     * this device.
@@ -318,6 +335,9 @@ public final class MediaDrm {
     * Contains the opaque data an app uses to request keys from a license server
     */
    public final static class KeyRequest {
        private byte[] mData;
        private String mDefaultUrl;

        KeyRequest() {}

        /**
@@ -331,9 +351,6 @@ public final class MediaDrm {
         * server URL from other sources.
         */
        public String getDefaultUrl() { return mDefaultUrl; }

        private byte[] mData;
        private String mDefaultUrl;
    };

    /**
@@ -458,7 +475,12 @@ public final class MediaDrm {
     * is returned in ProvisionRequest.data. The recommended URL to deliver the provision
     * request to is returned in ProvisionRequest.defaultUrl.
     */
    public native ProvisionRequest getProvisionRequest();
    public ProvisionRequest getProvisionRequest() {
        return getProvisionRequestNative(CERTIFICATE_TYPE_NONE, "");
    }

    private native ProvisionRequest getProvisionRequestNative(int certType,
                                                              String certAuthority);

    /**
     * After a provision response is received by the app, it is provided to the DRM
@@ -470,7 +492,12 @@ public final class MediaDrm {
     * @throws DeniedByServerException if the response indicates that the
     * server rejected the request
     */
    public native void provideProvisionResponse(byte[] response)
    public void provideProvisionResponse(byte[] response)
        throws DeniedByServerException {
        provideProvisionResponseNative(response);
    }

    private native Certificate provideProvisionResponseNative(byte[] response)
        throws DeniedByServerException;

    /**
@@ -685,6 +712,120 @@ public final class MediaDrm {
        return new CryptoSession(this, sessionId, cipherAlgorithm, macAlgorithm);
    }

    /**
     * Contains the opaque data an app uses to request a certificate from a provisioning
     * server
     *
     * @hide - not part of the public API at this time
     */
    public final static class CertificateRequest {
        private byte[] mData;
        private String mDefaultUrl;

        CertificateRequest(byte[] data, String defaultUrl) {
            mData = data;
            mDefaultUrl = defaultUrl;
        }

        /**
         * Get the opaque message data
         */
        public byte[] getData() { return mData; }

        /**
         * Get the default URL to use when sending the certificate request
         * message to a server, if known. The app may prefer to use a different
         * certificate server URL obtained from other sources.
         */
        public String getDefaultUrl() { return mDefaultUrl; }
    }

    /**
     * Generate a certificate request, specifying the certificate type
     * and authority. The response received should be passed to
     * provideCertificateResponse.
     *
     * @param certType Specifies the certificate type.
     *
     * @param certAuthority is passed to the certificate server to specify
     * the chain of authority.
     *
     * @hide - not part of the public API at this time
     */
    public CertificateRequest getCertificateRequest(int certType,
                                                    String certAuthority)
    {
        ProvisionRequest provisionRequest = getProvisionRequestNative(certType, certAuthority);
        return new CertificateRequest(provisionRequest.getData(),
                                      provisionRequest.getDefaultUrl());
    }

    /**
     * Contains the wrapped private key and public certificate data associated
     * with a certificate.
     *
     * @hide - not part of the public API at this time
     */
    public final static class Certificate {
        Certificate() {}

        /**
         * Get the wrapped private key data
         */
        public byte[] getWrappedPrivateKey() { return mWrappedKey; }

        /**
         * Get the PEM-encoded certificate chain
         */
        public byte[] getContent() { return mCertificateData; }

        private byte[] mWrappedKey;
        private byte[] mCertificateData;
    }


    /**
     * Process a response from the certificate server.  The response
     * is obtained from an HTTP Post to the url provided by getCertificateRequest.
     * <p>
     * The public X509 certificate chain and wrapped private key are returned
     * in the returned Certificate objec.  The certificate chain is in PEM format.
     * The wrapped private key should be stored in application private
     * storage, and used when invoking the signRSA method.
     *
     * @param response the opaque certificate response byte array to provide to the
     * DRM engine plugin.
     *
     * @throws DeniedByServerException if the response indicates that the
     * server rejected the request
     *
     * @hide - not part of the public API at this time
     */
    public Certificate provideCertificateResponse(byte[] response)
        throws DeniedByServerException {
        return provideProvisionResponseNative(response);
    }

    private static final native byte[] signRSANative(MediaDrm drm, byte[] sessionId,
                                                     String algorithm, byte[] wrappedKey,
                                                     byte[] message);

    /**
     * Sign data using an RSA key
     *
     * @param context the app context
     * @param sessionId a sessionId obtained from openSession on the MediaDrm object
     * @param algorithm the signing algorithm to use, e.g. "PKCS1-BlockType1"
     * @param wrappedKey - the wrapped (encrypted) RSA private key obtained
     * from provideCertificateResponse
     * @param message the data for which a signature is to be computed
     *
     * @hide - not part of the public API at this time
     */
    public byte[] signRSA(Context context, byte[] sessionId, String algorithm, byte[] wrappedKey, byte[] message) {
        return signRSANative(this, sessionId, algorithm, wrappedKey, message);
    }

    @Override
    protected void finalize() {
        native_finalize();
+109 −17
Original line number Diff line number Diff line
@@ -100,6 +100,16 @@ struct KeyTypes {
    jint kKeyTypeRelease;
} gKeyTypes;

struct CertificateTypes {
    jint kCertificateTypeNone;
    jint kCertificateTypeX509;
} gCertificateTypes;

struct CertificateFields {
    jfieldID wrappedPrivateKey;
    jfieldID certificateData;
};

struct fields_t {
    jfieldID context;
    jmethodID post_event;
@@ -110,6 +120,11 @@ struct fields_t {
    SetFields set;
    IteratorFields iterator;
    EntryFields entry;
    CertificateFields certificate;
    jclass certificateClassId;
    jclass hashmapClassId;
    jclass arraylistClassId;
    jclass stringClassId;
};

static fields_t gFields;
@@ -406,8 +421,7 @@ static String8 JStringToString8(JNIEnv *env, jstring const &jstr) {
*/

static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &hashMap) {
    jclass clazz;
    FIND_CLASS(clazz, "java/lang/String");
    jclass clazz = gFields.stringClassId;
    KeyedVector<String8, String8> keyedVector;

    jobject entrySet = env->CallObjectMethod(hashMap, gFields.hashmap.entrySet);
@@ -450,8 +464,7 @@ static KeyedVector<String8, String8> HashMapToKeyedVector(JNIEnv *env, jobject &
}

static jobject KeyedVectorToHashMap (JNIEnv *env, KeyedVector<String8, String8> const &map) {
    jclass clazz;
    FIND_CLASS(clazz, "java/util/HashMap");
    jclass clazz = gFields.hashmapClassId;
    jobject hashMap = env->NewObject(clazz, gFields.hashmap.init);
    for (size_t i = 0; i < map.size(); ++i) {
        jstring jkey = env->NewStringUTF(map.keyAt(i).string());
@@ -465,8 +478,7 @@ static jobject KeyedVectorToHashMap (JNIEnv *env, KeyedVector<String8, String8>

static jobject ListOfVectorsToArrayListOfByteArray(JNIEnv *env,
                                                   List<Vector<uint8_t> > list) {
    jclass clazz;
    FIND_CLASS(clazz, "java/util/ArrayList");
    jclass clazz = gFields.arraylistClassId;
    jobject arrayList = env->NewObject(clazz, gFields.arraylist.init);
    List<Vector<uint8_t> >::iterator iter = list.begin();
    while (iter != list.end()) {
@@ -542,6 +554,11 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
    GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I");
    gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field);

    GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_NONE", "I");
    gCertificateTypes.kCertificateTypeNone = env->GetStaticIntField(clazz, field);
    GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_X509", "I");
    gCertificateTypes.kCertificateTypeX509 = env->GetStaticIntField(clazz, field);

    FIND_CLASS(clazz, "android/media/MediaDrm$KeyRequest");
    GET_FIELD_ID(gFields.keyRequest.data, clazz, "mData", "[B");
    GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
@@ -550,6 +567,11 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
    GET_FIELD_ID(gFields.provisionRequest.data, clazz, "mData", "[B");
    GET_FIELD_ID(gFields.provisionRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");

    FIND_CLASS(clazz, "android/media/MediaDrm$Certificate");
    GET_FIELD_ID(gFields.certificate.wrappedPrivateKey, clazz, "mWrappedKey", "[B");
    GET_FIELD_ID(gFields.certificate.certificateData, clazz, "mCertificateData", "[B");
    gFields.certificateClassId = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));

    FIND_CLASS(clazz, "java/util/ArrayList");
    GET_METHOD_ID(gFields.arraylist.init, clazz, "<init>", "()V");
    GET_METHOD_ID(gFields.arraylist.add, clazz, "add", "(Ljava/lang/Object;)Z");
@@ -571,6 +593,15 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
    FIND_CLASS(clazz, "java/util/Map$Entry");
    GET_METHOD_ID(gFields.entry.getKey, clazz, "getKey", "()Ljava/lang/Object;");
    GET_METHOD_ID(gFields.entry.getValue, clazz, "getValue", "()Ljava/lang/Object;");

    FIND_CLASS(clazz, "java/util/HashMap");
    gFields.hashmapClassId = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));

    FIND_CLASS(clazz, "java/lang/String");
    gFields.stringClassId = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));

    FIND_CLASS(clazz, "java/util/ArrayList");
    gFields.arraylistClassId = reinterpret_cast<jclass>(env->NewGlobalRef(clazz));
}

static void android_media_MediaDrm_native_setup(
@@ -826,8 +857,8 @@ static jobject android_media_MediaDrm_queryKeyStatus(
    return KeyedVectorToHashMap(env, infoMap);
}

static jobject android_media_MediaDrm_getProvisionRequest(
    JNIEnv *env, jobject thiz) {
static jobject android_media_MediaDrm_getProvisionRequestNative(
    JNIEnv *env, jobject thiz, jint jcertType, jstring jcertAuthority) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (drm == NULL) {
@@ -839,7 +870,17 @@ static jobject android_media_MediaDrm_getProvisionRequest(
    Vector<uint8_t> request;
    String8 defaultUrl;

    status_t err = drm->getProvisionRequest(request, defaultUrl);
    String8 certType;
    if (jcertType == gCertificateTypes.kCertificateTypeX509) {
        certType = "X.509";
    } else if (jcertType == gCertificateTypes.kCertificateTypeNone) {
        certType = "none";
    } else {
        certType = "invalid";
    }

    String8 certAuthority = JStringToString8(env, jcertAuthority);
    status_t err = drm->getProvisionRequest(certType, certAuthority, request, defaultUrl);

    if (throwExceptionAsNecessary(env, err, "Failed to get provision request")) {
        return NULL;
@@ -863,27 +904,43 @@ static jobject android_media_MediaDrm_getProvisionRequest(
    return provisionObj;
}

static void android_media_MediaDrm_provideProvisionResponse(
static jobject android_media_MediaDrm_provideProvisionResponseNative(
    JNIEnv *env, jobject thiz, jbyteArray jresponse) {
    sp<IDrm> drm = GetDrm(env, thiz);

    if (drm == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                          "MediaDrm obj is null");
        return;
        return NULL;
    }

    if (jresponse == NULL) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          "provision response is null");
        return;
        return NULL;
    }

    Vector<uint8_t> response(JByteArrayToVector(env, jresponse));
    Vector<uint8_t> certificate, wrappedKey;

    status_t err = drm->provideProvisionResponse(response, certificate, wrappedKey);

    // Fill out return obj
    jclass clazz = gFields.certificateClassId;

    jobject certificateObj = NULL;

    if (clazz && certificate.size() && wrappedKey.size()) {
        certificateObj = env->AllocObject(clazz);
        jbyteArray jcertificate = VectorToJByteArray(env, certificate);
        env->SetObjectField(certificateObj, gFields.certificate.certificateData, jcertificate);

    status_t err = drm->provideProvisionResponse(response);
        jbyteArray jwrappedKey = VectorToJByteArray(env, wrappedKey);
        env->SetObjectField(certificateObj, gFields.certificate.wrappedPrivateKey, jwrappedKey);
    }

    throwExceptionAsNecessary(env, err, "Failed to handle provision response");
    return certificateObj;
}

static jobject android_media_MediaDrm_getSecureStops(
@@ -1209,6 +1266,38 @@ static jboolean android_media_MediaDrm_verifyNative(
}


static jbyteArray android_media_MediaDrm_signRSANative(
    JNIEnv *env, jobject thiz, jobject jdrm, jbyteArray jsessionId,
    jstring jalgorithm, jbyteArray jwrappedKey, jbyteArray jmessage) {

    sp<IDrm> drm = GetDrm(env, jdrm);

    if (!CheckSession(env, drm, jsessionId)) {
        return NULL;
    }

    if (jalgorithm == NULL || jwrappedKey == NULL || jmessage == NULL) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          "required argument is null");
        return NULL;
    }

    Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
    String8 algorithm = JStringToString8(env, jalgorithm);
    Vector<uint8_t> wrappedKey(JByteArrayToVector(env, jwrappedKey));
    Vector<uint8_t> message(JByteArrayToVector(env, jmessage));
    Vector<uint8_t> signature;

    status_t err = drm->signRSA(sessionId, algorithm, message, wrappedKey, signature);

    if (throwExceptionAsNecessary(env, err, "Failed to sign")) {
        return NULL;
    }

    return VectorToJByteArray(env, signature);
}


static JNINativeMethod gMethods[] = {
    { "release", "()V", (void *)android_media_MediaDrm_release },
    { "native_init", "()V", (void *)android_media_MediaDrm_native_init },
@@ -1244,11 +1333,11 @@ static JNINativeMethod gMethods[] = {
    { "queryKeyStatus", "([B)Ljava/util/HashMap;",
      (void *)android_media_MediaDrm_queryKeyStatus },

    { "getProvisionRequest", "()Landroid/media/MediaDrm$ProvisionRequest;",
      (void *)android_media_MediaDrm_getProvisionRequest },
    { "getProvisionRequestNative", "(ILjava/lang/String;)Landroid/media/MediaDrm$ProvisionRequest;",
      (void *)android_media_MediaDrm_getProvisionRequestNative },

    { "provideProvisionResponse", "([B)V",
      (void *)android_media_MediaDrm_provideProvisionResponse },
    { "provideProvisionResponseNative", "([B)Landroid/media/MediaDrm$Certificate;",
      (void *)android_media_MediaDrm_provideProvisionResponseNative },

    { "getSecureStops", "()Ljava/util/List;",
      (void *)android_media_MediaDrm_getSecureStops },
@@ -1287,6 +1376,9 @@ static JNINativeMethod gMethods[] = {

    { "verifyNative", "(Landroid/media/MediaDrm;[B[B[B[B)Z",
      (void *)android_media_MediaDrm_verifyNative },

    { "signRSANative", "(Landroid/media/MediaDrm;[BLjava/lang/String;[B[B)[B",
      (void *)android_media_MediaDrm_signRSANative },
};

int register_android_media_Drm(JNIEnv *env) {
+2 −2
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@
#
LOCAL_PATH := $(call my-dir)

# the library
# the remotedisplay library
# ============================================================
include $(CLEAR_VARS)

@@ -23,7 +23,7 @@ LOCAL_MODULE:= com.android.media.remotedisplay
LOCAL_MODULE_TAGS := optional

LOCAL_SRC_FILES := \
            $(call all-subdir-java-files) \
            $(call all-java-files-under, java) \
            $(call all-aidl-files-under, java)

include $(BUILD_JAVA_LIBRARY)
+0 −1
Original line number Diff line number Diff line
@@ -25,4 +25,3 @@ with the framework in a new API. That API isn't ready yet so this
library is a compromise to make new capabilities available to the system
without exposing the full surface area of the support library media
route provider protocol.
Loading