blob: cb3f7cd9bf2123e91d2df7a451d1c6e8189ed44d [file] [log] [blame]
/*
* Copyright (C) 2020-2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "RTCRtpSFrameTransformer.h"
#if ENABLE(WEB_RTC)
#include "SFrameUtils.h"
#include <wtf/Algorithms.h>
namespace WebCore {
static constexpr unsigned AES_CM_128_HMAC_SHA256_NONCE_SIZE = 12;
static inline void writeUInt64(uint8_t* data, uint64_t value, uint8_t valueLength)
{
for (unsigned i = 0; i < valueLength; ++i)
*data++ = (value >> ((valueLength - 1 - i) * 8)) & 0xff;
}
static inline uint64_t readUInt64(const uint8_t* data, size_t size)
{
uint64_t value = 0;
while (size--)
value = (value << 8) | *data++;
return value;
}
static inline uint8_t lengthOfUInt64(uint64_t value)
{
uint8_t length = 0;
do {
++length;
value = value >> 8;
} while (value);
return length;
}
static inline uint8_t computeFirstHeaderByte(uint64_t keyId, uint64_t counter)
{
uint8_t value = 0;
value |= (lengthOfUInt64(counter) - 1) << 4;
if (keyId < 8)
value |= keyId;
else {
value |= (lengthOfUInt64(keyId) - 1);
value |= 1 << 3;
}
return value;
}
static inline Vector<uint8_t> computeIV(uint64_t counter, const Vector<uint8_t>& saltKey)
{
// The saltKey is 12 bytes (AES_CM_128_HMAC_SHA256_NONCE_SIZE), we XOR with the counter as 12 bytes.
// We then extend it to 16 bytes since that is what is expected by the crypto routines.
Vector<uint8_t> iv(16);
for (unsigned i = 0; i < 4; ++i)
iv[i] = saltKey[i];
for (unsigned i = 11; i >= 4; --i) {
auto value = counter & 0xff;
counter = counter >> 8;
iv[i] = value ^ saltKey[i];
}
for (unsigned i = 12; i < 16; ++i)
iv[i] = 0;
return iv;
}
static inline bool hasSignature(uint8_t firstByte)
{
return firstByte & 0x80;
}
static inline bool hasLongKeyLength(uint8_t firstByte)
{
return firstByte & 0x08;
}
struct SFrameHeaderInfo {
uint8_t size;
uint64_t keyId;
uint64_t counter;
};
static inline std::optional<SFrameHeaderInfo> parseSFrameHeader(const uint8_t* data, size_t size)
{
auto* start = data;
uint64_t keyId = 0;
uint64_t counter = 0;
auto firstByte = *data++;
// Signature bit.
if (hasSignature(firstByte))
return { };
size_t counterLength = ((firstByte >> 4) & 0x07) + 1;
if (size < counterLength + 1)
return { };
if (hasLongKeyLength(firstByte)) {
size_t keyLength = (firstByte & 0x07) + 1;
if (size < counterLength + keyLength + 1)
return { };
keyId = readUInt64(data, keyLength);
data += keyLength;
counter = readUInt64(data, counterLength);
data += counterLength;
} else {
keyId = firstByte & 0x07;
counter = readUInt64(data, counterLength);
data += counterLength;
}
uint8_t headerSize = data - start;
return SFrameHeaderInfo { headerSize, keyId, counter };
}
Ref<RTCRtpSFrameTransformer> RTCRtpSFrameTransformer::create(CompatibilityMode mode)
{
return adoptRef(*new RTCRtpSFrameTransformer(mode));
}
RTCRtpSFrameTransformer::RTCRtpSFrameTransformer(CompatibilityMode mode)
: m_compatibilityMode(mode)
{
}
RTCRtpSFrameTransformer::~RTCRtpSFrameTransformer()
{
}
ExceptionOr<void> RTCRtpSFrameTransformer::setEncryptionKey(const Vector<uint8_t>& rawKey, std::optional<uint64_t> keyId)
{
Locker locker { m_keyLock };
return updateEncryptionKey(rawKey, keyId, ShouldUpdateKeys::Yes);
}
bool RTCRtpSFrameTransformer::hasKey(uint64_t keyId) const
{
Locker locker { m_keyLock };
return WTF::anyOf(m_keys, [keyId](auto& key) { return keyId == key.keyId; });
}
ExceptionOr<void> RTCRtpSFrameTransformer::updateEncryptionKey(const Vector<uint8_t>& rawKey, std::optional<uint64_t> keyId, ShouldUpdateKeys shouldUpdateKeys)
{
ASSERT(m_keyLock.isLocked());
auto saltKeyResult = computeSaltKey(rawKey);
if (saltKeyResult.hasException())
return saltKeyResult.releaseException();
ASSERT(saltKeyResult.returnValue().size() >= AES_CM_128_HMAC_SHA256_NONCE_SIZE);
auto authenticationKeyResult = computeAuthenticationKey(rawKey);
if (authenticationKeyResult.hasException())
return authenticationKeyResult.releaseException();
auto encryptionKeyResult = computeEncryptionKey(rawKey);
if (encryptionKeyResult.hasException())
return encryptionKeyResult.releaseException();
if (shouldUpdateKeys == ShouldUpdateKeys::No)
m_keyId = *keyId;
else {
// FIXME: In case keyId is not set, it might be best to use the first non used ID.
if (!keyId)
keyId = m_keys.size();
m_keyId = *keyId;
m_keys.append({ m_keyId, rawKey });
}
m_saltKey = saltKeyResult.releaseReturnValue();
m_authenticationKey = authenticationKeyResult.releaseReturnValue();
m_encryptionKey = encryptionKeyResult.releaseReturnValue();
updateAuthenticationSize();
m_hasKey = true;
return { };
}
RTCRtpSFrameTransformer::TransformResult RTCRtpSFrameTransformer::decryptFrame(Span<const uint8_t> data)
{
auto* frameData = data.data();
auto frameSize = data.size();
Vector<uint8_t> buffer;
switch (m_compatibilityMode) {
case CompatibilityMode::H264: {
auto offset = computeH264PrefixOffset(frameData, frameSize);
frameData += offset;
frameSize -= offset;
if (needsRbspUnescaping(frameData, frameSize)) {
buffer = fromRbsp(frameData, frameSize);
frameData = buffer.data();
frameSize = buffer.size();
}
break;
}
case CompatibilityMode::VP8: {
auto offset = computeVP8PrefixOffset(frameData, frameSize);
frameData += offset;
frameSize -= offset;
break;
}
case CompatibilityMode::None:
break;
}
Locker locker { m_keyLock };
auto header = parseSFrameHeader(frameData, frameSize);
if (!header)
return makeUnexpected(ErrorInformation {Error::Syntax, "Invalid header"_s, 0 });
if (header->counter <= m_counter && m_counter)
return makeUnexpected(ErrorInformation {Error::Syntax, "Invalid counter"_s, 0 });
m_counter = header->counter;
if (header->keyId != m_keyId) {
auto position = m_keys.findIf([keyId = header->keyId](auto& item) { return item.keyId == keyId; });
if (position == notFound)
return makeUnexpected(ErrorInformation { Error::KeyID, "Key ID is unknown"_s, header->keyId });
auto result = updateEncryptionKey(m_keys[position].keyData, header->keyId, ShouldUpdateKeys::No);
if (result.hasException())
return makeUnexpected(ErrorInformation {Error::Other, result.exception().message(), 0 });
}
if (frameSize < (header->size + m_authenticationSize))
return makeUnexpected(ErrorInformation { Error::Syntax, "Chunk is too small for authentication size"_s, 0 });
auto iv = computeIV(m_counter, m_saltKey);
// Compute signature
auto* transmittedSignature = frameData + frameSize - m_authenticationSize;
auto signature = computeEncryptedDataSignature(iv, frameData, header->size, frameData + header->size, frameSize - m_authenticationSize - header->size, m_authenticationKey);
for (size_t cptr = 0; cptr < m_authenticationSize; ++cptr) {
if (signature[cptr] != transmittedSignature[cptr]) {
// FIXME: We should try ratcheting.
return makeUnexpected(ErrorInformation { Error::Authentication, "Authentication failed"_s, 0 });
}
}
// Decrypt data
auto dataSize = frameSize - header->size - m_authenticationSize;
auto result = decryptData(frameData + header->size, dataSize, iv, m_encryptionKey);
if (result.hasException())
return makeUnexpected(ErrorInformation { Error::Other, result.exception().message(), 0 });
return result.releaseReturnValue();
}
RTCRtpSFrameTransformer::TransformResult RTCRtpSFrameTransformer::encryptFrame(Span<const uint8_t> data)
{
auto* frameData = data.data();
auto frameSize = data.size();
static const unsigned MaxHeaderSize = 17;
Vector<uint8_t> transformedData;
SFrameCompatibilityPrefixBuffer prefixBuffer;
switch (m_compatibilityMode) {
case CompatibilityMode::H264:
prefixBuffer = computeH264PrefixBuffer(frameData, frameSize);
break;
case CompatibilityMode::VP8:
prefixBuffer = computeVP8PrefixBuffer(frameData, frameSize);
break;
case CompatibilityMode::None:
break;
}
Locker locker { m_keyLock };
auto iv = computeIV(m_counter, m_saltKey);
transformedData.resize(prefixBuffer.size + frameSize + MaxHeaderSize + m_authenticationSize);
if (prefixBuffer.data)
std::memcpy(transformedData.data(), prefixBuffer.data, prefixBuffer.size);
auto* newDataPointer = transformedData.data() + prefixBuffer.size;
// Fill header.
size_t headerSize = 1;
*newDataPointer = computeFirstHeaderByte(m_keyId, m_counter);
if (m_keyId >= 8) {
auto keyIdLength = lengthOfUInt64(m_keyId);
writeUInt64(newDataPointer + headerSize, m_keyId, keyIdLength);
headerSize += keyIdLength;
}
auto counterLength = lengthOfUInt64(m_counter);
writeUInt64(newDataPointer + headerSize, m_counter, counterLength);
headerSize += counterLength;
transformedData.resize(prefixBuffer.size + frameSize + headerSize + m_authenticationSize);
// Fill encrypted data
auto encryptedData = encryptData(frameData, frameSize, iv, m_encryptionKey);
ASSERT(!encryptedData.hasException());
if (encryptedData.hasException())
return makeUnexpected(ErrorInformation { Error::Other, encryptedData.exception().message(), 0 });
std::memcpy(newDataPointer + headerSize, encryptedData.returnValue().data(), frameSize);
// Fill signature
auto signature = computeEncryptedDataSignature(iv, newDataPointer, headerSize, newDataPointer + headerSize, frameSize, m_authenticationKey);
std::memcpy(newDataPointer + frameSize + headerSize, signature.data(), m_authenticationSize);
if (m_compatibilityMode == CompatibilityMode::H264)
toRbsp(transformedData, prefixBuffer.size);
++m_counter;
return transformedData;
}
RTCRtpSFrameTransformer::TransformResult RTCRtpSFrameTransformer::transform(Span<const uint8_t> data)
{
if (!m_hasKey)
return makeUnexpected(ErrorInformation { Error::KeyID, "Key is not initialized", 0 });
return m_isEncrypting ? encryptFrame(data) : decryptFrame(data);
}
#if !PLATFORM(COCOA)
ExceptionOr<Vector<uint8_t>> RTCRtpSFrameTransformer::computeSaltKey(const Vector<uint8_t>&)
{
return Exception { NotSupportedError };
}
ExceptionOr<Vector<uint8_t>> RTCRtpSFrameTransformer::computeAuthenticationKey(const Vector<uint8_t>&)
{
return Exception { NotSupportedError };
}
ExceptionOr<Vector<uint8_t>> RTCRtpSFrameTransformer::computeEncryptionKey(const Vector<uint8_t>&)
{
return Exception { NotSupportedError };
}
ExceptionOr<Vector<uint8_t>> RTCRtpSFrameTransformer::decryptData(const uint8_t*, size_t, const Vector<uint8_t>&, const Vector<uint8_t>&)
{
return Exception { NotSupportedError };
}
ExceptionOr<Vector<uint8_t>> RTCRtpSFrameTransformer::encryptData(const uint8_t*, size_t, const Vector<uint8_t>&, const Vector<uint8_t>&)
{
return Exception { NotSupportedError };
}
Vector<uint8_t> RTCRtpSFrameTransformer::computeEncryptedDataSignature(const Vector<uint8_t>&, const uint8_t*, size_t, const uint8_t*, size_t, const Vector<uint8_t>&)
{
return { };
}
void RTCRtpSFrameTransformer::updateAuthenticationSize()
{
}
#endif // !PLATFORM(COCOA)
} // namespace WebCore
#endif // ENABLE(WEB_RTC)