CMService.java-Merge if statement
[aaf/authz.git] / auth / auth-certman / src / main / java / org / onap / aaf / auth / cm / service / CMService.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2018 IBM.
7  * ===========================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
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,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END====================================================
20  *
21  */
22
23 package org.onap.aaf.auth.cm.service;
24
25 import java.io.IOException;
26 import java.net.InetAddress;
27 import java.net.UnknownHostException;
28 import java.nio.ByteBuffer;
29 import java.security.NoSuchAlgorithmException;
30 import java.security.cert.Certificate;
31 import java.security.cert.X509Certificate;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.Date;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Set;
39
40 import org.onap.aaf.auth.cm.AAF_CM;
41 import org.onap.aaf.auth.cm.ca.CA;
42 import org.onap.aaf.auth.cm.ca.X509andChain;
43 import org.onap.aaf.auth.cm.cert.BCFactory;
44 import org.onap.aaf.auth.cm.cert.CSRMeta;
45 import org.onap.aaf.auth.cm.data.CertDrop;
46 import org.onap.aaf.auth.cm.data.CertRenew;
47 import org.onap.aaf.auth.cm.data.CertReq;
48 import org.onap.aaf.auth.cm.data.CertResp;
49 import org.onap.aaf.auth.cm.validation.CertmanValidator;
50 import org.onap.aaf.auth.common.Define;
51 import org.onap.aaf.auth.dao.CassAccess;
52 import org.onap.aaf.auth.dao.cass.ArtiDAO;
53 import org.onap.aaf.auth.dao.cass.CacheInfoDAO;
54 import org.onap.aaf.auth.dao.cass.CertDAO;
55 import org.onap.aaf.auth.dao.cass.CertDAO.Data;
56 import org.onap.aaf.auth.dao.cass.CredDAO;
57 import org.onap.aaf.auth.dao.cass.HistoryDAO;
58 import org.onap.aaf.auth.dao.cass.Status;
59 import org.onap.aaf.auth.dao.hl.Question;
60 import org.onap.aaf.auth.env.AuthzTrans;
61 import org.onap.aaf.auth.layer.Result;
62 import org.onap.aaf.auth.org.Organization;
63 import org.onap.aaf.auth.org.Organization.Identity;
64 import org.onap.aaf.auth.org.OrganizationException;
65 import org.onap.aaf.cadi.Access.Level;
66 import org.onap.aaf.cadi.Hash;
67 import org.onap.aaf.cadi.Permission;
68 import org.onap.aaf.cadi.aaf.AAFPermission;
69 import org.onap.aaf.cadi.config.Config;
70 import org.onap.aaf.cadi.configure.Factory;
71 import org.onap.aaf.cadi.util.FQI;
72 import org.onap.aaf.misc.env.APIException;
73 import org.onap.aaf.misc.env.util.Chrono;
74
75 public class CMService {
76     // If we add more CAs, may want to parameterize
77     private static final int STD_RENEWAL = 30;
78     private static final int MAX_RENEWAL = 60;
79     private static final int MIN_RENEWAL = 10;
80     // Limit total requests
81     private static final int MAX_X509s = 200; // Need a "LIMIT Exception" DB.
82     private static final String MAX_X509S_TAG = "cm_max_x509s"; // be able to adjust limit in future
83
84     public static final String REQUEST = "request";
85     public static final String IGNORE_IPS = "ignoreIPs";
86     public static final String RENEW = "renew";
87     public static final String DROP = "drop";
88     public static final String DOMAIN = "domain";
89     public static final String DYNAMIC_SANS="dynamic_sans";
90
91     private static final String CERTMAN = "certman";
92     private static final String ACCESS = "access";
93
94     private static final String[] NO_NOTES = new String[0];
95     private final Permission root_read_permission;
96     private final CertDAO certDAO;
97     private final CredDAO credDAO;
98     private final ArtiDAO artiDAO;
99     private AAF_CM certManager;
100     private Boolean allowIgnoreIPs;
101     private AAFPermission limitOverridePerm;
102     private int max_509s;
103
104     // @SuppressWarnings("unchecked")
105     public CMService(final AuthzTrans trans, AAF_CM certman) throws APIException, IOException {
106         // Jonathan 4/2015 SessionFilter unneeded... DataStax already deals with
107         // Multithreading well
108
109         HistoryDAO hd = new HistoryDAO(trans, certman.cluster, CassAccess.KEYSPACE);
110         CacheInfoDAO cid = new CacheInfoDAO(trans, hd);
111         certDAO = new CertDAO(trans, hd, cid);
112         credDAO = new CredDAO(trans, hd, cid);
113         artiDAO = new ArtiDAO(trans, hd, cid);
114
115         this.certManager = certman;
116
117         root_read_permission=new AAFPermission(
118                 trans.getProperty(Config.AAF_ROOT_NS, Config.AAF_ROOT_NS_DEF),
119                 ACCESS,
120                 "*",
121                 "read"
122         );
123         try {
124             max_509s = Integer.parseInt(trans.env().getProperty(MAX_X509S_TAG,Integer.toString(MAX_X509s)));
125         } catch (Exception e) {
126             trans.env().log(e, "");
127             max_509s = MAX_X509s;
128         }
129         limitOverridePerm = new AAFPermission(Define.ROOT_NS(),"certman","quantity","override");
130         allowIgnoreIPs = Boolean.valueOf(certman.access.getProperty(Config.CM_ALLOW_IGNORE_IPS, "false"));
131         if(allowIgnoreIPs) {
132             trans.env().access().log(Level.INIT, "Allowing DNS Evaluation to be turned off with <ns>.certman|<ca name>|"+IGNORE_IPS);
133         }
134     }
135
136     public Result<CertResp> requestCert(final AuthzTrans trans, final Result<CertReq> req, final CA ca) {
137         if (req.isOK()) {
138             if (req.value.fqdns.isEmpty()) {
139                 return Result.err(Result.ERR_BadData, "No Machines passed in Request");
140             }
141
142             String key = req.value.fqdns.get(0);
143
144             // Policy 6: Requester must be granted Change permission in Namespace requested
145             String mechNS = FQI.reverseDomain(req.value.mechid);
146             if (mechNS == null) {
147                 return Result.err(Status.ERR_Denied, "%s does not reflect a valid AAF Namespace", req.value.mechid);
148             }
149
150             List<String> notes = null;
151             List<String> fqdns;
152             boolean domain_based = false;
153             boolean dynamic_sans = false;
154
155             if(req.value.fqdns.isEmpty()) {
156                 fqdns = new ArrayList<>();
157             } else {
158                 // Only Template or Dynamic permitted to pass in FQDNs
159                 if (req.value.fqdns.get(0).startsWith("*")) { // Domain set
160                     if (trans.fish(new AAFPermission(null,ca.getPermType(), ca.getName(), DOMAIN))) {
161                         domain_based = true;
162                     } else {
163                         return Result.err(Result.ERR_Denied,
164                               "Domain based Authorizations (" + req.value.fqdns.get(0) + ") requires Exception");
165                     }
166                 } else {
167                     if(trans.fish(new AAFPermission(null, ca.getPermType(), ca.getName(),DYNAMIC_SANS))) {
168                         dynamic_sans = true;
169                     } else {
170                         return Result.err(Result.ERR_Denied,
171                             "Dynamic SANs for (" + req.value.mechid + ") requires Permission");
172                     }
173                 }
174                 fqdns = new ArrayList<>(req.value.fqdns);
175             }
176
177             String email = null;
178
179             try {
180                 Organization org = trans.org();
181
182                 boolean ignoreIPs;
183                 if(allowIgnoreIPs) {
184                     ignoreIPs = trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), IGNORE_IPS));
185                 } else {
186                     ignoreIPs = false;
187                 }
188
189
190                 InetAddress primary = null;
191                 // Organize incoming information to get to appropriate Artifact
192                 if (!fqdns.isEmpty()) { // Passed in FQDNS, validated above
193                     // Accept domain wild cards, but turn into real machines
194                     // Need *domain.com:real.machine.domain.com:san.machine.domain.com:...
195                     if (domain_based) { // Domain set
196                         // check for Permission in Add Artifact?
197                         String domain = fqdns.get(0).substring(1); // starts with *, see above
198                         fqdns.remove(0);
199                         if (fqdns.isEmpty()) {
200                             return Result.err(Result.ERR_Denied,
201                                 "Requests using domain require machine declaration");
202                         }
203
204                         if (!ignoreIPs) {
205                             InetAddress ia = InetAddress.getByName(fqdns.get(0));
206                             if (ia == null) {
207                                 return Result.err(Result.ERR_Denied,
208                                      "Request not made from matching IP matching domain");
209                             } else if (ia.getHostName().endsWith(domain)) {
210                                 primary = ia;
211                             }
212                         }
213
214                     } else {
215                         // Passed in FQDNs, but not starting with *
216                         if (!ignoreIPs) {
217                             for (String cn : req.value.fqdns) {
218                                 try {
219                                     InetAddress[] ias = InetAddress.getAllByName(cn);
220                                     Set<String> potentialSanNames = new HashSet<>();
221                                     for (InetAddress ia1 : ias) {
222                                         InetAddress ia2 = InetAddress.getByAddress(ia1.getAddress());
223                                         if (primary == null && ias.length == 1 && trans.ip().equals(ia1.getHostAddress())) {
224                                             primary = ia1;
225                                         } else if (!cn.equals(ia1.getHostName())
226                                                 && !ia2.getHostName().equals(ia2.getHostAddress())) {
227                                             potentialSanNames.add(ia1.getHostName());
228                                         }
229                                     }
230                                 } catch (UnknownHostException e1) {
231                                     trans.debug().log(e1);
232                                     return Result.err(Result.ERR_BadData, "There is no DNS lookup for %s", cn);
233                                 }
234                             }
235                         }
236                     }
237                 }
238
239                 final String host;
240                 if (ignoreIPs) {
241                     host = req.value.fqdns.get(0);
242                 } else if (primary == null) {
243                     return Result.err(Result.ERR_Denied, "Request not made from matching IP (%s)", req.value.fqdns.get(0));
244                 } else {
245                     String thost = primary.getHostName();
246                     host = thost==null?primary.getHostAddress():thost;
247                 }
248
249                 ArtiDAO.Data add = null;
250                 Result<List<ArtiDAO.Data>> ra = artiDAO.read(trans, req.value.mechid, host);
251                 if (ra.isOKhasData()) {
252                     add = ra.value.get(0); // single key
253                     if(dynamic_sans && (add.sans!=null && !add.sans.isEmpty())) { // do not allow both Dynamic and Artifact SANS
254                         return Result.err(Result.ERR_Denied,"Authorization must not include SANS when doing Dynamic SANS (%s, %s)", req.value.mechid, key);
255                     }
256                 } else {
257                     if(domain_based) {
258                         ra = artiDAO.read(trans, req.value.mechid, key);
259                         if (ra.isOKhasData()) { // is the Template available?
260                             add = ra.value.get(0);
261                             add.machine = host;
262                             for (String s : fqdns) {
263                                 if (!s.equals(add.machine)) {
264                                     add.sans(true).add(s);
265                                 }
266                             }
267                             Result<ArtiDAO.Data> rc = artiDAO.create(trans, add); // Create new Artifact from Template
268                             if (rc.notOK()) {
269                                 return Result.err(rc);
270                             }
271                         } else {
272                             return Result.err(Result.ERR_Denied,"No Authorization Template for %s, %s", req.value.mechid, key);
273                         }
274                     } else {
275                         return Result.err(Result.ERR_Denied,"No Authorization found for %s, %s", req.value.mechid, key);
276                     }
277                 }
278
279                 // Add Artifact listed FQDNs
280                 if(!dynamic_sans) {
281                     if (add.sans != null) {
282                         for (String s : add.sans) {
283                             if (!fqdns.contains(s)) {
284                                 fqdns.add(s);
285                             }
286                         }
287                     }
288                 }
289
290                 // Policy 2: If Config marked as Expired, do not create or renew
291                 Date now = new Date();
292                 if (add.expires != null && now.after(add.expires)) {
293                     return Result.err(Result.ERR_Policy, "Configuration for %s %s is expired %s", add.mechid,
294                             add.machine, Chrono.dateFmt.format(add.expires));
295                 }
296
297                 // Policy 3: MechID must be current
298                 Identity muser = org.getIdentity(trans, add.mechid);
299                 if (muser == null) {
300                     return Result.err(Result.ERR_Policy, "MechID must exist in %s", org.getName());
301                 }
302
303                 // Policy 4: Sponsor must be current
304                 Identity ouser = muser.responsibleTo();
305                 if (ouser == null) {
306                     return Result.err(Result.ERR_Policy, "%s does not have a current sponsor at %s", add.mechid,
307                             org.getName());
308                 } else if (!ouser.isFound() || ouser.mayOwn() != null) {
309                     return Result.err(Result.ERR_Policy, "%s reports that %s cannot be responsible for %s",
310                             org.getName(), trans.user());
311                 }
312
313                 // Set Email from most current Sponsor
314                 email = ouser.email();
315
316                 // Policy 5: keep Artifact data current
317                 if (!ouser.fullID().equals(add.sponsor)) {
318                     add.sponsor = ouser.fullID();
319                     artiDAO.update(trans, add);
320                 }
321
322                 // Policy 7: Caller must be the MechID or have specifically delegated
323                 // permissions
324                 if (!(trans.user().equals(req.value.mechid)
325                         || trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), REQUEST)))) {
326                     return Result.err(Status.ERR_Denied, "%s must have access to modify x509 certs in NS %s",
327                             trans.user(), mechNS);
328                 }
329
330                 // Make sure Primary is the first in fqdns
331                 if (fqdns.size() > 1) {
332                     for (int i = 0; i < fqdns.size(); ++i) {
333                         if (primary==null && !ignoreIPs) {
334                             trans.error().log("CMService var primary is null");
335                         } else {
336                             String fg = fqdns.get(i);
337                             if (fg!=null && primary!=null && fg.equals(primary.getHostName())) {
338                                 if (i != 0) {
339                                     String tmp = fqdns.get(0);
340                                     fqdns.set(0, primary.getHostName());
341                                     fqdns.set(i, tmp);
342                                 }
343                             }
344                         }
345                     }
346                 }
347             } catch (Exception e) {
348                 trans.error().log(e);
349                 return Result.err(Status.ERR_Denied,
350                         "AppID Sponsorship cannot be determined at this time.  Try later.");
351             }
352
353             CSRMeta csrMeta;
354             try {
355                 csrMeta = BCFactory.createCSRMeta(ca, req.value.mechid, email, fqdns);
356                 csrMeta.environment(ca.getEnv());
357
358                 // Before creating, make sure they don't have too many
359                 if(!trans.fish(limitOverridePerm)) {
360                     Result<List<CertDAO.Data>> existing = certDAO.readID(trans, req.value.mechid);
361                     if(existing.isOK()) {
362                         String cn = "CN=" + csrMeta.cn();
363                         int count = 0;
364                         Date now = new Date();
365                         for (CertDAO.Data cdd : existing.value) {
366                             Collection<? extends Certificate> certs = Factory.toX509Certificate(cdd.x509);
367                             for(Iterator<? extends Certificate> iter = certs.iterator(); iter.hasNext();) {
368                                 X509Certificate x509 = (X509Certificate)iter.next();
369                                 if((x509.getNotAfter().after(now) && x509.getSubjectDN().getName().contains(cn))&&(++count>max_509s)) {
370                                         break;
371                                      }
372                             }
373                         }
374                         if(count>max_509s) {
375                             return Result.err(Result.ERR_Denied, "There are too many Certificates generated for " + cn + " for " + req.value.mechid);
376                         }
377                     }
378                 }
379                 // Here is where we send off to CA for Signing.
380                 X509andChain x509ac = ca.sign(trans, csrMeta);
381                 if (x509ac == null) {
382                     return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA");
383                 }
384                 trans.info().printf("X509 Subject: %s", x509ac.getX509().getSubjectDN());
385
386                 X509Certificate x509 = x509ac.getX509();
387                 CertDAO.Data cdd = new CertDAO.Data();
388                 cdd.ca = ca.getName();
389                 cdd.serial = x509.getSerialNumber();
390                 cdd.id = req.value.mechid;
391                 cdd.x500 = x509.getSubjectDN().getName();
392                 cdd.x509 = Factory.toString(trans, x509);
393
394                 certDAO.create(trans, cdd);
395
396                 CredDAO.Data crdd = new CredDAO.Data();
397                 crdd.other = Question.random.nextInt();
398                 crdd.cred = getChallenge256SaltedHash(csrMeta.challenge(), crdd.other);
399                 crdd.expires = x509.getNotAfter();
400                 crdd.id = req.value.mechid;
401                 crdd.ns = Question.domain2ns(crdd.id);
402                 crdd.type = CredDAO.CERT_SHA256_RSA;
403                 crdd.tag = cdd.ca + '|' + cdd.serial.toString();
404                 credDAO.create(trans, crdd);
405
406                 CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(notes));
407                 return Result.ok(cr);
408             } catch (Exception e) {
409                 trans.debug().log(e);
410                 return Result.err(Result.ERR_ActionNotCompleted, e.getMessage());
411             }
412         } else {
413             return Result.err(req);
414         }
415     }
416
417     public Result<CertResp> renewCert(AuthzTrans trans, Result<CertRenew> renew) {
418         if (renew.isOK()) {
419             return Result.err(Result.ERR_NotImplemented, "Not implemented yet");
420         } else {
421             return Result.err(renew);
422         }
423     }
424
425     public Result<Void> dropCert(AuthzTrans trans, Result<CertDrop> drop) {
426         if (drop.isOK()) {
427             return Result.err(Result.ERR_NotImplemented, "Not implemented yet");
428         } else {
429             return Result.err(drop);
430         }
431     }
432
433     public Result<List<Data>> readCertsByMechID(AuthzTrans trans, String mechID) {
434         // Policy 1: To Read, must have NS Read or is Sponsor
435         String ns = Question.domain2ns(mechID);
436         try {
437             if (trans.user().equals(mechID) || trans.fish(new AAFPermission(ns,ACCESS, "*", "read"))
438                     || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, mechID)) == null) {
439                 return certDAO.readID(trans, mechID);
440             } else {
441                 return Result.err(Result.ERR_Denied, "%s is not the ID, Sponsor or NS Owner/Admin for %s at %s",
442                         trans.user(), mechID, trans.org().getName());
443             }
444         } catch (OrganizationException e) {
445             return Result.err(e);
446         }
447     }
448
449     public Result<CertResp> requestPersonalCert(AuthzTrans trans, CA ca) {
450         if (ca.inPersonalDomains(trans.getUserPrincipal())) {
451             Organization org = trans.org();
452
453             // Policy 1: MechID must be current
454             Identity ouser;
455             try {
456                 ouser = org.getIdentity(trans, trans.user());
457             } catch (OrganizationException e1) {
458                 trans.debug().log(e1);
459                 ouser = null;
460             }
461             if (ouser == null) {
462                 return Result.err(Result.ERR_Policy, "Requesting User must exist in %s", org.getName());
463             }
464
465             // Set Email from most current Sponsor
466
467             CSRMeta csrMeta;
468             try {
469                 csrMeta = BCFactory.createPersonalCSRMeta(ca, trans.user(), ouser.email());
470                 X509andChain x509ac = ca.sign(trans, csrMeta);
471                 if (x509ac == null) {
472                     return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA");
473                 }
474                 X509Certificate x509 = x509ac.getX509();
475                 CertDAO.Data cdd = new CertDAO.Data();
476                 cdd.ca = ca.getName();
477                 cdd.serial = x509.getSerialNumber();
478                 cdd.id = trans.user();
479                 cdd.x500 = x509.getSubjectDN().getName();
480                 cdd.x509 = Factory.toString(trans, x509);
481                 certDAO.create(trans, cdd);
482
483                 CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(null));
484                 return Result.ok(cr);
485             } catch (Exception e) {
486                 trans.debug().log(e);
487                 return Result.err(Result.ERR_ActionNotCompleted, e.getMessage());
488             }
489         } else {
490             return Result.err(Result.ERR_Denied, trans.user(), " not supported for CA", ca.getName());
491         }
492     }
493
494     ///////////////
495     // Artifact
496     //////////////
497     public Result<Void> createArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {
498         CertmanValidator v = new CertmanValidator().artisRequired(list, 1);
499         if (v.err()) {
500             return Result.err(Result.ERR_BadData, v.errs());
501         }
502         for (ArtiDAO.Data add : list) {
503             try {
504                 // Policy 1: MechID must exist in Org
505                 Identity muser = trans.org().getIdentity(trans, add.mechid);
506                 if (muser == null) {
507                     return Result.err(Result.ERR_Denied, "%s is not valid for %s", add.mechid, trans.org().getName());
508                 }
509
510                 // Policy 2: MechID must have valid Organization Owner
511                 Identity emailUser;
512                 if (muser.isPerson()) {
513                     emailUser = muser;
514                 } else {
515                     Identity ouser = muser.responsibleTo();
516                     if (ouser == null) {
517                         return Result.err(Result.ERR_Denied, "%s is not a valid Sponsor for %s at %s", trans.user(),
518                                 add.mechid, trans.org().getName());
519                     }
520
521                     // Policy 3: Calling ID must be MechID Owner
522                     if (!trans.user().startsWith(ouser.id())) {
523                         return Result.err(Result.ERR_Denied, "%s is not the Sponsor for %s at %s", trans.user(),
524                                 add.mechid, trans.org().getName());
525                     }
526                     emailUser = ouser;
527                 }
528
529                 // Policy 4: Renewal Days are between 10 and 60 (constants, may be
530                 // parameterized)
531                 if (add.renewDays < MIN_RENEWAL) {
532                     add.renewDays = STD_RENEWAL;
533                 } else if (add.renewDays > MAX_RENEWAL) {
534                     add.renewDays = MAX_RENEWAL;
535                 }
536
537                 // Policy 5: If Notify is blank, set to Owner's Email
538                 if (add.notify == null || add.notify.length() == 0) {
539                     add.notify = "mailto:" + emailUser.email();
540                 }
541
542                 // Policy 6: Only do Domain by Exception
543                 if (add.machine.startsWith("*")) { // Domain set
544                     CA ca = certManager.getCA(add.ca);
545                     if (!trans.fish(new AAFPermission(ca.getPermNS(),ca.getPermType(), add.ca, DOMAIN))) {
546                         return Result.err(Result.ERR_Denied, "Domain Artifacts (%s) requires specific Permission",
547                                 add.machine);
548                     }
549                 }
550
551                 // Set Sponsor from Golden Source
552                 add.sponsor = emailUser.fullID();
553
554             } catch (OrganizationException e) {
555                 return Result.err(e);
556             }
557             // Add to DB
558             Result<ArtiDAO.Data> rv = artiDAO.create(trans, add);
559             //  come up with Partial Reporting Scheme, or allow only one at a time.
560             if (rv.notOK()) {
561                 return Result.err(rv);
562             }
563         }
564         return Result.ok();
565     }
566
567     public Result<List<ArtiDAO.Data>> readArtifacts(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {
568         CertmanValidator v = new CertmanValidator().keys(add);
569         if (v.err()) {
570             return Result.err(Result.ERR_BadData, v.errs());
571         }
572         Result<List<ArtiDAO.Data>> data = artiDAO.read(trans, add);
573         if (data.notOKorIsEmpty()) {
574             return data;
575         }
576         add = data.value.get(0);
577         if (trans.user().equals(add.mechid)
578                 || trans.fish(root_read_permission,
579                 new AAFPermission(add.ns,ACCESS, "*", "read"),
580                 new AAFPermission(add.ns,CERTMAN, add.ca, "read"),
581                 new AAFPermission(add.ns,CERTMAN, add.ca, REQUEST))
582                 || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, add.mechid)) == null) {
583             return data;
584         } else {
585             return Result.err(Result.ERR_Denied,
586                     "%s is not %s, is not the sponsor, and doesn't have delegated permission.", trans.user(),
587                     add.mechid, add.ns + ".certman|" + add.ca + "|read or ...|request"); // note: reason is set by 2nd
588             // case, if 1st case misses
589         }
590
591     }
592
593     public Result<List<ArtiDAO.Data>> readArtifactsByMechID(AuthzTrans trans, String mechid)
594             throws OrganizationException {
595         CertmanValidator v = new CertmanValidator();
596         v.nullOrBlank("mechid", mechid);
597         if (v.err()) {
598             return Result.err(Result.ERR_BadData, v.errs());
599         }
600         String ns = FQI.reverseDomain(mechid);
601
602         String reason;
603         if (trans.fish(new AAFPermission(ns, ACCESS, "*", "read"))
604                 || (reason = trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, mechid)) == null) {
605             return artiDAO.readByMechID(trans, mechid);
606         } else {
607             return Result.err(Result.ERR_Denied, reason); // note: reason is set by 2nd case, if 1st case misses
608         }
609
610     }
611
612     public Result<List<ArtiDAO.Data>> readArtifactsByMachine(AuthzTrans trans, String machine) {
613         CertmanValidator v = new CertmanValidator();
614         v.nullOrBlank("machine", machine);
615         if (v.err()) {
616             return Result.err(Result.ERR_BadData, v.errs());
617         }
618
619         //  do some checks?
620
621         return artiDAO.readByMachine(trans, machine);
622     }
623
624     public Result<List<ArtiDAO.Data>> readArtifactsByNs(AuthzTrans trans, String ns) {
625         CertmanValidator v = new CertmanValidator();
626         v.nullOrBlank("ns", ns);
627         if (v.err()) {
628             return Result.err(Result.ERR_BadData, v.errs());
629         }
630
631         //  do some checks?
632         return artiDAO.readByNs(trans, ns);
633     }
634
635     public Result<Void> updateArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) throws OrganizationException {
636         CertmanValidator v = new CertmanValidator();
637         v.artisRequired(list, 1);
638         if (v.err()) {
639             return Result.err(Result.ERR_BadData, v.errs());
640         }
641
642         // Check if requesting User is Sponsor
643         //  Shall we do one, or multiples?
644         for (ArtiDAO.Data add : list) {
645             // Policy 1: MechID must exist in Org
646             Identity muser = trans.org().getIdentity(trans, add.mechid);
647             if (muser == null) {
648                 return Result.err(Result.ERR_Denied, "%s is not valid for %s", add.mechid, trans.org().getName());
649             }
650
651             // Policy 2: MechID must have valid Organization Owner
652             Identity ouser = muser.responsibleTo();
653             if (ouser == null) {
654                 return Result.err(Result.ERR_Denied, "%s is not a valid Sponsor for %s at %s", trans.user(), add.mechid,
655                         trans.org().getName());
656             }
657
658             // Policy 3: Renewal Days are between 10 and 60 (constants, may be
659             // parameterized)
660             if (add.renewDays < MIN_RENEWAL) {
661                 add.renewDays = STD_RENEWAL;
662             } else if (add.renewDays > MAX_RENEWAL) {
663                 add.renewDays = MAX_RENEWAL;
664             }
665
666             // Policy 4: Data is always updated with the latest Sponsor
667             // Add to Sponsor, to make sure we are always up to date.
668             add.sponsor = ouser.fullID();
669
670             // Policy 5: If Notify is blank, set to Owner's Email
671             if (add.notify == null || add.notify.length() == 0) {
672                 add.notify = "mailto:" + ouser.email();
673             }
674             // Policy 6: Only do Domain by Exception
675             if (add.machine.startsWith("*")) { // Domain set
676                 CA ca = certManager.getCA(add.ca);
677                 if (ca == null) {
678                     return Result.err(Result.ERR_BadData, "CA is required in Artifact");
679                 }
680                 if (!trans.fish(new AAFPermission(null,ca.getPermType(), add.ca, DOMAIN))) {
681                     return Result.err(Result.ERR_Denied, "Domain Artifacts (%s) requires specific Permission",
682                             add.machine);
683                 }
684             }
685
686             // Policy 7: only Owner may update info
687             if (trans.user().startsWith(ouser.id())) {
688                 return artiDAO.update(trans, add);
689             } else {
690                 return Result.err(Result.ERR_Denied, "%s may not update info for %s", trans.user(), muser.fullID());
691             }
692         }
693         return Result.err(Result.ERR_BadData, "No Artifacts to update");
694     }
695
696     public Result<Void> deleteArtifact(AuthzTrans trans, String mechid, String machine) throws OrganizationException {
697         CertmanValidator v = new CertmanValidator();
698         v.nullOrBlank("mechid", mechid).nullOrBlank("machine", machine);
699         if (v.err()) {
700             return Result.err(Result.ERR_BadData, v.errs());
701         }
702
703         Result<List<ArtiDAO.Data>> rlad = artiDAO.read(trans, mechid, machine);
704         if (rlad.notOKorIsEmpty()) {
705             return Result.err(Result.ERR_NotFound, "Artifact for %s %s does not exist.", mechid, machine);
706         }
707
708         return deleteArtifact(trans, rlad.value.get(0));
709     }
710
711     private Result<Void> deleteArtifact(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {
712         // Policy 1: Record should be delete able only by Existing Sponsor.
713         String sponsor = null;
714         Identity muser = trans.org().getIdentity(trans, add.mechid);
715         if (muser != null) {
716             Identity ouser = muser.responsibleTo();
717             if (ouser != null) {
718                 sponsor = ouser.fullID();
719             }
720         }
721         // Policy 1.a: If Sponsorship is deleted in system of Record, then
722         // accept deletion by sponsor in Artifact Table
723         if (sponsor == null) {
724             sponsor = add.sponsor;
725         }
726
727         String ns = FQI.reverseDomain(add.mechid);
728
729         if (trans.fish(new AAFPermission(ns,ACCESS, "*", "write")) || trans.user().equals(sponsor)) {
730             return artiDAO.delete(trans, add, false);
731         }
732         return Result.err(Result.ERR_Denied, "%1 is not allowed to delete this item", trans.user());
733     }
734
735     public Result<Void> deleteArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {
736         CertmanValidator v = new CertmanValidator().artisRequired(list, 1);
737         if (v.err()) {
738             return Result.err(Result.ERR_BadData, v.errs());
739         }
740
741         try {
742             boolean partial = false;
743             Result<Void> result = null;
744             for (ArtiDAO.Data add : list) {
745                 result = deleteArtifact(trans, add);
746                 if (result.notOK()) {
747                     partial = true;
748                 }
749             }
750             if (result == null) {
751                 result = Result.err(Result.ERR_BadData, "No Artifacts to delete");
752             } else if (partial) {
753                 result.partialContent(true);
754             }
755             return result;
756         } catch (Exception e) {
757             return Result.err(e);
758         }
759     }
760
761     private String[] compileNotes(List<String> notes) {
762         String[] rv;
763         if (notes == null) {
764             rv = NO_NOTES;
765         } else {
766             rv = new String[notes.size()];
767             notes.toArray(rv);
768         }
769         return rv;
770     }
771
772     private ByteBuffer getChallenge256SaltedHash(String challenge, int salt) throws NoSuchAlgorithmException {
773         ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + challenge.length());
774         bb.putInt(salt);
775         bb.put(challenge.getBytes());
776         byte[] hash = Hash.hashSHA256(bb.array());
777         return ByteBuffer.wrap(hash);
778     }
779 }