MediaDrm.java revision 16b8cffb2893c10c35788191847500004da466d1
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.media; 18 19import android.media.MediaDrmException; 20import java.lang.ref.WeakReference; 21import java.util.UUID; 22import java.util.HashMap; 23import java.util.List; 24import android.os.Handler; 25import android.os.Looper; 26import android.os.Message; 27import android.os.Bundle; 28import android.util.Log; 29 30/** 31 * MediaDrm can be used in conjunction with {@link android.media.MediaCrypto} 32 * to obtain keys for decrypting protected media data. 33 * 34 * Crypto schemes are assigned 16 byte UUIDs, 35 * the method {@link #isCryptoSchemeSupported} can be used to query if a given 36 * scheme is supported on the device. 37 * 38 * <a name="Callbacks"></a> 39 * <h3>Callbacks</h3> 40 * <p>Applications may want to register for informational events in order 41 * to be informed of some internal state update during playback or streaming. 42 * Registration for these events is done via a call to 43 * {@link #setOnEventListener(OnInfoListener)}setOnInfoListener, 44 * In order to receive the respective callback 45 * associated with this listener, applications are required to create 46 * MediaDrm objects on a thread with its own Looper running (main UI 47 * thread by default has a Looper running). 48 * 49 * @hide -- don't expose yet 50 */ 51public final class MediaDrm { 52 53 private final static String TAG = "MediaDrm"; 54 55 private EventHandler mEventHandler; 56 private OnEventListener mOnEventListener; 57 58 private int mNativeContext; 59 60 /** 61 * Query if the given scheme identified by its UUID is supported on 62 * this device. 63 * @param uuid The UUID of the crypto scheme. 64 */ 65 public static final boolean isCryptoSchemeSupported(UUID uuid) { 66 return isCryptoSchemeSupportedNative(getByteArrayFromUUID(uuid)); 67 } 68 69 private static final byte[] getByteArrayFromUUID(UUID uuid) { 70 long msb = uuid.getMostSignificantBits(); 71 long lsb = uuid.getLeastSignificantBits(); 72 73 byte[] uuidBytes = new byte[16]; 74 for (int i = 0; i < 8; ++i) { 75 uuidBytes[i] = (byte)(msb >>> (8 * (7 - i))); 76 uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i))); 77 } 78 79 return uuidBytes; 80 } 81 82 private static final native boolean isCryptoSchemeSupportedNative(byte[] uuid); 83 84 /** 85 * Instantiate a MediaDrm object using opaque, crypto scheme specific 86 * data. 87 * @param uuid The UUID of the crypto scheme. 88 */ 89 public MediaDrm(UUID uuid) throws MediaDrmException { 90 Looper looper; 91 if ((looper = Looper.myLooper()) != null) { 92 mEventHandler = new EventHandler(this, looper); 93 } else if ((looper = Looper.getMainLooper()) != null) { 94 mEventHandler = new EventHandler(this, looper); 95 } else { 96 mEventHandler = null; 97 } 98 99 /* Native setup requires a weak reference to our object. 100 * It's easier to create it here than in C++. 101 */ 102 native_setup(new WeakReference<MediaDrm>(this), 103 getByteArrayFromUUID(uuid)); 104 } 105 106 /** 107 * Register a callback to be invoked when an event occurs 108 * 109 * @param listener the callback that will be run 110 */ 111 public void setOnEventListener(OnEventListener listener) 112 { 113 mOnEventListener = listener; 114 } 115 116 /** 117 * Interface definition for a callback to be invoked when a drm event 118 * occurs. 119 */ 120 public interface OnEventListener 121 { 122 /** 123 * Called when an event occurs that requires the app to be notified 124 * 125 * @param md the MediaDrm object on which the event occurred 126 * @param sessionId the DRM session ID on which the event occurred 127 * @param event indicates the event type 128 * @param extra an secondary error code 129 * @param data optional byte array of data that may be associated with the event 130 */ 131 void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data); 132 } 133 134 public static final int MEDIA_DRM_EVENT_PROVISION_REQUIRED = 1; 135 public static final int MEDIA_DRM_EVENT_KEY_REQUIRED = 2; 136 public static final int MEDIA_DRM_EVENT_KEY_EXPIRED = 3; 137 public static final int MEDIA_DRM_EVENT_VENDOR_DEFINED = 4; 138 139 /* Do not change these values without updating their counterparts 140 * in include/media/mediadrm.h! 141 */ 142 private static final int DRM_EVENT = 200; 143 private class EventHandler extends Handler 144 { 145 private MediaDrm mMediaDrm; 146 147 public EventHandler(MediaDrm md, Looper looper) { 148 super(looper); 149 mMediaDrm = md; 150 } 151 152 @Override 153 public void handleMessage(Message msg) { 154 if (mMediaDrm.mNativeContext == 0) { 155 Log.w(TAG, "MediaDrm went away with unhandled events"); 156 return; 157 } 158 switch(msg.what) { 159 160 case DRM_EVENT: 161 Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")"); 162 163 if (mOnEventListener != null) { 164 Bundle bundle = msg.getData(); 165 byte[] sessionId = bundle.getByteArray("sessionId"); 166 byte[] data = bundle.getByteArray("data"); 167 mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data); 168 } 169 return; 170 171 default: 172 Log.e(TAG, "Unknown message type " + msg.what); 173 return; 174 } 175 } 176 } 177 178 /* 179 * Called from native code when an interesting event happens. This method 180 * just uses the EventHandler system to post the event back to the main app thread. 181 * We use a weak reference to the original MediaPlayer object so that the native 182 * code is safe from the object disappearing from underneath it. (This is 183 * the cookie passed to native_setup().) 184 */ 185 private static void postEventFromNative(Object mediadrm_ref, 186 int what, int arg1, int arg2, Object obj) 187 { 188 MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get(); 189 if (md == null) { 190 return; 191 } 192 if (md.mEventHandler != null) { 193 Message m = md.mEventHandler.obtainMessage(what, arg1, arg2, obj); 194 md.mEventHandler.sendMessage(m); 195 } 196 } 197 198 /** 199 * Open a new session with the MediaDrm object. A session ID is returned. 200 */ 201 public native byte[] openSession() throws MediaDrmException; 202 203 /** 204 * Close a session on the MediaDrm object that was previously opened 205 * with {@link #openSession}. 206 */ 207 public native void closeSession(byte[] sessionId) throws MediaDrmException; 208 209 public static final int MEDIA_DRM_KEY_TYPE_STREAMING = 1; 210 public static final int MEDIA_DRM_KEY_TYPE_OFFLINE = 2; 211 212 public final class KeyRequest { 213 public KeyRequest() {} 214 public byte[] data; 215 public String defaultUrl; 216 }; 217 218 /** 219 * A key request/response exchange occurs between the app and a license 220 * server to obtain the keys to decrypt encrypted content. getKeyRequest() 221 * is used to obtain an opaque key request byte array that is delivered to the 222 * license server. The opaque key request byte array is returned in 223 * KeyRequest.data. The recommended URL to deliver the key request to is 224 * returned in KeyRequest.defaultUrl. 225 * 226 * After the app has received the key request response from the server, 227 * it should deliver to the response to the DRM engine plugin using the method 228 * {@link #provideKeyResponse}. 229 * 230 * @param sessonId the session ID for the drm session 231 * @param init container-specific data, its meaning is interpreted based on the 232 * mime type provided in the mimeType parameter. It could contain, for example, 233 * the content ID, key ID or other data obtained from the content metadata that is 234 * required in generating the key request. 235 * @param mimeType identifies the mime type of the content 236 * @param keyType specifes if the request is for streaming or offline content 237 * @param optionalParameters are included in the key request message to 238 * allow a client application to provide additional message parameters to the server. 239 */ 240 public native KeyRequest getKeyRequest(byte[] sessionId, byte[] init, 241 String mimeType, int keyType, 242 HashMap<String, String> optionalParameters) 243 throws MediaDrmException; 244 245 /** 246 * A key response is received from the license server by the app, then it is 247 * provided to the DRM engine plugin using provideKeyResponse. The byte array 248 * returned is a keySetId that can be used to later restore the keys to a new 249 * session with the method {@link restoreKeys}, enabling offline key use. 250 * 251 * @param sessionId the session ID for the DRM session 252 * @param response the byte array response from the server 253 */ 254 public native byte[] provideKeyResponse(byte[] sessionId, byte[] response) 255 throws MediaDrmException; 256 257 /** 258 * Restore persisted offline keys into a new session. keySetId identifies the 259 * keys to load, obtained from a prior call to {@link provideKeyResponse}. 260 * 261 * @param sessionId the session ID for the DRM session 262 * @param keySetId identifies the saved key set to restore 263 */ 264 public native void restoreKeys(byte[] sessionId, byte[] keySetId) 265 throws MediaDrmException; 266 267 /** 268 * Remove the persisted keys associated with an offline license. Keys are persisted 269 * when {@link provideKeyResponse} is called with keys obtained from the method 270 * {@link getKeyRequest} using keyType = MEDIA_DRM_KEY_TYPE_OFFLINE. 271 * 272 * @param keySetId identifies the saved key set to remove 273 */ 274 public native void removeKeys(byte[] keySetId) throws MediaDrmException; 275 276 /** 277 * Request an informative description of the key status for the session. The status is 278 * in the form of {name, value} pairs. Since DRM license policies vary by vendor, 279 * the specific status field names are determined by each DRM vendor. Refer to your 280 * DRM provider documentation for definitions of the field names for a particular 281 * DRM engine plugin. 282 * 283 * @param sessionId the session ID for the DRM session 284 */ 285 public native HashMap<String, String> queryKeyStatus(byte[] sessionId) 286 throws MediaDrmException; 287 288 public final class ProvisionRequest { 289 public ProvisionRequest() {} 290 public byte[] data; 291 public String defaultUrl; 292 } 293 294 /** 295 * A provision request/response exchange occurs between the app and a provisioning 296 * server to retrieve a device certificate. If provisionining is required, the 297 * MEDIA_DRM_EVENT_PROVISION_REQUIRED event will be sent to the event handler. 298 * getProvisionRequest is used to obtain the opaque provision request byte array that 299 * should be delivered to the provisioning server. The provision request byte array 300 * is returned in ProvisionRequest.data. The recommended URL to deliver the provision 301 * request to is returned in ProvisionRequest.defaultUrl. 302 */ 303 public native ProvisionRequest getProvisionRequest() throws MediaDrmException; 304 305 /** 306 * After a provision response is received by the app, it is provided to the DRM 307 * engine plugin using this method. 308 * 309 * @param response the opaque provisioning response byte array to provide to the 310 * DRM engine plugin. 311 */ 312 public native void provideProvisionResponse(byte[] response) 313 throws MediaDrmException; 314 315 /** 316 * A means of enforcing the contractual requirement for a concurrent stream limit 317 * per subscriber across devices is provided via SecureStop. SecureStop is a means 318 * of securely monitoring the lifetime of sessions. Since playback on a device can 319 * be interrupted due to reboot, power failure, etc. a means of persisting the 320 * lifetime information on the device is needed. 321 * 322 * A signed version of the sessionID is written to persistent storage on the device 323 * when each MediaCrypto object is created. The sessionID is signed by the device 324 * private key to prevent tampering. 325 * 326 * In the normal case, playback will be completed, the session destroyed and the 327 * Secure Stops will be queried. The App queries secure stops and forwards the 328 * secure stop message to the server which verifies the signature and notifies the 329 * server side database that the session destruction has been confirmed. The persisted 330 * record on the client is only removed after positive confirmation that the server 331 * received the message using releaseSecureStops(). 332 */ 333 public native List<byte[]> getSecureStops() throws MediaDrmException; 334 335 336 /** 337 * Process the SecureStop server response message ssRelease. After authenticating 338 * the message, remove the SecureStops identiied in the response. 339 * 340 * @param ssRelease the server response indicating which secure stops to release 341 */ 342 public native void releaseSecureStops(byte[] ssRelease) 343 throws MediaDrmException; 344 345 346 /** 347 * Read a DRM engine plugin property value, given the property name string. There are 348 * several forms of property access functions, depending on the data type returned. 349 * 350 * Standard fields names are: 351 * vendor String - identifies the maker of the DRM engine plugin 352 * version String - identifies the version of the DRM engine plugin 353 * description String - describes the DRM engine plugin 354 * deviceUniqueId byte[] - The device unique identifier is established during device 355 * provisioning and provides a means of uniquely identifying 356 * each device 357 * algorithms String - a comma-separate list of cipher and mac algorithms supported 358 * by CryptoSession. The list may be empty if the DRM engine 359 * plugin does not support CryptoSession operations. 360 */ 361 public native String getPropertyString(String propertyName) 362 throws MediaDrmException; 363 364 public native byte[] getPropertyByteArray(String propertyName) 365 throws MediaDrmException; 366 367 /** 368 * Write a DRM engine plugin property value. There are several forms of 369 * property setting functions, depending on the data type being set. 370 */ 371 public native void setPropertyString(String propertyName, String value) 372 throws MediaDrmException; 373 374 public native void setPropertyByteArray(String propertyName, byte[] value) 375 throws MediaDrmException; 376 377 /** 378 * In addition to supporting decryption of DASH Common Encrypted Media, the 379 * MediaDrm APIs provide the ability to securely deliver session keys from 380 * an operator's session key server to a client device, based on the factory-installed 381 * root of trust, and provide the ability to do encrypt, decrypt, sign and verify 382 * with the session key on arbitrary user data. 383 * 384 * The CryptoSession class implements generic encrypt/decrypt/sign/verify methods 385 * based on the established session keys. These keys are exchanged using the 386 * getKeyRequest/provideKeyResponse methods. 387 * 388 * Applications of this capability could include securing various types of 389 * purchased or private content, such as applications, books and other media, 390 * photos or media delivery protocols. 391 * 392 * Operators can create session key servers that are functionally similar to a 393 * license key server, except that instead of receiving license key requests and 394 * providing encrypted content keys which are used specifically to decrypt A/V media 395 * content, the session key server receives session key requests and provides 396 * encrypted session keys which can be used for general purpose crypto operations. 397 */ 398 399 private static final native void setCipherAlgorithmNative(MediaDrm drm, byte[] sessionId, 400 String algorithm); 401 402 private static final native void setMacAlgorithmNative(MediaDrm drm, byte[] sessionId, 403 String algorithm); 404 405 private static final native byte[] encryptNative(MediaDrm drm, byte[] sessionId, 406 byte[] keyId, byte[] input, byte[] iv); 407 408 private static final native byte[] decryptNative(MediaDrm drm, byte[] sessionId, 409 byte[] keyId, byte[] input, byte[] iv); 410 411 private static final native byte[] signNative(MediaDrm drm, byte[] sessionId, 412 byte[] keyId, byte[] message); 413 414 private static final native boolean verifyNative(MediaDrm drm, byte[] sessionId, 415 byte[] keyId, byte[] message, 416 byte[] signature); 417 418 public final class CryptoSession { 419 private MediaDrm mDrm; 420 private byte[] mSessionId; 421 422 /** 423 * Construct a CryptoSession which can be used to encrypt, decrypt, 424 * sign and verify messages or data using the session keys established 425 * for the session using methods {@link getKeyRequest} and 426 * {@link provideKeyResponse} using a session key server. 427 * 428 * @param sessionId the session ID for the session containing keys 429 * to be used for encrypt, decrypt, sign and/or verify 430 * 431 * @param cipherAlgorithm the algorithm to use for encryption and 432 * decryption ciphers. The algorithm string conforms to JCA Standard 433 * Names for Cipher Transforms and is case insensitive. For example 434 * "AES/CBC/PKCS5Padding". 435 * 436 * @param macAlgorithm the algorithm to use for sign and verify 437 * The algorithm string conforms to JCA Standard Names for Mac 438 * Algorithms and is case insensitive. For example "HmacSHA256". 439 * 440 * The list of supported algorithms for a DRM engine plugin can be obtained 441 * using the method {@link getPropertyString("algorithms")} 442 */ 443 444 public CryptoSession(MediaDrm drm, byte[] sessionId, 445 String cipherAlgorithm, String macAlgorithm) 446 throws MediaDrmException { 447 mSessionId = sessionId; 448 mDrm = drm; 449 setCipherAlgorithmNative(drm, sessionId, cipherAlgorithm); 450 setMacAlgorithmNative(drm, sessionId, macAlgorithm); 451 } 452 453 public byte[] encrypt(byte[] keyid, byte[] input, byte[] iv) { 454 return encryptNative(mDrm, mSessionId, keyid, input, iv); 455 } 456 457 public byte[] decrypt(byte[] keyid, byte[] input, byte[] iv) { 458 return decryptNative(mDrm, mSessionId, keyid, input, iv); 459 } 460 461 public byte[] sign(byte[] keyid, byte[] message) { 462 return signNative(mDrm, mSessionId, keyid, message); 463 } 464 public boolean verify(byte[] keyid, byte[] message, byte[] signature) { 465 return verifyNative(mDrm, mSessionId, keyid, message, signature); 466 } 467 }; 468 469 public CryptoSession getCryptoSession(byte[] sessionId, 470 String cipherAlgorithm, 471 String macAlgorithm) 472 throws MediaDrmException { 473 return new CryptoSession(this, sessionId, cipherAlgorithm, macAlgorithm); 474 } 475 476 @Override 477 protected void finalize() { 478 native_finalize(); 479 } 480 481 public native final void release(); 482 private static native final void native_init(); 483 484 private native final void native_setup(Object mediadrm_this, byte[] uuid) 485 throws MediaDrmException; 486 487 private native final void native_finalize(); 488 489 static { 490 System.loadLibrary("media_jni"); 491 native_init(); 492 } 493} 494