X-Git-Url: https://gerrit.onap.org/r/gitweb?p=aaf%2Fauthz.git;a=blobdiff_plain;f=auth%2Fauth-certman%2Fsrc%2Fmain%2Fjava%2Forg%2Fonap%2Faaf%2Fauth%2Fcm%2Fservice%2FCMService.java;h=aa145f1c74c05bf45076f38702239049d15e7059;hp=f9cd060bfa7b4095696099f24c1c0c62db7a357e;hb=07fb3ece74a9aa1fad8e2a9fab73b4de3e36853b;hpb=32cdd553a8668e6d03a9cf5b11b360d35a63c87f diff --git a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/service/CMService.java b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/service/CMService.java index f9cd060b..aa145f1c 100644 --- a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/service/CMService.java +++ b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/service/CMService.java @@ -3,13 +3,14 @@ * org.onap.aaf * =========================================================================== * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * Modifications Copyright (C) 2018 IBM. * =========================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,10 +27,13 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; @@ -43,6 +47,7 @@ import org.onap.aaf.auth.cm.data.CertRenew; import org.onap.aaf.auth.cm.data.CertReq; import org.onap.aaf.auth.cm.data.CertResp; import org.onap.aaf.auth.cm.validation.CertmanValidator; +import org.onap.aaf.auth.common.Define; import org.onap.aaf.auth.dao.CassAccess; import org.onap.aaf.auth.dao.cass.ArtiDAO; import org.onap.aaf.auth.dao.cass.CacheInfoDAO; @@ -57,6 +62,7 @@ import org.onap.aaf.auth.layer.Result; import org.onap.aaf.auth.org.Organization; import org.onap.aaf.auth.org.Organization.Identity; import org.onap.aaf.auth.org.OrganizationException; +import org.onap.aaf.cadi.Access.Level; import org.onap.aaf.cadi.Hash; import org.onap.aaf.cadi.Permission; import org.onap.aaf.cadi.aaf.AAFPermission; @@ -67,631 +73,709 @@ import org.onap.aaf.misc.env.APIException; import org.onap.aaf.misc.env.util.Chrono; public class CMService { - // If we add more CAs, may want to parameterize - private static final int STD_RENEWAL = 30; - private static final int MAX_RENEWAL = 60; - private static final int MIN_RENEWAL = 10; - - public static final String REQUEST = "request"; - public static final String IGNORE_IPS = "ignoreIPs"; - public static final String RENEW = "renew"; - public static final String DROP = "drop"; - public static final String DOMAIN = "domain"; - - private static final String CERTMAN = "certman"; - private static final String ACCESS = "access"; - - private static final String[] NO_NOTES = new String[0]; - private final Permission root_read_permission; - private final CertDAO certDAO; - private final CredDAO credDAO; - private final ArtiDAO artiDAO; - private AAF_CM certman; - - // @SuppressWarnings("unchecked") - public CMService(final AuthzTrans trans, AAF_CM certman) throws APIException, IOException { - // Jonathan 4/2015 SessionFilter unneeded... DataStax already deals with - // Multithreading well - - HistoryDAO hd = new HistoryDAO(trans, certman.cluster, CassAccess.KEYSPACE); - CacheInfoDAO cid = new CacheInfoDAO(trans, hd); - certDAO = new CertDAO(trans, hd, cid); - credDAO = new CredDAO(trans, hd, cid); - artiDAO = new ArtiDAO(trans, hd, cid); - - this.certman = certman; - - root_read_permission=new AAFPermission( - trans.getProperty(Config.AAF_ROOT_NS, Config.AAF_ROOT_NS_DEF), - "access", - "*", - "read" - ); - } - - public Result requestCert(final AuthzTrans trans, final Result req, final CA ca) { - if (req.isOK()) { - - if (req.value.fqdns.isEmpty()) { - return Result.err(Result.ERR_BadData, "No Machines passed in Request"); - } - - String key = req.value.fqdns.get(0); - - // Policy 6: Requester must be granted Change permission in Namespace requested - String mechNS = FQI.reverseDomain(req.value.mechid); - if (mechNS == null) { - return Result.err(Status.ERR_Denied, "%s does not reflect a valid AAF Namespace", req.value.mechid); - } - - List notes = null; - List fqdns = new ArrayList<>(req.value.fqdns); - - String email = null; - - try { - Organization org = trans.org(); - - boolean ignoreIPs = trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), IGNORE_IPS)); - - InetAddress primary = null; - // Organize incoming information to get to appropriate Artifact - if (!fqdns.isEmpty()) { - // Accept domain wild cards, but turn into real machines - // Need *domain.com:real.machine.domain.com:san.machine.domain.com:... - if (fqdns.get(0).startsWith("*")) { // Domain set - if (!trans.fish(new AAFPermission(null,ca.getPermType(), ca.getName(), DOMAIN))) { - return Result.err(Result.ERR_Denied, - "Domain based Authorizations (" + fqdns.get(0) + ") requires Exception"); - } - - // TODO check for Permission in Add Artifact? - String domain = fqdns.get(0).substring(1); - fqdns.remove(0); - if (fqdns.isEmpty()) { - return Result.err(Result.ERR_Denied, "Requests using domain require machine declaration"); - } - - if (!ignoreIPs) { - InetAddress ia = InetAddress.getByName(fqdns.get(0)); - if (ia == null) { - return Result.err(Result.ERR_Denied, - "Request not made from matching IP matching domain"); - } else if (ia.getHostName().endsWith(domain)) { - primary = ia; - } - } - - } else { - for (String cn : req.value.fqdns) { - try { - InetAddress[] ias = InetAddress.getAllByName(cn); - Set potentialSanNames = new HashSet<>(); - for (InetAddress ia1 : ias) { - InetAddress ia2 = InetAddress.getByAddress(ia1.getAddress()); - if (primary == null && ias.length == 1 && trans.ip().equals(ia1.getHostAddress())) { - primary = ia1; - } else if (!cn.equals(ia1.getHostName()) - && !ia2.getHostName().equals(ia2.getHostAddress())) { - potentialSanNames.add(ia1.getHostName()); - } - } - } catch (UnknownHostException e1) { - return Result.err(Result.ERR_BadData, "There is no DNS lookup for %s", cn); - } - - } - } - } - - final String host; - if(ignoreIPs) { - host = req.value.fqdns.get(0); - } else if (primary == null) { - return Result.err(Result.ERR_Denied, "Request not made from matching IP (%s)", trans.ip()); - } else { - host = primary.getHostAddress(); - } - - ArtiDAO.Data add = null; - Result> ra = artiDAO.read(trans, req.value.mechid, host); - if (ra.isOKhasData()) { - if (add == null) { - add = ra.value.get(0); // single key - } - } else { - ra = artiDAO.read(trans, req.value.mechid, key); - if (ra.isOKhasData()) { // is the Template available? - add = ra.value.get(0); - add.machine = host; - for (String s : fqdns) { - if (!s.equals(add.machine)) { - add.sans(true).add(s); - } - } - Result rc = artiDAO.create(trans, add); // Create new Artifact from Template - if (rc.notOK()) { - return Result.err(rc); - } - } else { - add = ra.value.get(0); - } - } - - // Add Artifact listed FQDNs - if (add.sans != null) { - for (String s : add.sans) { - if (!fqdns.contains(s)) { - fqdns.add(s); - } - } - } - - // Policy 2: If Config marked as Expired, do not create or renew - Date now = new Date(); - if (add.expires != null && now.after(add.expires)) { - return Result.err(Result.ERR_Policy, "Configuration for %s %s is expired %s", add.mechid, - add.machine, Chrono.dateFmt.format(add.expires)); - } - - // Policy 3: MechID must be current - Identity muser = org.getIdentity(trans, add.mechid); - if (muser == null) { - return Result.err(Result.ERR_Policy, "MechID must exist in %s", org.getName()); - } - - // Policy 4: Sponsor must be current - Identity ouser = muser.responsibleTo(); - if (ouser == null) { - return Result.err(Result.ERR_Policy, "%s does not have a current sponsor at %s", add.mechid, - org.getName()); - } else if (!ouser.isFound() || ouser.mayOwn() != null) { - return Result.err(Result.ERR_Policy, "%s reports that %s cannot be responsible for %s", - org.getName(), trans.user()); - } - - // Set Email from most current Sponsor - email = ouser.email(); - - // Policy 5: keep Artifact data current - if (!ouser.fullID().equals(add.sponsor)) { - add.sponsor = ouser.fullID(); - artiDAO.update(trans, add); - } - - // Policy 7: Caller must be the MechID or have specifically delegated - // permissions - if (!(trans.user().equals(req.value.mechid) - || trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), REQUEST)))) { - return Result.err(Status.ERR_Denied, "%s must have access to modify x509 certs in NS %s", - trans.user(), mechNS); - } - - // Make sure Primary is the first in fqdns - if (fqdns.size() > 1) { - for (int i = 0; i < fqdns.size(); ++i) { - if(primary==null) { - trans.error().log("CMService var primary is null"); - } else { - String fg = fqdns.get(i); - if (fg!=null && fg.equals(primary.getHostName())) { - if (i != 0) { - String tmp = fqdns.get(0); - fqdns.set(0, primary.getHostName()); - fqdns.set(i, tmp); - } - } - } - } - } - } catch (Exception e) { - e.printStackTrace(); - trans.error().log(e); - return Result.err(Status.ERR_Denied, - "AppID Sponsorship cannot be determined at this time. Try later."); - } - - CSRMeta csrMeta; - try { - csrMeta = BCFactory.createCSRMeta(ca, req.value.mechid, email, fqdns); - X509andChain x509ac = ca.sign(trans, csrMeta); - if (x509ac == null) { - return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA"); - } - trans.info().printf("X509 Subject: %s", x509ac.getX509().getSubjectDN()); - - X509Certificate x509 = x509ac.getX509(); - CertDAO.Data cdd = new CertDAO.Data(); - cdd.ca = ca.getName(); - cdd.serial = x509.getSerialNumber(); - cdd.id = req.value.mechid; - cdd.x500 = x509.getSubjectDN().getName(); - cdd.x509 = Factory.toString(trans, x509); - certDAO.create(trans, cdd); - - CredDAO.Data crdd = new CredDAO.Data(); - crdd.other = Question.random.nextInt(); - crdd.cred = getChallenge256SaltedHash(csrMeta.challenge(), crdd.other); - crdd.expires = x509.getNotAfter(); - crdd.id = req.value.mechid; - crdd.ns = Question.domain2ns(crdd.id); - crdd.type = CredDAO.CERT_SHA256_RSA; - credDAO.create(trans, crdd); - - CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(notes)); - return Result.ok(cr); - } catch (Exception e) { - trans.error().log(e); - return Result.err(Result.ERR_ActionNotCompleted, e.getMessage()); - } - } else { - return Result.err(req); - } - } - - public Result renewCert(AuthzTrans trans, Result renew) { - if (renew.isOK()) { - return Result.err(Result.ERR_NotImplemented, "Not implemented yet"); - } else { - return Result.err(renew); - } - } - - public Result dropCert(AuthzTrans trans, Result drop) { - if (drop.isOK()) { - return Result.err(Result.ERR_NotImplemented, "Not implemented yet"); - } else { - return Result.err(drop); - } - } - - public Result> readCertsByMechID(AuthzTrans trans, String mechID) { - // Policy 1: To Read, must have NS Read or is Sponsor - String ns = Question.domain2ns(mechID); - try { - if (trans.user().equals(mechID) || trans.fish(new AAFPermission(ns,ACCESS, "*", "read")) - || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, mechID)) == null) { - return certDAO.readID(trans, mechID); - } else { - return Result.err(Result.ERR_Denied, "%s is not the ID, Sponsor or NS Owner/Admin for %s at %s", - trans.user(), mechID, trans.org().getName()); - } - } catch (OrganizationException e) { - return Result.err(e); - } - } - - public Result requestPersonalCert(AuthzTrans trans, CA ca) { - if (ca.inPersonalDomains(trans.getUserPrincipal())) { - Organization org = trans.org(); - - // Policy 1: MechID must be current - Identity ouser; - try { - ouser = org.getIdentity(trans, trans.user()); - } catch (OrganizationException e1) { - trans.error().log(e1); - ouser = null; - } - if (ouser == null) { - return Result.err(Result.ERR_Policy, "Requesting User must exist in %s", org.getName()); - } - - // Set Email from most current Sponsor - - CSRMeta csrMeta; - try { - csrMeta = BCFactory.createPersonalCSRMeta(ca, trans.user(), ouser.email()); - X509andChain x509ac = ca.sign(trans, csrMeta); - if (x509ac == null) { - return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA"); - } - X509Certificate x509 = x509ac.getX509(); - CertDAO.Data cdd = new CertDAO.Data(); - cdd.ca = ca.getName(); - cdd.serial = x509.getSerialNumber(); - cdd.id = trans.user(); - cdd.x500 = x509.getSubjectDN().getName(); - cdd.x509 = Factory.toString(trans, x509); - certDAO.create(trans, cdd); - - CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(null)); - return Result.ok(cr); - } catch (Exception e) { - trans.error().log(e); - return Result.err(Result.ERR_ActionNotCompleted, e.getMessage()); - } - } else { - return Result.err(Result.ERR_Denied, trans.user(), " not supported for CA", ca.getName()); - } - } - - /////////////// - // Artifact - ////////////// - public Result createArtifact(AuthzTrans trans, List list) { - CertmanValidator v = new CertmanValidator().artisRequired(list, 1); - if (v.err()) { - return Result.err(Result.ERR_BadData, v.errs()); - } - for (ArtiDAO.Data add : list) { - try { - // Policy 1: MechID must exist in Org - Identity muser = trans.org().getIdentity(trans, add.mechid); - if (muser == null) { - return Result.err(Result.ERR_Denied, "%s is not valid for %s", add.mechid, trans.org().getName()); - } - - // Policy 2: MechID must have valid Organization Owner - Identity emailUser; - if (muser.isPerson()) { - emailUser = muser; - } else { - Identity ouser = muser.responsibleTo(); - if (ouser == null) { - return Result.err(Result.ERR_Denied, "%s is not a valid Sponsor for %s at %s", trans.user(), - add.mechid, trans.org().getName()); - } - - // Policy 3: Calling ID must be MechID Owner - if (!trans.user().startsWith(ouser.id())) { - return Result.err(Result.ERR_Denied, "%s is not the Sponsor for %s at %s", trans.user(), - add.mechid, trans.org().getName()); - } - emailUser = ouser; - } - - // Policy 4: Renewal Days are between 10 and 60 (constants, may be - // parameterized) - if (add.renewDays < MIN_RENEWAL) { - add.renewDays = STD_RENEWAL; - } else if (add.renewDays > MAX_RENEWAL) { - add.renewDays = MAX_RENEWAL; - } - - // Policy 5: If Notify is blank, set to Owner's Email - if (add.notify == null || add.notify.length() == 0) { - add.notify = "mailto:" + emailUser.email(); - } - - // Policy 6: Only do Domain by Exception - if (add.machine.startsWith("*")) { // Domain set - CA ca = certman.getCA(add.ca); - - if (!trans.fish(new AAFPermission(ca.getPermNS(),ca.getPermType(), add.ca, DOMAIN))) { - return Result.err(Result.ERR_Denied, "Domain Artifacts (%s) requires specific Permission", - add.machine); - } - } - - // Set Sponsor from Golden Source - add.sponsor = emailUser.fullID(); - - } catch (OrganizationException e) { - return Result.err(e); - } - // Add to DB - Result rv = artiDAO.create(trans, add); - // TODO come up with Partial Reporting Scheme, or allow only one at a time. - if (rv.notOK()) { - return Result.err(rv); - } - } - return Result.ok(); - } - - public Result> readArtifacts(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException { - CertmanValidator v = new CertmanValidator().keys(add); - if (v.err()) { - return Result.err(Result.ERR_BadData, v.errs()); - } - Result> data = artiDAO.read(trans, add); - if (data.notOKorIsEmpty()) { - return data; - } - add = data.value.get(0); - if (trans.user().equals(add.mechid) - || trans.fish(root_read_permission, - new AAFPermission(add.ns,ACCESS, "*", "read"), - new AAFPermission(add.ns,CERTMAN, add.ca, "read"), - new AAFPermission(add.ns,CERTMAN, add.ca, "request")) - || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, add.mechid)) == null) { - return data; - } else { - return Result.err(Result.ERR_Denied, - "%s is not %s, is not the sponsor, and doesn't have delegated permission.", trans.user(), - add.mechid, add.ns + ".certman|" + add.ca + "|read or ...|request"); // note: reason is set by 2nd - // case, if 1st case misses - } - - } - - public Result> readArtifactsByMechID(AuthzTrans trans, String mechid) - throws OrganizationException { - CertmanValidator v = new CertmanValidator(); - v.nullOrBlank("mechid", mechid); - if (v.err()) { - return Result.err(Result.ERR_BadData, v.errs()); - } - String ns = FQI.reverseDomain(mechid); - - String reason; - if (trans.fish(new AAFPermission(ns, ACCESS, "*", "read")) - || (reason = trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, mechid)) == null) { - return artiDAO.readByMechID(trans, mechid); - } else { - return Result.err(Result.ERR_Denied, reason); // note: reason is set by 2nd case, if 1st case misses - } - - } - - public Result> readArtifactsByMachine(AuthzTrans trans, String machine) { - CertmanValidator v = new CertmanValidator(); - v.nullOrBlank("machine", machine); - if (v.err()) { - return Result.err(Result.ERR_BadData, v.errs()); - } - - // TODO do some checks? - - Result> rv = artiDAO.readByMachine(trans, machine); - return rv; - } - - public Result> readArtifactsByNs(AuthzTrans trans, String ns) { - CertmanValidator v = new CertmanValidator(); - v.nullOrBlank("ns", ns); - if (v.err()) { - return Result.err(Result.ERR_BadData, v.errs()); - } - - // TODO do some checks? - - return artiDAO.readByNs(trans, ns); - } - - public Result updateArtifact(AuthzTrans trans, List list) throws OrganizationException { - CertmanValidator v = new CertmanValidator(); - v.artisRequired(list, 1); - if (v.err()) { - return Result.err(Result.ERR_BadData, v.errs()); - } - - // Check if requesting User is Sponsor - // TODO - Shall we do one, or multiples? - for (ArtiDAO.Data add : list) { - // Policy 1: MechID must exist in Org - Identity muser = trans.org().getIdentity(trans, add.mechid); - if (muser == null) { - return Result.err(Result.ERR_Denied, "%s is not valid for %s", add.mechid, trans.org().getName()); - } - - // Policy 2: MechID must have valid Organization Owner - Identity ouser = muser.responsibleTo(); - if (ouser == null) { - return Result.err(Result.ERR_Denied, "%s is not a valid Sponsor for %s at %s", trans.user(), add.mechid, - trans.org().getName()); - } - - // Policy 3: Renewal Days are between 10 and 60 (constants, may be - // parameterized) - if (add.renewDays < MIN_RENEWAL) { - add.renewDays = STD_RENEWAL; - } else if (add.renewDays > MAX_RENEWAL) { - add.renewDays = MAX_RENEWAL; - } - - // Policy 4: Data is always updated with the latest Sponsor - // Add to Sponsor, to make sure we are always up to date. - add.sponsor = ouser.fullID(); - - // Policy 5: If Notify is blank, set to Owner's Email - if (add.notify == null || add.notify.length() == 0) { - add.notify = "mailto:" + ouser.email(); - } - // Policy 6: Only do Domain by Exception - if (add.machine.startsWith("*")) { // Domain set - CA ca = certman.getCA(add.ca); - if (ca == null) { - return Result.err(Result.ERR_BadData, "CA is required in Artifact"); - } - if (!trans.fish(new AAFPermission(null,ca.getPermType(), add.ca, DOMAIN))) { - return Result.err(Result.ERR_Denied, "Domain Artifacts (%s) requires specific Permission", - add.machine); - } - } - - // Policy 7: only Owner may update info - if (trans.user().startsWith(ouser.id())) { - return artiDAO.update(trans, add); - } else { - return Result.err(Result.ERR_Denied, "%s may not update info for %s", trans.user(), muser.fullID()); - } - } - return Result.err(Result.ERR_BadData, "No Artifacts to update"); - } - - public Result deleteArtifact(AuthzTrans trans, String mechid, String machine) throws OrganizationException { - CertmanValidator v = new CertmanValidator(); - v.nullOrBlank("mechid", mechid).nullOrBlank("machine", machine); - if (v.err()) { - return Result.err(Result.ERR_BadData, v.errs()); - } - - Result> rlad = artiDAO.read(trans, mechid, machine); - if (rlad.notOKorIsEmpty()) { - return Result.err(Result.ERR_NotFound, "Artifact for %s %s does not exist.", mechid, machine); - } - - return deleteArtifact(trans, rlad.value.get(0)); - } - - private Result deleteArtifact(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException { - // Policy 1: Record should be delete able only by Existing Sponsor. - String sponsor = null; - Identity muser = trans.org().getIdentity(trans, add.mechid); - if (muser != null) { - Identity ouser = muser.responsibleTo(); - if (ouser != null) { - sponsor = ouser.fullID(); - } - } - // Policy 1.a: If Sponsorship is deleted in system of Record, then - // accept deletion by sponsor in Artifact Table - if (sponsor == null) { - sponsor = add.sponsor; - } - - String ns = FQI.reverseDomain(add.mechid); - - if (trans.fish(new AAFPermission(ns,ACCESS, "*", "write")) || trans.user().equals(sponsor)) { - return artiDAO.delete(trans, add, false); - } - return Result.err(Result.ERR_Denied, "%1 is not allowed to delete this item", trans.user()); - } - - public Result deleteArtifact(AuthzTrans trans, List list) { - CertmanValidator v = new CertmanValidator().artisRequired(list, 1); - if (v.err()) { - return Result.err(Result.ERR_BadData, v.errs()); - } - - try { - boolean partial = false; - Result result = null; - for (ArtiDAO.Data add : list) { - result = deleteArtifact(trans, add); - if (result.notOK()) { - partial = true; - } - } - if (result == null) { - result = Result.err(Result.ERR_BadData, "No Artifacts to delete"); - } else if (partial) { - result.partialContent(true); - } - return result; - } catch (Exception e) { - return Result.err(e); - } - } - - private String[] compileNotes(List notes) { - String[] rv; - if (notes == null) { - rv = NO_NOTES; - } else { - rv = new String[notes.size()]; - notes.toArray(rv); - } - return rv; - } - - private ByteBuffer getChallenge256SaltedHash(String challenge, int salt) throws NoSuchAlgorithmException { - ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + challenge.length()); - bb.putInt(salt); - bb.put(challenge.getBytes()); - byte[] hash = Hash.hashSHA256(bb.array()); - return ByteBuffer.wrap(hash); - } + // If we add more CAs, may want to parameterize + private static final int STD_RENEWAL = 30; + private static final int MAX_RENEWAL = 60; + private static final int MIN_RENEWAL = 10; + // Limit total requests + private static final int MAX_X509s = 200; // Need a "LIMIT Exception" DB. + private static final String MAX_X509S_TAG = "cm_max_x509s"; // be able to adjust limit in future + + public static final String REQUEST = "request"; + public static final String IGNORE_IPS = "ignoreIPs"; + public static final String RENEW = "renew"; + public static final String DROP = "drop"; + public static final String DOMAIN = "domain"; + public static final String DYNAMIC_SANS="dynamic_sans"; + + private static final String CERTMAN = "certman"; + private static final String ACCESS = "access"; + + private static final String[] NO_NOTES = new String[0]; + private final Permission root_read_permission; + private final CertDAO certDAO; + private final CredDAO credDAO; + private final ArtiDAO artiDAO; + private AAF_CM certManager; + private Boolean allowIgnoreIPs; + private AAFPermission limitOverridePerm; + private int max_509s; + + // @SuppressWarnings("unchecked") + public CMService(final AuthzTrans trans, AAF_CM certman) throws APIException, IOException { + // Jonathan 4/2015 SessionFilter unneeded... DataStax already deals with + // Multithreading well + + HistoryDAO hd = new HistoryDAO(trans, certman.cluster, CassAccess.KEYSPACE); + CacheInfoDAO cid = new CacheInfoDAO(trans, hd); + certDAO = new CertDAO(trans, hd, cid); + credDAO = new CredDAO(trans, hd, cid); + artiDAO = new ArtiDAO(trans, hd, cid); + + this.certManager = certman; + + root_read_permission=new AAFPermission( + trans.getProperty(Config.AAF_ROOT_NS, Config.AAF_ROOT_NS_DEF), + ACCESS, + "*", + "read" + ); + try { + max_509s = Integer.parseInt(trans.env().getProperty(MAX_X509S_TAG,Integer.toString(MAX_X509s))); + } catch (Exception e) { + trans.env().log(e, ""); + max_509s = MAX_X509s; + } + limitOverridePerm = new AAFPermission(Define.ROOT_NS(),"certman","quantity","override"); + allowIgnoreIPs = Boolean.valueOf(certman.access.getProperty(Config.CM_ALLOW_IGNORE_IPS, "false")); + if(allowIgnoreIPs) { + trans.env().access().log(Level.INIT, "Allowing DNS Evaluation to be turned off with .certman||"+IGNORE_IPS); + } + } + + public Result requestCert(final AuthzTrans trans, final Result req, final CA ca) { + if (req.isOK()) { + if (req.value.fqdns.isEmpty()) { + return Result.err(Result.ERR_BadData, "No Machines passed in Request"); + } + + String key = req.value.fqdns.get(0); + + // Policy 6: Requester must be granted Change permission in Namespace requested + String mechNS = FQI.reverseDomain(req.value.mechid); + if (mechNS == null) { + return Result.err(Status.ERR_Denied, "%s does not reflect a valid AAF Namespace", req.value.mechid); + } + + List notes = null; + List fqdns; + boolean domain_based = false; + boolean dynamic_sans = false; + + if(req.value.fqdns.isEmpty()) { + fqdns = new ArrayList<>(); + } else { + // Only Template or Dynamic permitted to pass in FQDNs + if (req.value.fqdns.get(0).startsWith("*")) { // Domain set + if (trans.fish(new AAFPermission(null,ca.getPermType(), ca.getName(), DOMAIN))) { + domain_based = true; + } else { + return Result.err(Result.ERR_Denied, + "Domain based Authorizations (" + req.value.fqdns.get(0) + ") requires Exception"); + } + } else { + if(trans.fish(new AAFPermission(null, ca.getPermType(), ca.getName(),DYNAMIC_SANS))) { + dynamic_sans = true; + } else { + return Result.err(Result.ERR_Denied, + "Dynamic SANs for (" + req.value.mechid + ") requires Permission"); + } + } + fqdns = new ArrayList<>(req.value.fqdns); + } + + String email = null; + + try { + Organization org = trans.org(); + + boolean ignoreIPs; + if(allowIgnoreIPs) { + ignoreIPs = trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), IGNORE_IPS)); + } else { + ignoreIPs = false; + } + + + InetAddress primary = null; + // Organize incoming information to get to appropriate Artifact + if (!fqdns.isEmpty()) { // Passed in FQDNS, validated above + // Accept domain wild cards, but turn into real machines + // Need *domain.com:real.machine.domain.com:san.machine.domain.com:... + if (domain_based) { // Domain set + // check for Permission in Add Artifact? + String domain = fqdns.get(0).substring(1); // starts with *, see above + fqdns.remove(0); + if (fqdns.isEmpty()) { + return Result.err(Result.ERR_Denied, + "Requests using domain require machine declaration"); + } + + if (!ignoreIPs) { + InetAddress ia = InetAddress.getByName(fqdns.get(0)); + if (ia == null) { + return Result.err(Result.ERR_Denied, + "Request not made from matching IP matching domain"); + } else if (ia.getHostName().endsWith(domain)) { + primary = ia; + } + } + + } else { + // Passed in FQDNs, but not starting with * + if (!ignoreIPs) { + for (String cn : req.value.fqdns) { + try { + InetAddress[] ias = InetAddress.getAllByName(cn); + Set potentialSanNames = new HashSet<>(); + for (InetAddress ia1 : ias) { + InetAddress ia2 = InetAddress.getByAddress(ia1.getAddress()); + if (primary == null && ias.length == 1 && trans.ip().equals(ia1.getHostAddress())) { + primary = ia1; + } else if (!cn.equals(ia1.getHostName()) + && !ia2.getHostName().equals(ia2.getHostAddress())) { + potentialSanNames.add(ia1.getHostName()); + } + } + } catch (UnknownHostException e1) { + trans.debug().log(e1); + return Result.err(Result.ERR_BadData, "There is no DNS lookup for %s", cn); + } + } + } + } + } + + final String host; + if (ignoreIPs) { + host = req.value.fqdns.get(0); + } else if (primary == null) { + return Result.err(Result.ERR_Denied, "Request not made from matching IP (%s)", req.value.fqdns.get(0)); + } else { + String thost = primary.getHostName(); + host = thost==null?primary.getHostAddress():thost; + } + + ArtiDAO.Data add = null; + Result> ra = artiDAO.read(trans, req.value.mechid, host); + if (ra.isOKhasData()) { + add = ra.value.get(0); // single key + if(dynamic_sans && (add.sans!=null && !add.sans.isEmpty())) { // do not allow both Dynamic and Artifact SANS + return Result.err(Result.ERR_Denied,"Authorization must not include SANS when doing Dynamic SANS (%s, %s)", req.value.mechid, key); + } + } else { + if(domain_based) { + ra = artiDAO.read(trans, req.value.mechid, key); + if (ra.isOKhasData()) { // is the Template available? + add = ra.value.get(0); + add.machine = host; + for (String s : fqdns) { + if (!s.equals(add.machine)) { + add.sans(true).add(s); + } + } + Result rc = artiDAO.create(trans, add); // Create new Artifact from Template + if (rc.notOK()) { + return Result.err(rc); + } + } else { + return Result.err(Result.ERR_Denied,"No Authorization Template for %s, %s", req.value.mechid, key); + } + } else { + return Result.err(Result.ERR_Denied,"No Authorization found for %s, %s", req.value.mechid, key); + } + } + + // Add Artifact listed FQDNs + if(!dynamic_sans) { + if (add.sans != null) { + for (String s : add.sans) { + if (!fqdns.contains(s)) { + fqdns.add(s); + } + } + } + } + + // Policy 2: If Config marked as Expired, do not create or renew + Date now = new Date(); + if (add.expires != null && now.after(add.expires)) { + return Result.err(Result.ERR_Policy, "Configuration for %s %s is expired %s", add.mechid, + add.machine, Chrono.dateFmt.format(add.expires)); + } + + // Policy 3: MechID must be current + Identity muser = org.getIdentity(trans, add.mechid); + if (muser == null) { + return Result.err(Result.ERR_Policy, "MechID must exist in %s", org.getName()); + } + + // Policy 4: Sponsor must be current + Identity ouser = muser.responsibleTo(); + if (ouser == null) { + return Result.err(Result.ERR_Policy, "%s does not have a current sponsor at %s", add.mechid, + org.getName()); + } else if (!ouser.isFound() || ouser.mayOwn() != null) { + return Result.err(Result.ERR_Policy, "%s reports that %s cannot be responsible for %s", + org.getName(), trans.user()); + } + + // Set Email from most current Sponsor + email = ouser.email(); + + // Policy 5: keep Artifact data current + if (!ouser.fullID().equals(add.sponsor)) { + add.sponsor = ouser.fullID(); + artiDAO.update(trans, add); + } + + // Policy 7: Caller must be the MechID or have specifically delegated + // permissions + if (!(trans.user().equals(req.value.mechid) + || trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), REQUEST)))) { + return Result.err(Status.ERR_Denied, "%s must have access to modify x509 certs in NS %s", + trans.user(), mechNS); + } + + // Make sure Primary is the first in fqdns + if (fqdns.size() > 1) { + for (int i = 0; i < fqdns.size(); ++i) { + if (primary==null && !ignoreIPs) { + trans.error().log("CMService var primary is null"); + } else { + String fg = fqdns.get(i); + if (fg!=null && primary!=null && fg.equals(primary.getHostName())) { + if (i != 0) { + String tmp = fqdns.get(0); + fqdns.set(0, primary.getHostName()); + fqdns.set(i, tmp); + } + } + } + } + } + } catch (Exception e) { + trans.error().log(e); + return Result.err(Status.ERR_Denied, + "AppID Sponsorship cannot be determined at this time. Try later."); + } + + CSRMeta csrMeta; + try { + csrMeta = BCFactory.createCSRMeta(ca, req.value.mechid, email, fqdns); + csrMeta.environment(ca.getEnv()); + + // Before creating, make sure they don't have too many + if(!trans.fish(limitOverridePerm)) { + Result> existing = certDAO.readID(trans, req.value.mechid); + if(existing.isOK()) { + String cn = "CN=" + csrMeta.cn(); + int count = 0; + Date now = new Date(); + for (CertDAO.Data cdd : existing.value) { + Collection certs = Factory.toX509Certificate(cdd.x509); + for(Iterator iter = certs.iterator(); iter.hasNext();) { + X509Certificate x509 = (X509Certificate)iter.next(); + if(x509.getNotAfter().after(now) && x509.getSubjectDN().getName().contains(cn)) { + if(++count>max_509s) { + break; + } + } + } + } + if(count>max_509s) { + return Result.err(Result.ERR_Denied, "There are too many Certificates generated for " + cn + " for " + req.value.mechid); + } + } + } + // Here is where we send off to CA for Signing. + X509andChain x509ac = ca.sign(trans, csrMeta); + if (x509ac == null) { + return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA"); + } + trans.info().printf("X509 Subject: %s", x509ac.getX509().getSubjectDN()); + + X509Certificate x509 = x509ac.getX509(); + CertDAO.Data cdd = new CertDAO.Data(); + cdd.ca = ca.getName(); + cdd.serial = x509.getSerialNumber(); + cdd.id = req.value.mechid; + cdd.x500 = x509.getSubjectDN().getName(); + cdd.x509 = Factory.toString(trans, x509); + + certDAO.create(trans, cdd); + + CredDAO.Data crdd = new CredDAO.Data(); + crdd.other = Question.random.nextInt(); + crdd.cred = getChallenge256SaltedHash(csrMeta.challenge(), crdd.other); + crdd.expires = x509.getNotAfter(); + crdd.id = req.value.mechid; + crdd.ns = Question.domain2ns(crdd.id); + crdd.type = CredDAO.CERT_SHA256_RSA; + crdd.tag = cdd.ca + '|' + cdd.serial.toString(); + credDAO.create(trans, crdd); + + CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(notes)); + return Result.ok(cr); + } catch (Exception e) { + trans.debug().log(e); + return Result.err(Result.ERR_ActionNotCompleted, e.getMessage()); + } + } else { + return Result.err(req); + } + } + + public Result renewCert(AuthzTrans trans, Result renew) { + if (renew.isOK()) { + return Result.err(Result.ERR_NotImplemented, "Not implemented yet"); + } else { + return Result.err(renew); + } + } + + public Result dropCert(AuthzTrans trans, Result drop) { + if (drop.isOK()) { + return Result.err(Result.ERR_NotImplemented, "Not implemented yet"); + } else { + return Result.err(drop); + } + } + + public Result> readCertsByMechID(AuthzTrans trans, String mechID) { + // Policy 1: To Read, must have NS Read or is Sponsor + String ns = Question.domain2ns(mechID); + try { + if (trans.user().equals(mechID) || trans.fish(new AAFPermission(ns,ACCESS, "*", "read")) + || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, mechID)) == null) { + return certDAO.readID(trans, mechID); + } else { + return Result.err(Result.ERR_Denied, "%s is not the ID, Sponsor or NS Owner/Admin for %s at %s", + trans.user(), mechID, trans.org().getName()); + } + } catch (OrganizationException e) { + return Result.err(e); + } + } + + public Result requestPersonalCert(AuthzTrans trans, CA ca) { + if (ca.inPersonalDomains(trans.getUserPrincipal())) { + Organization org = trans.org(); + + // Policy 1: MechID must be current + Identity ouser; + try { + ouser = org.getIdentity(trans, trans.user()); + } catch (OrganizationException e1) { + trans.debug().log(e1); + ouser = null; + } + if (ouser == null) { + return Result.err(Result.ERR_Policy, "Requesting User must exist in %s", org.getName()); + } + + // Set Email from most current Sponsor + + CSRMeta csrMeta; + try { + csrMeta = BCFactory.createPersonalCSRMeta(ca, trans.user(), ouser.email()); + X509andChain x509ac = ca.sign(trans, csrMeta); + if (x509ac == null) { + return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA"); + } + X509Certificate x509 = x509ac.getX509(); + CertDAO.Data cdd = new CertDAO.Data(); + cdd.ca = ca.getName(); + cdd.serial = x509.getSerialNumber(); + cdd.id = trans.user(); + cdd.x500 = x509.getSubjectDN().getName(); + cdd.x509 = Factory.toString(trans, x509); + certDAO.create(trans, cdd); + + CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(null)); + return Result.ok(cr); + } catch (Exception e) { + trans.debug().log(e); + return Result.err(Result.ERR_ActionNotCompleted, e.getMessage()); + } + } else { + return Result.err(Result.ERR_Denied, trans.user(), " not supported for CA", ca.getName()); + } + } + + /////////////// + // Artifact + ////////////// + public Result createArtifact(AuthzTrans trans, List list) { + CertmanValidator v = new CertmanValidator().artisRequired(list, 1); + if (v.err()) { + return Result.err(Result.ERR_BadData, v.errs()); + } + for (ArtiDAO.Data add : list) { + try { + // Policy 1: MechID must exist in Org + Identity muser = trans.org().getIdentity(trans, add.mechid); + if (muser == null) { + return Result.err(Result.ERR_Denied, "%s is not valid for %s", add.mechid, trans.org().getName()); + } + + // Policy 2: MechID must have valid Organization Owner + Identity emailUser; + if (muser.isPerson()) { + emailUser = muser; + } else { + Identity ouser = muser.responsibleTo(); + if (ouser == null) { + return Result.err(Result.ERR_Denied, "%s is not a valid Sponsor for %s at %s", trans.user(), + add.mechid, trans.org().getName()); + } + + // Policy 3: Calling ID must be MechID Owner + if (!trans.user().startsWith(ouser.id())) { + return Result.err(Result.ERR_Denied, "%s is not the Sponsor for %s at %s", trans.user(), + add.mechid, trans.org().getName()); + } + emailUser = ouser; + } + + // Policy 4: Renewal Days are between 10 and 60 (constants, may be + // parameterized) + if (add.renewDays < MIN_RENEWAL) { + add.renewDays = STD_RENEWAL; + } else if (add.renewDays > MAX_RENEWAL) { + add.renewDays = MAX_RENEWAL; + } + + // Policy 5: If Notify is blank, set to Owner's Email + if (add.notify == null || add.notify.length() == 0) { + add.notify = "mailto:" + emailUser.email(); + } + + // Policy 6: Only do Domain by Exception + if (add.machine.startsWith("*")) { // Domain set + CA ca = certManager.getCA(add.ca); + if (!trans.fish(new AAFPermission(ca.getPermNS(),ca.getPermType(), add.ca, DOMAIN))) { + return Result.err(Result.ERR_Denied, "Domain Artifacts (%s) requires specific Permission", + add.machine); + } + } + + // Set Sponsor from Golden Source + add.sponsor = emailUser.fullID(); + + } catch (OrganizationException e) { + return Result.err(e); + } + // Add to DB + Result rv = artiDAO.create(trans, add); + // come up with Partial Reporting Scheme, or allow only one at a time. + if (rv.notOK()) { + return Result.err(rv); + } + } + return Result.ok(); + } + + public Result> readArtifacts(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException { + CertmanValidator v = new CertmanValidator().keys(add); + if (v.err()) { + return Result.err(Result.ERR_BadData, v.errs()); + } + Result> data = artiDAO.read(trans, add); + if (data.notOKorIsEmpty()) { + return data; + } + add = data.value.get(0); + if (trans.user().equals(add.mechid) + || trans.fish(root_read_permission, + new AAFPermission(add.ns,ACCESS, "*", "read"), + new AAFPermission(add.ns,CERTMAN, add.ca, "read"), + new AAFPermission(add.ns,CERTMAN, add.ca, REQUEST)) + || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, add.mechid)) == null) { + return data; + } else { + return Result.err(Result.ERR_Denied, + "%s is not %s, is not the sponsor, and doesn't have delegated permission.", trans.user(), + add.mechid, add.ns + ".certman|" + add.ca + "|read or ...|request"); // note: reason is set by 2nd + // case, if 1st case misses + } + + } + + public Result> readArtifactsByMechID(AuthzTrans trans, String mechid) + throws OrganizationException { + CertmanValidator v = new CertmanValidator(); + v.nullOrBlank("mechid", mechid); + if (v.err()) { + return Result.err(Result.ERR_BadData, v.errs()); + } + String ns = FQI.reverseDomain(mechid); + + String reason; + if (trans.fish(new AAFPermission(ns, ACCESS, "*", "read")) + || (reason = trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, mechid)) == null) { + return artiDAO.readByMechID(trans, mechid); + } else { + return Result.err(Result.ERR_Denied, reason); // note: reason is set by 2nd case, if 1st case misses + } + + } + + public Result> readArtifactsByMachine(AuthzTrans trans, String machine) { + CertmanValidator v = new CertmanValidator(); + v.nullOrBlank("machine", machine); + if (v.err()) { + return Result.err(Result.ERR_BadData, v.errs()); + } + + // do some checks? + + return artiDAO.readByMachine(trans, machine); + } + + public Result> readArtifactsByNs(AuthzTrans trans, String ns) { + CertmanValidator v = new CertmanValidator(); + v.nullOrBlank("ns", ns); + if (v.err()) { + return Result.err(Result.ERR_BadData, v.errs()); + } + + // do some checks? + return artiDAO.readByNs(trans, ns); + } + + public Result updateArtifact(AuthzTrans trans, List list) throws OrganizationException { + CertmanValidator v = new CertmanValidator(); + v.artisRequired(list, 1); + if (v.err()) { + return Result.err(Result.ERR_BadData, v.errs()); + } + + // Check if requesting User is Sponsor + // Shall we do one, or multiples? + for (ArtiDAO.Data add : list) { + // Policy 1: MechID must exist in Org + Identity muser = trans.org().getIdentity(trans, add.mechid); + if (muser == null) { + return Result.err(Result.ERR_Denied, "%s is not valid for %s", add.mechid, trans.org().getName()); + } + + // Policy 2: MechID must have valid Organization Owner + Identity ouser = muser.responsibleTo(); + if (ouser == null) { + return Result.err(Result.ERR_Denied, "%s is not a valid Sponsor for %s at %s", trans.user(), add.mechid, + trans.org().getName()); + } + + // Policy 3: Renewal Days are between 10 and 60 (constants, may be + // parameterized) + if (add.renewDays < MIN_RENEWAL) { + add.renewDays = STD_RENEWAL; + } else if (add.renewDays > MAX_RENEWAL) { + add.renewDays = MAX_RENEWAL; + } + + // Policy 4: Data is always updated with the latest Sponsor + // Add to Sponsor, to make sure we are always up to date. + add.sponsor = ouser.fullID(); + + // Policy 5: If Notify is blank, set to Owner's Email + if (add.notify == null || add.notify.length() == 0) { + add.notify = "mailto:" + ouser.email(); + } + // Policy 6: Only do Domain by Exception + if (add.machine.startsWith("*")) { // Domain set + CA ca = certManager.getCA(add.ca); + if (ca == null) { + return Result.err(Result.ERR_BadData, "CA is required in Artifact"); + } + if (!trans.fish(new AAFPermission(null,ca.getPermType(), add.ca, DOMAIN))) { + return Result.err(Result.ERR_Denied, "Domain Artifacts (%s) requires specific Permission", + add.machine); + } + } + + // Policy 7: only Owner may update info + if (trans.user().startsWith(ouser.id())) { + return artiDAO.update(trans, add); + } else { + return Result.err(Result.ERR_Denied, "%s may not update info for %s", trans.user(), muser.fullID()); + } + } + return Result.err(Result.ERR_BadData, "No Artifacts to update"); + } + + public Result deleteArtifact(AuthzTrans trans, String mechid, String machine) throws OrganizationException { + CertmanValidator v = new CertmanValidator(); + v.nullOrBlank("mechid", mechid).nullOrBlank("machine", machine); + if (v.err()) { + return Result.err(Result.ERR_BadData, v.errs()); + } + + Result> rlad = artiDAO.read(trans, mechid, machine); + if (rlad.notOKorIsEmpty()) { + return Result.err(Result.ERR_NotFound, "Artifact for %s %s does not exist.", mechid, machine); + } + + return deleteArtifact(trans, rlad.value.get(0)); + } + + private Result deleteArtifact(AuthzTrans trans, ArtiDAO.Data add) throws OrganizationException { + // Policy 1: Record should be delete able only by Existing Sponsor. + String sponsor = null; + Identity muser = trans.org().getIdentity(trans, add.mechid); + if (muser != null) { + Identity ouser = muser.responsibleTo(); + if (ouser != null) { + sponsor = ouser.fullID(); + } + } + // Policy 1.a: If Sponsorship is deleted in system of Record, then + // accept deletion by sponsor in Artifact Table + if (sponsor == null) { + sponsor = add.sponsor; + } + + String ns = FQI.reverseDomain(add.mechid); + + if (trans.fish(new AAFPermission(ns,ACCESS, "*", "write")) || trans.user().equals(sponsor)) { + return artiDAO.delete(trans, add, false); + } + return Result.err(Result.ERR_Denied, "%1 is not allowed to delete this item", trans.user()); + } + + public Result deleteArtifact(AuthzTrans trans, List list) { + CertmanValidator v = new CertmanValidator().artisRequired(list, 1); + if (v.err()) { + return Result.err(Result.ERR_BadData, v.errs()); + } + + try { + boolean partial = false; + Result result = null; + for (ArtiDAO.Data add : list) { + result = deleteArtifact(trans, add); + if (result.notOK()) { + partial = true; + } + } + if (result == null) { + result = Result.err(Result.ERR_BadData, "No Artifacts to delete"); + } else if (partial) { + result.partialContent(true); + } + return result; + } catch (Exception e) { + return Result.err(e); + } + } + + private String[] compileNotes(List notes) { + String[] rv; + if (notes == null) { + rv = NO_NOTES; + } else { + rv = new String[notes.size()]; + notes.toArray(rv); + } + return rv; + } + + private ByteBuffer getChallenge256SaltedHash(String challenge, int salt) throws NoSuchAlgorithmException { + ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE + challenge.length()); + bb.putInt(salt); + bb.put(challenge.getBytes()); + byte[] hash = Hash.hashSHA256(bb.array()); + return ByteBuffer.wrap(hash); + } }