+++ /dev/null
-/*******************************************************************************\r
- * ============LICENSE_START====================================================\r
- * * org.onap.aaf\r
- * * ===========================================================================\r
- * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
- * * ===========================================================================\r
- * * Licensed under the Apache License, Version 2.0 (the "License");\r
- * * you may not use this file except in compliance with the License.\r
- * * You may obtain a copy of the License at\r
- * * \r
- * * http://www.apache.org/licenses/LICENSE-2.0\r
- * * \r
- * * Unless required by applicable law or agreed to in writing, software\r
- * * distributed under the License is distributed on an "AS IS" BASIS,\r
- * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * * See the License for the specific language governing permissions and\r
- * * limitations under the License.\r
- * * ============LICENSE_END====================================================\r
- * *\r
- * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
- * *\r
- ******************************************************************************/\r
-package org.onap.aaf.authz.cm.service;\r
-\r
-import java.io.IOException;\r
-import java.net.InetAddress;\r
-import java.net.UnknownHostException;\r
-import java.nio.ByteBuffer;\r
-import java.security.NoSuchAlgorithmException;\r
-import java.security.cert.X509Certificate;\r
-import java.util.ArrayList;\r
-import java.util.Date;\r
-import java.util.List;\r
-\r
-import org.onap.aaf.authz.cm.api.API_Cert;\r
-import org.onap.aaf.authz.cm.ca.CA;\r
-import org.onap.aaf.authz.cm.cert.BCFactory;\r
-import org.onap.aaf.authz.cm.cert.CSRMeta;\r
-import org.onap.aaf.authz.cm.data.CertDrop;\r
-import org.onap.aaf.authz.cm.data.CertRenew;\r
-import org.onap.aaf.authz.cm.data.CertReq;\r
-import org.onap.aaf.authz.cm.data.CertResp;\r
-import org.onap.aaf.authz.cm.validation.Validator;\r
-import org.onap.aaf.authz.env.AuthzTrans;\r
-import org.onap.aaf.authz.layer.Result;\r
-import org.onap.aaf.authz.org.Organization;\r
-import org.onap.aaf.authz.org.OrganizationException;\r
-import org.onap.aaf.authz.org.Organization.Identity;\r
-import org.onap.aaf.dao.CassAccess;\r
-import org.onap.aaf.dao.DAO;\r
-import org.onap.aaf.dao.aaf.cass.ArtiDAO;\r
-import org.onap.aaf.dao.aaf.cass.CacheInfoDAO;\r
-import org.onap.aaf.dao.aaf.cass.CertDAO;\r
-import org.onap.aaf.dao.aaf.cass.CredDAO;\r
-import org.onap.aaf.dao.aaf.cass.HistoryDAO;\r
-import org.onap.aaf.dao.aaf.cass.Status;\r
-import org.onap.aaf.dao.aaf.hl.Question;\r
-\r
-import org.onap.aaf.cadi.Hash;\r
-import org.onap.aaf.cadi.aaf.AAFPermission;\r
-import org.onap.aaf.cadi.aaf.v2_0.AAFCon;\r
-import org.onap.aaf.cadi.cm.Factory;\r
-import org.onap.aaf.inno.env.APIException;\r
-import org.onap.aaf.inno.env.Slot;\r
-import org.onap.aaf.inno.env.util.Chrono;\r
-import com.datastax.driver.core.Cluster;\r
-\r
-\r
-public class CMService {\r
- // If we add more CAs, may want to parameterize\r
- private static final int STD_RENEWAL = 30;\r
- private static final int MAX_RENEWAL = 60;\r
- private static final int MIN_RENEWAL = 10;\r
- \r
- public static final String REQUEST = "request";\r
- public static final String RENEW = "renew";\r
- public static final String DROP = "drop";\r
- public static final String SANS = "san";\r
- \r
- private static final String[] NO_NOTES = new String[0];\r
- private Slot sCertAuth;\r
- private final CertDAO certDAO;\r
- private final CredDAO credDAO;\r
- private final ArtiDAO artiDAO;\r
- private DAO<AuthzTrans, ?>[] daos;\r
-\r
- @SuppressWarnings("unchecked")\r
- public CMService(AuthzTrans trans, CertManAPI certman) throws APIException, IOException {\r
-\r
- sCertAuth = certman.env.slot(API_Cert.CERT_AUTH);\r
- Cluster cluster;\r
- try {\r
- cluster = org.onap.aaf.dao.CassAccess.cluster(certman.env,null);\r
- } catch (IOException e) {\r
- throw new APIException(e);\r
- }\r
-\r
- // jg 4/2015 SessionFilter unneeded... DataStax already deals with Multithreading well\r
- \r
- HistoryDAO hd = new HistoryDAO(trans, cluster, CassAccess.KEYSPACE);\r
- CacheInfoDAO cid = new CacheInfoDAO(trans, hd);\r
- certDAO = new CertDAO(trans, hd, cid);\r
- credDAO = new CredDAO(trans, hd, cid);\r
- artiDAO = new ArtiDAO(trans, hd, cid);\r
- \r
- daos =(DAO<AuthzTrans, ?>[]) new DAO<?,?>[] {\r
- hd,cid,certDAO,credDAO,artiDAO\r
- };\r
-\r
- // Setup Shutdown Hooks for Cluster and Pooled Sessions\r
- Runtime.getRuntime().addShutdownHook(new Thread() {\r
- @Override\r
- public void run() {\r
- for(DAO<AuthzTrans,?> dao : daos) {\r
- dao.close(trans);\r
- }\r
-\r
-// sessionFilter.destroy();\r
- cluster.close();\r
- }\r
- }); \r
- }\r
- \r
- public Result<CertResp> requestCert(AuthzTrans trans,Result<CertReq> req) {\r
- if(req.isOK()) {\r
- CA ca = trans.get(sCertAuth, null);\r
- if(ca==null) {\r
- return Result.err(Result.err(Result.ERR_BadData, "Invalid Cert Authority requested"));\r
- }\r
-\r
- // Allow only AAF CA without special permission\r
- if(!ca.getName().equals("aaf") && !trans.fish( new AAFPermission(ca.getPermType(), ca.getName(), REQUEST))) {\r
- return Result.err(Status.ERR_Denied, "'%s' does not have permission to request Certificates from Certificate Authority '%s'", \r
- trans.user(),ca.getName());\r
- }\r
-\r
- List<String> notes = null;\r
- List<String> fqdns;\r
- String email = null;\r
-\r
- try {\r
- Organization org = trans.org();\r
- \r
- // Policy 1: Requests are only by Pre-Authorized Configurations\r
- ArtiDAO.Data add = null;\r
- try {\r
- for(InetAddress ia : InetAddress.getAllByName(trans.ip())) {\r
- Result<List<ArtiDAO.Data>> ra = artiDAO.read(trans, req.value.mechid,ia.getHostName());\r
- if(ra.isOKhasData()) {\r
- add = ra.value.get(0);\r
- break;\r
- }\r
- }\r
- } catch (UnknownHostException e1) {\r
- return Result.err(Result.ERR_BadData,"There is no host for %s",trans.ip());\r
- }\r
- \r
- if(add==null) {\r
- return Result.err(Result.ERR_BadData,"There is no configuration for %s",req.value.mechid);\r
- }\r
- \r
- // Policy 2: If Config marked as Expired, do not create or renew\r
- Date now = new Date();\r
- if(add.expires!=null && now.after(add.expires)) {\r
- return Result.err(Result.ERR_Policy,"Configuration for %s %s is expired %s",add.mechid,add.machine,Chrono.dateFmt.format(add.expires));\r
- }\r
- \r
- // Policy 3: MechID must be current\r
- Identity muser = org.getIdentity(trans, add.mechid);\r
- if(muser == null) {\r
- return Result.err(Result.ERR_Policy,"MechID must exist in %s",org.getName());\r
- }\r
- \r
- // Policy 4: Sponsor must be current\r
- Identity ouser = muser.owner();\r
- if(ouser==null) {\r
- return Result.err(Result.ERR_Policy,"%s does not have a current sponsor at %s",add.mechid,org.getName());\r
- } else if(!ouser.isFound() || !ouser.isResponsible()) {\r
- return Result.err(Result.ERR_Policy,"%s reports that %s cannot be responsible for %s",org.getName(),trans.user());\r
- }\r
- \r
- // Set Email from most current Sponsor\r
- email = ouser.email();\r
- \r
- // Policy 5: keep Artifact data current\r
- if(!ouser.fullID().equals(add.sponsor)) {\r
- add.sponsor = ouser.fullID();\r
- artiDAO.update(trans, add);\r
- }\r
- \r
- // Policy 6: Requester must be granted Change permission in Namespace requested\r
- String mechNS = AAFCon.reverseDomain(req.value.mechid);\r
- if(mechNS==null) {\r
- return Result.err(Status.ERR_Denied, "%s does not reflect a valid AAF Namespace",req.value.mechid);\r
- }\r
- \r
- // Policy 7: Caller must be the MechID or have specifically delegated permissions\r
- if(!trans.user().equals(req.value.mechid) && !trans.fish(new AAFPermission(mechNS + ".certman", ca.getName() , "request"))) {\r
- return Result.err(Status.ERR_Denied, "%s must have access to modify x509 certs in NS %s",trans.user(),mechNS);\r
- }\r
- \r
- \r
- // Policy 8: SANs only allowed by Exception... need permission\r
- fqdns = new ArrayList<String>();\r
- fqdns.add(add.machine); // machine is first\r
- if(req.value.fqdns.size()>1 && !trans.fish(new AAFPermission(ca.getPermType(), ca.getName(), SANS))) {\r
- if(notes==null) {notes = new ArrayList<String>();}\r
- notes.add("Warning: Subject Alternative Names only allowed by Permission: Get CSO Exception. This Certificate will be created, but without SANs");\r
- } else {\r
- for(String m : req.value.fqdns) {\r
- if(!add.machine.equals(m)) {\r
- fqdns.add(m);\r
- }\r
- }\r
- }\r
- \r
- } catch (Exception e) {\r
- trans.error().log(e);\r
- return Result.err(Status.ERR_Denied,"MechID Sponsorship cannot be determined at this time. Try later");\r
- }\r
- \r
- CSRMeta csrMeta;\r
- try {\r
- csrMeta = BCFactory.createCSRMeta(\r
- ca, \r
- req.value.mechid, \r
- email, \r
- fqdns);\r
- X509Certificate x509 = ca.sign(trans, csrMeta);\r
- if(x509==null) {\r
- return Result.err(Result.ERR_ActionNotCompleted,"x509 Certificate not signed by CA");\r
- }\r
- CertDAO.Data cdd = new CertDAO.Data();\r
- cdd.ca=ca.getName();\r
- cdd.serial=x509.getSerialNumber();\r
- cdd.id=req.value.mechid;\r
- cdd.x500=x509.getSubjectDN().getName();\r
- cdd.x509=Factory.toString(trans, x509);\r
- certDAO.create(trans, cdd);\r
- \r
- CredDAO.Data crdd = new CredDAO.Data();\r
- crdd.other = Question.random.nextInt();\r
- crdd.cred=getChallenge256SaltedHash(csrMeta.challenge(),crdd.other);\r
- crdd.expires = x509.getNotAfter();\r
- crdd.id = req.value.mechid;\r
- crdd.ns = Question.domain2ns(crdd.id);\r
- crdd.type = CredDAO.CERT_SHA256_RSA;\r
- credDAO.create(trans, crdd);\r
- \r
- CertResp cr = new CertResp(trans,x509,csrMeta, compileNotes(notes));\r
- return Result.ok(cr);\r
- } catch (Exception e) {\r
- trans.error().log(e);\r
- return Result.err(Result.ERR_ActionNotCompleted,e.getMessage());\r
- }\r
- } else {\r
- return Result.err(req);\r
- }\r
- }\r
-\r
- public Result<CertResp> renewCert(AuthzTrans trans, Result<CertRenew> renew) {\r
- if(renew.isOK()) {\r
- return Result.err(Result.ERR_NotImplemented,"Not implemented yet");\r
- } else {\r
- return Result.err(renew);\r
- } \r
- }\r
-\r
- public Result<Void> dropCert(AuthzTrans trans, Result<CertDrop> drop) {\r
- if(drop.isOK()) {\r
- return Result.err(Result.ERR_NotImplemented,"Not implemented yet");\r
- } else {\r
- return Result.err(drop);\r
- } \r
- }\r
-\r
- ///////////////\r
- // Artifact\r
- //////////////\r
- public Result<Void> createArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {\r
- Validator v = new Validator().artisRequired(list, 1);\r
- if(v.err()) {\r
- return Result.err(Result.ERR_BadData,v.errs());\r
- }\r
- for(ArtiDAO.Data add : list) {\r
- try {\r
- // Policy 1: MechID must exist in Org\r
- Identity muser = trans.org().getIdentity(trans, add.mechid);\r
- if(muser == null) {\r
- return Result.err(Result.ERR_Denied,"%s is not valid for %s", add.mechid,trans.org().getName());\r
- }\r
- \r
- // Policy 2: MechID must have valid Organization Owner\r
- Identity ouser = muser.owner();\r
- if(ouser == null) {\r
- return Result.err(Result.ERR_Denied,"%s is not a valid Sponsor for %s at %s",\r
- trans.user(),add.mechid,trans.org().getName());\r
- }\r
- \r
- // Policy 3: Calling ID must be MechID Owner\r
- if(!trans.user().equals(ouser.fullID())) {\r
- return Result.err(Result.ERR_Denied,"%s is not the Sponsor for %s at %s",\r
- trans.user(),add.mechid,trans.org().getName());\r
- }\r
-\r
- // Policy 4: Renewal Days are between 10 and 60 (constants, may be parameterized)\r
- if(add.renewDays<MIN_RENEWAL) {\r
- add.renewDays = STD_RENEWAL;\r
- } else if(add.renewDays>MAX_RENEWAL) {\r
- add.renewDays = MAX_RENEWAL;\r
- }\r
- \r
- // Policy 5: If Notify is blank, set to Owner's Email\r
- if(add.notify==null || add.notify.length()==0) {\r
- add.notify = "mailto:"+ouser.email();\r
- }\r
-\r
- // Set Sponsor from Golden Source\r
- add.sponsor = ouser.fullID();\r
- \r
- \r
- } catch (OrganizationException e) {\r
- return Result.err(e);\r
- }\r
- // Add to DB\r
- Result<ArtiDAO.Data> rv = artiDAO.create(trans, add);\r
- // TODO come up with Partial Reporting Scheme, or allow only one at a time.\r
- if(rv.notOK()) {\r
- return Result.err(rv);\r
- }\r
- }\r
- return Result.ok();\r
- }\r
-\r
- public Result<List<ArtiDAO.Data>> readArtifacts(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {\r
- Validator v = new Validator().keys(add);\r
- if(v.err()) {\r
- return Result.err(Result.ERR_BadData,v.errs());\r
- }\r
- String ns = AAFCon.reverseDomain(add.mechid);\r
- \r
- if( trans.user().equals(add.mechid)\r
- || trans.fish(new AAFPermission(ns + ".access", "*", "read"))\r
- || (trans.org().validate(trans,Organization.Policy.OWNS_MECHID,null,add.mechid))==null) {\r
- return artiDAO.read(trans, add);\r
- } else {\r
- 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
- }\r
-\r
- }\r
-\r
- public Result<List<ArtiDAO.Data>> readArtifactsByMechID(AuthzTrans trans, String mechid) throws OrganizationException {\r
- Validator v = new Validator().nullOrBlank("mechid", mechid);\r
- if(v.err()) {\r
- return Result.err(Result.ERR_BadData,v.errs());\r
- }\r
- String ns = AAFCon.reverseDomain(mechid);\r
- \r
- String reason;\r
- if(trans.fish(new AAFPermission(ns + ".access", "*", "read"))\r
- || (reason=trans.org().validate(trans,Organization.Policy.OWNS_MECHID,null,mechid))==null) {\r
- return artiDAO.readByMechID(trans, mechid);\r
- } else {\r
- return Result.err(Result.ERR_Denied,reason); // note: reason is set by 2nd case, if 1st case misses\r
- }\r
-\r
- }\r
-\r
- public Result<List<ArtiDAO.Data>> readArtifactsByMachine(AuthzTrans trans, String machine) {\r
- Validator v = new Validator().nullOrBlank("machine", machine);\r
- if(v.err()) {\r
- return Result.err(Result.ERR_BadData,v.errs());\r
- }\r
- \r
- // TODO do some checks?\r
-\r
- Result<List<ArtiDAO.Data>> rv = artiDAO.readByMachine(trans, machine);\r
- return rv;\r
- }\r
-\r
- public Result<Void> updateArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) throws OrganizationException {\r
- Validator v = new Validator().artisRequired(list, 1);\r
- if(v.err()) {\r
- return Result.err(Result.ERR_BadData,v.errs());\r
- }\r
- \r
- // Check if requesting User is Sponsor\r
- //TODO - Shall we do one, or multiples?\r
- for(ArtiDAO.Data add : list) {\r
- // Policy 1: MechID must exist in Org\r
- Identity muser = trans.org().getIdentity(trans, add.mechid);\r
- if(muser == null) {\r
- return Result.err(Result.ERR_Denied,"%s is not valid for %s", add.mechid,trans.org().getName());\r
- }\r
- \r
- // Policy 2: MechID must have valid Organization Owner\r
- Identity ouser = muser.owner();\r
- if(ouser == null) {\r
- return Result.err(Result.ERR_Denied,"%s is not a valid Sponsor for %s at %s",\r
- trans.user(),add.mechid,trans.org().getName());\r
- }\r
-\r
- // Policy 3: Renewal Days are between 10 and 60 (constants, may be parameterized)\r
- if(add.renewDays<MIN_RENEWAL) {\r
- add.renewDays = STD_RENEWAL;\r
- } else if(add.renewDays>MAX_RENEWAL) {\r
- add.renewDays = MAX_RENEWAL;\r
- }\r
-\r
- // Policy 4: Data is always updated with the latest Sponsor\r
- // Add to Sponsor, to make sure we are always up to date.\r
- add.sponsor = ouser.fullID();\r
-\r
- // Policy 5: If Notify is blank, set to Owner's Email\r
- if(add.notify==null || add.notify.length()==0) {\r
- add.notify = "mailto:"+ouser.email();\r
- }\r
-\r
- // Policy 4: only Owner may update info\r
- if(trans.user().equals(add.sponsor)) {\r
- return artiDAO.update(trans, add);\r
- } else {\r
- return Result.err(Result.ERR_Denied,"%s may not update info for %s",trans.user(),muser.fullID());\r
- }\r
- \r
- }\r
- return Result.err(Result.ERR_BadData,"No Artifacts to update");\r
- }\r
- \r
- public Result<Void> deleteArtifact(AuthzTrans trans, String mechid, String machine) throws OrganizationException {\r
- Validator v = new Validator()\r
- .nullOrBlank("mechid", mechid)\r
- .nullOrBlank("machine", machine);\r
- if(v.err()) {\r
- return Result.err(Result.ERR_BadData,v.errs());\r
- }\r
-\r
- Result<List<ArtiDAO.Data>> rlad = artiDAO.read(trans, mechid, machine);\r
- if(rlad.notOKorIsEmpty()) {\r
- return Result.err(Result.ERR_NotFound,"Artifact for %s %s does not exist.",mechid,machine);\r
- }\r
- \r
- return deleteArtifact(trans,rlad.value.get(0));\r
- }\r
- \r
- private Result<Void> deleteArtifact(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException {\r
- // Policy 1: Record should be delete able only by Existing Sponsor. \r
- String sponsor=null;\r
- Identity muser = trans.org().getIdentity(trans, add.mechid);\r
- if(muser != null) {\r
- Identity ouser = muser.owner();\r
- if(ouser!=null) {\r
- sponsor = ouser.fullID();\r
- }\r
- }\r
- // Policy 1.a: If Sponsorship is deleted in system of Record, then \r
- // accept deletion by sponsor in Artifact Table\r
- if(sponsor==null) {\r
- sponsor = add.sponsor;\r
- }\r
- \r
- String ns = AAFCon.reverseDomain(add.mechid);\r
-\r
- if(trans.fish(new AAFPermission(ns + ".access", "*", "write"))\r
- || trans.user().equals(sponsor)) {\r
- return artiDAO.delete(trans, add, false);\r
- }\r
- return null;\r
- }\r
-\r
- public Result<Void> deleteArtifact(AuthzTrans trans, List<ArtiDAO.Data> list) {\r
- Validator v = new Validator().artisRequired(list, 1);\r
- if(v.err()) {\r
- return Result.err(Result.ERR_BadData,v.errs());\r
- }\r
-\r
- try {\r
- boolean partial = false;\r
- Result<Void> result=null;\r
- for(ArtiDAO.Data add : list) {\r
- result = deleteArtifact(trans, add);\r
- if(result.notOK()) {\r
- partial = true;\r
- }\r
- }\r
- if(result == null) {\r
- result = Result.err(Result.ERR_BadData,"No Artifacts to delete"); \r
- } else if(partial) {\r
- result.partialContent(true);\r
- }\r
- return result;\r
- } catch(Exception e) {\r
- return Result.err(e);\r
- }\r
- }\r
-\r
- private String[] compileNotes(List<String> notes) {\r
- String[] rv;\r
- if(notes==null) {\r
- rv = NO_NOTES;\r
- } else {\r
- rv = new String[notes.size()];\r
- notes.toArray(rv);\r
- }\r
- return rv;\r
- }\r
-\r
- private ByteBuffer getChallenge256SaltedHash(String challenge, int salt) throws NoSuchAlgorithmException {\r
- ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + challenge.length());\r
- bb.putInt(salt);\r
- bb.put(challenge.getBytes());\r
- byte[] hash = Hash.hashSHA256(bb.array());\r
- return ByteBuffer.wrap(hash);\r
- }\r
-}\r