1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "MediaDrmCryptoSession.h"
10
11 #include "filesystem/File.h"
12 #include "utils/Base64.h"
13 #include "utils/StringUtils.h"
14 #include "utils/log.h"
15
16 #include <stdexcept>
17
18 #include <androidjni/MediaDrm.h>
19 #include <androidjni/UUID.h>
20
21 using namespace DRM;
22 using namespace XbmcCommons;
23
24
25 class CharVecBuffer : public Buffer
26 {
27 public:
CharVecBuffer(const Buffer & buf)28 inline CharVecBuffer(const Buffer& buf) : Buffer(buf) {};
29
CharVecBuffer(const std::vector<char> & vec)30 inline CharVecBuffer(const std::vector<char>& vec)
31 : Buffer(vec.size())
32 {
33 memcpy(data(), vec.data(), vec.size());
34 }
35
operator std::vector<char>() const36 inline operator std::vector<char>() const
37 {
38 return std::vector<char>(data(), data() + capacity());
39 }
40 };
41
42
Register()43 void CMediaDrmCryptoSession::Register()
44 {
45 CCryptoSession::RegisterInterface(CMediaDrmCryptoSession::Create);
46 }
47
CMediaDrmCryptoSession(const std::string & UUID,const std::string & cipherAlgo,const std::string & macAlgo)48 CMediaDrmCryptoSession::CMediaDrmCryptoSession(const std::string& UUID, const std::string& cipherAlgo, const std::string& macAlgo)
49 : m_mediaDrm(nullptr)
50 , m_cryptoSession(nullptr)
51 , m_cipherAlgo(cipherAlgo)
52 , m_macAlgo(macAlgo)
53 , m_hasKeys(false)
54 , m_sessionId(nullptr)
55 {
56 if (UUID.length() != 36 || UUID[8] != '-' || UUID[13] != '-' || UUID[18] != '-' ||
57 UUID[23] != '-')
58 throw std::runtime_error("MediaDrmCryptoSession: Invalid UUID format, expected "
59 "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
60
61 int64_t mostSigBits(0), leastSigBits(0);
62 unsigned int uuidPtr[16];
63 if (sscanf(UUID.c_str(), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
64 &uuidPtr[0], &uuidPtr[1], &uuidPtr[2], &uuidPtr[3], &uuidPtr[4], &uuidPtr[5],
65 &uuidPtr[6], &uuidPtr[7], &uuidPtr[8], &uuidPtr[9], &uuidPtr[10], &uuidPtr[11],
66 &uuidPtr[12], &uuidPtr[13], &uuidPtr[14], &uuidPtr[15]) != 16)
67 {
68 throw std::runtime_error("MediaDrmCryptoSession: Cannot parse UUID: " + UUID);
69 }
70
71 for (unsigned int i(0); i < 8; ++i)
72 mostSigBits = (mostSigBits << 8) | uuidPtr[i];
73 for (unsigned int i(8); i < 16; ++i)
74 leastSigBits = (leastSigBits << 8) | uuidPtr[i];
75
76 CJNIUUID uuid(mostSigBits, leastSigBits);
77
78 m_mediaDrm = new CJNIMediaDrm(uuid);
79
80 if (xbmc_jnienv()->ExceptionCheck())
81 {
82 xbmc_jnienv()->ExceptionClear();
83 CLog::Log(LOGERROR, "MediaDrm: Failure creating instance");
84 throw std::runtime_error("Failure creating MediaDrm");
85 }
86
87 if (!OpenSession())
88 {
89 CLog::Log(LOGERROR, "MediaDrm: Unable to create a session");
90 throw std::runtime_error("Unable to create a session");
91 }
92 }
93
~CMediaDrmCryptoSession()94 CMediaDrmCryptoSession::~CMediaDrmCryptoSession()
95 {
96 if (!m_mediaDrm)
97 return;
98
99 CloseSession();
100
101 m_mediaDrm->release();
102 delete m_mediaDrm, m_mediaDrm = nullptr;
103 }
104
Create(const std::string & UUID,const std::string & cipherAlgo,const std::string & macAlgo)105 CCryptoSession* CMediaDrmCryptoSession::Create(const std::string& UUID, const std::string& cipherAlgo, const std::string& macAlgo)
106 {
107 CMediaDrmCryptoSession *res = nullptr;;
108 try
109 {
110 res = new CMediaDrmCryptoSession(UUID, cipherAlgo, macAlgo);
111 }
112 catch (std::runtime_error& e)
113 {
114 delete res, res = nullptr;
115 }
116 return res;
117 }
118
119 /*****************/
120
121 // Interface methods
GetKeyRequest(const Buffer & init,const std::string & mimeType,bool offlineKey,const std::map<std::string,std::string> & parameters)122 Buffer CMediaDrmCryptoSession::GetKeyRequest(const Buffer& init,
123 const std::string& mimeType,
124 bool offlineKey,
125 const std::map<std::string, std::string>& parameters)
126 {
127 if (m_mediaDrm && m_sessionId)
128 {
129 CJNIMediaDrmKeyRequest req = m_mediaDrm->getKeyRequest(*m_sessionId, CharVecBuffer(init), mimeType,
130 offlineKey ? CJNIMediaDrm::KEY_TYPE_OFFLINE : CJNIMediaDrm::KEY_TYPE_STREAMING, parameters);
131 return CharVecBuffer(req.getData());
132 }
133
134 return Buffer();
135 }
136
137
GetPropertyString(const std::string & name)138 std::string CMediaDrmCryptoSession::GetPropertyString(const std::string& name)
139 {
140 if (m_mediaDrm)
141 return m_mediaDrm->getPropertyString(name);
142
143 return "";
144 }
145
146
ProvideKeyResponse(const Buffer & response)147 std::string CMediaDrmCryptoSession::ProvideKeyResponse(const Buffer& response)
148 {
149 if (m_mediaDrm)
150 {
151 m_hasKeys = true;
152 std::vector<char> res = m_mediaDrm->provideKeyResponse(*m_sessionId, CharVecBuffer(response));
153 return std::string(res.data(), res.size());
154 }
155
156 return "";
157 }
158
RemoveKeys()159 void CMediaDrmCryptoSession::RemoveKeys()
160 {
161 if (m_mediaDrm && m_sessionId && m_hasKeys)
162 {
163 CloseSession();
164 OpenSession();
165 }
166 }
167
RestoreKeys(const std::string & keySetId)168 void CMediaDrmCryptoSession::RestoreKeys(const std::string& keySetId)
169 {
170 if (m_mediaDrm && keySetId != m_keySetId)
171 {
172 m_mediaDrm->restoreKeys(*m_sessionId, std::vector<char>(keySetId.begin(), keySetId.end()));
173 m_hasKeys = true;
174 m_keySetId = keySetId;
175 }
176 }
177
SetPropertyString(const std::string & name,const std::string & value)178 void CMediaDrmCryptoSession::SetPropertyString(const std::string& name, const std::string& value)
179 {
180 if (m_mediaDrm)
181 m_mediaDrm->setPropertyString(name, value);
182 }
183
184 // Crypto methods
Decrypt(const Buffer & cipherKeyId,const Buffer & input,const Buffer & iv)185 Buffer CMediaDrmCryptoSession::Decrypt(const Buffer& cipherKeyId, const Buffer& input, const Buffer& iv)
186 {
187 if (m_cryptoSession)
188 return CharVecBuffer(m_cryptoSession->decrypt(CharVecBuffer(cipherKeyId), CharVecBuffer(input), CharVecBuffer(iv)));
189
190 return Buffer();
191 }
192
Encrypt(const Buffer & cipherKeyId,const Buffer & input,const Buffer & iv)193 Buffer CMediaDrmCryptoSession::Encrypt(const Buffer& cipherKeyId, const Buffer& input, const Buffer& iv)
194 {
195 if (m_cryptoSession)
196 return CharVecBuffer(m_cryptoSession->encrypt(CharVecBuffer(cipherKeyId), CharVecBuffer(input), CharVecBuffer(iv)));
197
198 return Buffer();
199 }
200
Sign(const Buffer & macKeyId,const Buffer & message)201 Buffer CMediaDrmCryptoSession::Sign(const Buffer& macKeyId, const Buffer& message)
202 {
203 if (m_cryptoSession)
204 return CharVecBuffer(m_cryptoSession->sign(CharVecBuffer(macKeyId), CharVecBuffer(message)));
205
206 return Buffer();
207 }
208
Verify(const Buffer & macKeyId,const Buffer & message,const Buffer & signature)209 bool CMediaDrmCryptoSession::Verify(const Buffer& macKeyId, const Buffer& message, const Buffer& signature)
210 {
211 if (m_cryptoSession)
212 return m_cryptoSession->verify(CharVecBuffer(macKeyId), CharVecBuffer(message), CharVecBuffer(signature));
213
214 return false;
215 }
216
217 //Private stuff
OpenSession()218 bool CMediaDrmCryptoSession::OpenSession()
219 {
220 bool provisioned = false;
221 TRYAGAIN:
222 m_sessionId = new CharVecBuffer(m_mediaDrm->openSession());
223 if (xbmc_jnienv()->ExceptionCheck())
224 {
225 xbmc_jnienv()->ExceptionClear();
226 if (provisioned || !ProvisionRequest())
227 {
228 delete m_sessionId, m_sessionId = nullptr;
229 return false;
230 }
231 else
232 {
233 provisioned = true;
234 goto TRYAGAIN;
235 }
236 }
237
238 m_cryptoSession = new CJNIMediaDrmCryptoSession(m_mediaDrm->getCryptoSession(*m_sessionId, m_cipherAlgo, m_macAlgo));
239
240 if (xbmc_jnienv()->ExceptionCheck())
241 {
242 CLog::Log(LOGERROR, "MediaDrm: getCryptoSession failed");
243 xbmc_jnienv()->ExceptionClear();
244 return false;
245 }
246 return true;
247 }
248
CloseSession()249 void CMediaDrmCryptoSession::CloseSession()
250 {
251 if (m_sessionId)
252 {
253 m_mediaDrm->removeKeys(*m_sessionId);
254 m_mediaDrm->closeSession(*m_sessionId);
255
256 if (m_cryptoSession)
257 delete(m_cryptoSession), m_cryptoSession = nullptr;
258
259 delete m_sessionId, m_sessionId = nullptr;
260 m_hasKeys = false;
261 m_keySetId.clear();
262 }
263 }
264
ProvisionRequest()265 bool CMediaDrmCryptoSession::ProvisionRequest()
266 {
267 CLog::Log(LOGINFO, "MediaDrm: starting provisioning");
268
269 CJNIMediaDrmProvisionRequest request = m_mediaDrm->getProvisionRequest();
270 if (xbmc_jnienv()->ExceptionCheck())
271 {
272 CLog::Log(LOGERROR, "MediaDrm: getProvisionRequest failed");
273 xbmc_jnienv()->ExceptionClear();
274 return false;
275 }
276
277 std::vector<char> provData = request.getData();
278 std::string url = request.getDefaultUrl();
279
280 CLog::Log(LOGDEBUG, "MediaDrm: Provisioning: size: %lu, url: %s", provData.size(), url.c_str());
281
282 std::string tmp_str("{\"signedRequest\":\"");
283 tmp_str += std::string(provData.data(), provData.size());
284 tmp_str += "\"}";
285
286 std::string encoded;
287 Base64::Encode(tmp_str.data(), tmp_str.size(), encoded);
288
289 XFILE::CFile file;
290 if (!file.CURLCreate(url))
291 {
292 CLog::Log(LOGERROR, "MediaDrm: CURLCreate failed!");
293 return false;
294 }
295
296 file.CURLAddOption(XFILE::CURL_OPTION_PROTOCOL, "Content-Type", "application/json");
297 file.CURLAddOption(XFILE::CURL_OPTION_PROTOCOL, "seekable", "0");
298 file.CURLAddOption(XFILE::CURL_OPTION_PROTOCOL, "postdata", encoded.c_str());
299
300 if (!file.CURLOpen(0))
301 {
302 CLog::Log(LOGERROR, "MediaDrm: Provisioning server returned failure");
303 return false;
304 }
305 provData.clear();
306 char buf[8192];
307 size_t nbRead;
308
309 // read the file
310 while ((nbRead = file.Read(buf, 8192)) > 0)
311 provData.insert(provData.end(), buf, buf + nbRead);
312
313 m_mediaDrm->provideProvisionResponse(provData);
314 if (xbmc_jnienv()->ExceptionCheck())
315 {
316 CLog::Log(LOGERROR, "MediaDrm: provideProvisionResponse failed");
317 xbmc_jnienv()->ExceptionClear();
318 return false;
319 }
320 return true;
321 }
322