View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  package org.opencastproject.security.urlsigning.provider.impl;
22  
23  import org.opencastproject.security.api.SecurityService;
24  import org.opencastproject.security.urlsigning.WowzaResourceStrategyImpl;
25  import org.opencastproject.security.urlsigning.exception.UrlSigningException;
26  import org.opencastproject.security.urlsigning.provider.UrlSigningProvider;
27  import org.opencastproject.urlsigning.common.Policy;
28  import org.opencastproject.urlsigning.common.ResourceStrategy;
29  
30  import org.apache.commons.lang3.exception.ExceptionUtils;
31  import org.osgi.service.cm.ManagedService;
32  import org.osgi.service.component.annotations.Component;
33  import org.osgi.service.component.annotations.Reference;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import java.net.URI;
38  import java.security.MessageDigest;
39  import java.util.Base64;
40  import java.util.Map;
41  import java.util.SortedMap;
42  import java.util.TreeMap;
43  
44  @Component(
45      immediate = true,
46      service = { ManagedService.class, UrlSigningProvider.class },
47      property = {
48          "service.description=Wowza Url Signing Provider",
49          "service.pid=org.opencastproject.security.urlsigning.provider.impl.WowzaUrlSigningProvider"
50      }
51  )
52  public class WowzaUrlSigningProvider extends AbstractUrlSigningProvider {
53  
54    private static final Logger logger = LoggerFactory.getLogger(WowzaUrlSigningProvider.class);
55  
56    /** The Wowza resource strategy to use to convert from the base url to a resource url. */
57    private ResourceStrategy resourceStrategy = new WowzaResourceStrategyImpl();
58  
59    @Override
60    public Logger getLogger() {
61      return logger;
62    }
63  
64    @Override
65    public ResourceStrategy getResourceStrategy() {
66      return resourceStrategy;
67    }
68  
69    @Override
70    public String toString() {
71      return "Wowza URL Signing Provider";
72    }
73  
74    /**
75     * @param policy
76     *             the policy
77     * @return the signed url
78     */
79    @Override
80    public String sign(Policy policy) throws UrlSigningException {
81      if (!accepts(policy.getBaseUrl())) {
82        throw UrlSigningException.urlNotSupported();
83      }
84  
85      try {
86        URI uri = new URI(policy.getBaseUrl());
87  
88        /*
89          For backward compatibility, but i can not see how this could work.
90          According to the documentation 
91          "https://www.wowza.com/docs/how-to-protect-streaming-using-securetoken-in-wowza-streaming-engine" 
92          if you using token v1 we need a TEA implimentation.
93        */
94        if ("rtmp".equals((uri.getScheme()))) {
95          return super.sign(policy);
96        }
97  
98          // Get the key that matches this URI since there must be one that matches as the base url has been accepted.
99        Key key = getKey(policy.getBaseUrl());
100 
101       policy.setResourceStrategy(getResourceStrategy());
102 
103       if (!key.getSecret().contains("@")) {
104         getLogger().error("Given key not valid. (prefix@secret)");
105 
106         throw new Exception("Given key not valid. (prefix@secret)");
107       }
108       String[] wowzaKeyPair = key.getSecret().split("@");
109       String wowzaPrefix = wowzaKeyPair[0];
110       String wowzaSecret = wowzaKeyPair[1];
111 
112       String newUri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(),
113           addSignutureToRequest(policy, wowzaPrefix, wowzaSecret), null).toString();
114       return newUri;
115     } catch (Exception e) {
116       getLogger().error("Unable to create signed URL because {}", ExceptionUtils.getStackTrace(e));
117       throw new UrlSigningException(e);
118     }
119   }
120 
121   /**
122    * @param policy
123    *             the policy
124    * @param encryptionKeyId
125    *             the encription key id
126    * @param encryptionKey
127    *             the encription key
128    * @return true
129    *             if the url is excluded, false otherwise
130    * @exception Exception
131    *             if something goes bad
132    */
133   private String addSignutureToRequest(Policy policy, String encryptionKeyId, String encryptionKey) throws Exception  {
134     final String startTime;
135     final String endTime = Long.toString(policy.getValidUntil().getMillis() / 1000);
136     final String ip;
137 
138     String baseUrl = policy.getBaseUrl();
139     URI url = new URI(policy.getBaseUrl());
140     String resource = policy.getResource();
141     if (policy.getClientIpAddress().isPresent()) {
142       // The ip comes with a slash: /192.168.1.2
143       String ipAux = policy.getClientIpAddress().get().toString();
144       ipAux = ipAux.substring(1, ipAux.length());
145       ip = ipAux;
146     } else {
147       ip = "";
148     }
149 
150     if (policy.getValidFrom().isPresent()) {
151       startTime = Long.toString(policy.getValidFrom().get().getMillis() / 1000);
152     } else {
153       startTime = "";
154     }
155 
156     String queryStringParameters = new String();
157 
158     queryStringParameters += encryptionKeyId + "endtime=" + endTime;
159 
160     if (!"".equals(startTime)) {
161       queryStringParameters += "&" + encryptionKeyId + "starttime=" + startTime;
162     }
163 
164     if (url.getQuery() != null) {
165       String query = url.getQuery();
166       String[] params = query.split("&");
167       for (String param : params) {
168         if (param.contains("=")) {
169           String[] keyValue = param.split("=");
170           queryStringParameters += "&" + encryptionKeyId + keyValue[0] + "=" + keyValue[1];
171         }
172       }
173     }
174 
175     queryStringParameters += "&" + encryptionKeyId + "hash=" + generateHash(baseUrl,
176         resource, ip, encryptionKeyId, encryptionKey, startTime, endTime);
177 
178     return queryStringParameters;
179   }
180 
181   /**
182    * @param baseUrl
183    *             the base url
184    * @param resource
185    *             the resource
186    * @param ip
187    *             the ip
188    * @param encryptionKeyId
189    *             the encription key id
190    * @param encryptionKey
191    *             the encription key
192    * @param startTime
193    *             start time
194    * @param endTime
195    *             end time
196    * @return the generated hashed
197    * @exception Exception
198    *             if something goes bad
199    */
200   private String generateHash(String baseUrl, String resource, String ip,
201       String encryptionKeyId, String encryptionKey, String startTime, String endTime) throws Exception {
202     String urlToHash = resource + "?";
203 
204     if (!"".equals(ip)) {
205       urlToHash += ip + "&" + encryptionKey;
206     } else {
207       urlToHash += encryptionKey;
208     }
209 
210     SortedMap<String, String> arguments = new TreeMap<>();
211 
212     arguments.put(encryptionKeyId + "endtime", endTime);
213 
214     if (!"".equals(startTime)) {
215       arguments.put(encryptionKeyId + "starttime", startTime);
216     }
217 
218     String query = new URI(baseUrl).getQuery();
219     if (null == query) {
220       query = "";
221     }
222 
223     String[] params = query.split("&");
224     for (String param : params) {
225       if (param.contains("=")) {
226         String[] keyValue = param.split("=");
227         arguments.put(encryptionKeyId + keyValue[0], keyValue[1]);
228       }
229     }
230 
231     for (Map.Entry<String,String> entry : arguments.entrySet()) {
232       String value = entry.getValue();
233       String key = entry.getKey();
234       urlToHash += "&" + key + "=" + value;
235     }
236 
237     MessageDigest md = MessageDigest.getInstance("SHA-256");
238     byte[] messageDigest = md.digest(urlToHash.getBytes());
239     String base64Hash = Base64.getEncoder().encodeToString(messageDigest);
240 
241     base64Hash = base64Hash.replaceAll("\\+", "-");
242     base64Hash = base64Hash.replaceAll("/", "_");
243 
244     return base64Hash;
245   }
246 
247   @Reference
248   @Override
249   public void setSecurityService(SecurityService securityService) {
250     super.setSecurityService(securityService);
251   }
252 
253 }