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