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