9e6d5fa999639a881c073131ee72d7dff24b26e5
[aaf/authz.git] / authz-certman / src / main / java / com / att / authz / cm / service / CMService.java
1 /*******************************************************************************\r
2  * ============LICENSE_START====================================================\r
3  * * org.onap.aai\r
4  * * ===========================================================================\r
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
6  * * Copyright © 2017 Amdocs\r
7  * * ===========================================================================\r
8  * * Licensed under the Apache License, Version 2.0 (the "License");\r
9  * * you may not use this file except in compliance with the License.\r
10  * * You may obtain a copy of the License at\r
11  * * \r
12  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
13  * * \r
14  *  * Unless required by applicable law or agreed to in writing, software\r
15  * * distributed under the License is distributed on an "AS IS" BASIS,\r
16  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
17  * * See the License for the specific language governing permissions and\r
18  * * limitations under the License.\r
19  * * ============LICENSE_END====================================================\r
20  * *\r
21  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
22  * *\r
23  ******************************************************************************/\r
24 package com.att.authz.cm.service;\r
25 \r
26 import java.io.IOException;\r
27 import java.net.InetAddress;\r
28 import java.net.UnknownHostException;\r
29 import java.nio.ByteBuffer;\r
30 import java.security.NoSuchAlgorithmException;\r
31 import java.security.cert.X509Certificate;\r
32 import java.util.ArrayList;\r
33 import java.util.Date;\r
34 import java.util.List;\r
35 \r
36 import com.att.authz.cm.api.API_Cert;\r
37 import com.att.authz.cm.ca.CA;\r
38 import com.att.authz.cm.cert.BCFactory;\r
39 import com.att.authz.cm.cert.CSRMeta;\r
40 import com.att.authz.cm.data.CertDrop;\r
41 import com.att.authz.cm.data.CertRenew;\r
42 import com.att.authz.cm.data.CertReq;\r
43 import com.att.authz.cm.data.CertResp;\r
44 import com.att.authz.cm.validation.Validator;\r
45 import com.att.authz.env.AuthzTrans;\r
46 import com.att.authz.layer.Result;\r
47 import com.att.authz.org.Organization;\r
48 import com.att.authz.org.Organization.Identity;\r
49 import com.att.authz.org.OrganizationException;\r
50 import com.att.cadi.Hash;\r
51 import com.att.cadi.aaf.AAFPermission;\r
52 import com.att.cadi.aaf.v2_0.AAFCon;\r
53 import com.att.cadi.cm.Factory;\r
54 import com.att.dao.CassAccess;\r
55 import com.att.dao.DAO;\r
56 import com.att.dao.aaf.cass.ArtiDAO;\r
57 import com.att.dao.aaf.cass.CacheInfoDAO;\r
58 import com.att.dao.aaf.cass.CertDAO;\r
59 import com.att.dao.aaf.cass.CredDAO;\r
60 import com.att.dao.aaf.cass.HistoryDAO;\r
61 import com.att.dao.aaf.cass.Status;\r
62 import com.att.dao.aaf.hl.Question;\r
63 import com.att.inno.env.APIException;\r
64 import com.att.inno.env.Slot;\r
65 import com.att.inno.env.util.Chrono;\r
66 import com.datastax.driver.core.Cluster;\r
67 \r
68 \r
69 public class CMService {\r
70         // If we add more CAs, may want to parameterize\r
71         private static final int STD_RENEWAL = 30;\r
72         private static final int MAX_RENEWAL = 60;\r
73         private static final int MIN_RENEWAL = 10;\r
74         \r
75         public static final String REQUEST = "request";\r
76         public static final String RENEW = "renew";\r
77         public static final String DROP = "drop";\r
78         public static final String SANS = "san";\r
79         \r
80         private static final String[] NO_NOTES = new String[0];\r
81         private Slot sCertAuth;\r
82         private final CertDAO certDAO;\r
83         private final CredDAO credDAO;\r
84         private final ArtiDAO artiDAO;\r
85         private DAO<AuthzTrans, ?>[] daos;\r
86 \r
87         @SuppressWarnings("unchecked")\r
88         public CMService(AuthzTrans trans, CertManAPI certman) throws APIException, IOException {\r
89 \r
90                 sCertAuth = certman.env.slot(API_Cert.CERT_AUTH);\r
91                 Cluster cluster;\r
92                 try {\r
93                         cluster = com.att.dao.CassAccess.cluster(certman.env,null);\r
94                 } catch (IOException e) {\r
95                         throw new APIException(e);\r
96                 }\r
97 \r
98                 // jg 4/2015 SessionFilter unneeded... DataStax already deals with Multithreading well\r
99                 \r
100                 HistoryDAO hd = new HistoryDAO(trans,  cluster, CassAccess.KEYSPACE);\r
101                 CacheInfoDAO cid = new CacheInfoDAO(trans, hd);\r
102                 certDAO = new CertDAO(trans, hd, cid);\r
103                 credDAO = new CredDAO(trans, hd, cid);\r
104                 artiDAO = new ArtiDAO(trans, hd, cid);\r
105                 \r
106                 daos =(DAO<AuthzTrans, ?>[]) new DAO<?,?>[] {\r
107                                 hd,cid,certDAO,credDAO,artiDAO\r
108                 };\r
109 \r
110                 // Setup Shutdown Hooks for Cluster and Pooled Sessions\r
111                 Runtime.getRuntime().addShutdownHook(new Thread() {\r
112                         @Override\r
113                         public void run() {\r
114                                 for(DAO<AuthzTrans,?> dao : daos) {\r
115                                         dao.close(trans);\r
116                                 }\r
117 \r
118 //                              sessionFilter.destroy();\r
119                                 cluster.close();\r
120                         }\r
121                 }); \r
122         }\r
123         \r
124         public Result<CertResp> requestCert(AuthzTrans trans,Result<CertReq> req) {\r
125                 if(req.isOK()) {\r
126                         CA ca = trans.get(sCertAuth, null);\r
127                         if(ca==null) {\r
128                                 return Result.err(Result.err(Result.ERR_BadData, "Invalid Cert Authority requested"));\r
129                         }\r
130 \r
131                         // Allow only AAF CA without special permission\r
132                         if(!ca.getName().equals("aaf") && !trans.fish( new AAFPermission(ca.getPermType(), ca.getName(), REQUEST))) {\r
133                                 return Result.err(Status.ERR_Denied, "'%s' does not have permission to request Certificates from Certificate Authority '%s'", \r
134                                                 trans.user(),ca.getName());\r
135                         }\r
136 \r
137                         List<String> notes = null;\r
138                         List<String> fqdns;\r
139                         String email = null;\r
140 \r
141                         try {\r
142                                 Organization org = trans.org();\r
143                                 \r
144                                 // Policy 1: Requests are only by Pre-Authorized Configurations\r
145                                 ArtiDAO.Data add = null;\r
146                                 try {\r
147                                         for(InetAddress ia : InetAddress.getAllByName(trans.ip())) {\r
148                                                 Result<List<ArtiDAO.Data>> ra = artiDAO.read(trans, req.value.mechid,ia.getHostName());\r
149                                                 if(ra.isOKhasData()) {\r
150                                                         add = ra.value.get(0);\r
151                                                         break;\r
152                                                 }\r
153                                         }\r
154                                 } catch (UnknownHostException e1) {\r
155                                         return Result.err(Result.ERR_BadData,"There is no host for %s",trans.ip());\r
156                                 }\r
157                                 \r
158                                 if(add==null) {\r
159                                         return Result.err(Result.ERR_BadData,"There is no configuration for %s",req.value.mechid);\r
160                                 }\r
161                                 \r
162                                 // Policy 2: If Config marked as Expired, do not create or renew\r
163                                 Date now = new Date();\r
164                                 if(add.expires!=null && now.after(add.expires)) {\r
165                                         return Result.err(Result.ERR_Policy,"Configuration for %s %s is expired %s",add.mechid,add.machine,Chrono.dateFmt.format(add.expires));\r
166                                 }\r
167                                 \r
168                                 // Policy 3: MechID must be current\r
169                                 Identity muser = org.getIdentity(trans, add.mechid);\r
170                                 if(muser == null) {\r
171                                         return Result.err(Result.ERR_Policy,"MechID must exist in %s",org.getName());\r
172                                 }\r
173                                 \r
174                                 // Policy 4: Sponsor must be current\r
175                                 Identity ouser = muser.owner();\r
176                                 if(ouser==null) {\r
177                                         return Result.err(Result.ERR_Policy,"%s does not have a current sponsor at %s",add.mechid,org.getName());\r
178                                 } else if(!ouser.isFound() || !ouser.isResponsible()) {\r
179                                         return Result.err(Result.ERR_Policy,"%s reports that %s cannot be responsible for %s",org.getName(),trans.user());\r
180                                 }\r
181                                 \r
182                                         // Set Email from most current Sponsor\r
183                                 email = ouser.email();\r
184                                 \r
185                                 // Policy 5: keep Artifact data current\r
186                                 if(!ouser.fullID().equals(add.sponsor)) {\r
187                                         add.sponsor = ouser.fullID();\r
188                                         artiDAO.update(trans, add);\r
189                                 }\r
190                 \r
191                                 // Policy 6: Requester must be granted Change permission in Namespace requested\r
192                                 String mechNS = AAFCon.reverseDomain(req.value.mechid);\r
193                                 if(mechNS==null) {\r
194                                         return Result.err(Status.ERR_Denied, "%s does not reflect a valid AAF Namespace",req.value.mechid);\r
195                                 }\r
196                                 \r
197                                 // Policy 7: Caller must be the MechID or have specifically delegated permissions\r
198                                 if(!trans.user().equals(req.value.mechid) && !trans.fish(new AAFPermission(mechNS + ".certman", ca.getName() , "request"))) {\r
199                                         return Result.err(Status.ERR_Denied, "%s must have access to modify x509 certs in NS %s",trans.user(),mechNS);\r
200                                 }\r
201                                 \r
202         \r
203                                 // Policy 8: SANs only allowed by Exception... need permission\r
204                                 fqdns = new ArrayList<String>();\r
205                                 fqdns.add(add.machine);  // machine is first\r
206                                 if(req.value.fqdns.size()>1 && !trans.fish(new AAFPermission(ca.getPermType(), ca.getName(), SANS))) {\r
207                                         if(notes==null) {notes = new ArrayList<String>();}\r
208                                         notes.add("Warning: Subject Alternative Names only allowed by Permission: Get CSO Exception.  This Certificate will be created, but without SANs");\r
209                                 } else {\r
210                                         for(String m : req.value.fqdns) {\r
211                                                 if(!add.machine.equals(m)) {\r
212                                                         fqdns.add(m);\r
213                                                 }\r
214                                         }\r
215                                 }\r
216                                 \r
217                         } catch (Exception e) {\r
218                                 trans.error().log(e);\r
219                                 return Result.err(Status.ERR_Denied,"MechID Sponsorship cannot be determined at this time.  Try later");\r
220                         }\r
221                         \r
222                         CSRMeta csrMeta;\r
223                         try {\r
224                                 csrMeta = BCFactory.createCSRMeta(\r
225                                                 ca, \r
226                                                 req.value.mechid, \r
227                                                 email, \r
228                                                 fqdns);\r
229                                 X509Certificate x509 = ca.sign(trans, csrMeta);\r
230                                 if(x509==null) {\r
231                                         return Result.err(Result.ERR_ActionNotCompleted,"x509 Certificate not signed by CA");\r
232                                 }\r
233                                 CertDAO.Data cdd = new CertDAO.Data();\r
234                                 cdd.ca=ca.getName();\r
235                                 cdd.serial=x509.getSerialNumber();\r
236                                 cdd.id=req.value.mechid;\r
237                                 cdd.x500=x509.getSubjectDN().getName();\r
238                                 cdd.x509=Factory.toString(trans, x509);\r
239                                 certDAO.create(trans, cdd);\r
240                                 \r
241                                 CredDAO.Data crdd = new CredDAO.Data();\r
242                                 crdd.other = Question.random.nextInt();\r
243                                 crdd.cred=getChallenge256SaltedHash(csrMeta.challenge(),crdd.other);\r
244                                 crdd.expires = x509.getNotAfter();\r
245                                 crdd.id = req.value.mechid;\r
246                                 crdd.ns = Question.domain2ns(crdd.id);\r
247                                 crdd.type = CredDAO.CERT_SHA256_RSA;\r
248                                 credDAO.create(trans, crdd);\r
249                                 \r
250                                 CertResp cr = new CertResp(trans,x509,csrMeta, compileNotes(notes));\r
251                                 return Result.ok(cr);\r
252                         } catch (Exception e) {\r
253                                 trans.error().log(e);\r
254                                 return Result.err(Result.ERR_ActionNotCompleted,e.getMessage());\r
255                         }\r
256                 } else {\r
257                         return Result.err(req);\r
258                 }\r
259         }\r
260 \r
261     public Result<CertResp> renewCert(AuthzTrans trans, Result<CertRenew> renew) {\r
262                 if(renew.isOK()) {\r
263                         return Result.err(Result.ERR_NotImplemented,"Not implemented yet");\r
264                 } else {\r
265                         return Result.err(renew);\r
266                 }       \r
267         }\r
268 \r
269         public Result<Void> dropCert(AuthzTrans trans, Result<CertDrop> drop) {\r
270                 if(drop.isOK()) {\r
271                         return Result.err(Result.ERR_NotImplemented,"Not implemented yet");\r
272                 } else {\r
273                         return Result.err(drop);\r
274                 }       \r
275         }\r
276 \r
277         ///////////////\r
278         // Artifact\r
279         //////////////\r
280         public Result<Void> createArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {\r
281                 Validator v = new Validator().artisRequired(list, 1);\r
282                 if(v.err()) {\r
283                         return Result.err(Result.ERR_BadData,v.errs());\r
284                 }\r
285                 for(ArtiDAO.Data add : list) {\r
286                         try {\r
287                                 // Policy 1: MechID must exist in Org\r
288                                 Identity muser = trans.org().getIdentity(trans, add.mechid);\r
289                                 if(muser == null) {\r
290                                         return Result.err(Result.ERR_Denied,"%s is not valid for %s", add.mechid,trans.org().getName());\r
291                                 }\r
292                                 \r
293                                 // Policy 2: MechID must have valid Organization Owner\r
294                                 Identity ouser = muser.owner();\r
295                                 if(ouser == null) {\r
296                                         return Result.err(Result.ERR_Denied,"%s is not a valid Sponsor for %s at %s",\r
297                                                         trans.user(),add.mechid,trans.org().getName());\r
298                                 }\r
299                                 \r
300                                 // Policy 3: Calling ID must be MechID Owner\r
301                                 if(!trans.user().equals(ouser.fullID())) {\r
302                                         return Result.err(Result.ERR_Denied,"%s is not the Sponsor for %s at %s",\r
303                                                         trans.user(),add.mechid,trans.org().getName());\r
304                                 }\r
305 \r
306                                 // Policy 4: Renewal Days are between 10 and 60 (constants, may be parameterized)\r
307                                 if(add.renewDays<MIN_RENEWAL) {\r
308                                         add.renewDays = STD_RENEWAL;\r
309                                 } else if(add.renewDays>MAX_RENEWAL) {\r
310                                         add.renewDays = MAX_RENEWAL;\r
311                                 }\r
312                                 \r
313                                 // Policy 5: If Notify is blank, set to Owner's Email\r
314                                 if(add.notify==null || add.notify.length()==0) {\r
315                                         add.notify = "mailto:"+ouser.email();\r
316                                 }\r
317 \r
318                                 // Set Sponsor from Golden Source\r
319                                 add.sponsor = ouser.fullID();\r
320                                 \r
321                                 \r
322                         } catch (OrganizationException e) {\r
323                                 return Result.err(e);\r
324                         }\r
325                         // Add to DB\r
326                         Result<ArtiDAO.Data> rv = artiDAO.create(trans, add);\r
327                         // TODO come up with Partial Reporting Scheme, or allow only one at a time.\r
328                         if(rv.notOK()) {\r
329                                 return Result.err(rv);\r
330                         }\r
331                 }\r
332                 return Result.ok();\r
333         }\r
334 \r
335         public Result<List<ArtiDAO.Data>> readArtifacts(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {\r
336                 Validator v = new Validator().keys(add);\r
337                 if(v.err()) {\r
338                         return Result.err(Result.ERR_BadData,v.errs());\r
339                 }\r
340                 String ns = AAFCon.reverseDomain(add.mechid);\r
341                 \r
342                 if( trans.user().equals(add.mechid)\r
343                         || trans.fish(new AAFPermission(ns + ".access", "*", "read"))\r
344                         || (trans.org().validate(trans,Organization.Policy.OWNS_MECHID,null,add.mechid))==null) {\r
345                                 return artiDAO.read(trans, add);\r
346                 } else {\r
347                         return Result.err(Result.ERR_Denied,"%s is not %s, is not the sponsor, and doesn't have delegated permission.",trans.user(),add.mechid); // note: reason is set by 2nd case, if 1st case misses\r
348                 }\r
349 \r
350         }\r
351 \r
352         public Result<List<ArtiDAO.Data>> readArtifactsByMechID(AuthzTrans trans, String mechid) throws OrganizationException {\r
353                 Validator v = new Validator().nullOrBlank("mechid", mechid);\r
354                 if(v.err()) {\r
355                         return Result.err(Result.ERR_BadData,v.errs());\r
356                 }\r
357                 String ns = AAFCon.reverseDomain(mechid);\r
358                 \r
359                 String reason;\r
360                 if(trans.fish(new AAFPermission(ns + ".access", "*", "read"))\r
361                         || (reason=trans.org().validate(trans,Organization.Policy.OWNS_MECHID,null,mechid))==null) {\r
362                         return artiDAO.readByMechID(trans, mechid);\r
363                 } else {\r
364                         return Result.err(Result.ERR_Denied,reason); // note: reason is set by 2nd case, if 1st case misses\r
365                 }\r
366 \r
367         }\r
368 \r
369         public Result<List<ArtiDAO.Data>> readArtifactsByMachine(AuthzTrans trans, String machine) {\r
370                 Validator v = new Validator().nullOrBlank("machine", machine);\r
371                 if(v.err()) {\r
372                         return Result.err(Result.ERR_BadData,v.errs());\r
373                 }\r
374                 \r
375                 // TODO do some checks?\r
376 \r
377                 Result<List<ArtiDAO.Data>> rv = artiDAO.readByMachine(trans, machine);\r
378                 return rv;\r
379         }\r
380 \r
381         public Result<Void> updateArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) throws OrganizationException {\r
382                 Validator v = new Validator().artisRequired(list, 1);\r
383                 if(v.err()) {\r
384                         return Result.err(Result.ERR_BadData,v.errs());\r
385                 }\r
386                 \r
387                 // Check if requesting User is Sponsor\r
388                 //TODO - Shall we do one, or multiples?\r
389                 for(ArtiDAO.Data add : list) {\r
390                         // Policy 1: MechID must exist in Org\r
391                         Identity muser = trans.org().getIdentity(trans, add.mechid);\r
392                         if(muser == null) {\r
393                                 return Result.err(Result.ERR_Denied,"%s is not valid for %s", add.mechid,trans.org().getName());\r
394                         }\r
395                         \r
396                         // Policy 2: MechID must have valid Organization Owner\r
397                         Identity ouser = muser.owner();\r
398                         if(ouser == null) {\r
399                                 return Result.err(Result.ERR_Denied,"%s is not a valid Sponsor for %s at %s",\r
400                                                 trans.user(),add.mechid,trans.org().getName());\r
401                         }\r
402 \r
403                         // Policy 3: Renewal Days are between 10 and 60 (constants, may be parameterized)\r
404                         if(add.renewDays<MIN_RENEWAL) {\r
405                                 add.renewDays = STD_RENEWAL;\r
406                         } else if(add.renewDays>MAX_RENEWAL) {\r
407                                 add.renewDays = MAX_RENEWAL;\r
408                         }\r
409 \r
410                         // Policy 4: Data is always updated with the latest Sponsor\r
411                         // Add to Sponsor, to make sure we are always up to date.\r
412                         add.sponsor = ouser.fullID();\r
413 \r
414                         // Policy 5: If Notify is blank, set to Owner's Email\r
415                         if(add.notify==null || add.notify.length()==0) {\r
416                                 add.notify = "mailto:"+ouser.email();\r
417                         }\r
418 \r
419                         // Policy 4: only Owner may update info\r
420                         if(trans.user().equals(add.sponsor)) {\r
421                                 return artiDAO.update(trans, add);\r
422                         } else {\r
423                                 return Result.err(Result.ERR_Denied,"%s may not update info for %s",trans.user(),muser.fullID());\r
424                         }\r
425                         \r
426                 }\r
427                 return Result.err(Result.ERR_BadData,"No Artifacts to update");\r
428         }\r
429         \r
430         public Result<Void> deleteArtifact(AuthzTrans trans, String mechid, String machine) throws OrganizationException {\r
431                 Validator v = new Validator()\r
432                                 .nullOrBlank("mechid", mechid)\r
433                                 .nullOrBlank("machine", machine);\r
434                 if(v.err()) {\r
435                         return Result.err(Result.ERR_BadData,v.errs());\r
436                 }\r
437 \r
438                 Result<List<ArtiDAO.Data>> rlad = artiDAO.read(trans, mechid, machine);\r
439                 if(rlad.notOKorIsEmpty()) {\r
440                         return Result.err(Result.ERR_NotFound,"Artifact for %s %s does not exist.",mechid,machine);\r
441                 }\r
442                 \r
443                 return deleteArtifact(trans,rlad.value.get(0));\r
444         }\r
445                 \r
446         private Result<Void> deleteArtifact(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {\r
447                 // Policy 1: Record should be delete able only by Existing Sponsor.  \r
448                 String sponsor=null;\r
449                 Identity muser = trans.org().getIdentity(trans, add.mechid);\r
450                 if(muser != null) {\r
451                         Identity ouser = muser.owner();\r
452                         if(ouser!=null) {\r
453                                 sponsor = ouser.fullID();\r
454                         }\r
455                 }\r
456                 // Policy 1.a: If Sponsorship is deleted in system of Record, then \r
457                 // accept deletion by sponsor in Artifact Table\r
458                 if(sponsor==null) {\r
459                         sponsor = add.sponsor;\r
460                 }\r
461                 \r
462                 String ns = AAFCon.reverseDomain(add.mechid);\r
463 \r
464                 if(trans.fish(new AAFPermission(ns + ".access", "*", "write"))\r
465                                 || trans.user().equals(sponsor)) {\r
466                         return artiDAO.delete(trans, add, false);\r
467                 }\r
468                 return null;\r
469         }\r
470 \r
471         public Result<Void> deleteArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {\r
472                 Validator v = new Validator().artisRequired(list, 1);\r
473                 if(v.err()) {\r
474                         return Result.err(Result.ERR_BadData,v.errs());\r
475                 }\r
476 \r
477                 try {\r
478                         boolean partial = false;\r
479                         Result<Void> result=null;\r
480                         for(ArtiDAO.Data add : list) {\r
481                                 result = deleteArtifact(trans, add);\r
482                                 if(result.notOK()) {\r
483                                         partial = true;\r
484                                 }\r
485                         }\r
486                         if(result == null) {\r
487                                 result = Result.err(Result.ERR_BadData,"No Artifacts to delete"); \r
488                         } else if(partial) {\r
489                                 result.partialContent(true);\r
490                         }\r
491                         return result;\r
492                 } catch(Exception e) {\r
493                         return Result.err(e);\r
494                 }\r
495         }\r
496 \r
497         private String[] compileNotes(List<String> notes) {\r
498                 String[] rv;\r
499                 if(notes==null) {\r
500                         rv = NO_NOTES;\r
501                 } else {\r
502                         rv = new String[notes.size()];\r
503                         notes.toArray(rv);\r
504                 }\r
505                 return rv;\r
506         }\r
507 \r
508         private ByteBuffer getChallenge256SaltedHash(String challenge, int salt) throws NoSuchAlgorithmException {\r
509                 ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + challenge.length());\r
510                 bb.putInt(salt);\r
511                 bb.put(challenge.getBytes());\r
512                 byte[] hash = Hash.hashSHA256(bb.array());\r
513                 return ByteBuffer.wrap(hash);\r
514         }\r
515 }\r