diff options
Diffstat (limited to 'drm')
27 files changed, 5370 insertions, 0 deletions
diff --git a/drm/libdrmframework/plugins/common/Android.mk b/drm/libdrmframework/plugins/common/Android.mk new file mode 100644 index 0000000..9ee7961 --- /dev/null +++ b/drm/libdrmframework/plugins/common/Android.mk @@ -0,0 +1,16 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +include $(call all-subdir-makefiles) diff --git a/drm/libdrmframework/plugins/common/util/Android.mk b/drm/libdrmframework/plugins/common/util/Android.mk new file mode 100644 index 0000000..15dda80 --- /dev/null +++ b/drm/libdrmframework/plugins/common/util/Android.mk @@ -0,0 +1,52 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + src/MimeTypeUtil.cpp + +LOCAL_MODULE := libdrmutility + +LOCAL_SHARED_LIBRARIES := \ + libutils \ + libdl \ + libdvm \ + libandroid_runtime \ + libnativehelper \ + liblog + + +base := frameworks/base + +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) \ + $(base)/include \ + $(base)/include/drm \ + $(base)/include/drm/plugins \ + $(LOCAL_PATH)/include + + +ifneq ($(TARGET_BUILD_VARIANT),user) +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/tools + +endif + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_LIBRARY) diff --git a/drm/libdrmframework/plugins/common/util/include/MimeTypeUtil.h b/drm/libdrmframework/plugins/common/util/include/MimeTypeUtil.h new file mode 100644 index 0000000..4d12a61 --- /dev/null +++ b/drm/libdrmframework/plugins/common/util/include/MimeTypeUtil.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __MIMETYPEUTIL_H__ +#define __MIMETYPEUTIL_H__ + +#include <utils/String8.h> + +namespace android { + +class MimeTypeUtil { + +public: + + MimeTypeUtil() {} + + virtual ~MimeTypeUtil() {} + +/** + * May convert the mimetype if there is a well known + * replacement mimetype otherwise the original mimetype + * is returned. + * + * @param mimeType - mimetype in lower case to convert. + * + * @return mimetype or null. + */ +static String8 convertMimeType(String8& mimeType); + +}; +}; + +#endif /* __MIMETYPEUTIL_H__ */ diff --git a/drm/libdrmframework/plugins/common/util/include/SessionMap.h b/drm/libdrmframework/plugins/common/util/include/SessionMap.h new file mode 100644 index 0000000..3dff58c --- /dev/null +++ b/drm/libdrmframework/plugins/common/util/include/SessionMap.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __SESSIONMAP_H__ +#define __SESSIONMAP_H__ + +#include <utils/KeyedVector.h> + +namespace android { + +/** + * A wrapper template class for handling DRM Engine sessions. + */ +template <typename NODE> +class SessionMap { + +public: + KeyedVector<int, NODE> map; + + SessionMap() {} + + virtual ~SessionMap() { + destroyMap(); + } + +/** + * Adds a new value in the session map table. It expects memory to be allocated already + * for the session object + * + * @param key - key or Session ID + * @param value - session object to add + * + * @return boolean result of adding value. returns false if key is already exist. + */ +bool addValue(int key, NODE value) { + bool result = false; + + if (!isCreated(key)) { + map.add(key, value); + result = true; + } + + return result; +} + + +/** + * returns the session object by the key + * + * @param key - key or Session ID + * + * @return session object as per the key + */ +NODE getValue(int key) { + NODE value = NULL; + + if (isCreated(key)) { + value = (NODE) map.valueFor(key); + } + + return value; +} + +/** + * returns the number of objects in the session map table + * + * @return count of number of session objects. + */ +int getSize() { + return map.size(); +} + +/** + * returns the session object by the index in the session map table + * + * @param index - index of the value required + * + * @return session object as per the index + */ +NODE getValueAt(unsigned int index) { + NODE value = NULL; + + if (map.size() > index) { + value = map.valueAt(index); + } + + return value; +} + +/** + * deletes the object from session map. It also frees up memory for the session object. + * + * @param key - key of the value to be deleted + * + */ +void removeValue(int key) { + deleteValue(getValue(key)); + map.removeItem(key); +} + +/** + * decides if session is already created. + * + * @param key - key of the value for the session + * + * @return boolean result of whether session is created + */ +bool isCreated(int key) { + return (0 <= map.indexOfKey(key)); +} + +/** + * empty the entire session table. It releases all the memory for session objects. + */ +void destroyMap() { + int size = map.size(); + int i = 0; + + for (i = 0; i < size; i++) { + deleteValue(map.valueAt(i)); + } + + map.clear(); +} + +/** + * free up the memory for the session object. + * Make sure if any reference to the session object anywhere, otherwise it will be a + * dangle pointer after this call. + * + * @param value - session object to free + * + */ +void deleteValue(NODE value) { + delete value; +} + +}; + +}; + +#endif /* __SESSIONMAP_H__ */ diff --git a/drm/libdrmframework/plugins/common/util/src/MimeTypeUtil.cpp b/drm/libdrmframework/plugins/common/util/src/MimeTypeUtil.cpp new file mode 100644 index 0000000..4ee903e --- /dev/null +++ b/drm/libdrmframework/plugins/common/util/src/MimeTypeUtil.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <MimeTypeUtil.h> +#include <utils/Log.h> + +namespace android { + +#undef LOG_TAG +#define LOG_TAG "MimeTypeUtil" + +enum { + MIMETYPE_AUDIO = 0, + MIMETYPE_APPLICATION = 1, + MIMETYPE_IMAGE = 2, + MIMETYPE_VIDEO = 3, + MIMETYPE_LAST = -1, +}; + +struct MimeGroup{ + int type; // Audio, video,.. use the enum values + const char* pGroup; // "audio/", "video/",.. should contain the last "/" + int size; // Number of bytes. e.g. "audio/" = 6 bytes +}; + +struct MimeTypeList{ + int type; + const char* pMimeExt; // Everything after the '/' e.g. audio/x-mpeg -> "x-mpeg" + int size; // Number of bytes. e.g. "x-mpeg" = 6 bytes + const char* pMimeType; // Mimetype that should be returned +}; + + +// Known mimetypes by android +static const char mime_type_audio_mpeg[] = "audio/mpeg"; +static const char mime_type_audio_3gpp[] = "audio/3gpp"; +static const char mime_type_audio_amr[] = "audio/amr-wb"; +static const char mime_type_audio_aac[] = "audio/mp4a-latm"; +static const char mime_type_audio_wav[] = "audio/wav"; + +static const char mime_type_video_mpeg4[] = "video/mpeg4"; +static const char mime_type_video_3gpp[] = "video/3gpp"; + +// Known mimetype groups +static const char mime_group_audio[] = "audio/"; +static const char mime_group_application[] = "application/"; +static const char mime_group_image[] = "image/"; +static const char mime_group_video[] = "video/"; + +static struct MimeGroup mimeGroup[] = { + {MIMETYPE_AUDIO, mime_group_audio, sizeof(mime_group_audio)-1}, + {MIMETYPE_APPLICATION, mime_group_application, sizeof(mime_group_application)-1}, + {MIMETYPE_IMAGE, mime_group_image, sizeof(mime_group_image)-1}, + {MIMETYPE_VIDEO, mime_group_video, sizeof(mime_group_video)-1}, + {MIMETYPE_LAST, NULL, 0} // Must be last entry +}; + +// List of all mimetypes that should be converted. +static struct MimeTypeList mimeTypeList[] = { + // Mp3 mime types + {MIMETYPE_AUDIO, "mp3", sizeof("mp3")-1, mime_type_audio_mpeg}, + {MIMETYPE_AUDIO, "x-mpeg", sizeof("x-mpeg")-1, mime_type_audio_mpeg}, + {MIMETYPE_AUDIO, "x-mp3", sizeof("x-mp3")-1, mime_type_audio_mpeg}, + {MIMETYPE_AUDIO, "mpg", sizeof("mpg")-1, mime_type_audio_mpeg}, + {MIMETYPE_AUDIO, "mpg3", sizeof("mpg")-1, mime_type_audio_mpeg}, + {MIMETYPE_AUDIO, "x-mpg", sizeof("x-mpg")-1, mime_type_audio_mpeg}, + {MIMETYPE_AUDIO, "x-mpegaudio", sizeof("x-mpegaudio")-1, mime_type_audio_mpeg}, + + // 3gpp audio mime types + {MIMETYPE_AUDIO, "3gp", sizeof("3gp")-1, mime_type_audio_3gpp}, + + // Amr audio mime types + {MIMETYPE_AUDIO, "amr", sizeof("amr")-1, mime_type_audio_amr}, + + // Aac audio mime types + {MIMETYPE_AUDIO, "aac", sizeof("aac")-1, mime_type_audio_aac}, + + // Wav audio mime types + {MIMETYPE_AUDIO, "x-wav", sizeof("x-wav")-1, mime_type_audio_wav}, + + // Mpeg4 video mime types + {MIMETYPE_VIDEO, "mpg4", sizeof("mpg4")-1, mime_type_video_mpeg4}, + {MIMETYPE_VIDEO, "mp4v-es", sizeof("mp4v-es")-1, mime_type_video_mpeg4}, + + // 3gpp video mime types + {MIMETYPE_VIDEO, "3gp", sizeof("3gp")-1, mime_type_video_3gpp}, + + // Must be last entry + {MIMETYPE_LAST, NULL, 0, NULL} +}; + +/** + * May convert the mimetype if there is a well known + * replacement mimetype otherwise the original mimetype + * is returned. + * + * @param mimeType - mimetype in lower case to convert. + * + * @return mimetype or null. + */ +String8 MimeTypeUtil::convertMimeType(String8& mimeType) { + String8 result = mimeType; + const char* pTmp; + const char* pMimeType; + struct MimeGroup* pGroup; + struct MimeTypeList* pMimeItem; + int len; + + pMimeType = mimeType.string(); + if (NULL != pMimeType) { + /* Check which group the mimetype is */ + pGroup = mimeGroup; + + while (MIMETYPE_LAST != pGroup->type) { + if (0 == strncmp(pMimeType, pGroup->pGroup, pGroup->size)) { + break; + } + pGroup++; + } + + /* Go through the mimetype list. Only check items of the correct group */ + if (MIMETYPE_LAST != pGroup->type) { + pMimeItem = mimeTypeList; + len = strlen (pMimeType+pGroup->size); + + while (MIMETYPE_LAST != pMimeItem->type) { + if ((len == pMimeItem->size) && + (0 == strcmp(pMimeType+pGroup->size, pMimeItem->pMimeExt))) { + result = String8(pMimeItem->pMimeType); + break; + } + pMimeItem++; + } + } + LOGI("convertMimeType got mimetype %s, converted into mimetype %s", + pMimeType, result.string()); + } + + return result; +} +}; diff --git a/drm/libdrmframework/plugins/forward-lock/Android.mk b/drm/libdrmframework/plugins/forward-lock/Android.mk new file mode 100644 index 0000000..9ee7961 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/Android.mk @@ -0,0 +1,16 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +include $(call all-subdir-makefiles) diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.mk b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.mk new file mode 100644 index 0000000..d4a6f18 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.mk @@ -0,0 +1,67 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +base := frameworks/base + +# Determine whether the DRM framework uses 64-bit data types for file offsets and do the same. +ifneq ($(shell grep -c 'off64_t offset' $(base)/drm/libdrmframework/plugins/common/include/IDrmEngine.h), 0) +LOCAL_CFLAGS += -DUSE_64BIT_DRM_API +endif + +LOCAL_SRC_FILES:= \ + src/FwdLockEngine.cpp + +LOCAL_MODULE := libfwdlockengine + +LOCAL_SHARED_LIBRARIES := \ + libicui18n \ + libicuuc \ + libutils \ + libdl \ + libandroid_runtime \ + libnativehelper \ + libcrypto \ + libssl \ + libdrmframework + +LOCAL_STATIC_LIBRARIES := \ + libdrmutility \ + libdrmframeworkcommon \ + libfwdlock-common \ + libfwdlock-converter \ + libfwdlock-decoder + +LOCAL_PRELINK_MODULE := false + +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) \ + $(base)/include/drm \ + $(base)/drm/libdrmframework/plugins/common/include \ + $(base)/drm/libdrmframework/plugins/common/util/include \ + $(base)/drm/libdrmframework/plugins/forward-lock/internal-format/common \ + $(base)/drm/libdrmframework/plugins/forward-lock/internal-format/converter \ + $(base)/drm/libdrmframework/plugins/forward-lock/internal-format/decoder \ + $(LOCAL_PATH)/include \ + external/openssl/include + +LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/drm/plugins/native + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SHARED_LIBRARY) diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngine.h b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngine.h new file mode 100644 index 0000000..34804cf --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngine.h @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FWDLOCKENGINE_H__ +#define __FWDLOCKENGINE_H__ + +#include <DrmEngineBase.h> +#include <DrmConstraints.h> +#include <DrmRights.h> +#include <DrmInfo.h> +#include <DrmInfoStatus.h> +#include <DrmConvertedStatus.h> +#include <DrmInfoRequest.h> +#include <DrmSupportInfo.h> +#include <DrmInfoEvent.h> + +#include "SessionMap.h" +#include "FwdLockConv.h" + +namespace android { + +/** + * Forward Lock Engine class. + */ +class FwdLockEngine : public android::DrmEngineBase { + +public: + FwdLockEngine(); + virtual ~FwdLockEngine(); + +protected: +/** + * Get constraint information associated with input content. + * + * @param uniqueId Unique identifier for a session + * @param path Path of the protected content + * @param action Actions defined such as, + * Action::DEFAULT, Action::PLAY, etc + * @return DrmConstraints + * key-value pairs of constraint are embedded in it + * @note + * In case of error, return NULL + */ +DrmConstraints* onGetConstraints(int uniqueId, const String8* path, int action); + +/** + * Get metadata information associated with input content. + * + * @param uniqueId Unique identifier for a session + * @param path Path of the protected content + * @return DrmMetadata + * For Forward Lock engine, it returns an empty object + * @note + * In case of error, returns NULL + */ +DrmMetadata* onGetMetadata(int uniqueId, const String8* path); + +/** + * Initialize plug-in. + * + * @param uniqueId Unique identifier for a session + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onInitialize(int uniqueId); + +/** + * Register a callback to be invoked when the caller required to + * receive necessary information. + * + * @param uniqueId Unique identifier for a session + * @param infoListener Listener + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onSetOnInfoListener(int uniqueId, const IDrmEngine::OnInfoListener* infoListener); + +/** + * Terminate the plug-in and release resources bound to it. + * + * @param uniqueId Unique identifier for a session + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onTerminate(int uniqueId); + +/** + * Get whether the given content can be handled by this plugin or not. + * + * @param uniqueId Unique identifier for a session + * @param path Path to the protected object + * @return bool + * Returns true if this plugin can handle , false in case of not able to handle + */ +bool onCanHandle(int uniqueId, const String8& path); + +/** + * Processes the given DRM information as appropriate for its type. + * Not used for Forward Lock Engine. + * + * @param uniqueId Unique identifier for a session + * @param drmInfo Information that needs to be processed + * @return DrmInfoStatus + * instance as a result of processing given input + */ +DrmInfoStatus* onProcessDrmInfo(int uniqueId, const DrmInfo* drmInfo); + +/** + * Save DRM rights to specified rights path + * and make association with content path. + * + * @param uniqueId Unique identifier for a session + * @param drmRights DrmRights to be saved + * @param rightsPath File path where rights to be saved + * @param contentPath File path where content was saved + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onSaveRights(int uniqueId, + const DrmRights& drmRights, + const String8& rightsPath, + const String8& contentPath); + +/** + * Retrieves necessary information for registration, unregistration or rights + * acquisition information. + * + * @param uniqueId Unique identifier for a session + * @param drmInfoRequest Request information to retrieve drmInfo + * @return DrmInfo + * instance as a result of processing given input + */ +DrmInfo* onAcquireDrmInfo(int uniqueId, + const DrmInfoRequest* drmInfoRequest); + +/** + * Retrieves the mime type embedded inside the original content. + * + * @param uniqueId Unique identifier for a session + * @param path Path of the protected content + * @return String8 + * Returns mime-type of the original content, such as "video/mpeg" + */ +String8 onGetOriginalMimeType(int uniqueId, const String8& path); + +/** + * Retrieves the type of the protected object (content, rights, etc..) + * using specified path or mimetype. At least one parameter should be non null + * to retrieve DRM object type. + * + * @param uniqueId Unique identifier for a session + * @param path Path of the content or null. + * @param mimeType Mime type of the content or null. + * @return type of the DRM content, + * such as DrmObjectType::CONTENT, DrmObjectType::RIGHTS_OBJECT + */ +int onGetDrmObjectType(int uniqueId, + const String8& path, + const String8& mimeType); + +/** + * Check whether the given content has valid rights or not. + * + * @param uniqueId Unique identifier for a session + * @param path Path of the protected content + * @param action Action to perform (Action::DEFAULT, Action::PLAY, etc) + * @return the status of the rights for the protected content, + * such as RightsStatus::RIGHTS_VALID, RightsStatus::RIGHTS_EXPIRED, etc. + */ +int onCheckRightsStatus(int uniqueId, + const String8& path, + int action); + +/** + * Consumes the rights for a content. + * If the reserve parameter is true the rights are reserved until the same + * application calls this api again with the reserve parameter set to false. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @param action Action to perform. (Action::DEFAULT, Action::PLAY, etc) + * @param reserve True if the rights should be reserved. + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onConsumeRights(int uniqueId, + DecryptHandle* decryptHandle, + int action, + bool reserve); + +/** + * Informs the DRM Engine about the playback actions performed on the DRM files. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @param playbackStatus Playback action (Playback::START, Playback::STOP, Playback::PAUSE) + * @param position Position in the file (in milliseconds) where the start occurs. + * Only valid together with Playback::START. + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +#ifdef USE_64BIT_DRM_API +status_t onSetPlaybackStatus(int uniqueId, + DecryptHandle* decryptHandle, + int playbackStatus, + int64_t position); +#else +status_t onSetPlaybackStatus(int uniqueId, + DecryptHandle* decryptHandle, + int playbackStatus, + int position); +#endif + +/** + * Validates whether an action on the DRM content is allowed or not. + * + * @param uniqueId Unique identifier for a session + * @param path Path of the protected content + * @param action Action to validate (Action::PLAY, Action::TRANSFER, etc) + * @param description Detailed description of the action + * @return true if the action is allowed. + */ +bool onValidateAction(int uniqueId, + const String8& path, + int action, + const ActionDescription& description); + +/** + * Removes the rights associated with the given protected content. + * Not used for Forward Lock Engine. + * + * @param uniqueId Unique identifier for a session + * @param path Path of the protected content + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onRemoveRights(int uniqueId, const String8& path); + +/** + * Removes all the rights information of each plug-in associated with + * DRM framework. Will be used in master reset but does nothing for + * Forward Lock Engine. + * + * @param uniqueId Unique identifier for a session + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onRemoveAllRights(int uniqueId); + +/** + * Starts the Forward Lock file conversion session. + * Each time the application tries to download a new DRM file + * which needs to be converted, then the application has to + * begin with calling this API. The convertId is used as the conversion session key + * and must not be the same for different convert sessions. + * + * @param uniqueId Unique identifier for a session + * @param convertId Handle for the convert session + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onOpenConvertSession(int uniqueId, int convertId); + +/** + * Accepts and converts the input data which is part of DRM file. + * The resultant converted data and the status is returned in the DrmConvertedInfo + * object. This method will be called each time there is a new block + * of data received by the application. + * + * @param uniqueId Unique identifier for a session + * @param convertId Handle for the convert session + * @param inputData Input Data which need to be converted + * @return Return object contains the status of the data conversion, + * the output converted data and offset. In this case the + * application will ignore the offset information. + */ +DrmConvertedStatus* onConvertData(int uniqueId, + int convertId, + const DrmBuffer* inputData); + +/** + * Closes the convert session in case of data supply completed or error occurred. + * Upon successful conversion of the complete data, it returns signature calculated over + * the entire data used over a conversion session. This signature must be copied to the offset + * mentioned in the DrmConvertedStatus. Signature is used for data integrity protection. + * + * @param uniqueId Unique identifier for a session + * @param convertId Handle for the convert session + * @return Return object contains the status of the data conversion, + * the header and body signature data. It also informs + * the application about the file offset at which this + * signature data should be written. + */ +DrmConvertedStatus* onCloseConvertSession(int uniqueId, int convertId); + +/** + * Returns the information about the Drm Engine capabilities which includes + * supported MimeTypes and file suffixes. + * + * @param uniqueId Unique identifier for a session + * @return DrmSupportInfo + * instance which holds the capabilities of a plug-in + */ +DrmSupportInfo* onGetSupportInfo(int uniqueId); + +/** + * Open the decrypt session to decrypt the given protected content. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the current decryption session + * @param fd File descriptor of the protected content to be decrypted + * @param offset Start position of the content + * @param length The length of the protected content + * @return + * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success + */ +#ifdef USE_64BIT_DRM_API +status_t onOpenDecryptSession(int uniqueId, + DecryptHandle* decryptHandle, + int fd, off64_t offset, off64_t length); +#else +status_t onOpenDecryptSession(int uniqueId, + DecryptHandle* decryptHandle, + int fd, int offset, int length); +#endif + +/** + * Open the decrypt session to decrypt the given protected content. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the current decryption session + * @param uri Path of the protected content to be decrypted + * @return + * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success + */ +status_t onOpenDecryptSession(int uniqueId, + DecryptHandle* decryptHandle, + const char* uri); + +/** + * Close the decrypt session for the given handle. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @return status_t + * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure + */ +status_t onCloseDecryptSession(int uniqueId, + DecryptHandle* decryptHandle); + +/** + * Initialize decryption for the given unit of the protected content. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @param decryptUnitId ID which specifies decryption unit, such as track ID + * @param headerInfo Information for initializing decryption of this decrypUnit + * @return + * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success + */ +status_t onInitializeDecryptUnit(int uniqueId, + DecryptHandle* decryptHandle, + int decryptUnitId, + const DrmBuffer* headerInfo); + +/** + * Decrypt the protected content buffers for the given unit. + * This method will be called any number of times, based on number of + * encrypted streams received from application. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @param decryptUnitId ID which specifies decryption unit, such as track ID + * @param encBuffer Encrypted data block + * @param decBuffer Decrypted data block + * @return status_t + * Returns the error code for this API + * DRM_NO_ERROR for success, and one of DRM_ERROR_UNKNOWN, DRM_ERROR_LICENSE_EXPIRED + * DRM_ERROR_SESSION_NOT_OPENED, DRM_ERROR_DECRYPT_UNIT_NOT_INITIALIZED, + * DRM_ERROR_DECRYPT for failure. + */ +status_t onDecrypt(int uniqueId, + DecryptHandle* decryptHandle, + int decryptUnitId, + const DrmBuffer* encBuffer, + DrmBuffer** decBuffer); + +/** + * Decrypt the protected content buffers for the given unit. + * This method will be called any number of times, based on number of + * encrypted streams received from application. + * + * @param uniqueId Unique identifier for a session + * @param decryptId Handle for the decryption session + * @param decryptUnitId ID Specifies decryption unit, such as track ID + * @param encBuffer Encrypted data block + * @param decBuffer Decrypted data block + * @param IV Optional buffer + * @return status_t + * Returns the error code for this API + * DRM_NO_ERROR for success, and one of DRM_ERROR_UNKNOWN, DRM_ERROR_LICENSE_EXPIRED + * DRM_ERROR_SESSION_NOT_OPENED, DRM_ERROR_DECRYPT_UNIT_NOT_INITIALIZED, + * DRM_ERROR_DECRYPT for failure. + */ +status_t onDecrypt(int uniqueId, DecryptHandle* decryptHandle, + int decryptUnitId, const DrmBuffer* encBuffer, + DrmBuffer** decBuffer, DrmBuffer* IV); + +/** + * Finalize decryption for the given unit of the protected content. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @param decryptUnitId ID Specifies decryption unit, such as track ID + * @return + * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success + */ +status_t onFinalizeDecryptUnit(int uniqueId, + DecryptHandle* decryptHandle, + int decryptUnitId); + +/** + * Reads the specified number of bytes from an open DRM file. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @param buffer Reference to the buffer that should receive the read data. + * @param numBytes Number of bytes to read. + * + * @return Number of bytes read. + * @retval -1 Failure. + */ +ssize_t onRead(int uniqueId, + DecryptHandle* decryptHandle, + void* pBuffer, + int numBytes); + +/** + * Updates the file position within an open DRM file. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @param offset Offset with which to update the file position. + * @param whence One of SEEK_SET, SEEK_CUR, and SEEK_END. + * These constants are defined in unistd.h. + * + * @return New file position. + * @retval ((off_t)-1) Failure. + */ +#ifdef USE_64BIT_DRM_API +off64_t onLseek(int uniqueId, + DecryptHandle* decryptHandle, + off64_t offset, + int whence); +#else +off_t onLseek(int uniqueId, + DecryptHandle* decryptHandle, + off_t offset, + int whence); +#endif + +/** + * Reads the specified number of bytes from an open DRM file. + * + * @param uniqueId Unique identifier for a session + * @param decryptHandle Handle for the decryption session + * @param buffer Reference to the buffer that should receive the read data. + * @param numBytes Number of bytes to read. + * @param offset Offset with which to update the file position. + * + * @return Number of bytes read. Returns -1 for Failure. + */ +#ifdef USE_64BIT_DRM_API +ssize_t onPread(int uniqueId, + DecryptHandle* decryptHandle, + void* buffer, + ssize_t numBytes, + off64_t offset); +#else +ssize_t onPread(int uniqueId, + DecryptHandle* decryptHandle, + void* buffer, + ssize_t numBytes, + off_t offset); +#endif + +private: + +/** + * Session Class for Forward Lock Conversion. An object of this class is created + * for every conversion. + */ +class ConvertSession { + public : + int uniqueId; + FwdLockConv_Output_t output; + + ConvertSession() { + uniqueId = 0; + memset(&output, 0, sizeof(FwdLockConv_Output_t)); + } + + virtual ~ConvertSession() {} +}; + +/** + * Session Class for Forward Lock decoder. An object of this class is created + * for every decoding session. + */ +class DecodeSession { + public : + int fileDesc; + off_t offset; + + DecodeSession() { + fileDesc = -1; + offset = 0; + } + + DecodeSession(int fd) { + fileDesc = fd; + offset = 0; + } + + virtual ~DecodeSession() {} +}; + +/** + * Session Map Tables for Conversion and Decoding of forward lock files. + */ +SessionMap<ConvertSession*> convertSessionMap; +SessionMap<DecodeSession*> decodeSessionMap; + +/** + * Converts the error code from Forward Lock Converter to DrmConvertStatus error code. + * + * @param Forward Lock Converter error code + * + * @return Status code from DrmConvertStatus. + */ +static int getConvertedStatus(FwdLockConv_Status_t status); +}; + +}; + +#endif /* __FWDLOCKENGINE_H__ */ diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngineConst.h b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngineConst.h new file mode 100644 index 0000000..da95d60 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngineConst.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FWDLOCKENGINECONST_H__ +#define __FWDLOCKENGINECONST_H__ + +namespace android { + +/** + * Constants for forward Lock Engine used for exposing engine's capabilities. + */ +#define FWDLOCK_EXTENSION_FL ("FL") +#define FWDLOCK_DOTEXTENSION_FL (".fl") +#define FWDLOCK_MIMETYPE_FL ("application/x-android-drm-fl") + +#define FWDLOCK_EXTENSION_DM ("DM") +#define FWDLOCK_DOTEXTENSION_DM (".dm") +#define FWDLOCK_MIMETYPE_DM ("application/vnd.oma.drm.message") + +#define FWDLOCK_DESCRIPTION ("OMA V1 Forward Lock") + +}; + +#endif /* __FWDLOCKENGINECONST_H__ */ diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp new file mode 100644 index 0000000..d430f72 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp @@ -0,0 +1,628 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SessionMap.h" +#include "FwdLockEngine.h" +#include <utils/Log.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include "drm_framework_common.h" +#include <fcntl.h> +#include <limits.h> +#include <DrmRights.h> +#include <DrmConstraints.h> +#include <DrmMetadata.h> +#include <DrmInfo.h> +#include <DrmInfoStatus.h> +#include <DrmInfoRequest.h> +#include <DrmSupportInfo.h> +#include <DrmConvertedStatus.h> +#include <utils/String8.h> +#include "FwdLockConv.h" +#include "FwdLockFile.h" +#include "FwdLockGlue.h" +#include "FwdLockEngineConst.h" +#include "MimeTypeUtil.h" + +#undef LOG_TAG +#define LOG_TAG "FwdLockEngine" + +using namespace android; +// This extern "C" is mandatory to be managed by TPlugInManager +extern "C" IDrmEngine* create() { + return new FwdLockEngine(); +} + +// This extern "C" is mandatory to be managed by TPlugInManager +extern "C" void destroy(IDrmEngine* plugIn) { + delete plugIn; +} + +FwdLockEngine::FwdLockEngine() { + LOGD("FwdLockEngine Construction"); +} + +FwdLockEngine::~FwdLockEngine() { + LOGD("FwdLockEngine Destruction"); + + convertSessionMap.destroyMap(); + decodeSessionMap.destroyMap(); +} + +int FwdLockEngine::getConvertedStatus(FwdLockConv_Status_t status) { + int retStatus = DrmConvertedStatus::STATUS_ERROR; + + switch(status) { + case FwdLockConv_Status_OK: + retStatus = DrmConvertedStatus::STATUS_OK; + break; + case FwdLockConv_Status_SyntaxError: + case FwdLockConv_Status_InvalidArgument: + case FwdLockConv_Status_UnsupportedFileFormat: + case FwdLockConv_Status_UnsupportedContentTransferEncoding: + LOGD("FwdLockEngine getConvertedStatus: file conversion Error %d. " \ + "Returning STATUS_INPUTDATA_ERROR", status); + retStatus = DrmConvertedStatus::STATUS_INPUTDATA_ERROR; + break; + default: + LOGD("FwdLockEngine getConvertedStatus: file conversion Error %d. " \ + "Returning STATUS_ERROR", status); + retStatus = DrmConvertedStatus::STATUS_ERROR; + break; + } + + return retStatus; +} + +DrmConstraints* FwdLockEngine::onGetConstraints(int uniqueId, const String8* path, int action) { + DrmConstraints* drmConstraints = NULL; + + LOGD("FwdLockEngine::onGetConstraints"); + + if (NULL != path && + (RightsStatus::RIGHTS_VALID == onCheckRightsStatus(uniqueId, *path, action))) { + // Return the empty constraints to show no error condition. + drmConstraints = new DrmConstraints(); + } + + return drmConstraints; +} + +DrmMetadata* FwdLockEngine::onGetMetadata(int uniqueId, const String8* path) { + DrmMetadata* drmMetadata = NULL; + + LOGD("FwdLockEngine::onGetMetadata"); + + if (NULL != path) { + // Returns empty metadata to show no error condition. + drmMetadata = new DrmMetadata(); + } + + return drmMetadata; +} + +android::status_t FwdLockEngine::onInitialize(int uniqueId) { + LOGD("FwdLockEngine::onInitialize"); + + + if (FwdLockGlue_InitializeKeyEncryption()) { + LOGD("FwdLockEngine::onInitialize -- FwdLockGlue_InitializeKeyEncryption succeeded"); + } else { + LOGD("FwdLockEngine::onInitialize -- FwdLockGlue_InitializeKeyEncryption failed:" + "errno = %d", errno); + } + + return DRM_NO_ERROR; +} + +android::status_t +FwdLockEngine::onSetOnInfoListener(int uniqueId, const IDrmEngine::OnInfoListener* infoListener) { + // Not used + LOGD("FwdLockEngine::onSetOnInfoListener"); + + return DRM_NO_ERROR; +} + +android::status_t FwdLockEngine::onTerminate(int uniqueId) { + LOGD("FwdLockEngine::onTerminate"); + + return DRM_NO_ERROR; +} + +DrmSupportInfo* FwdLockEngine::onGetSupportInfo(int uniqueId) { + DrmSupportInfo* pSupportInfo = new DrmSupportInfo(); + + LOGD("FwdLockEngine::onGetSupportInfo"); + + // fill all Forward Lock mimetypes and extensions + if (NULL != pSupportInfo) { + pSupportInfo->addMimeType(String8(FWDLOCK_MIMETYPE_FL)); + pSupportInfo->addFileSuffix(String8(FWDLOCK_DOTEXTENSION_FL)); + pSupportInfo->addMimeType(String8(FWDLOCK_MIMETYPE_DM)); + pSupportInfo->addFileSuffix(String8(FWDLOCK_DOTEXTENSION_DM)); + + pSupportInfo->setDescription(String8(FWDLOCK_DESCRIPTION)); + } + + return pSupportInfo; +} + +bool FwdLockEngine::onCanHandle(int uniqueId, const String8& path) { + bool result = false; + + String8 extString = path.getPathExtension(); + + extString.toLower(); + + if ((extString == String8(FWDLOCK_DOTEXTENSION_FL)) || + (extString == String8(FWDLOCK_DOTEXTENSION_DM))) { + result = true; + } + return result; +} + +DrmInfoStatus* FwdLockEngine::onProcessDrmInfo(int uniqueId, const DrmInfo* drmInfo) { + DrmInfoStatus *drmInfoStatus = NULL; + + // Nothing to process + + drmInfoStatus = new DrmInfoStatus((int)DrmInfoStatus::STATUS_OK, 0, NULL, String8("")); + + LOGD("FwdLockEngine::onProcessDrmInfo"); + + return drmInfoStatus; +} + +status_t FwdLockEngine::onSaveRights( + int uniqueId, + const DrmRights& drmRights, + const String8& rightsPath, + const String8& contentPath) { + // No rights to save. Return + LOGD("FwdLockEngine::onSaveRights"); + return DRM_ERROR_UNKNOWN; +} + +DrmInfo* FwdLockEngine::onAcquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest) { + DrmInfo* drmInfo = NULL; + + // Nothing to be done for Forward Lock file + LOGD("FwdLockEngine::onAcquireDrmInfo"); + + return drmInfo; +} + +int FwdLockEngine::onCheckRightsStatus(int uniqueId, + const String8& path, + int action) { + int result = RightsStatus::RIGHTS_INVALID; + + LOGD("FwdLockEngine::onCheckRightsStatus"); + + // Only Transfer action is not allowed for forward Lock files. + if (onCanHandle(uniqueId, path)) { + switch(action) { + case Action::DEFAULT: + case Action::PLAY: + case Action::RINGTONE: + case Action::OUTPUT: + case Action::PREVIEW: + case Action::EXECUTE: + case Action::DISPLAY: + result = RightsStatus::RIGHTS_VALID; + break; + + case Action::TRANSFER: + default: + result = RightsStatus::RIGHTS_INVALID; + break; + } + } + + return result; +} + +status_t FwdLockEngine::onConsumeRights(int uniqueId, + DecryptHandle* decryptHandle, + int action, + bool reserve) { + // No rights consumption + LOGD("FwdLockEngine::onConsumeRights"); + return DRM_NO_ERROR; +} + +bool FwdLockEngine::onValidateAction(int uniqueId, + const String8& path, + int action, + const ActionDescription& description) { + LOGD("FwdLockEngine::onValidateAction"); + + // For the forwardlock engine checkRights and ValidateAction are the same. + return (onCheckRightsStatus(uniqueId, path, action) == RightsStatus::RIGHTS_VALID); +} + +String8 FwdLockEngine::onGetOriginalMimeType(int uniqueId, const String8& path) { + LOGD("FwdLockEngine::onGetOriginalMimeType"); + String8 mimeString = String8(""); + int fileDesc = FwdLockFile_open(path.string()); + + if (-1 < fileDesc) { + const char* pMimeType = FwdLockFile_GetContentType(fileDesc); + + if (NULL != pMimeType) { + String8 contentType = String8(pMimeType); + contentType.toLower(); + mimeString = MimeTypeUtil::convertMimeType(contentType); + } + + FwdLockFile_close(fileDesc); + } + + return mimeString; +} + +int FwdLockEngine::onGetDrmObjectType(int uniqueId, + const String8& path, + const String8& mimeType) { + String8 mimeStr = String8(mimeType); + + LOGD("FwdLockEngine::onGetDrmObjectType"); + + mimeStr.toLower(); + + /* Checks whether + * 1. path and mime type both are not empty strings (meaning unavailable) else content is unknown + * 2. if one of them is empty string and if other is known then its a DRM Content Object. + * 3. if both of them are available, then both may be of known type + * (regardless of the relation between them to make it compatible with other DRM Engines) + */ + if (((0 == path.length()) || onCanHandle(uniqueId, path)) && + ((0 == mimeType.length()) || ((mimeStr == String8(FWDLOCK_MIMETYPE_FL)) || + (mimeStr == String8(FWDLOCK_MIMETYPE_DM)))) && (mimeType != path) ) { + return DrmObjectType::CONTENT; + } + + return DrmObjectType::UNKNOWN; +} + +status_t FwdLockEngine::onRemoveRights(int uniqueId, const String8& path) { + // No Rights to remove + LOGD("FwdLockEngine::onRemoveRights"); + return DRM_NO_ERROR; +} + +status_t FwdLockEngine::onRemoveAllRights(int uniqueId) { + // No rights to remove + LOGD("FwdLockEngine::onRemoveAllRights"); + return DRM_NO_ERROR; +} + +#ifdef USE_64BIT_DRM_API +status_t FwdLockEngine::onSetPlaybackStatus(int uniqueId, DecryptHandle* decryptHandle, + int playbackStatus, int64_t position) { +#else +status_t FwdLockEngine::onSetPlaybackStatus(int uniqueId, DecryptHandle* decryptHandle, + int playbackStatus, int position) { +#endif + // Not used + LOGD("FwdLockEngine::onSetPlaybackStatus"); + return DRM_NO_ERROR; +} + +status_t FwdLockEngine::onOpenConvertSession(int uniqueId, + int convertId) { + status_t result = DRM_ERROR_UNKNOWN; + LOGD("FwdLockEngine::onOpenConvertSession"); + if (!convertSessionMap.isCreated(convertId)) { + ConvertSession *newSession = new ConvertSession(); + if (FwdLockConv_Status_OK == + FwdLockConv_OpenSession(&(newSession->uniqueId), &(newSession->output))) { + convertSessionMap.addValue(convertId, newSession); + result = DRM_NO_ERROR; + } else { + LOGD("FwdLockEngine::onOpenConvertSession -- FwdLockConv_OpenSession failed."); + delete newSession; + } + } + return result; +} + +DrmConvertedStatus* FwdLockEngine::onConvertData(int uniqueId, + int convertId, + const DrmBuffer* inputData) { + FwdLockConv_Status_t retStatus = FwdLockConv_Status_InvalidArgument; + DrmBuffer *convResult = new DrmBuffer(NULL, 0); + int offset = -1; + + if (NULL != inputData && convertSessionMap.isCreated(convertId)) { + ConvertSession *convSession = convertSessionMap.getValue(convertId); + + if (NULL != convSession) { + retStatus = FwdLockConv_ConvertData(convSession->uniqueId, + inputData->data, + inputData->length, + &(convSession->output)); + + if (FwdLockConv_Status_OK == retStatus) { + // return bytes from conversion if available + if (convSession->output.fromConvertData.numBytes > 0) { + convResult->data = new char[convSession->output.fromConvertData.numBytes]; + + if (NULL != convResult->data) { + convResult->length = convSession->output.fromConvertData.numBytes; + memcpy(convResult->data, + (char *)convSession->output.fromConvertData.pBuffer, + convResult->length); + } + } + } else { + offset = convSession->output.fromConvertData.errorPos; + } + } + } + return new DrmConvertedStatus(getConvertedStatus(retStatus), convResult, offset); +} + +DrmConvertedStatus* FwdLockEngine::onCloseConvertSession(int uniqueId, + int convertId) { + FwdLockConv_Status_t retStatus = FwdLockConv_Status_InvalidArgument; + DrmBuffer *convResult = new DrmBuffer(NULL, 0); + int offset = -1; + + LOGD("FwdLockEngine::onCloseConvertSession"); + + if (convertSessionMap.isCreated(convertId)) { + ConvertSession *convSession = convertSessionMap.getValue(convertId); + + if (NULL != convSession) { + retStatus = FwdLockConv_CloseSession(convSession->uniqueId, &(convSession->output)); + + if (FwdLockConv_Status_OK == retStatus) { + offset = convSession->output.fromCloseSession.fileOffset; + convResult->data = new char[FWD_LOCK_SIGNATURES_SIZE]; + + if (NULL != convResult->data) { + convResult->length = FWD_LOCK_SIGNATURES_SIZE; + memcpy(convResult->data, + (char *)convSession->output.fromCloseSession.signatures, + convResult->length); + } + } + } + convertSessionMap.removeValue(convertId); + } + return new DrmConvertedStatus(getConvertedStatus(retStatus), convResult, offset); +} + +#ifdef USE_64BIT_DRM_API +status_t FwdLockEngine::onOpenDecryptSession(int uniqueId, + DecryptHandle* decryptHandle, + int fd, + off64_t offset, + off64_t length) { +#else +status_t FwdLockEngine::onOpenDecryptSession(int uniqueId, + DecryptHandle* decryptHandle, + int fd, + int offset, + int length) { +#endif + status_t result = DRM_ERROR_CANNOT_HANDLE; + int fileDesc = -1; + + LOGD("FwdLockEngine::onOpenDecryptSession"); + + if ((-1 < fd) && + (NULL != decryptHandle) && + (!decodeSessionMap.isCreated(decryptHandle->decryptId))) { + fileDesc = dup(fd); + } else { + LOGD("FwdLockEngine::onOpenDecryptSession parameter error"); + return result; + } + + if (-1 < fileDesc && + -1 < ::lseek(fileDesc, offset, SEEK_SET) && + -1 < FwdLockFile_attach(fileDesc)) { + // check for file integrity. This must be done to protect the content mangling. + int retVal = FwdLockFile_CheckHeaderIntegrity(fileDesc); + DecodeSession* decodeSession = new DecodeSession(fileDesc); + + if (retVal && NULL != decodeSession) { + decodeSessionMap.addValue(decryptHandle->decryptId, decodeSession); + const char *pmime= FwdLockFile_GetContentType(fileDesc); + String8 contentType = String8(pmime == NULL ? "" : pmime); + contentType.toLower(); + decryptHandle->mimeType = MimeTypeUtil::convertMimeType(contentType); + decryptHandle->decryptApiType = DecryptApiType::CONTAINER_BASED; + decryptHandle->status = RightsStatus::RIGHTS_VALID; + decryptHandle->decryptInfo = NULL; + result = DRM_NO_ERROR; + } else { + LOGD("FwdLockEngine::onOpenDecryptSession Integrity Check failed for the fd"); + FwdLockFile_detach(fileDesc); + ::close(fileDesc); + delete decodeSession; + } + } + + LOGD("FwdLockEngine::onOpenDecryptSession Exit. result = %d", result); + + return result; +} + +status_t FwdLockEngine::onOpenDecryptSession(int uniqueId, + DecryptHandle* decryptHandle, + const char* uri) { + status_t result = DRM_ERROR_CANNOT_HANDLE; + const char fileTag [] = "file://"; + + if (NULL != decryptHandle && NULL != uri && strlen(uri) > sizeof(fileTag)) { + String8 uriTag = String8(uri); + uriTag.toLower(); + + if (0 == strncmp(uriTag.string(), fileTag, sizeof(fileTag) - 1)) { + const char *filePath = strchr(uri + sizeof(fileTag) - 1, '/'); + if (NULL != filePath && onCanHandle(uniqueId, String8(filePath))) { + int fd = open(filePath, O_RDONLY); + + if (-1 < fd) { + // offset is always 0 and length is not used. so any positive size. + result = onOpenDecryptSession(uniqueId, decryptHandle, fd, 0, 1); + + // fd is duplicated already if success. closing the file + close(fd); + } + } + } + } + + return result; +} + +status_t FwdLockEngine::onCloseDecryptSession(int uniqueId, + DecryptHandle* decryptHandle) { + status_t result = DRM_ERROR_UNKNOWN; + LOGD("FwdLockEngine::onCloseDecryptSession"); + + if (NULL != decryptHandle && decodeSessionMap.isCreated(decryptHandle->decryptId)) { + DecodeSession* session = decodeSessionMap.getValue(decryptHandle->decryptId); + if (NULL != session && session->fileDesc > -1) { + FwdLockFile_detach(session->fileDesc); + ::close(session->fileDesc); + decodeSessionMap.removeValue(decryptHandle->decryptId); + result = DRM_NO_ERROR; + } + } + + LOGD("FwdLockEngine::onCloseDecryptSession Exit"); + return result; +} + +status_t FwdLockEngine::onInitializeDecryptUnit(int uniqueId, + DecryptHandle* decryptHandle, + int decryptUnitId, + const DrmBuffer* headerInfo) { + LOGD("FwdLockEngine::onInitializeDecryptUnit"); + return DRM_ERROR_UNKNOWN; +} + +status_t FwdLockEngine::onDecrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) { + LOGD("FwdLockEngine::onDecrypt"); + return DRM_ERROR_UNKNOWN; +} + +status_t FwdLockEngine::onDecrypt(int uniqueId, + DecryptHandle* decryptHandle, + int decryptUnitId, + const DrmBuffer* encBuffer, + DrmBuffer** decBuffer) { + LOGD("FwdLockEngine::onDecrypt"); + return DRM_ERROR_UNKNOWN; +} + +status_t FwdLockEngine::onFinalizeDecryptUnit(int uniqueId, + DecryptHandle* decryptHandle, + int decryptUnitId) { + LOGD("FwdLockEngine::onFinalizeDecryptUnit"); + return DRM_ERROR_UNKNOWN; +} + +ssize_t FwdLockEngine::onRead(int uniqueId, + DecryptHandle* decryptHandle, + void* buffer, + int numBytes) { + ssize_t size = -1; + + if (NULL != decryptHandle && + decodeSessionMap.isCreated(decryptHandle->decryptId) && + NULL != buffer && + numBytes > -1) { + DecodeSession* session = decodeSessionMap.getValue(decryptHandle->decryptId); + if (NULL != session && session->fileDesc > -1) { + size = FwdLockFile_read(session->fileDesc, buffer, numBytes); + + if (0 > size) { + session->offset = ((off_t)-1); + } else { + session->offset += size; + } + } + } + + return size; +} + +#ifdef USE_64BIT_DRM_API +off64_t FwdLockEngine::onLseek(int uniqueId, DecryptHandle* decryptHandle, + off64_t offset, int whence) { +#else +off_t FwdLockEngine::onLseek(int uniqueId, DecryptHandle* decryptHandle, + off_t offset, int whence) { +#endif + off_t offval = -1; + + if (NULL != decryptHandle && decodeSessionMap.isCreated(decryptHandle->decryptId)) { + DecodeSession* session = decodeSessionMap.getValue(decryptHandle->decryptId); + if (NULL != session && session->fileDesc > -1) { + offval = FwdLockFile_lseek(session->fileDesc, offset, whence); + session->offset = offval; + } + } + + return offval; +} + +#ifdef USE_64BIT_DRM_API +ssize_t FwdLockEngine::onPread(int uniqueId, + DecryptHandle* decryptHandle, + void* buffer, + ssize_t numBytes, + off64_t offset) { +#else +ssize_t FwdLockEngine::onPread(int uniqueId, + DecryptHandle* decryptHandle, + void* buffer, + ssize_t numBytes, + off_t offset) { +#endif + ssize_t bytesRead = -1; + + DecodeSession* decoderSession = NULL; + + if ((NULL != decryptHandle) && + (NULL != (decoderSession = decodeSessionMap.getValue(decryptHandle->decryptId))) && + (NULL != buffer) && + (numBytes > -1) && + (offset > -1)) { + if (offset != decoderSession->offset) { + decoderSession->offset = onLseek(uniqueId, decryptHandle, offset, SEEK_SET); + } + + if (((off_t)-1) != decoderSession->offset) { + bytesRead = onRead(uniqueId, decryptHandle, buffer, numBytes); + if (bytesRead < 0) { + LOGD("FwdLockEngine::onPread error reading"); + } + } + } else { + LOGD("FwdLockEngine::onPread decryptId not found"); + } + + return bytesRead; +} diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/Android.mk b/drm/libdrmframework/plugins/forward-lock/internal-format/Android.mk new file mode 100644 index 0000000..9ee7961 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/Android.mk @@ -0,0 +1,16 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +include $(call all-subdir-makefiles) diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/common/Android.mk b/drm/libdrmframework/plugins/forward-lock/internal-format/common/Android.mk new file mode 100644 index 0000000..6c5d3cf --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/common/Android.mk @@ -0,0 +1,32 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + FwdLockGlue.c + +LOCAL_C_INCLUDES := \ + external/openssl/include + +LOCAL_SHARED_LIBRARIES := libcrypto + +LOCAL_MODULE := libfwdlock-common + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_LIBRARY) diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/common/FwdLockGlue.c b/drm/libdrmframework/plugins/forward-lock/internal-format/common/FwdLockGlue.c new file mode 100644 index 0000000..92bda8f --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/common/FwdLockGlue.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <openssl/aes.h> + +#include "FwdLockGlue.h" + +#define TRUE 1 +#define FALSE 0 + +#define KEY_SIZE 16 +#define KEY_SIZE_IN_BITS (KEY_SIZE * 8) + +static int isInitialized = FALSE; + +static const char strKeyFilename[] = "/data/drm/fwdlock/kek.dat"; + +static AES_KEY encryptionRoundKeys; +static AES_KEY decryptionRoundKeys; + +/** + * Creates all directories along the fully qualified path of the given file. + * + * @param[in] path A reference to the fully qualified path of a file. + * @param[in] mode The access mode to use for the directories being created. + * + * @return A Boolean value indicating whether the operation was successful. + */ +static int FwdLockGlue_CreateDirectories(const char *path, mode_t mode) { + int result = TRUE; + size_t partialPathLength = strlen(path); + char *partialPath = malloc(partialPathLength + 1); + if (partialPath == NULL) { + result = FALSE; + } else { + size_t i; + for (i = 0; i < partialPathLength; ++i) { + if (path[i] == '/' && i > 0) { + partialPath[i] = '\0'; + if (mkdir(partialPath, mode) != 0 && errno != EEXIST) { + result = FALSE; + break; + } + } + partialPath[i] = path[i]; + } + free(partialPath); + } + return result; +} + +/** + * Initializes the round keys used for encryption and decryption of session keys. First creates a + * device-unique key-encryption key if none exists yet. + */ +static void FwdLockGlue_InitializeRoundKeys() { + unsigned char keyEncryptionKey[KEY_SIZE]; + int fileDesc = open(strKeyFilename, O_RDONLY); + if (fileDesc >= 0) { + if (read(fileDesc, keyEncryptionKey, KEY_SIZE) == KEY_SIZE) { + isInitialized = TRUE; + } + (void)close(fileDesc); + } else if (errno == ENOENT && + FwdLockGlue_GetRandomNumber(keyEncryptionKey, KEY_SIZE) && + FwdLockGlue_CreateDirectories(strKeyFilename, S_IRWXU)) { + fileDesc = open(strKeyFilename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR); + if (fileDesc >= 0) { + if (write(fileDesc, keyEncryptionKey, KEY_SIZE) == KEY_SIZE) { + isInitialized = TRUE; + } + (void)close(fileDesc); + } + } + if (isInitialized) { + if (AES_set_encrypt_key(keyEncryptionKey, KEY_SIZE_IN_BITS, &encryptionRoundKeys) != 0 || + AES_set_decrypt_key(keyEncryptionKey, KEY_SIZE_IN_BITS, &decryptionRoundKeys) != 0) { + isInitialized = FALSE; + } + } + memset(keyEncryptionKey, 0, KEY_SIZE); // Zero out key data. +} + +/** + * Validates the padding of a decrypted key. + * + * @param[in] pData A reference to the buffer containing the decrypted key and padding. + * @param[in] decryptedKeyLength The length in bytes of the decrypted key. + * + * @return A Boolean value indicating whether the padding was valid. + */ +static int FwdLockGlue_ValidatePadding(const unsigned char *pData, size_t decryptedKeyLength) { + size_t i; + size_t padding = AES_BLOCK_SIZE - (decryptedKeyLength % AES_BLOCK_SIZE); + pData += decryptedKeyLength; + for (i = 0; i < padding; ++i) { + if ((size_t)*pData != padding) { + return FALSE; + } + ++pData; + } + return TRUE; +} + +int FwdLockGlue_GetRandomNumber(void *pBuffer, size_t numBytes) { + // Generate 'cryptographically secure' random bytes by reading them from "/dev/urandom" (the + // non-blocking version of "/dev/random"). + ssize_t numBytesRead = 0; + int fileDesc = open("/dev/urandom", O_RDONLY); + if (fileDesc >= 0) { + numBytesRead = read(fileDesc, pBuffer, numBytes); + (void)close(fileDesc); + } + return numBytesRead >= 0 && (size_t)numBytesRead == numBytes; +} + +int FwdLockGlue_InitializeKeyEncryption() { + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, FwdLockGlue_InitializeRoundKeys); + return isInitialized; +} + +size_t FwdLockGlue_GetEncryptedKeyLength(size_t plaintextKeyLength) { + return ((plaintextKeyLength / AES_BLOCK_SIZE) + 2) * AES_BLOCK_SIZE; +} + +int FwdLockGlue_EncryptKey(const void *pPlaintextKey, + size_t plaintextKeyLength, + void *pEncryptedKey, + size_t encryptedKeyLength) { + int result = FALSE; + assert(encryptedKeyLength == FwdLockGlue_GetEncryptedKeyLength(plaintextKeyLength)); + if (FwdLockGlue_InitializeKeyEncryption()) { + unsigned char initVector[AES_BLOCK_SIZE]; + if (FwdLockGlue_GetRandomNumber(initVector, AES_BLOCK_SIZE)) { + size_t padding = AES_BLOCK_SIZE - (plaintextKeyLength % AES_BLOCK_SIZE); + size_t dataLength = encryptedKeyLength - AES_BLOCK_SIZE; + memcpy(pEncryptedKey, pPlaintextKey, plaintextKeyLength); + memset((unsigned char *)pEncryptedKey + plaintextKeyLength, (int)padding, padding); + memcpy((unsigned char *)pEncryptedKey + dataLength, initVector, AES_BLOCK_SIZE); + AES_cbc_encrypt(pEncryptedKey, pEncryptedKey, dataLength, &encryptionRoundKeys, + initVector, AES_ENCRYPT); + result = TRUE; + } + } + return result; +} + +int FwdLockGlue_DecryptKey(const void *pEncryptedKey, + size_t encryptedKeyLength, + void *pDecryptedKey, + size_t decryptedKeyLength) { + int result = FALSE; + assert(encryptedKeyLength == FwdLockGlue_GetEncryptedKeyLength(decryptedKeyLength)); + if (FwdLockGlue_InitializeKeyEncryption()) { + size_t dataLength = encryptedKeyLength - AES_BLOCK_SIZE; + unsigned char *pData = malloc(dataLength); + if (pData != NULL) { + unsigned char initVector[AES_BLOCK_SIZE]; + memcpy(pData, pEncryptedKey, dataLength); + memcpy(initVector, (const unsigned char *)pEncryptedKey + dataLength, AES_BLOCK_SIZE); + AES_cbc_encrypt(pData, pData, dataLength, &decryptionRoundKeys, initVector, + AES_DECRYPT); + memcpy(pDecryptedKey, pData, decryptedKeyLength); + result = FwdLockGlue_ValidatePadding(pData, decryptedKeyLength); + free(pData); + } + } + return result; +} diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/common/FwdLockGlue.h b/drm/libdrmframework/plugins/forward-lock/internal-format/common/FwdLockGlue.h new file mode 100644 index 0000000..f36f6ea --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/common/FwdLockGlue.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FWDLOCKGLUE_H__ +#define __FWDLOCKGLUE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Generates the specified number of cryptographically secure random bytes. + * + * @param[out] pBuffer A reference to the buffer that should receive the random data. + * @param[in] numBytes The number of random bytes to generate. + * + * @return A Boolean value indicating whether the operation was successful. + */ +int FwdLockGlue_GetRandomNumber(void *pBuffer, size_t numBytes); + +/** + * Performs initialization of the key-encryption key. Should be called once during startup to + * facilitate encryption and decryption of session keys. + * + * @return A Boolean value indicating whether the operation was successful. + */ +int FwdLockGlue_InitializeKeyEncryption(); + +/** + * Returns the length of the encrypted key, given the length of the plaintext key. + * + * @param[in] plaintextKeyLength The length in bytes of the plaintext key. + * + * @return The length in bytes of the encrypted key. + */ +size_t FwdLockGlue_GetEncryptedKeyLength(size_t plaintextKeyLength); + +/** + * Encrypts the given session key using a key-encryption key unique to this device. + * + * @param[in] pPlaintextKey A reference to the buffer containing the plaintext key. + * @param[in] plaintextKeyLength The length in bytes of the plaintext key. + * @param[out] pEncryptedKey A reference to the buffer containing the encrypted key. + * @param[in] encryptedKeyLength The length in bytes of the encrypted key. + * + * @return A Boolean value indicating whether the operation was successful. + */ +int FwdLockGlue_EncryptKey(const void *pPlaintextKey, + size_t plaintextKeyLength, + void *pEncryptedKey, + size_t encryptedKeyLength); + +/** + * Decrypts the given session key using a key-encryption key unique to this device. + * + * @param[in] pEncryptedKey A reference to the buffer containing the encrypted key. + * @param[in] encryptedKeyLength The length in bytes of the encrypted key. + * @param[out] pDecryptedKey A reference to the buffer containing the decrypted key. + * @param[in] decryptedKeyLength The length in bytes of the decrypted key. + * + * @return A Boolean value indicating whether the operation was successful. + */ +int FwdLockGlue_DecryptKey(const void *pEncryptedKey, + size_t encryptedKeyLength, + void *pDecryptedKey, + size_t decryptedKeyLength); + +#ifdef __cplusplus +} +#endif + +#endif // __FWDLOCKGLUE_H__ diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/converter/Android.mk b/drm/libdrmframework/plugins/forward-lock/internal-format/converter/Android.mk new file mode 100644 index 0000000..00bb788 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/converter/Android.mk @@ -0,0 +1,37 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + FwdLockConv.c + +LOCAL_C_INCLUDES := \ + frameworks/base/drm/libdrmframework/plugins/forward-lock/internal-format/common \ + external/openssl/include + +LOCAL_SHARED_LIBRARIES := libcrypto + +LOCAL_WHOLE_STATIC_LIBRARIES := libfwdlock-common + +LOCAL_STATIC_LIBRARIES := libfwdlock-common + +LOCAL_MODULE := libfwdlock-converter + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_LIBRARY) diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/converter/FwdLockConv.c b/drm/libdrmframework/plugins/forward-lock/internal-format/converter/FwdLockConv.c new file mode 100644 index 0000000..14ea9e9 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/converter/FwdLockConv.c @@ -0,0 +1,1339 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <assert.h> +#include <ctype.h> +#include <fcntl.h> +#include <limits.h> +#include <pthread.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <openssl/aes.h> +#include <openssl/hmac.h> + +#include "FwdLockConv.h" +#include "FwdLockGlue.h" + +#define TRUE 1 +#define FALSE 0 + +#define INVALID_OFFSET ((off64_t)-1) + +#define MAX_NUM_SESSIONS 32 + +#define OUTPUT_BUFFER_SIZE_INCREMENT 1024 +#define READ_BUFFER_SIZE 1024 + +#define MAX_BOUNDARY_LENGTH 70 +#define MAX_DELIMITER_LENGTH (MAX_BOUNDARY_LENGTH + 4) + +#define STRING_LENGTH_INCREMENT 25 + +#define KEY_SIZE AES_BLOCK_SIZE +#define KEY_SIZE_IN_BITS (KEY_SIZE * 8) + +#define SHA1_HASH_SIZE 20 + +#define FWD_LOCK_VERSION 0 +#define FWD_LOCK_SUBFORMAT 0 +#define USAGE_RESTRICTION_FLAGS 0 +#define CONTENT_TYPE_LENGTH_POS 7 +#define TOP_HEADER_SIZE 8 + +/** + * Data type for the parser states of the converter. + */ +typedef enum FwdLockConv_ParserState { + FwdLockConv_ParserState_WantsOpenDelimiter, + FwdLockConv_ParserState_WantsMimeHeaders, + FwdLockConv_ParserState_WantsBinaryEncodedData, + FwdLockConv_ParserState_WantsBase64EncodedData, + FwdLockConv_ParserState_Done +} FwdLockConv_ParserState_t; + +/** + * Data type for the scanner states of the converter. + */ +typedef enum FwdLockConv_ScannerState { + FwdLockConv_ScannerState_WantsFirstDash, + FwdLockConv_ScannerState_WantsSecondDash, + FwdLockConv_ScannerState_WantsCR, + FwdLockConv_ScannerState_WantsLF, + FwdLockConv_ScannerState_WantsBoundary, + FwdLockConv_ScannerState_WantsBoundaryEnd, + FwdLockConv_ScannerState_WantsMimeHeaderNameStart, + FwdLockConv_ScannerState_WantsMimeHeaderName, + FwdLockConv_ScannerState_WantsMimeHeaderNameEnd, + FwdLockConv_ScannerState_WantsContentTypeStart, + FwdLockConv_ScannerState_WantsContentType, + FwdLockConv_ScannerState_WantsContentTransferEncodingStart, + FwdLockConv_ScannerState_Wants_A_OR_I, + FwdLockConv_ScannerState_Wants_N, + FwdLockConv_ScannerState_Wants_A, + FwdLockConv_ScannerState_Wants_R, + FwdLockConv_ScannerState_Wants_Y, + FwdLockConv_ScannerState_Wants_S, + FwdLockConv_ScannerState_Wants_E, + FwdLockConv_ScannerState_Wants_6, + FwdLockConv_ScannerState_Wants_4, + FwdLockConv_ScannerState_Wants_B, + FwdLockConv_ScannerState_Wants_I, + FwdLockConv_ScannerState_Wants_T, + FwdLockConv_ScannerState_WantsContentTransferEncodingEnd, + FwdLockConv_ScannerState_WantsMimeHeaderValueEnd, + FwdLockConv_ScannerState_WantsMimeHeadersEnd, + FwdLockConv_ScannerState_WantsByte1, + FwdLockConv_ScannerState_WantsByte1_AfterCRLF, + FwdLockConv_ScannerState_WantsByte2, + FwdLockConv_ScannerState_WantsByte3, + FwdLockConv_ScannerState_WantsByte4, + FwdLockConv_ScannerState_WantsPadding, + FwdLockConv_ScannerState_WantsWhitespace, + FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF, + FwdLockConv_ScannerState_WantsDelimiter +} FwdLockConv_ScannerState_t; + +/** + * Data type for the content transfer encoding. + */ +typedef enum FwdLockConv_ContentTransferEncoding { + FwdLockConv_ContentTransferEncoding_Undefined, + FwdLockConv_ContentTransferEncoding_Binary, + FwdLockConv_ContentTransferEncoding_Base64 +} FwdLockConv_ContentTransferEncoding_t; + +/** + * Data type for a dynamically growing string. + */ +typedef struct FwdLockConv_String { + char *ptr; + size_t length; + size_t maxLength; + size_t lengthIncrement; +} FwdLockConv_String_t; + +/** + * Data type for the per-file state information needed by the converter. + */ +typedef struct FwdLockConv_Session { + FwdLockConv_ParserState_t parserState; + FwdLockConv_ScannerState_t scannerState; + FwdLockConv_ScannerState_t savedScannerState; + off64_t numCharsConsumed; + char delimiter[MAX_DELIMITER_LENGTH]; + size_t delimiterLength; + size_t delimiterMatchPos; + FwdLockConv_String_t mimeHeaderName; + FwdLockConv_String_t contentType; + FwdLockConv_ContentTransferEncoding_t contentTransferEncoding; + unsigned char sessionKey[KEY_SIZE]; + void *pEncryptedSessionKey; + size_t encryptedSessionKeyLength; + AES_KEY encryptionRoundKeys; + HMAC_CTX signingContext; + unsigned char topHeader[TOP_HEADER_SIZE]; + unsigned char counter[AES_BLOCK_SIZE]; + unsigned char keyStream[AES_BLOCK_SIZE]; + int keyStreamIndex; + unsigned char ch; + size_t outputBufferSize; + size_t dataOffset; + size_t numDataBytes; +} FwdLockConv_Session_t; + +static FwdLockConv_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL }; + +static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER; + +static const FwdLockConv_String_t nullString = { NULL, 0, 0, STRING_LENGTH_INCREMENT }; + +static const unsigned char topHeaderTemplate[] = + { 'F', 'W', 'L', 'K', FWD_LOCK_VERSION, FWD_LOCK_SUBFORMAT, USAGE_RESTRICTION_FLAGS }; + +static const char strContent[] = "content-"; +static const char strType[] = "type"; +static const char strTransferEncoding[] = "transfer-encoding"; +static const char strTextPlain[] = "text/plain"; +static const char strApplicationVndOmaDrmRightsXml[] = "application/vnd.oma.drm.rights+xml"; +static const char strApplicationVndOmaDrmContent[] = "application/vnd.oma.drm.content"; + +static const size_t strlenContent = sizeof strContent - 1; +static const size_t strlenTextPlain = sizeof strTextPlain - 1; + +static const signed char base64Values[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +/** + * Acquires an unused converter session. + * + * @return A session ID. + */ +static int FwdLockConv_AcquireSession() { + int sessionId = -1; + int i; + pthread_mutex_lock(&sessionAcquisitionMutex); + for (i = 0; i < MAX_NUM_SESSIONS; ++i) { + if (sessionPtrs[i] == NULL) { + sessionPtrs[i] = malloc(sizeof *sessionPtrs[i]); + if (sessionPtrs[i] != NULL) { + sessionId = i; + } + break; + } + } + pthread_mutex_unlock(&sessionAcquisitionMutex); + return sessionId; +} + +/** + * Checks whether a session ID is in range and currently in use. + * + * @param[in] sessionID A session ID. + * + * @return A Boolean value indicating whether the session ID is in range and currently in use. + */ +static int FwdLockConv_IsValidSession(int sessionId) { + return 0 <= sessionId && sessionId < MAX_NUM_SESSIONS && sessionPtrs[sessionId] != NULL; +} + +/** + * Releases a converter session. + * + * @param[in] sessionID A session ID. + */ +static void FwdLockConv_ReleaseSession(int sessionId) { + pthread_mutex_lock(&sessionAcquisitionMutex); + assert(FwdLockConv_IsValidSession(sessionId)); + memset(sessionPtrs[sessionId], 0, sizeof *sessionPtrs[sessionId]); // Zero out key data. + free(sessionPtrs[sessionId]); + sessionPtrs[sessionId] = NULL; + pthread_mutex_unlock(&sessionAcquisitionMutex); +} + +/** + * Derives cryptographically independent keys for encryption and signing from the session key. + * + * @param[in,out] pSession A reference to a converter session. + * + * @return A status code. + */ +static int FwdLockConv_DeriveKeys(FwdLockConv_Session_t *pSession) { + FwdLockConv_Status_t status; + struct FwdLockConv_DeriveKeys_Data { + AES_KEY sessionRoundKeys; + unsigned char value[KEY_SIZE]; + unsigned char key[KEY_SIZE]; + } *pData = malloc(sizeof *pData); + if (pData == NULL) { + status = FwdLockConv_Status_OutOfMemory; + } else { + if (AES_set_encrypt_key(pSession->sessionKey, KEY_SIZE_IN_BITS, + &pData->sessionRoundKeys) != 0) { + status = FwdLockConv_Status_ProgramError; + } else { + // Encrypt the 16-byte value {0, 0, ..., 0} to produce the encryption key. + memset(pData->value, 0, KEY_SIZE); + AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys); + if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, + &pSession->encryptionRoundKeys) != 0) { + status = FwdLockConv_Status_ProgramError; + } else { + // Encrypt the 16-byte value {1, 0, ..., 0} to produce the signing key. + ++pData->value[0]; + AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys); + HMAC_CTX_init(&pSession->signingContext); + HMAC_Init_ex(&pSession->signingContext, pData->key, KEY_SIZE, EVP_sha1(), NULL); + status = FwdLockConv_Status_OK; + } + } + memset(pData, 0, sizeof pData); // Zero out key data. + free(pData); + } + return status; +} + +/** + * Checks whether a given character is valid in a boundary. Note that the boundary may contain + * leading and internal spaces. + * + * @param[in] ch The character to check. + * + * @return A Boolean value indicating whether the given character is valid in a boundary. + */ +static int FwdLockConv_IsBoundaryChar(int ch) { + return isalnum(ch) || ch == '\'' || + ch == '(' || ch == ')' || ch == '+' || ch == '_' || ch == ',' || ch == '-' || + ch == '.' || ch == '/' || ch == ':' || ch == '=' || ch == '?' || ch == ' '; +} + +/** + * Checks whether a given character should be considered whitespace, using a narrower definition + * than the standard-library isspace() function. + * + * @param[in] ch The character to check. + * + * @return A Boolean value indicating whether the given character should be considered whitespace. + */ +static int FwdLockConv_IsWhitespace(int ch) { + return ch == ' ' || ch == '\t'; +} + +/** + * Removes trailing spaces from the delimiter. + * + * @param[in,out] pSession A reference to a converter session. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_RightTrimDelimiter(FwdLockConv_Session_t *pSession) { + while (pSession->delimiterLength > 4 && + pSession->delimiter[pSession->delimiterLength - 1] == ' ') { + --pSession->delimiterLength; + } + if (pSession->delimiterLength > 4) { + return FwdLockConv_Status_OK; + } + return FwdLockConv_Status_SyntaxError; +} + +/** + * Matches the open delimiter. + * + * @param[in,out] pSession A reference to a converter session. + * @param[in] ch A character. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_MatchOpenDelimiter(FwdLockConv_Session_t *pSession, + int ch) { + FwdLockConv_Status_t status = FwdLockConv_Status_OK; + switch (pSession->scannerState) { + case FwdLockConv_ScannerState_WantsFirstDash: + if (ch == '-') { + pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash; + } else if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else { + pSession->scannerState = FwdLockConv_ScannerState_WantsCR; + } + break; + case FwdLockConv_ScannerState_WantsSecondDash: + if (ch == '-') { + // The delimiter starts with "\r\n--" (the open delimiter may omit the initial "\r\n"). + // The rest is the user-defined boundary that should come next. + pSession->delimiter[0] = '\r'; + pSession->delimiter[1] = '\n'; + pSession->delimiter[2] = '-'; + pSession->delimiter[3] = '-'; + pSession->delimiterLength = 4; + pSession->scannerState = FwdLockConv_ScannerState_WantsBoundary; + } else if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else { + pSession->scannerState = FwdLockConv_ScannerState_WantsCR; + } + break; + case FwdLockConv_ScannerState_WantsCR: + if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } + break; + case FwdLockConv_ScannerState_WantsLF: + if (ch == '\n') { + pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash; + } else if (ch != '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsCR; + } + break; + case FwdLockConv_ScannerState_WantsBoundary: + if (FwdLockConv_IsBoundaryChar(ch)) { + // The boundary may contain leading and internal spaces, so trailing spaces will also be + // matched here. These will be removed later. + if (pSession->delimiterLength < MAX_DELIMITER_LENGTH) { + pSession->delimiter[pSession->delimiterLength++] = ch; + } else if (ch != ' ') { + status = FwdLockConv_Status_SyntaxError; + } + } else if (ch == '\r') { + status = FwdLockConv_RightTrimDelimiter(pSession); + if (status == FwdLockConv_Status_OK) { + pSession->scannerState = FwdLockConv_ScannerState_WantsBoundaryEnd; + } + } else if (ch == '\t') { + status = FwdLockConv_RightTrimDelimiter(pSession); + if (status == FwdLockConv_Status_OK) { + pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace; + } + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsWhitespace: + if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsBoundaryEnd; + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsBoundaryEnd: + if (ch == '\n') { + pSession->parserState = FwdLockConv_ParserState_WantsMimeHeaders; + pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameStart; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + default: + status = FwdLockConv_Status_ProgramError; + break; + } + return status; +} + +/** + * Checks whether a given character is valid in a MIME header name. + * + * @param[in] ch The character to check. + * + * @return A Boolean value indicating whether the given character is valid in a MIME header name. + */ +static int FwdLockConv_IsMimeHeaderNameChar(int ch) { + return isgraph(ch) && ch != ':'; +} + +/** + * Checks whether a given character is valid in a MIME header value. + * + * @param[in] ch The character to check. + * + * @return A Boolean value indicating whether the given character is valid in a MIME header value. + */ +static int FwdLockConv_IsMimeHeaderValueChar(int ch) { + return isgraph(ch) && ch != ';'; +} + +/** + * Appends a character to the specified dynamically growing string. + * + * @param[in,out] pString A reference to a dynamically growing string. + * @param[in] ch The character to append. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_StringAppend(FwdLockConv_String_t *pString, int ch) { + if (pString->length == pString->maxLength) { + size_t newMaxLength = pString->maxLength + pString->lengthIncrement; + char *newPtr = realloc(pString->ptr, newMaxLength + 1); + if (newPtr == NULL) { + return FwdLockConv_Status_OutOfMemory; + } + pString->ptr = newPtr; + pString->maxLength = newMaxLength; + } + pString->ptr[pString->length++] = ch; + pString->ptr[pString->length] = '\0'; + return FwdLockConv_Status_OK; +} + +/** + * Attempts to recognize the MIME header name and changes the scanner state accordingly. + * + * @param[in,out] pSession A reference to a converter session. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_RecognizeMimeHeaderName(FwdLockConv_Session_t *pSession) { + FwdLockConv_Status_t status = FwdLockConv_Status_OK; + if (strncmp(pSession->mimeHeaderName.ptr, strContent, strlenContent) == 0) { + if (strcmp(pSession->mimeHeaderName.ptr + strlenContent, strType) == 0) { + if (pSession->contentType.ptr == NULL) { + pSession->scannerState = FwdLockConv_ScannerState_WantsContentTypeStart; + } else { + status = FwdLockConv_Status_SyntaxError; + } + } else if (strcmp(pSession->mimeHeaderName.ptr + strlenContent, strTransferEncoding) == 0) { + if (pSession->contentTransferEncoding == + FwdLockConv_ContentTransferEncoding_Undefined) { + pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingStart; + } else { + status = FwdLockConv_Status_SyntaxError; + } + } else { + pSession->scannerState = FwdLockConv_ScannerState_WantsCR; + } + } else { + pSession->scannerState = FwdLockConv_ScannerState_WantsCR; + } + return status; +} + +/** + * Applies defaults to missing MIME header values. + * + * @param[in,out] pSession A reference to a converter session. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_ApplyDefaults(FwdLockConv_Session_t *pSession) { + if (pSession->contentType.ptr == NULL) { + // Content type is missing: default to "text/plain". + pSession->contentType.ptr = malloc(sizeof strTextPlain); + if (pSession->contentType.ptr == NULL) { + return FwdLockConv_Status_OutOfMemory; + } + memcpy(pSession->contentType.ptr, strTextPlain, sizeof strTextPlain); + pSession->contentType.length = strlenTextPlain; + pSession->contentType.maxLength = strlenTextPlain; + } + if (pSession->contentTransferEncoding == FwdLockConv_ContentTransferEncoding_Undefined) { + // Content transfer encoding is missing: default to binary. + pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary; + } + return FwdLockConv_Status_OK; +} + +/** + * Verifies that the content type is supported. + * + * @param[in,out] pSession A reference to a converter session. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_VerifyContentType(FwdLockConv_Session_t *pSession) { + FwdLockConv_Status_t status; + if (pSession->contentType.ptr == NULL) { + status = FwdLockConv_Status_ProgramError; + } else if (strcmp(pSession->contentType.ptr, strApplicationVndOmaDrmRightsXml) == 0 || + strcmp(pSession->contentType.ptr, strApplicationVndOmaDrmContent) == 0) { + status = FwdLockConv_Status_UnsupportedFileFormat; + } else { + status = FwdLockConv_Status_OK; + } + return status; +} + +/** + * Writes the header of the output file. + * + * @param[in,out] pSession A reference to a converter session. + * @param[out] pOutput The output from the conversion process. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_WriteHeader(FwdLockConv_Session_t *pSession, + FwdLockConv_Output_t *pOutput) { + FwdLockConv_Status_t status; + if (pSession->contentType.length > UCHAR_MAX) { + status = FwdLockConv_Status_SyntaxError; + } else { + pSession->outputBufferSize = OUTPUT_BUFFER_SIZE_INCREMENT; + pOutput->fromConvertData.pBuffer = malloc(pSession->outputBufferSize); + if (pOutput->fromConvertData.pBuffer == NULL) { + status = FwdLockConv_Status_OutOfMemory; + } else { + size_t encryptedSessionKeyPos = TOP_HEADER_SIZE + pSession->contentType.length; + size_t dataSignaturePos = encryptedSessionKeyPos + pSession->encryptedSessionKeyLength; + size_t headerSignaturePos = dataSignaturePos + SHA1_HASH_SIZE; + pSession->dataOffset = headerSignaturePos + SHA1_HASH_SIZE; + memcpy(pSession->topHeader, topHeaderTemplate, sizeof topHeaderTemplate); + pSession->topHeader[CONTENT_TYPE_LENGTH_POS] = + (unsigned char)pSession->contentType.length; + memcpy(pOutput->fromConvertData.pBuffer, pSession->topHeader, TOP_HEADER_SIZE); + memcpy((char *)pOutput->fromConvertData.pBuffer + TOP_HEADER_SIZE, + pSession->contentType.ptr, pSession->contentType.length); + memcpy((char *)pOutput->fromConvertData.pBuffer + encryptedSessionKeyPos, + pSession->pEncryptedSessionKey, pSession->encryptedSessionKeyLength); + + // Set the signatures to all zeros for now; they will have to be updated later. + memset((char *)pOutput->fromConvertData.pBuffer + dataSignaturePos, 0, + SHA1_HASH_SIZE); + memset((char *)pOutput->fromConvertData.pBuffer + headerSignaturePos, 0, + SHA1_HASH_SIZE); + + pOutput->fromConvertData.numBytes = pSession->dataOffset; + status = FwdLockConv_Status_OK; + } + } + return status; +} + +/** + * Matches the MIME headers. + * + * @param[in,out] pSession A reference to a converter session. + * @param[in] ch A character. + * @param[out] pOutput The output from the conversion process. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_MatchMimeHeaders(FwdLockConv_Session_t *pSession, + int ch, + FwdLockConv_Output_t *pOutput) { + FwdLockConv_Status_t status = FwdLockConv_Status_OK; + switch (pSession->scannerState) { + case FwdLockConv_ScannerState_WantsMimeHeaderNameStart: + if (FwdLockConv_IsMimeHeaderNameChar(ch)) { + pSession->mimeHeaderName.length = 0; + status = FwdLockConv_StringAppend(&pSession->mimeHeaderName, tolower(ch)); + if (status == FwdLockConv_Status_OK) { + pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderName; + } + } else if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeadersEnd; + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsMimeHeaderName: + if (FwdLockConv_IsMimeHeaderNameChar(ch)) { + status = FwdLockConv_StringAppend(&pSession->mimeHeaderName, tolower(ch)); + } else if (ch == ':') { + status = FwdLockConv_RecognizeMimeHeaderName(pSession); + } else if (FwdLockConv_IsWhitespace(ch)) { + pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameEnd; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsMimeHeaderNameEnd: + if (ch == ':') { + status = FwdLockConv_RecognizeMimeHeaderName(pSession); + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsContentTypeStart: + if (FwdLockConv_IsMimeHeaderValueChar(ch)) { + status = FwdLockConv_StringAppend(&pSession->contentType, tolower(ch)); + if (status == FwdLockConv_Status_OK) { + pSession->scannerState = FwdLockConv_ScannerState_WantsContentType; + } + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsContentType: + if (FwdLockConv_IsMimeHeaderValueChar(ch)) { + status = FwdLockConv_StringAppend(&pSession->contentType, tolower(ch)); + } else if (ch == ';') { + pSession->scannerState = FwdLockConv_ScannerState_WantsCR; + } else if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else if (FwdLockConv_IsWhitespace(ch)) { + pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderValueEnd; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsContentTransferEncodingStart: + if (ch == 'b' || ch == 'B') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_A_OR_I; + } else if (ch == '7' || ch == '8') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_B; + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_A_OR_I: + if (ch == 'i' || ch == 'I') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_N; + } else if (ch == 'a' || ch == 'A') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_S; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_N: + if (ch == 'n' || ch == 'N') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_A; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_A: + if (ch == 'a' || ch == 'A') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_R; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_R: + if (ch == 'r' || ch == 'R') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_Y; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_Y: + if (ch == 'y' || ch == 'Y') { + pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary; + pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_S: + if (ch == 's' || ch == 'S') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_E; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_E: + if (ch == 'e' || ch == 'E') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_6; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_6: + if (ch == '6') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_4; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_4: + if (ch == '4') { + pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Base64; + pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_B: + if (ch == 'b' || ch == 'B') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_I; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_I: + if (ch == 'i' || ch == 'I') { + pSession->scannerState = FwdLockConv_ScannerState_Wants_T; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_Wants_T: + if (ch == 't' || ch == 'T') { + pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary; + pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_WantsContentTransferEncodingEnd: + if (ch == ';') { + pSession->scannerState = FwdLockConv_ScannerState_WantsCR; + } else if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else if (FwdLockConv_IsWhitespace(ch)) { + pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderValueEnd; + } else { + status = FwdLockConv_Status_UnsupportedContentTransferEncoding; + } + break; + case FwdLockConv_ScannerState_WantsMimeHeaderValueEnd: + if (ch == ';') { + pSession->scannerState = FwdLockConv_ScannerState_WantsCR; + } else if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsCR: + if (ch == '\r') { + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } + break; + case FwdLockConv_ScannerState_WantsLF: + if (ch == '\n') { + pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameStart; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsMimeHeadersEnd: + if (ch == '\n') { + status = FwdLockConv_ApplyDefaults(pSession); + if (status == FwdLockConv_Status_OK) { + status = FwdLockConv_VerifyContentType(pSession); + } + if (status == FwdLockConv_Status_OK) { + status = FwdLockConv_WriteHeader(pSession, pOutput); + } + if (status == FwdLockConv_Status_OK) { + if (pSession->contentTransferEncoding == + FwdLockConv_ContentTransferEncoding_Binary) { + pSession->parserState = FwdLockConv_ParserState_WantsBinaryEncodedData; + } else { + pSession->parserState = FwdLockConv_ParserState_WantsBase64EncodedData; + } + pSession->scannerState = FwdLockConv_ScannerState_WantsByte1; + } + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + default: + status = FwdLockConv_Status_ProgramError; + break; + } + return status; +} + +/** + * Increments the counter, treated as a 16-byte little-endian number, by one. + * + * @param[in,out] pSession A reference to a converter session. + */ +static void FwdLockConv_IncrementCounter(FwdLockConv_Session_t *pSession) { + size_t i = 0; + while ((++pSession->counter[i] == 0) && (++i < AES_BLOCK_SIZE)) + ; +} + +/** + * Encrypts the given character and writes it to the output buffer. + * + * @param[in,out] pSession A reference to a converter session. + * @param[in] ch The character to encrypt and write. + * @param[in,out] pOutput The output from the conversion process. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_WriteEncryptedChar(FwdLockConv_Session_t *pSession, + unsigned char ch, + FwdLockConv_Output_t *pOutput) { + if (pOutput->fromConvertData.numBytes == pSession->outputBufferSize) { + void *pBuffer; + pSession->outputBufferSize += OUTPUT_BUFFER_SIZE_INCREMENT; + pBuffer = realloc(pOutput->fromConvertData.pBuffer, pSession->outputBufferSize); + if (pBuffer == NULL) { + return FwdLockConv_Status_OutOfMemory; + } + pOutput->fromConvertData.pBuffer = pBuffer; + } + if (++pSession->keyStreamIndex == AES_BLOCK_SIZE) { + FwdLockConv_IncrementCounter(pSession); + pSession->keyStreamIndex = 0; + } + if (pSession->keyStreamIndex == 0) { + AES_encrypt(pSession->counter, pSession->keyStream, &pSession->encryptionRoundKeys); + } + ch ^= pSession->keyStream[pSession->keyStreamIndex]; + ((unsigned char *)pOutput->fromConvertData.pBuffer)[pOutput->fromConvertData.numBytes++] = ch; + ++pSession->numDataBytes; + return FwdLockConv_Status_OK; +} + +/** + * Matches binary-encoded content data and encrypts it, while looking out for the close delimiter. + * + * @param[in,out] pSession A reference to a converter session. + * @param[in] ch A character. + * @param[in,out] pOutput The output from the conversion process. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_MatchBinaryEncodedData(FwdLockConv_Session_t *pSession, + int ch, + FwdLockConv_Output_t *pOutput) { + FwdLockConv_Status_t status = FwdLockConv_Status_OK; + switch (pSession->scannerState) { + case FwdLockConv_ScannerState_WantsByte1: + if (ch != pSession->delimiter[pSession->delimiterMatchPos]) { + // The partial match of the delimiter turned out to be spurious. Flush the matched bytes + // to the output buffer and start over. + size_t i; + for (i = 0; i < pSession->delimiterMatchPos; ++i) { + status = FwdLockConv_WriteEncryptedChar(pSession, pSession->delimiter[i], pOutput); + if (status != FwdLockConv_Status_OK) { + return status; + } + } + pSession->delimiterMatchPos = 0; + } + if (ch != pSession->delimiter[pSession->delimiterMatchPos]) { + // The current character isn't part of the delimiter. Write it to the output buffer. + status = FwdLockConv_WriteEncryptedChar(pSession, ch, pOutput); + } else if (++pSession->delimiterMatchPos == pSession->delimiterLength) { + // The entire delimiter has been matched. The only valid characters now are the "--" + // that complete the close delimiter (no more message parts are expected). + pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash; + } + break; + case FwdLockConv_ScannerState_WantsFirstDash: + if (ch == '-') { + pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsSecondDash: + if (ch == '-') { + pSession->parserState = FwdLockConv_ParserState_Done; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + default: + status = FwdLockConv_Status_ProgramError; + break; + } + return status; +} + +/** + * Checks whether a given character is valid in base64-encoded data. + * + * @param[in] ch The character to check. + * + * @return A Boolean value indicating whether the given character is valid in base64-encoded data. + */ +static int FwdLockConv_IsBase64Char(int ch) { + return 0 <= ch && ch <= 'z' && base64Values[ch] >= 0; +} + +/** + * Matches base64-encoded content data and encrypts it, while looking out for the close delimiter. + * + * @param[in,out] pSession A reference to a converter session. + * @param[in] ch A character. + * @param[in,out] pOutput The output from the conversion process. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_MatchBase64EncodedData(FwdLockConv_Session_t *pSession, + int ch, + FwdLockConv_Output_t *pOutput) { + FwdLockConv_Status_t status = FwdLockConv_Status_OK; + switch (pSession->scannerState) { + case FwdLockConv_ScannerState_WantsByte1: + case FwdLockConv_ScannerState_WantsByte1_AfterCRLF: + if (FwdLockConv_IsBase64Char(ch)) { + pSession->ch = base64Values[ch] << 2; + pSession->scannerState = FwdLockConv_ScannerState_WantsByte2; + } else if (ch == '\r') { + pSession->savedScannerState = FwdLockConv_ScannerState_WantsByte1_AfterCRLF; + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else if (ch == '-') { + if (pSession->scannerState == FwdLockConv_ScannerState_WantsByte1_AfterCRLF) { + pSession->delimiterMatchPos = 3; + pSession->scannerState = FwdLockConv_ScannerState_WantsDelimiter; + } else { + status = FwdLockConv_Status_SyntaxError; + } + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsByte2: + if (FwdLockConv_IsBase64Char(ch)) { + pSession->ch |= base64Values[ch] >> 4; + status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput); + if (status == FwdLockConv_Status_OK) { + pSession->ch = base64Values[ch] << 4; + pSession->scannerState = FwdLockConv_ScannerState_WantsByte3; + } + } else if (ch == '\r') { + pSession->savedScannerState = pSession->scannerState; + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsByte3: + if (FwdLockConv_IsBase64Char(ch)) { + pSession->ch |= base64Values[ch] >> 2; + status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput); + if (status == FwdLockConv_Status_OK) { + pSession->ch = base64Values[ch] << 6; + pSession->scannerState = FwdLockConv_ScannerState_WantsByte4; + } + } else if (ch == '\r') { + pSession->savedScannerState = pSession->scannerState; + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else if (ch == '=') { + pSession->scannerState = FwdLockConv_ScannerState_WantsPadding; + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsByte4: + if (FwdLockConv_IsBase64Char(ch)) { + pSession->ch |= base64Values[ch]; + status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput); + if (status == FwdLockConv_Status_OK) { + pSession->scannerState = FwdLockConv_ScannerState_WantsByte1; + } + } else if (ch == '\r') { + pSession->savedScannerState = pSession->scannerState; + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else if (ch == '=') { + pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace; + } else if (!FwdLockConv_IsWhitespace(ch)) { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsLF: + if (ch == '\n') { + pSession->scannerState = pSession->savedScannerState; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsPadding: + if (ch == '=') { + pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsWhitespace: + case FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF: + if (ch == '\r') { + pSession->savedScannerState = FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF; + pSession->scannerState = FwdLockConv_ScannerState_WantsLF; + } else if (ch == '-') { + if (pSession->scannerState == FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF) { + pSession->delimiterMatchPos = 3; + pSession->scannerState = FwdLockConv_ScannerState_WantsDelimiter; + } else { + status = FwdLockConv_Status_SyntaxError; + } + } else if (FwdLockConv_IsWhitespace(ch)) { + pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsDelimiter: + if (ch != pSession->delimiter[pSession->delimiterMatchPos]) { + status = FwdLockConv_Status_SyntaxError; + } else if (++pSession->delimiterMatchPos == pSession->delimiterLength) { + pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash; + } + break; + case FwdLockConv_ScannerState_WantsFirstDash: + if (ch == '-') { + pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + case FwdLockConv_ScannerState_WantsSecondDash: + if (ch == '-') { + pSession->parserState = FwdLockConv_ParserState_Done; + } else { + status = FwdLockConv_Status_SyntaxError; + } + break; + default: + status = FwdLockConv_Status_ProgramError; + break; + } + return status; +} + +/** + * Pushes a single character into the converter's state machine. + * + * @param[in,out] pSession A reference to a converter session. + * @param[in] ch A character. + * @param[in,out] pOutput The output from the conversion process. + * + * @return A status code. + */ +static FwdLockConv_Status_t FwdLockConv_PushChar(FwdLockConv_Session_t *pSession, + int ch, + FwdLockConv_Output_t *pOutput) { + FwdLockConv_Status_t status; + ++pSession->numCharsConsumed; + switch (pSession->parserState) { + case FwdLockConv_ParserState_WantsOpenDelimiter: + status = FwdLockConv_MatchOpenDelimiter(pSession, ch); + break; + case FwdLockConv_ParserState_WantsMimeHeaders: + status = FwdLockConv_MatchMimeHeaders(pSession, ch, pOutput); + break; + case FwdLockConv_ParserState_WantsBinaryEncodedData: + status = FwdLockConv_MatchBinaryEncodedData(pSession, ch, pOutput); + break; + case FwdLockConv_ParserState_WantsBase64EncodedData: + status = FwdLockConv_MatchBase64EncodedData(pSession, ch, pOutput); + break; + case FwdLockConv_ParserState_Done: + status = FwdLockConv_Status_OK; + break; + default: + status = FwdLockConv_Status_ProgramError; + break; + } + return status; +} + +FwdLockConv_Status_t FwdLockConv_OpenSession(int *pSessionId, FwdLockConv_Output_t *pOutput) { + FwdLockConv_Status_t status; + if (pSessionId == NULL || pOutput == NULL) { + status = FwdLockConv_Status_InvalidArgument; + } else { + *pSessionId = FwdLockConv_AcquireSession(); + if (*pSessionId < 0) { + status = FwdLockConv_Status_TooManySessions; + } else { + FwdLockConv_Session_t *pSession = sessionPtrs[*pSessionId]; + pSession->encryptedSessionKeyLength = FwdLockGlue_GetEncryptedKeyLength(KEY_SIZE); + if (pSession->encryptedSessionKeyLength < AES_BLOCK_SIZE) { + // The encrypted session key is used as the CTR-mode nonce, so it must be at least + // the size of a single AES block. + status = FwdLockConv_Status_ProgramError; + } else { + pSession->pEncryptedSessionKey = malloc(pSession->encryptedSessionKeyLength); + if (pSession->pEncryptedSessionKey == NULL) { + status = FwdLockConv_Status_OutOfMemory; + } else { + if (!FwdLockGlue_GetRandomNumber(pSession->sessionKey, KEY_SIZE)) { + status = FwdLockConv_Status_RandomNumberGenerationFailed; + } else if (!FwdLockGlue_EncryptKey(pSession->sessionKey, KEY_SIZE, + pSession->pEncryptedSessionKey, + pSession->encryptedSessionKeyLength)) { + status = FwdLockConv_Status_KeyEncryptionFailed; + } else { + status = FwdLockConv_DeriveKeys(pSession); + } + if (status == FwdLockConv_Status_OK) { + memset(pSession->sessionKey, 0, KEY_SIZE); // Zero out key data. + memcpy(pSession->counter, pSession->pEncryptedSessionKey, AES_BLOCK_SIZE); + pSession->parserState = FwdLockConv_ParserState_WantsOpenDelimiter; + pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash; + pSession->numCharsConsumed = 0; + pSession->delimiterMatchPos = 0; + pSession->mimeHeaderName = nullString; + pSession->contentType = nullString; + pSession->contentTransferEncoding = + FwdLockConv_ContentTransferEncoding_Undefined; + pSession->keyStreamIndex = -1; + pOutput->fromConvertData.pBuffer = NULL; + pOutput->fromConvertData.errorPos = INVALID_OFFSET; + } else { + free(pSession->pEncryptedSessionKey); + } + } + } + if (status != FwdLockConv_Status_OK) { + FwdLockConv_ReleaseSession(*pSessionId); + *pSessionId = -1; + } + } + } + return status; +} + +FwdLockConv_Status_t FwdLockConv_ConvertData(int sessionId, + const void *pBuffer, + size_t numBytes, + FwdLockConv_Output_t *pOutput) { + FwdLockConv_Status_t status; + if (!FwdLockConv_IsValidSession(sessionId) || pBuffer == NULL || pOutput == NULL) { + status = FwdLockConv_Status_InvalidArgument; + } else { + size_t i; + FwdLockConv_Session_t *pSession = sessionPtrs[sessionId]; + pSession->dataOffset = 0; + pSession->numDataBytes = 0; + pOutput->fromConvertData.numBytes = 0; + status = FwdLockConv_Status_OK; + + for (i = 0; i < numBytes; ++i) { + status = FwdLockConv_PushChar(pSession, ((char *)pBuffer)[i], pOutput); + if (status != FwdLockConv_Status_OK) { + break; + } + } + if (status == FwdLockConv_Status_OK) { + // Update the data signature. + HMAC_Update(&pSession->signingContext, + &((unsigned char *)pOutput->fromConvertData.pBuffer)[pSession->dataOffset], + pSession->numDataBytes); + } else if (status == FwdLockConv_Status_SyntaxError) { + pOutput->fromConvertData.errorPos = pSession->numCharsConsumed; + } + } + return status; +} + +FwdLockConv_Status_t FwdLockConv_CloseSession(int sessionId, FwdLockConv_Output_t *pOutput) { + FwdLockConv_Status_t status; + if (!FwdLockConv_IsValidSession(sessionId) || pOutput == NULL) { + status = FwdLockConv_Status_InvalidArgument; + } else { + FwdLockConv_Session_t *pSession = sessionPtrs[sessionId]; + free(pOutput->fromConvertData.pBuffer); + if (pSession->parserState != FwdLockConv_ParserState_Done) { + pOutput->fromCloseSession.errorPos = pSession->numCharsConsumed; + status = FwdLockConv_Status_SyntaxError; + } else { + // Finalize the data signature. + size_t signatureSize; + HMAC_Final(&pSession->signingContext, pOutput->fromCloseSession.signatures, + &signatureSize); + if (signatureSize != SHA1_HASH_SIZE) { + status = FwdLockConv_Status_ProgramError; + } else { + // Calculate the header signature, which is a signature of the rest of the header + // including the data signature. + HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL); + HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE); + HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->contentType.ptr, + pSession->contentType.length); + HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey, + pSession->encryptedSessionKeyLength); + HMAC_Update(&pSession->signingContext, pOutput->fromCloseSession.signatures, + signatureSize); + HMAC_Final(&pSession->signingContext, &pOutput->fromCloseSession. + signatures[signatureSize], &signatureSize); + if (signatureSize != SHA1_HASH_SIZE) { + status = FwdLockConv_Status_ProgramError; + } else { + pOutput->fromCloseSession.fileOffset = TOP_HEADER_SIZE + + pSession->contentType.length + pSession->encryptedSessionKeyLength; + status = FwdLockConv_Status_OK; + } + } + pOutput->fromCloseSession.errorPos = INVALID_OFFSET; + } + free(pSession->mimeHeaderName.ptr); + free(pSession->contentType.ptr); + free(pSession->pEncryptedSessionKey); + HMAC_CTX_cleanup(&pSession->signingContext); + FwdLockConv_ReleaseSession(sessionId); + } + return status; +} + +FwdLockConv_Status_t FwdLockConv_ConvertOpenFile(int inputFileDesc, + FwdLockConv_ReadFunc_t *fpReadFunc, + int outputFileDesc, + FwdLockConv_WriteFunc_t *fpWriteFunc, + FwdLockConv_LSeekFunc_t *fpLSeekFunc, + off64_t *pErrorPos) { + FwdLockConv_Status_t status; + if (pErrorPos != NULL) { + *pErrorPos = INVALID_OFFSET; + } + if (fpReadFunc == NULL || fpWriteFunc == NULL || fpLSeekFunc == NULL || inputFileDesc < 0 || + outputFileDesc < 0) { + status = FwdLockConv_Status_InvalidArgument; + } else { + char *pReadBuffer = malloc(READ_BUFFER_SIZE); + if (pReadBuffer == NULL) { + status = FwdLockConv_Status_OutOfMemory; + } else { + int sessionId; + FwdLockConv_Output_t output; + status = FwdLockConv_OpenSession(&sessionId, &output); + if (status == FwdLockConv_Status_OK) { + ssize_t numBytesRead; + FwdLockConv_Status_t closeStatus; + while ((numBytesRead = + fpReadFunc(inputFileDesc, pReadBuffer, READ_BUFFER_SIZE)) > 0) { + status = FwdLockConv_ConvertData(sessionId, pReadBuffer, (size_t)numBytesRead, + &output); + if (status == FwdLockConv_Status_OK) { + if (output.fromConvertData.pBuffer != NULL && + output.fromConvertData.numBytes > 0) { + ssize_t numBytesWritten = fpWriteFunc(outputFileDesc, + output.fromConvertData.pBuffer, + output.fromConvertData.numBytes); + if (numBytesWritten != (ssize_t)output.fromConvertData.numBytes) { + status = FwdLockConv_Status_FileWriteError; + break; + } + } + } else { + if (status == FwdLockConv_Status_SyntaxError && pErrorPos != NULL) { + *pErrorPos = output.fromConvertData.errorPos; + } + break; + } + } // end while + if (numBytesRead < 0) { + status = FwdLockConv_Status_FileReadError; + } + closeStatus = FwdLockConv_CloseSession(sessionId, &output); + if (status == FwdLockConv_Status_OK) { + if (closeStatus != FwdLockConv_Status_OK) { + if (closeStatus == FwdLockConv_Status_SyntaxError && pErrorPos != NULL) { + *pErrorPos = output.fromCloseSession.errorPos; + } + status = closeStatus; + } else if (fpLSeekFunc(outputFileDesc, output.fromCloseSession.fileOffset, + SEEK_SET) < 0) { + status = FwdLockConv_Status_FileSeekError; + } else if (fpWriteFunc(outputFileDesc, output.fromCloseSession.signatures, + FWD_LOCK_SIGNATURES_SIZE) != FWD_LOCK_SIGNATURES_SIZE) { + status = FwdLockConv_Status_FileWriteError; + } + } + } + free(pReadBuffer); + } + } + return status; +} + +FwdLockConv_Status_t FwdLockConv_ConvertFile(const char *pInputFilename, + const char *pOutputFilename, + off64_t *pErrorPos) { + FwdLockConv_Status_t status; + if (pErrorPos != NULL) { + *pErrorPos = INVALID_OFFSET; + } + if (pInputFilename == NULL || pOutputFilename == NULL) { + status = FwdLockConv_Status_InvalidArgument; + } else { + int inputFileDesc = open(pInputFilename, O_RDONLY); + if (inputFileDesc < 0) { + status = FwdLockConv_Status_FileNotFound; + } else { + int outputFileDesc = open(pOutputFilename, O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (outputFileDesc < 0) { + status = FwdLockConv_Status_FileCreationFailed; + } else { + status = FwdLockConv_ConvertOpenFile(inputFileDesc, read, outputFileDesc, write, + lseek64, pErrorPos); + if (close(outputFileDesc) == 0 && status != FwdLockConv_Status_OK) { + remove(pOutputFilename); + } + } + (void)close(inputFileDesc); + } + } + return status; +} diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/converter/FwdLockConv.h b/drm/libdrmframework/plugins/forward-lock/internal-format/converter/FwdLockConv.h new file mode 100644 index 0000000..e20c0c3 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/converter/FwdLockConv.h @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FWDLOCKCONV_H__ +#define __FWDLOCKCONV_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +/** + * The size of the data and header signatures combined. The signatures are adjacent to each other in + * the produced output file. + */ +#define FWD_LOCK_SIGNATURES_SIZE (2 * 20) + +/** + * Data type for the output from FwdLockConv_ConvertData. + */ +typedef struct FwdLockConv_ConvertData_Output { + /// The converted data. + void *pBuffer; + + /// The size of the converted data. + size_t numBytes; + + /// The file position where the error occurred, in the case of a syntax error. + off64_t errorPos; +} FwdLockConv_ConvertData_Output_t; + +/** + * Data type for the output from FwdLockConv_CloseSession. + */ +typedef struct FwdLockConv_CloseSession_Output { + /// The final set of signatures. + unsigned char signatures[FWD_LOCK_SIGNATURES_SIZE]; + + /// The offset in the produced output file where the signatures are located. + off64_t fileOffset; + + /// The file position where the error occurred, in the case of a syntax error. + off64_t errorPos; +} FwdLockConv_CloseSession_Output_t; + +/** + * Data type for the output from the conversion process. + */ +typedef union FwdLockConv_Output { + FwdLockConv_ConvertData_Output_t fromConvertData; + FwdLockConv_CloseSession_Output_t fromCloseSession; +} FwdLockConv_Output_t; + +/** + * Data type for the Posix-style read function used by the converter in pull mode. + * + * @param[in] fileDesc The file descriptor of a file opened for reading. + * @param[out] pBuffer A reference to the buffer that should receive the read data. + * @param[in] numBytes The number of bytes to read. + * + * @return The number of bytes read. + * @retval -1 Failure. + */ +typedef ssize_t FwdLockConv_ReadFunc_t(int fileDesc, void *pBuffer, size_t numBytes); + +/** + * Data type for the Posix-style write function used by the converter in pull mode. + * + * @param[in] fileDesc The file descriptor of a file opened for writing. + * @param[in] pBuffer A reference to the buffer containing the data to be written. + * @param[in] numBytes The number of bytes to write. + * + * @return The number of bytes written. + * @retval -1 Failure. + */ +typedef ssize_t FwdLockConv_WriteFunc_t(int fileDesc, const void *pBuffer, size_t numBytes); + +/** + * Data type for the Posix-style lseek function used by the converter in pull mode. + * + * @param[in] fileDesc The file descriptor of a file opened for writing. + * @param[in] offset The offset with which to update the file position. + * @param[in] whence One of SEEK_SET, SEEK_CUR, and SEEK_END. + * + * @return The new file position. + * @retval ((off64_t)-1) Failure. + */ +typedef off64_t FwdLockConv_LSeekFunc_t(int fileDesc, off64_t offset, int whence); + +/** + * The status codes returned by the converter functions. + */ +typedef enum FwdLockConv_Status { + /// The operation was successful. + FwdLockConv_Status_OK = 0, + + /// An actual argument to the function is invalid (a program error on the caller's part). + FwdLockConv_Status_InvalidArgument = 1, + + /// There is not enough free dynamic memory to complete the operation. + FwdLockConv_Status_OutOfMemory = 2, + + /// An error occurred while opening the input file. + FwdLockConv_Status_FileNotFound = 3, + + /// An error occurred while creating the output file. + FwdLockConv_Status_FileCreationFailed = 4, + + /// An error occurred while reading from the input file. + FwdLockConv_Status_FileReadError = 5, + + /// An error occurred while writing to the output file. + FwdLockConv_Status_FileWriteError = 6, + + /// An error occurred while seeking to a new file position within the output file. + FwdLockConv_Status_FileSeekError = 7, + + /// The input file is not a syntactically correct OMA DRM v1 Forward Lock file. + FwdLockConv_Status_SyntaxError = 8, + + /// Support for this DRM file format has been disabled in the current product configuration. + FwdLockConv_Status_UnsupportedFileFormat = 9, + + /// The content transfer encoding is not one of "binary", "base64", "7bit", or "8bit" + /// (case-insensitive). + FwdLockConv_Status_UnsupportedContentTransferEncoding = 10, + + /// The generation of a random number failed. + FwdLockConv_Status_RandomNumberGenerationFailed = 11, + + /// Key encryption failed. + FwdLockConv_Status_KeyEncryptionFailed = 12, + + /// The calculation of a keyed hash for integrity protection failed. + FwdLockConv_Status_IntegrityProtectionFailed = 13, + + /// There are too many ongoing sessions for another one to be opened. + FwdLockConv_Status_TooManySessions = 14, + + /// An unexpected error occurred. + FwdLockConv_Status_ProgramError = 15 +} FwdLockConv_Status_t; + +/** + * Opens a session for converting an OMA DRM v1 Forward Lock file to the internal Forward Lock file + * format. + * + * @param[out] pSessionId The session ID. + * @param[out] pOutput The output from the conversion process (initialized). + * + * @return A status code. + * @retval FwdLockConv_Status_OK + * @retval FwdLockConv_Status_InvalidArgument + * @retval FwdLockConv_Status_TooManySessions + */ +FwdLockConv_Status_t FwdLockConv_OpenSession(int *pSessionId, FwdLockConv_Output_t *pOutput); + +/** + * Supplies the converter with data to convert. The caller is expected to write the converted data + * to file. Can be called an arbitrary number of times. + * + * @param[in] sessionId The session ID. + * @param[in] pBuffer A reference to a buffer containing the data to convert. + * @param[in] numBytes The number of bytes to convert. + * @param[in,out] pOutput The output from the conversion process (allocated/reallocated). + * + * @return A status code. + * @retval FwdLockConv_Status_OK + * @retval FwdLockConv_Status_InvalidArgument + * @retval FwdLockConv_Status_OutOfMemory + * @retval FwdLockConv_Status_SyntaxError + * @retval FwdLockConv_Status_UnsupportedFileFormat + * @retval FwdLockConv_Status_UnsupportedContentTransferEncoding + * @retval FwdLockConv_Status_RandomNumberGenerationFailed + * @retval FwdLockConv_Status_KeyEncryptionFailed + * @retval FwdLockConv_Status_DataEncryptionFailed + */ +FwdLockConv_Status_t FwdLockConv_ConvertData(int sessionId, + const void *pBuffer, + size_t numBytes, + FwdLockConv_Output_t *pOutput); + +/** + * Closes a session for converting an OMA DRM v1 Forward Lock file to the internal Forward Lock + * file format. The caller must update the produced output file at the indicated file offset with + * the final set of signatures. + * + * @param[in] sessionId The session ID. + * @param[in,out] pOutput The output from the conversion process (deallocated and overwritten). + * + * @return A status code. + * @retval FwdLockConv_Status_OK + * @retval FwdLockConv_Status_InvalidArgument + * @retval FwdLockConv_Status_OutOfMemory + * @retval FwdLockConv_Status_IntegrityProtectionFailed + */ +FwdLockConv_Status_t FwdLockConv_CloseSession(int sessionId, FwdLockConv_Output_t *pOutput); + +/** + * Converts an open OMA DRM v1 Forward Lock file to the internal Forward Lock file format in pull + * mode. + * + * @param[in] inputFileDesc The file descriptor of the open input file. + * @param[in] fpReadFunc A reference to a read function that can operate on the open input file. + * @param[in] outputFileDesc The file descriptor of the open output file. + * @param[in] fpWriteFunc A reference to a write function that can operate on the open output file. + * @param[in] fpLSeekFunc A reference to an lseek function that can operate on the open output file. + * @param[out] pErrorPos + * The file position where the error occurred, in the case of a syntax error. May be NULL. + * + * @return A status code. + * @retval FwdLockConv_Status_OK + * @retval FwdLockConv_Status_InvalidArgument + * @retval FwdLockConv_Status_OutOfMemory + * @retval FwdLockConv_Status_FileReadError + * @retval FwdLockConv_Status_FileWriteError + * @retval FwdLockConv_Status_FileSeekError + * @retval FwdLockConv_Status_SyntaxError + * @retval FwdLockConv_Status_UnsupportedFileFormat + * @retval FwdLockConv_Status_UnsupportedContentTransferEncoding + * @retval FwdLockConv_Status_RandomNumberGenerationFailed + * @retval FwdLockConv_Status_KeyEncryptionFailed + * @retval FwdLockConv_Status_DataEncryptionFailed + * @retval FwdLockConv_Status_IntegrityProtectionFailed + * @retval FwdLockConv_Status_TooManySessions + */ +FwdLockConv_Status_t FwdLockConv_ConvertOpenFile(int inputFileDesc, + FwdLockConv_ReadFunc_t *fpReadFunc, + int outputFileDesc, + FwdLockConv_WriteFunc_t *fpWriteFunc, + FwdLockConv_LSeekFunc_t *fpLSeekFunc, + off64_t *pErrorPos); + +/** + * Converts an OMA DRM v1 Forward Lock file to the internal Forward Lock file format in pull mode. + * + * @param[in] pInputFilename A reference to the input filename. + * @param[in] pOutputFilename A reference to the output filename. + * @param[out] pErrorPos + * The file position where the error occurred, in the case of a syntax error. May be NULL. + * + * @return A status code. + * @retval FwdLockConv_Status_OK + * @retval FwdLockConv_Status_InvalidArgument + * @retval FwdLockConv_Status_OutOfMemory + * @retval FwdLockConv_Status_FileNotFound + * @retval FwdLockConv_Status_FileCreationFailed + * @retval FwdLockConv_Status_FileReadError + * @retval FwdLockConv_Status_FileWriteError + * @retval FwdLockConv_Status_FileSeekError + * @retval FwdLockConv_Status_SyntaxError + * @retval FwdLockConv_Status_UnsupportedFileFormat + * @retval FwdLockConv_Status_UnsupportedContentTransferEncoding + * @retval FwdLockConv_Status_RandomNumberGenerationFailed + * @retval FwdLockConv_Status_KeyEncryptionFailed + * @retval FwdLockConv_Status_DataEncryptionFailed + * @retval FwdLockConv_Status_IntegrityProtectionFailed + * @retval FwdLockConv_Status_TooManySessions + */ +FwdLockConv_Status_t FwdLockConv_ConvertFile(const char *pInputFilename, + const char *pOutputFilename, + off64_t *pErrorPos); + +#ifdef __cplusplus +} +#endif + +#endif // __FWDLOCKCONV_H__ diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.mk b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.mk new file mode 100644 index 0000000..b625edf --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/Android.mk @@ -0,0 +1,37 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + FwdLockFile.c + +LOCAL_C_INCLUDES := \ + frameworks/base/drm/libdrmframework/plugins/forward-lock/internal-format/common \ + external/openssl/include + +LOCAL_SHARED_LIBRARIES := libcrypto + +LOCAL_WHOLE_STATIC_LIBRARIES := libfwdlock-common + +LOCAL_STATIC_LIBRARIES := libfwdlock-common + +LOCAL_MODULE := libfwdlock-decoder + +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_LIBRARY) diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.c b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.c new file mode 100644 index 0000000..98284e7 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <openssl/aes.h> +#include <openssl/hmac.h> + +#include "FwdLockFile.h" +#include "FwdLockGlue.h" + +#define TRUE 1 +#define FALSE 0 + +#define INVALID_OFFSET ((off64_t)-1) + +#define INVALID_BLOCK_INDEX ((uint64_t)-1) + +#define MAX_NUM_SESSIONS 128 + +#define KEY_SIZE AES_BLOCK_SIZE +#define KEY_SIZE_IN_BITS (KEY_SIZE * 8) + +#define SHA1_HASH_SIZE 20 +#define SHA1_BLOCK_SIZE 64 + +#define FWD_LOCK_VERSION 0 +#define FWD_LOCK_SUBFORMAT 0 +#define USAGE_RESTRICTION_FLAGS 0 +#define CONTENT_TYPE_LENGTH_POS 7 +#define TOP_HEADER_SIZE 8 + +#define SIG_CALC_BUFFER_SIZE (16 * SHA1_BLOCK_SIZE) + +/** + * Data type for the per-file state information needed by the decoder. + */ +typedef struct FwdLockFile_Session { + int fileDesc; + unsigned char topHeader[TOP_HEADER_SIZE]; + char *pContentType; + size_t contentTypeLength; + void *pEncryptedSessionKey; + size_t encryptedSessionKeyLength; + unsigned char dataSignature[SHA1_HASH_SIZE]; + unsigned char headerSignature[SHA1_HASH_SIZE]; + off64_t dataOffset; + off64_t filePos; + AES_KEY encryptionRoundKeys; + HMAC_CTX signingContext; + unsigned char keyStream[AES_BLOCK_SIZE]; + uint64_t blockIndex; +} FwdLockFile_Session_t; + +static FwdLockFile_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL }; + +static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER; + +static const unsigned char topHeaderTemplate[] = + { 'F', 'W', 'L', 'K', FWD_LOCK_VERSION, FWD_LOCK_SUBFORMAT, USAGE_RESTRICTION_FLAGS }; + +/** + * Acquires an unused file session for the given file descriptor. + * + * @param[in] fileDesc A file descriptor. + * + * @return A session ID. + */ +static int FwdLockFile_AcquireSession(int fileDesc) { + int sessionId = -1; + if (fileDesc < 0) { + errno = EBADF; + } else { + int i; + pthread_mutex_lock(&sessionAcquisitionMutex); + for (i = 0; i < MAX_NUM_SESSIONS; ++i) { + int candidateSessionId = (fileDesc + i) % MAX_NUM_SESSIONS; + if (sessionPtrs[candidateSessionId] == NULL) { + sessionPtrs[candidateSessionId] = malloc(sizeof **sessionPtrs); + if (sessionPtrs[candidateSessionId] != NULL) { + sessionPtrs[candidateSessionId]->fileDesc = fileDesc; + sessionPtrs[candidateSessionId]->pContentType = NULL; + sessionPtrs[candidateSessionId]->pEncryptedSessionKey = NULL; + sessionId = candidateSessionId; + } + break; + } + } + pthread_mutex_unlock(&sessionAcquisitionMutex); + if (i == MAX_NUM_SESSIONS) { + errno = ENFILE; + } + } + return sessionId; +} + +/** + * Finds the file session associated to the given file descriptor. + * + * @param[in] fileDesc A file descriptor. + * + * @return A session ID. + */ +static int FwdLockFile_FindSession(int fileDesc) { + int sessionId = -1; + if (fileDesc < 0) { + errno = EBADF; + } else { + int i; + pthread_mutex_lock(&sessionAcquisitionMutex); + for (i = 0; i < MAX_NUM_SESSIONS; ++i) { + int candidateSessionId = (fileDesc + i) % MAX_NUM_SESSIONS; + if (sessionPtrs[candidateSessionId] != NULL && + sessionPtrs[candidateSessionId]->fileDesc == fileDesc) { + sessionId = candidateSessionId; + break; + } + } + pthread_mutex_unlock(&sessionAcquisitionMutex); + if (i == MAX_NUM_SESSIONS) { + errno = EBADF; + } + } + return sessionId; +} + +/** + * Releases a file session. + * + * @param[in] sessionID A session ID. + */ +static void FwdLockFile_ReleaseSession(int sessionId) { + pthread_mutex_lock(&sessionAcquisitionMutex); + assert(0 <= sessionId && sessionId < MAX_NUM_SESSIONS && sessionPtrs[sessionId] != NULL); + free(sessionPtrs[sessionId]->pContentType); + free(sessionPtrs[sessionId]->pEncryptedSessionKey); + memset(sessionPtrs[sessionId], 0, sizeof *sessionPtrs[sessionId]); // Zero out key data. + free(sessionPtrs[sessionId]); + sessionPtrs[sessionId] = NULL; + pthread_mutex_unlock(&sessionAcquisitionMutex); +} + +/** + * Derives keys for encryption and signing from the encrypted session key. + * + * @param[in,out] pSession A reference to a file session. + * + * @return A Boolean value indicating whether key derivation was successful. + */ +static int FwdLockFile_DeriveKeys(FwdLockFile_Session_t * pSession) { + int result; + struct FwdLockFile_DeriveKeys_Data { + AES_KEY sessionRoundKeys; + unsigned char value[KEY_SIZE]; + unsigned char key[KEY_SIZE]; + } *pData = malloc(sizeof *pData); + if (pData == NULL) { + result = FALSE; + } else { + result = FwdLockGlue_DecryptKey(pSession->pEncryptedSessionKey, + pSession->encryptedSessionKeyLength, pData->key, KEY_SIZE); + if (result) { + if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, &pData->sessionRoundKeys) != 0) { + result = FALSE; + } else { + // Encrypt the 16-byte value {0, 0, ..., 0} to produce the encryption key. + memset(pData->value, 0, KEY_SIZE); + AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys); + if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, + &pSession->encryptionRoundKeys) != 0) { + result = FALSE; + } else { + // Encrypt the 16-byte value {1, 0, ..., 0} to produce the signing key. + ++pData->value[0]; + AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys); + HMAC_CTX_init(&pSession->signingContext); + HMAC_Init_ex(&pSession->signingContext, pData->key, KEY_SIZE, EVP_sha1(), NULL); + } + } + } + if (!result) { + errno = ENOSYS; + } + memset(pData, 0, sizeof pData); // Zero out key data. + free(pData); + } + return result; +} + +/** + * Calculates the counter, treated as a 16-byte little-endian number, used to generate the keystream + * for the given block. + * + * @param[in] pNonce A reference to the nonce. + * @param[in] blockIndex The index number of the block. + * @param[out] pCounter A reference to the counter. + */ +static void FwdLockFile_CalculateCounter(const unsigned char *pNonce, + uint64_t blockIndex, + unsigned char *pCounter) { + unsigned char carry = 0; + size_t i = 0; + for (; i < sizeof blockIndex; ++i) { + unsigned char part = pNonce[i] + (unsigned char)(blockIndex >> (i * CHAR_BIT)); + pCounter[i] = part + carry; + carry = (part < pNonce[i] || pCounter[i] < part) ? 1 : 0; + } + for (; i < AES_BLOCK_SIZE; ++i) { + pCounter[i] = pNonce[i] + carry; + carry = (pCounter[i] < pNonce[i]) ? 1 : 0; + } +} + +/** + * Decrypts the byte at the current file position using AES-128-CTR. In CTR (or "counter") mode, + * encryption and decryption are performed using the same algorithm. + * + * @param[in,out] pSession A reference to a file session. + * @param[in] pByte The byte to decrypt. + */ +void FwdLockFile_DecryptByte(FwdLockFile_Session_t * pSession, unsigned char *pByte) { + uint64_t blockIndex = pSession->filePos / AES_BLOCK_SIZE; + uint64_t blockOffset = pSession->filePos % AES_BLOCK_SIZE; + if (blockIndex != pSession->blockIndex) { + // The first 16 bytes of the encrypted session key is used as the nonce. + unsigned char counter[AES_BLOCK_SIZE]; + FwdLockFile_CalculateCounter(pSession->pEncryptedSessionKey, blockIndex, counter); + AES_encrypt(counter, pSession->keyStream, &pSession->encryptionRoundKeys); + pSession->blockIndex = blockIndex; + } + *pByte ^= pSession->keyStream[blockOffset]; +} + +int FwdLockFile_attach(int fileDesc) { + int sessionId = FwdLockFile_AcquireSession(fileDesc); + if (sessionId >= 0) { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + int isSuccess = FALSE; + if (read(fileDesc, pSession->topHeader, TOP_HEADER_SIZE) == TOP_HEADER_SIZE && + memcmp(pSession->topHeader, topHeaderTemplate, sizeof topHeaderTemplate) == 0) { + pSession->contentTypeLength = pSession->topHeader[CONTENT_TYPE_LENGTH_POS]; + assert(pSession->contentTypeLength <= UCHAR_MAX); // Untaint scalar for code checkers. + pSession->pContentType = malloc(pSession->contentTypeLength + 1); + if (pSession->pContentType != NULL && + read(fileDesc, pSession->pContentType, pSession->contentTypeLength) == + (ssize_t)pSession->contentTypeLength) { + pSession->pContentType[pSession->contentTypeLength] = '\0'; + pSession->encryptedSessionKeyLength = FwdLockGlue_GetEncryptedKeyLength(KEY_SIZE); + pSession->pEncryptedSessionKey = malloc(pSession->encryptedSessionKeyLength); + if (pSession->pEncryptedSessionKey != NULL && + read(fileDesc, pSession->pEncryptedSessionKey, + pSession->encryptedSessionKeyLength) == + (ssize_t)pSession->encryptedSessionKeyLength && + read(fileDesc, pSession->dataSignature, SHA1_HASH_SIZE) == + SHA1_HASH_SIZE && + read(fileDesc, pSession->headerSignature, SHA1_HASH_SIZE) == + SHA1_HASH_SIZE) { + isSuccess = FwdLockFile_DeriveKeys(pSession); + } + } + } + if (isSuccess) { + pSession->dataOffset = pSession->contentTypeLength + + pSession->encryptedSessionKeyLength + TOP_HEADER_SIZE + 2 * SHA1_HASH_SIZE; + pSession->filePos = 0; + pSession->blockIndex = INVALID_BLOCK_INDEX; + } else { + FwdLockFile_ReleaseSession(sessionId); + sessionId = -1; + } + } + return (sessionId >= 0) ? 0 : -1; +} + +int FwdLockFile_open(const char *pFilename) { + int fileDesc = open(pFilename, O_RDONLY); + if (fileDesc >= 0 && FwdLockFile_attach(fileDesc) < 0) { + (void)close(fileDesc); + fileDesc = -1; + } + return fileDesc; +} + +ssize_t FwdLockFile_read(int fileDesc, void *pBuffer, size_t numBytes) { + ssize_t numBytesRead; + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + numBytesRead = -1; + } else { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + ssize_t i; + numBytesRead = read(pSession->fileDesc, pBuffer, numBytes); + for (i = 0; i < numBytesRead; ++i) { + FwdLockFile_DecryptByte(pSession, &((unsigned char *)pBuffer)[i]); + ++pSession->filePos; + } + } + return numBytesRead; +} + +off64_t FwdLockFile_lseek(int fileDesc, off64_t offset, int whence) { + off64_t newFilePos; + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + newFilePos = INVALID_OFFSET; + } else { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + switch (whence) { + case SEEK_SET: + newFilePos = lseek64(pSession->fileDesc, pSession->dataOffset + offset, whence); + break; + case SEEK_CUR: + case SEEK_END: + newFilePos = lseek64(pSession->fileDesc, offset, whence); + break; + default: + errno = EINVAL; + newFilePos = INVALID_OFFSET; + break; + } + if (newFilePos != INVALID_OFFSET) { + if (newFilePos < pSession->dataOffset) { + // The new file position is illegal for an internal Forward Lock file. Restore the + // original file position. + (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos, + SEEK_SET); + errno = EINVAL; + newFilePos = INVALID_OFFSET; + } else { + // The return value should be the file position that lseek64() would have returned + // for the embedded content file. + pSession->filePos = newFilePos - pSession->dataOffset; + newFilePos = pSession->filePos; + } + } + } + return newFilePos; +} + +int FwdLockFile_detach(int fileDesc) { + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + return -1; + } + HMAC_CTX_cleanup(&sessionPtrs[sessionId]->signingContext); + FwdLockFile_ReleaseSession(sessionId); + return 0; +} + +int FwdLockFile_close(int fileDesc) { + return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1; +} + +int FwdLockFile_CheckDataIntegrity(int fileDesc) { + int result; + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + result = FALSE; + } else { + struct FwdLockFile_CheckDataIntegrity_Data { + unsigned char signature[SHA1_HASH_SIZE]; + unsigned char buffer[SIG_CALC_BUFFER_SIZE]; + } *pData = malloc(sizeof *pData); + if (pData == NULL) { + result = FALSE; + } else { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + if (lseek64(pSession->fileDesc, pSession->dataOffset, SEEK_SET) != + pSession->dataOffset) { + result = FALSE; + } else { + ssize_t numBytesRead; + size_t signatureSize = SHA1_HASH_SIZE; + while ((numBytesRead = + read(pSession->fileDesc, pData->buffer, SIG_CALC_BUFFER_SIZE)) > 0) { + HMAC_Update(&pSession->signingContext, pData->buffer, (size_t)numBytesRead); + } + if (numBytesRead < 0) { + result = FALSE; + } else { + HMAC_Final(&pSession->signingContext, pData->signature, &signatureSize); + assert(signatureSize == SHA1_HASH_SIZE); + result = memcmp(pData->signature, pSession->dataSignature, signatureSize) == 0; + } + HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL); + (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos, + SEEK_SET); + } + free(pData); + } + } + return result; +} + +int FwdLockFile_CheckHeaderIntegrity(int fileDesc) { + int result; + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + result = FALSE; + } else { + FwdLockFile_Session_t *pSession = sessionPtrs[sessionId]; + unsigned char signature[SHA1_HASH_SIZE]; + size_t signatureSize = SHA1_HASH_SIZE; + HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE); + HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->pContentType, + pSession->contentTypeLength); + HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey, + pSession->encryptedSessionKeyLength); + HMAC_Update(&pSession->signingContext, pSession->dataSignature, signatureSize); + HMAC_Final(&pSession->signingContext, signature, &signatureSize); + assert(signatureSize == SHA1_HASH_SIZE); + result = memcmp(signature, pSession->headerSignature, signatureSize) == 0; + HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL); + } + return result; +} + +int FwdLockFile_CheckIntegrity(int fileDesc) { + return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc); +} + +const char *FwdLockFile_GetContentType(int fileDesc) { + int sessionId = FwdLockFile_FindSession(fileDesc); + if (sessionId < 0) { + return NULL; + } + return sessionPtrs[sessionId]->pContentType; +} diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.h b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.h new file mode 100644 index 0000000..fc64050 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/decoder/FwdLockFile.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FWDLOCKFILE_H__ +#define __FWDLOCKFILE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +/** + * Attaches to an open Forward Lock file. The file position is assumed to be at the beginning of the + * file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A status code. + * @retval 0 Success. + * @retval -1 Failure. + */ +int FwdLockFile_attach(int fileDesc); + +/** + * Opens a Forward Lock file for reading. + * + * @param[in] pFilename A reference to a filename. + * + * @return A file descriptor. + * @retval -1 Failure. + */ +int FwdLockFile_open(const char *pFilename); + +/** + * Reads the specified number of bytes from an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * @param[out] pBuffer A reference to the buffer that should receive the read data. + * @param[in] numBytes The number of bytes to read. + * + * @return The number of bytes read. + * @retval -1 Failure. + */ +ssize_t FwdLockFile_read(int fileDesc, void *pBuffer, size_t numBytes); + +/** + * Updates the file position within an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * @param[in] offset The offset with which to update the file position. + * @param[in] whence One of SEEK_SET, SEEK_CUR, and SEEK_END. + * + * @return The new file position. + * @retval ((off64_t)-1) Failure. + */ +off64_t FwdLockFile_lseek(int fileDesc, off64_t offset, int whence); + +/** + * Detaches from an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A status code. + * @retval 0 Success. + * @retval -1 Failure. + */ +int FwdLockFile_detach(int fileDesc); + +/** + * Closes an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A status code. + * @retval 0 Success. + * @retval -1 Failure. + */ +int FwdLockFile_close(int fileDesc); + +/** + * Checks the data integrity of an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A Boolean value indicating whether the integrity check was successful. + */ +int FwdLockFile_CheckDataIntegrity(int fileDesc); + +/** + * Checks the header integrity of an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A Boolean value indicating whether the integrity check was successful. + */ +int FwdLockFile_CheckHeaderIntegrity(int fileDesc); + +/** + * Checks both the data and header integrity of an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return A Boolean value indicating whether the integrity check was successful. + */ +int FwdLockFile_CheckIntegrity(int fileDesc); + +/** + * Returns the content type of an open Forward Lock file. + * + * @param[in] fileDesc The file descriptor of an open Forward Lock file. + * + * @return + * A reference to the content type. The reference remains valid as long as the file is kept open. + */ +const char *FwdLockFile_GetContentType(int fileDesc); + +#ifdef __cplusplus +} +#endif + +#endif // __FWDLOCKFILE_H__ diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/doc/FwdLock.html b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/FwdLock.html new file mode 100755 index 0000000..8f95cd2 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/FwdLock.html @@ -0,0 +1,1039 @@ +<html> + +<head> +<meta http-equiv=Content-Type content="text/html; charset=windows-1252"> +<meta name=Generator content="Microsoft Word 12 (filtered)"> +<title>Forward Lock Converter and Decoder</title> +<style> +<!-- + /* Font Definitions */ + @font-face + {font-family:SimSun; + panose-1:2 1 6 0 3 1 1 1 1 1;} +@font-face + {font-family:"Cambria Math"; + panose-1:2 4 5 3 5 4 6 3 2 4;} +@font-face + {font-family:Tahoma; + panose-1:2 11 6 4 3 5 4 4 2 4;} +@font-face + {font-family:"Lucida Console","DejaVu Sans Mono"; + panose-1:2 11 6 9 4 5 4 2 2 4;} +@font-face + {font-family:"\@SimSun"; + panose-1:2 1 6 0 3 1 1 1 1 1;} + /* Style Definitions */ + p.MsoNormal, li.MsoNormal, div.MsoNormal + {margin:0cm; + margin-bottom:.0001pt; + font-size:12.0pt; + font-family:"Times New Roman","serif";} +h1 + {margin-right:0cm; + margin-left:21.6pt; + text-indent:-21.6pt; + page-break-after:avoid; + font-size:16.0pt; + font-family:"Arial","sans-serif";} +h2 + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:3.0pt; + margin-left:28.8pt; + text-indent:-28.8pt; + page-break-after:avoid; + font-size:14.0pt; + font-family:"Arial","sans-serif"; + font-style:italic;} +h3 + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:3.0pt; + margin-left:36.0pt; + text-indent:-36.0pt; + page-break-after:avoid; + font-size:13.0pt; + font-family:"Arial","sans-serif";} +h4 + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:3.0pt; + margin-left:43.2pt; + text-indent:-43.2pt; + page-break-after:avoid; + font-size:14.0pt; + font-family:"Times New Roman","serif";} +h5 + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:3.0pt; + margin-left:50.4pt; + text-indent:-50.4pt; + font-size:13.0pt; + font-family:"Times New Roman","serif"; + font-style:italic;} +h6 + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:3.0pt; + margin-left:57.6pt; + text-indent:-57.6pt; + font-size:11.0pt; + font-family:"Times New Roman","serif";} +p.MsoHeading7, li.MsoHeading7, div.MsoHeading7 + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:3.0pt; + margin-left:64.8pt; + text-indent:-64.8pt; + font-size:12.0pt; + font-family:"Times New Roman","serif";} +p.MsoHeading8, li.MsoHeading8, div.MsoHeading8 + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:3.0pt; + margin-left:72.0pt; + text-indent:-72.0pt; + font-size:12.0pt; + font-family:"Times New Roman","serif"; + font-style:italic;} +p.MsoHeading9, li.MsoHeading9, div.MsoHeading9 + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:3.0pt; + margin-left:79.2pt; + text-indent:-79.2pt; + font-size:11.0pt; + font-family:"Arial","sans-serif";} +p.MsoToc1, li.MsoToc1, div.MsoToc1 + {margin-top:6.0pt; + margin-right:0cm; + margin-bottom:6.0pt; + margin-left:0cm; + line-height:150%; + font-size:10.5pt; + font-family:"Times New Roman","serif"; + text-transform:uppercase; + font-weight:bold;} +p.MsoToc2, li.MsoToc2, div.MsoToc2 + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:12.0pt; + margin-bottom:.0001pt; + line-height:150%; + font-size:10.5pt; + font-family:"Times New Roman","serif"; + font-variant:small-caps;} +p.MsoToc3, li.MsoToc3, div.MsoToc3 + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:24.0pt; + margin-bottom:.0001pt; + line-height:150%; + font-size:10.5pt; + font-family:"Times New Roman","serif"; + font-style:italic;} +p.MsoToc4, li.MsoToc4, div.MsoToc4 + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:36.0pt; + margin-bottom:.0001pt; + font-size:9.0pt; + font-family:"Times New Roman","serif";} +p.MsoToc5, li.MsoToc5, div.MsoToc5 + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:48.0pt; + margin-bottom:.0001pt; + font-size:9.0pt; + font-family:"Times New Roman","serif";} +p.MsoToc6, li.MsoToc6, div.MsoToc6 + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:60.0pt; + margin-bottom:.0001pt; + font-size:9.0pt; + font-family:"Times New Roman","serif";} +p.MsoToc7, li.MsoToc7, div.MsoToc7 + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:72.0pt; + margin-bottom:.0001pt; + font-size:9.0pt; + font-family:"Times New Roman","serif";} +p.MsoToc8, li.MsoToc8, div.MsoToc8 + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:84.0pt; + margin-bottom:.0001pt; + font-size:9.0pt; + font-family:"Times New Roman","serif";} +p.MsoToc9, li.MsoToc9, div.MsoToc9 + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:96.0pt; + margin-bottom:.0001pt; + font-size:9.0pt; + font-family:"Times New Roman","serif";} +p.MsoFootnoteText, li.MsoFootnoteText, div.MsoFootnoteText + {margin:0cm; + margin-bottom:.0001pt; + font-size:10.0pt; + font-family:"Times New Roman","serif";} +p.MsoHeader, li.MsoHeader, div.MsoHeader + {margin:0cm; + margin-bottom:.0001pt; + font-size:12.0pt; + font-family:"Times New Roman","serif";} +p.MsoFooter, li.MsoFooter, div.MsoFooter + {margin:0cm; + margin-bottom:.0001pt; + font-size:12.0pt; + font-family:"Times New Roman","serif";} +p.MsoCaption, li.MsoCaption, div.MsoCaption + {margin:0cm; + margin-bottom:.0001pt; + font-size:11.0pt; + font-family:"Times New Roman","serif"; + font-weight:bold;} +span.MsoFootnoteReference + {vertical-align:super;} +p.MsoTitle, li.MsoTitle, div.MsoTitle + {margin-top:12.0pt; + margin-right:0cm; + margin-bottom:120.0pt; + margin-left:0cm; + text-align:center; + font-size:16.0pt; + font-family:"Arial","sans-serif"; + font-weight:bold;} +p.MsoBodyText, li.MsoBodyText, div.MsoBodyText + {mso-style-link:"Body Text Char"; + margin-top:0cm; + margin-right:0cm; + margin-bottom:6.0pt; + margin-left:0cm; + font-size:12.0pt; + font-family:"Times New Roman","serif";} +a:link, span.MsoHyperlink + {color:blue; + text-decoration:underline;} +a:visited, span.MsoHyperlinkFollowed + {color:purple; + text-decoration:underline;} +p.MsoAcetate, li.MsoAcetate, div.MsoAcetate + {margin:0cm; + margin-bottom:.0001pt; + font-size:8.0pt; + font-family:"Tahoma","sans-serif";} +span.BodyTextChar + {mso-style-name:"Body Text Char"; + mso-style-link:"Body Text";} + /* Page Definitions */ + @page WordSection1 + {size:595.45pt 841.7pt; + margin:72.0pt 90.0pt 72.0pt 90.0pt;} +div.WordSection1 + {page:WordSection1;} +@page WordSection2 + {size:595.45pt 841.7pt; + margin:72.0pt 90.0pt 72.0pt 90.0pt;} +div.WordSection2 + {page:WordSection2;} + /* List Definitions */ + ol + {margin-bottom:0cm;} +ul + {margin-bottom:0cm;} +--> +</style> + +</head> + +<body lang=EN-US link=blue vlink=purple> + +<div class=WordSection1> + +<p class=MsoTitle>Forward Lock Converter And Decoder</p> + +<p class=MsoToc1><span +class=MsoHyperlink><a href="#_Toc276471422">1<span style='font-size:12.0pt; +line-height:150%;color:windowtext;text-transform:none;font-weight:normal; +text-decoration:none'> </span>Introduction<span style='color:windowtext; +display:none;text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>3</span></a></span></p> + +<p class=MsoToc1><span class=MsoHyperlink><a href="#_Toc276471423">2<span +style='font-size:12.0pt;line-height:150%;color:windowtext;text-transform:none; +font-weight:normal;text-decoration:none'> </span>Overview<span +style='color:windowtext;display:none;text-decoration:none'>... </span><span +style='color:windowtext;display:none;text-decoration:none'>3</span></a></span></p> + +<p class=MsoToc1><span class=MsoHyperlink><a href="#_Toc276471424">3<span +style='font-size:12.0pt;line-height:150%;color:windowtext;text-transform:none; +font-weight:normal;text-decoration:none'> </span>Use Cases<span +style='color:windowtext;display:none;text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>4</span></a></span></p> + +<p class=MsoToc2><span class=MsoHyperlink><span style='font-variant:normal !important; +text-transform:uppercase'><a href="#_Toc276471425">3.1<span style='font-size: +12.0pt;line-height:150%;color:windowtext;text-transform:none;text-decoration: +none'> </span>Converter<span style='color:windowtext;display:none; +text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>4</span></a></span></span></p> + +<p class=MsoToc3><span class=MsoHyperlink><a href="#_Toc276471426">3.1.1<span +style='font-size:12.0pt;line-height:150%;color:windowtext;font-style:normal; +text-decoration:none'> </span>Convert Data (Push-Mode Conversion)<span +style='color:windowtext;display:none;text-decoration:none'> </span><span +style='color:windowtext;display:none;text-decoration:none'>4</span></a></span></p> + +<p class=MsoToc3><span class=MsoHyperlink><a href="#_Toc276471427">3.1.2<span +style='font-size:12.0pt;line-height:150%;color:windowtext;font-style:normal; +text-decoration:none'> </span>Convert File (Pull-Mode Conversion)<span +style='color:windowtext;display:none;text-decoration:none'> </span><span +style='color:windowtext;display:none;text-decoration:none'>6</span></a></span></p> + +<p class=MsoToc2><span class=MsoHyperlink><span style='font-variant:normal !important; +text-transform:uppercase'><a href="#_Toc276471428">3.2<span style='font-size: +12.0pt;line-height:150%;color:windowtext;text-transform:none;text-decoration: +none'> </span>Decoder<span style='color:windowtext;display:none; +text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>7</span></a></span></span></p> + +<p class=MsoToc3><span class=MsoHyperlink><a href="#_Toc276471429">3.2.1<span +style='font-size:12.0pt;line-height:150%;color:windowtext;font-style:normal; +text-decoration:none'> </span>Check Integrity<span style='color:windowtext; +display:none;text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>8</span></a></span></p> + +<p class=MsoToc3><span class=MsoHyperlink><a href="#_Toc276471430">3.2.2<span +style='font-size:12.0pt;line-height:150%;color:windowtext;font-style:normal; +text-decoration:none'> </span>Get Content Type<span style='color:windowtext; +display:none;text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>9</span></a></span></p> + +<p class=MsoToc3><span class=MsoHyperlink><a href="#_Toc276471431">3.2.3<span +style='font-size:12.0pt;line-height:150%;color:windowtext;font-style:normal; +text-decoration:none'> </span>Decode File<span style='color:windowtext; +display:none;text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>10</span></a></span></p> + +<p class=MsoToc1><span class=MsoHyperlink><a href="#_Toc276471432">4<span +style='font-size:12.0pt;line-height:150%;color:windowtext;text-transform:none; +font-weight:normal;text-decoration:none'> </span>Definition of the +Internal Forward Lock File Format<span style='color:windowtext;display:none; +text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>11</span></a></span></p> + +<p class=MsoToc2><span class=MsoHyperlink><span style='font-variant:normal !important; +text-transform:uppercase'><a href="#_Toc276471433">4.1<span style='font-size: +12.0pt;line-height:150%;color:windowtext;text-transform:none;text-decoration: +none'> </span>Key Derivation<span style='color:windowtext;display:none; +text-decoration:none'>.. </span><span +style='color:windowtext;display:none;text-decoration:none'>11</span></a></span></span></p> + +<p class=MsoToc2><span class=MsoHyperlink><span style='font-variant:normal !important; +text-transform:uppercase'><a href="#_Toc276471434">4.2<span style='font-size: +12.0pt;line-height:150%;color:windowtext;text-transform:none;text-decoration: +none'> </span>Calculation of the Counters<span style='color:windowtext; +display:none;text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>12</span></a></span></span></p> + +<p class=MsoToc1><span class=MsoHyperlink><a href="#_Toc276471435">5<span +style='font-size:12.0pt;line-height:150%;color:windowtext;text-transform:none; +font-weight:normal;text-decoration:none'> </span>Unit Test Cases<span +style='color:windowtext;display:none;text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>12</span></a></span></p> + +<p class=MsoToc1><span class=MsoHyperlink><a href="#_Toc276471436">6<span +style='font-size:12.0pt;line-height:150%;color:windowtext;text-transform:none; +font-weight:normal;text-decoration:none'> </span>References<span +style='color:windowtext;display:none;text-decoration:none'>. </span><span +style='color:windowtext;display:none;text-decoration:none'>12</span></a></span></p> + +<p class=MsoBodyText></p> + +</div> + +<span style='font-size:12.0pt;font-family:"Times New Roman","serif"'><br +clear=all style='page-break-before:right'> +</span> + +<div class=WordSection2> + +<h1><a name="_Toc276471422"></a><a name="_Ref263085474">1<span +style='font:7.0pt "Times New Roman"'> </span>Introduction</a></h1> + +<p class=MsoBodyText>The internal Forward Lock file format is used for encrypting +inherently unencrypted OMA DRM version 1 Forward Lock and Combined Delivery +files so they can be securely stored on externally accessible file system partitions +such as memory stick.</p> + +<p class=MsoBodyText>Our general strategy is to convert such <i>OMA DRM Message</i> +(‘.dm’) files to internal Forward Lock (‘.fl’) files as soon as they are +downloaded or otherwise transferred to the phone, and not actually provide any +decoders for ‘.dm’ files.</p> + +<h1><a name="_Toc276471423">2<span style='font:7.0pt "Times New Roman"'> +</span>Overview</a></h1> + +<p class=MsoBodyText>The <i>Forward Lock Converter</i> converts OMA DRM Message +files to the internal file format. The <i>Forward Lock Decoder</i> provides a +POSIX-level API for transparent reading and seeking through such a converted +file as if it were unencrypted. The API also includes functions for checking a +file’s integrity and getting the MIME type of its embedded content.</p> + +<p class=MsoBodyText style='margin-bottom:24.0pt'>The converter and decoder are +built into two separate libraries, which share common code for random number +generation and key encryption in a third library. For test purposes there is +also a unit test application. See Figure 1.</p> + +<p class=MsoBodyText style='page-break-after:avoid'><img width=288 height=364 +src="images/image001.gif"></p> + +<p class=MsoCaption style='margin-top:12.0pt;margin-right:0cm;margin-bottom: +12.0pt;margin-left:0cm'><a name="_Ref262730885">Figure </a>1. Block diagram illustrating the dependencies between the executable modules.</p> + +<b><span style='font-size:16.0pt;font-family:"Arial","sans-serif"'><br +clear=all style='page-break-before:always'> +</span></b> + +<h1><a name="_Toc276471424">3<span style='font:7.0pt "Times New Roman"'> +</span>Use Cases</a></h1> + +<p class=MsoBodyText>This section describes all the use cases for the converter +and decoder. It shows the sequence of API calls that should be used to solve +these use cases.</p> + +<h2><a name="_Toc276471425">3.1<span style='font:7.0pt "Times New Roman"'> +</span>Converter</a></h2> + +<p class=MsoBodyText>Through the converter API, conversion can be performed in one +of two ways:</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>1.<span +style='font:7.0pt "Times New Roman"'> </span><i>Push-mode +conversion</i> is when the client progressively feeds data to the converter as +it arrives. This is appropriate when data arrives gradually in chunks, with +idle time in between. Consequently, push mode is used for converting files +being downloaded through HTTP. See section 3.1.1.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>2.<span +style='font:7.0pt "Times New Roman"'> </span><i>Pull-mode +conversion</i> is when the converter drives the process and consumes data from +the client as it needs it. This is appropriate when the entire file to be +converted is readily available. Hence, pull mode is used by the unit test application. +See section 3.1.2.</p> + +<p class=MsoBodyText>Internally, pull-mode conversion is implemented in terms +of the API for push-mode conversion.</p> + +<h3><a name="_Toc276471426"></a><a name="_Ref263085478">3.1.1<span +style='font:7.0pt "Times New Roman"'> </span>Convert Data +(Push-Mode Conversion)</a></h3> + +<p class=MsoBodyText>Push-mode conversion is performed as follows (see also Figure 2):</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>1.<span +style='font:7.0pt "Times New Roman"'> </span><span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockConv_OpenSession</span> +initializes the output parameter and returns a <i>session ID</i> to be used in +subsequent calls to the API. The output parameter is a union of return values +whose correct use at any given moment is determined by the API function last +called.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>2.<span +style='font:7.0pt "Times New Roman"'> </span><span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockConv_ConvertData</span> +is called repeatedly until no more input data remains. Each call converts the +maximum amount of data possible and writes it to the output buffer. The client then +writes this data to file.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>3.<span +style='font:7.0pt "Times New Roman"'> </span><span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockConv_CloseSession</span> +cleans up the session and deallocates the output buffer. If all has gone well, a +two-part cryptographic signature of the output file is calculated. The client +must go back and rewrite part of the file header with this updated signature +information.</p> + +<p class=MsoBodyText>Every time a file is being converted, the converter calls <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockGlue_GetRandomNumber</span> +to generate a new, unique session key. No two converted files look alike, even +if the original files are the same.</p> + +<p class=MsoBodyText><b>Note:</b> The random bytes cannot come from any bare-minimum +implementation of the C-library <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>rand</span> +function—they must be cryptographically secure. Otherwise, security will be +compromised.</p> + +<p class=MsoBodyText>The session key is encrypted and stored within the +converted file. Key encryption is performed using <span style='font-size:10.0pt; +font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockGlue_GetEncryptedKeyLength</span> and <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockGlue_EncryptKey</span>. +These two functions, together with the corresponding decryption function (<span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockGlue_DecryptKey</span>), +are the integration points where an OEM manufacturer may implement their own +key-encryption scheme.</p> + +<p class=MsoBodyText><b>Note:</b> The key-encryption key must be unique to each +device; this is what makes the files forward lock–protected. Ideally, it should +be derived from secret hardware parameters, but at the very least it should be +persistent from one master reset to the next.</p> + +<div style='margin-bottom:24.0pt;border:solid windowtext 1.0pt;padding:1.0pt 4.0pt 1.0pt 4.0pt; +background:#F2F2F2'> + +<p class=MsoBodyText style='background:#F2F2F2;border: +none;padding:0cm'><b>Note:</b> In the open-source implementation of the <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>libfwdlock-common</span> +library, a random key-encryption key is generated and stored in plaintext in +the file system, without being obfuscated in any way (doing so would be futile +since the source code is openly available). This key must be kept secret from +the user, and shouldn’t be possible to extract through backup-and-restore +functionality or the like. OEM manufacturers will probably want to implement a +truly hardware-based device-unique key.</p> + +</div> + +<p class=MsoBodyText style='page-break-after:avoid'><img width=531 height=563 +src="images/image002.gif"></p> + +<p class=MsoCaption style='margin-top:6.0pt;margin-right:0cm;margin-bottom: +12.0pt;margin-left:0cm'><a name="_Ref263085187">Figure </a>2. Converter UC: Convert Data.</p> + +<b><span style='font-size:13.0pt;font-family:"Arial","sans-serif"'><br +clear=all style='page-break-before:always'> +</span></b> + +<h3><a name="_Toc276471427"></a><a name="_Ref263163082">3.1.2<span +style='font:7.0pt "Times New Roman"'> </span>Convert File +(Pull-Mode Conversion)</a></h3> + +<p class=MsoBodyText>Pull-mode conversion is performed by calling <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_ConvertFile</span> +with the filename, unless there is need for a specialized <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>read</span> function, in +which case <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_ConvertOpenFile</span> +should be used directly instead. See Figure 3.</p> + +<p class=MsoBodyText style='margin-bottom:24.0pt'>Internally, <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_ConvertFile</span> +calls <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_ConvertOpenFile</span>. +The latter then proceeds with the conversion using the push-mode API, acting as +the client in the previous use case; see section 3.1.1.</p> + +<p class=MsoBodyText style='page-break-after:avoid'><img width=531 height=731 +src="images/image003.gif"></p> + +<p class=MsoCaption style='margin-top:6.0pt;margin-right:0cm;margin-bottom: +12.0pt;margin-left:0cm'><a name="_Ref263085208">Figure </a>3. Converter UC: Convert File.</p> + +<b><i><span style='font-size:14.0pt;font-family:"Arial","sans-serif"'><br +clear=all style='page-break-before:always'> +</span></i></b> + +<h2><a name="_Toc276471428">3.2<span style='font:7.0pt "Times New Roman"'> +</span>Decoder</a></h2> + +<p class=MsoBodyText>The decoder API allows the client to do the following:</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>1.<span +style='font:7.0pt "Times New Roman"'> </span>Check +the integrity of an internal Forward Lock file, i.e., detect whether it has +been manipulated in any way; see section 3.2.1.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>2.<span +style='font:7.0pt "Times New Roman"'> </span>Get +the MIME type of the embedded content (the “original” MIME type before DRM protection +was applied); see section 3.2.2.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>3.<span +style='font:7.0pt "Times New Roman"'> </span>Decode +the file by random access, i.e., read and seek through it in an arbitrary +manner; see section 3.2.3.</p> + +<p class=MsoBodyText>All subsequent operations on a file first require it to be +opened. Opening a file returns a <i>file descriptor</i>—a handle to be used in +these subsequent operations.</p> + +<p class=MsoBodyText>If the filename is known, an internal Forward Lock file +can be opened using <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_open</span>. +If only the file descriptor of an already open file is available, a decoding +session can instead be initialized using <span style='font-size:10.0pt; +font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_attach</span>.</p> + +<p class=MsoBodyText>Internally, <span style='font-size:10.0pt;font-family: +"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_open</span> calls <span style='font-size:10.0pt; +font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_attach</span>. For efficiency +reasons, <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_attach</span> +therefore assumes that the file position is at the beginning of the file when +the function gets called. A client who calls it directly must make sure that +this assumption holds.</p> + +<p class=MsoBodyText>When a file is being attached, the session key stored in +the file during conversion is decrypted using <span style='font-size:10.0pt; +font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockGlue_GetEncryptedKeyLength</span> and <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockGlue_DecryptKey</span>, +in order to set up for decoding and integrity checking.</p> + +<p class=MsoBodyText>For just getting the content type, however, retrieving the +session key would strictly speaking not be necessary, so there is an +opportunity here to optimize for that if it proves necessary later.</p> + +<p class=MsoBodyText>Symmetrical to <span style='font-size:10.0pt;font-family: +"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_open</span> and <span style='font-size:10.0pt; +font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_attach</span>, there are also functions +for closing a file or detaching from it:</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>1.<span +style='font:7.0pt "Times New Roman"'> </span>If +it was opened with <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_open</span> +it should be closed with <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_close</span>.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>2.<span +style='font:7.0pt "Times New Roman"'> </span>If +it was attached with <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_attach</span> +it should be detached with <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_detach</span>.</p> + +<b><span style='font-size:13.0pt;font-family:"Arial","sans-serif"'><br +clear=all style='page-break-before:always'> +</span></b> + +<h3><a name="_Ref263163099"></a><a name="_Toc276471429">3.2.1<span +style='font:7.0pt "Times New Roman"'> </span>Check Integrity</a></h3> + +<p class=MsoBodyText>There are three methods for checking the integrity of an +internal Forward Lock file, in whole or in part (see also Figure 4):</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>1.<span +style='font:7.0pt "Times New Roman"'> </span><span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_CheckDataIntegrity</span>, +which checks the integrity of the encrypted content data.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>2.<span +style='font:7.0pt "Times New Roman"'> </span><span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_CheckHeaderIntegrity</span>, +which checks the integrity of the file header, including the content type and +other fields not currently supported but reserved for future use.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>3.<span +style='font:7.0pt "Times New Roman"'> </span><span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_CheckIntegrity</span>, +which internally calls first <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_CheckHeaderIntegrity</span> +and then <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_CheckDataIntegrity</span>.</p> + +<p class=MsoBodyText style='margin-bottom:24.0pt'><span style='font-size:10.0pt; +font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_CheckHeaderIntegrity</span> is +generally much faster than <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_CheckDataIntegrity</span>, +whose running time is directly proportional to the size of the file.</p> + +<p class=MsoBodyText style='page-break-after:avoid'><img width=543 height=575 +src="images/image004.gif"></p> + +<p class=MsoCaption style='margin-top:6.0pt;margin-right:0cm;margin-bottom: +12.0pt;margin-left:0cm'><a name="_Ref263163308">Figure </a>4. Decoder UC: Check Integrity.</p> + +<b><span style='font-size:13.0pt;font-family:"Arial","sans-serif"'><br +clear=all style='page-break-before:always'> +</span></b> + +<h3><a name="_Toc276471430"></a><a name="_Ref263163117">3.2.2<span +style='font:7.0pt "Times New Roman"'> </span>Get Content Type</a></h3> + +<p class=MsoBodyText style='margin-bottom:24.0pt'><span style='font-size:10.0pt; +font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_GetContentType</span> returns a +read-only reference to an ASCII string containing the MIME type of the +embedded content. This reference is valid as long as the file is kept open. +Clients who need access to the content type after closing the file should make +a copy of the string. See Figure 5 below.</p> + +<p class=MsoBodyText style='page-break-after:avoid'><img width=543 height=488 +src="images/image005.gif"></p> + +<p class=MsoCaption style='margin-top:6.0pt;margin-right:0cm;margin-bottom: +12.0pt;margin-left:0cm'><a name="_Ref263163392">Figure </a>5. Decoder UC: Get Content Type.</p> + +<b><span style='font-size:13.0pt;font-family:"Arial","sans-serif"'><br +clear=all style='page-break-before:always'> +</span></b> + +<h3><a name="_Toc276471431"></a><a name="_Ref263163137">3.2.3<span +style='font:7.0pt "Times New Roman"'> </span>Decode File</a></h3> + +<p class=MsoBodyText>After opening an internal Forward Lock file (or attaching +to an already open one), it can be transparently read from as if it were +unencrypted. Any number of calls to read data from the current file position or +set it to a new one (which is what <span style='font-size:10.0pt;font-family: +"Lucida Console","DejaVu Sans Mono"'>lseek</span> does) can be made in any order; this is what we +call <i>random access</i>. See Figure 6.</p> + +<p class=MsoBodyText>The Forward Lock Decoder versions of the <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>read</span>, <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>lseek</span>, and <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>close</span> functions +have the exact same signatures as their POSIX counterparts. So, for example, +the call <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_lseek(fd, +0, SEEK_END)</span> returns the size of the embedded content data, i.e., the +size of the original file before DRM protection.</p> + +<p class=MsoBodyText style='margin-bottom:24.0pt'>Moreover, <span +style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>FwdLockFile_open</span> +is like regular POSIX <span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'>open</span> +except it takes only the filename as a parameter—access is always read-only.</p> + +<p class=MsoBodyText style='page-break-after:avoid'><img width=543 height=522 +src="images/image006.gif"></p> + +<p class=MsoCaption style='margin-top:6.0pt;margin-right:0cm;margin-bottom: +12.0pt;margin-left:0cm'><a name="_Ref263166303">Figure </a>6. Decoder UC: Decode File.</p> + +<b><span style='font-size:16.0pt;font-family:"Arial","sans-serif"'><br +clear=all style='page-break-before:always'> +</span></b> + +<h1><a name="_Toc276471432">4<span style='font:7.0pt "Times New Roman"'> +</span>Definition of the Internal Forward Lock File Format</a></h1> + +<p class=MsoBodyText style='margin-bottom:12.0pt'>The inner structure of an internal +Forward Lock file is defined in Table 1 below.</p> + +<table class=MsoNormalTable border=1 cellspacing=0 cellpadding=0 + style='border-collapse:collapse;border:none'> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'><b>Offset [bytes]</b></p> + </td> + <td width=96 valign=top style='width:72.0pt;border:solid windowtext 1.0pt; + border-left:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'><b>Size [bytes]</b></p> + </td> + <td width=361 valign=top style='width:270.85pt;border:solid windowtext 1.0pt; + border-left:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'><b>Description</b></p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>0</p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>4</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>The file signature (so-called + <i>magic number</i>): a four-character code consisting of the letters + F-W-L-K.</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>4</p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>1</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>Version number (0 for the + first version).</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>5</p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>1</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>Indicates the subformat:</p> + <p class=MsoNormal style='page-break-after:avoid'><i>0x00 Forward Lock</i></p> + <p class=MsoNormal style='page-break-after:avoid'><i>0x01 Combined Delivery</i></p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>6</p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>1</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>Usage restriction flags (prohibitions + against usage as ringtone or as wallpaper and screen saver). Also indicates + if the file is bound to a specific SIM card.</p> + <p class=MsoNormal style='page-break-after:avoid'><i>0x00 No usage + restrictions</i></p> + <p class=MsoNormal style='page-break-after:avoid'><i>0x01 Ringtone usage + prohibited</i></p> + <p class=MsoNormal style='page-break-after:avoid'><i>0x02 Screen usage + prohibited</i></p> + <p class=MsoNormal style='page-break-after:avoid'><i>0x80 Bound to SIM</i></p> + <p class=MsoNormal style='page-break-after:avoid'>(Any number of these may be + OR-ed together.)</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>7</p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>1</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>Length of the MIME content + type (<i>k</i>).</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>8</p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'><i>k</i></p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>The MIME content type + (ASCII-encoded without null-character termination).</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>8+<i>k</i></p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'><i>l </i>= 0 or 16</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>If the subformat is + Combined Delivery, this field contains the auto-generated content ID (16 bytes). + If not, this field is zero-size.</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>8+<i>k</i>+<i>l</i></p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'><i>m </i>= 0 or 9</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>If the file is bound to a + specific SIM card, this field contains the 9-byte packed IMSI number. If not, + this field is zero-size.</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>8+<i>k</i>+<i>l</i>+<i>m</i></p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'><i>n</i> ≥ 16</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>The encrypted session key, the + first sixteen bytes of which are also used as the CTR-mode <i>nonce</i> (similar + to the CBC-mode <i>initialization vector</i>).</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>8+<i>k</i>+<i>l</i>+<i>m</i>+<i>n</i></p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>20</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>Data signature—the SHA-1 + HMAC of the encrypted content data.</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>28+<i>k</i>+<i>l</i>+<i>m</i>+<i>n</i></p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>20</p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>Header signature—the SHA-1 + HMAC of all the fields above, including the encrypted session key and data + signature.</p> + </td> + </tr> + <tr> + <td width=111 valign=top style='width:83.4pt;border:solid windowtext 1.0pt; + border-top:none;padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>48+<i>k</i>+<i>l</i>+<i>m</i>+<i>n</i></p> + </td> + <td width=96 valign=top style='width:72.0pt;border-top:none;border-left:none; + border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'><i><to the end of the + file></i></p> + </td> + <td width=361 valign=top style='width:270.85pt;border-top:none;border-left: + none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt; + padding:0cm 5.4pt 0cm 5.4pt'> + <p class=MsoNormal style='page-break-after:avoid'>The content data encrypted + using 128-bit AES in CTR mode.</p> + </td> + </tr> +</table> + +<p class=MsoCaption style='margin-top:6.0pt;margin-right:0cm;margin-bottom: +12.0pt;margin-left:0cm;page-break-after:avoid'><a name="_Ref151269206">Table </a>1. Definition of the fields of an internal Forward Lock file.</p> + +<p class=MsoBodyText>As of now, neither Combined Delivery nor usage +restrictions (including SIM binding) are supported. These fields are reserved +for future use.</p> + +<h2><a name="_Toc276471433">4.1<span style='font:7.0pt "Times New Roman"'> +</span>Key Derivation</a></h2> + +<p class=MsoBodyText>The session key consists of sixteen bytes fetched from a +cryptographically secure random number generator. From the session key, two +separate keys are derived: one used for encryption, the other for signing.</p> + +<p class=MsoBodyText>The encryption key is the output from encrypting the +16-byte all-zero input block {0, 0, …, 0} using 128-bit AES with the random session +key as the key. The signing key is the output from encrypting the 16-byte input +block {1, 0, …, 0} the same way. The keys so derived will be cryptographically +independent from each other.</p> + +<p class=MsoBodyText>The session key is encrypted using a hardware-dependent +key-encryption key unique to each device. The encrypted session key is stored +inside the file, and its first sixteen bytes are also used as the <i>nonce</i> +for the CTR-mode encryption of the content data.</p> + +<h2><a name="_Toc276471434">4.2<span style='font:7.0pt "Times New Roman"'> +</span>Calculation of the Counters</a></h2> + +<p class=MsoBodyText>Using CTR (“counter”) mode, a block cipher such as AES can +be turned into a stream cipher. The process of encryption and decryption is +well defined in [1], except for the specifics of the calculation of the +counters. For the internal Forward Lock file format, the counters are +calculated as follows:</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>1.<span +style='font:7.0pt "Times New Roman"'> </span>The +nonce is interpreted as a 128-bit unsigned integer in little-endian format.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>2.<span +style='font:7.0pt "Times New Roman"'> </span>The +zero-based block sequence number (also a little-endian unsigned integer) is +added modulo 2<sup>128</sup> to the nonce to produce the counter for a given +block.</p> + +<h1><a name="_Toc276471435">5<span style='font:7.0pt "Times New Roman"'> +</span>Unit Test Cases</a></h1> + +<p class=MsoBodyText>Unit test cases for the converter and decoder come in two +varieties:</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>1.<span +style='font:7.0pt "Times New Roman"'> </span><i>Black-box</i> +test cases aim to verify that you get sensible results from malformed or +“tricky” input data.</p> + +<p class=MsoBodyText style='margin-left:36.0pt;text-indent:-18.0pt'>2.<span +style='font:7.0pt "Times New Roman"'> </span><i>White-box</i> +test cases aim to maximize code coverage using knowledge of code internals.</p> + +<p class=MsoBodyText>The black-box test cases are dependent on a specifically +designed set of input files found in the <span style='font-size:10.0pt; +font-family:"Lucida Console","DejaVu Sans Mono"'>forward-lock/internal-format/test/res</span> +directory in the repository. For ‘tests’ variants of the software, these input +files will be automatically installed in the file system image during build.</p> + +<p class=MsoBodyText>Run the test cases from the ADB shell command line as +follows:</p> + +<p class=MsoNormal style='margin-top:0cm;margin-right:0cm;margin-bottom:6.0pt; +margin-left:21.55pt'><span style='font-size:10.0pt;font-family:"Lucida Console","DejaVu Sans Mono"'># +gtest_fwdlock</span></p> + +<p class=MsoBodyText>If all black-box but no white-box test cases fail, the +input files probably can’t be found in the working directory.</p> + +<h1><a name="_Toc276471436">6<span style='font:7.0pt "Times New Roman"'> +</span>References</a></h1> + +<p class=MsoBodyText style='margin-left:28.9pt;text-indent:-28.9pt'>[1]<span +style='font:7.0pt "Times New Roman"'> +</span><a +href="http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf">Dworkin, +Morris: “Recommendation for Block Cipher Modes of Operation—Methods and +Techniques,” NIST Special Publication 800-38A, December 2001.</a><a +name="_Ref151269073"></a></p> + +</div> + +</body> + +</html> diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image001.gif b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image001.gif Binary files differnew file mode 100644 index 0000000..ee94513 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image001.gif diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image002.gif b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image002.gif Binary files differnew file mode 100644 index 0000000..8c12f46 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image002.gif diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image003.gif b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image003.gif Binary files differnew file mode 100644 index 0000000..9e019ca --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image003.gif diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image004.gif b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image004.gif Binary files differnew file mode 100644 index 0000000..cae1d01 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image004.gif diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image005.gif b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image005.gif Binary files differnew file mode 100644 index 0000000..0d87be9 --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image005.gif diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image006.gif b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image006.gif Binary files differnew file mode 100644 index 0000000..9445b6b --- /dev/null +++ b/drm/libdrmframework/plugins/forward-lock/internal-format/doc/images/image006.gif |