//* Licensed Materials - Property of * //* IBM * //* Miracle A/S * //* Alexandra Instituttet A/S * //* * //* eu.abc4trust.pabce.1.34 * //* * //* (C) Copyright IBM Corp. 2014. All Rights Reserved. * //* (C) Copyright Miracle A/S, Denmark. 2014. All Rights Reserved. * //* (C) Copyright Alexandra Instituttet A/S, Denmark. 2014. All * //* Rights Reserved. * //* US Government Users Restricted Rights - Use, duplication or * //* disclosure restricted by GSA ADP Schedule Contract with IBM Corp. * //* * //* This file is 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. See the License for the * //* specific language governing permissions and limitations * //* under the License. * //*/**/**************************************************************** package eu.abc4trust.ri.servicehelper.verifier; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.bind.DatatypeConverter; import com.google.inject.Guice; import com.google.inject.Injector; import eu.abc4trust.abce.external.verifier.SynchronizedVerifierAbcEngineImpl; import eu.abc4trust.abce.external.verifier.VerifierAbcEngine; import eu.abc4trust.abce.internal.verifier.tokenManager.TokenStorage; import eu.abc4trust.exceptions.TokenVerificationException; import eu.abc4trust.guice.ProductionModuleFactory; import eu.abc4trust.keyManager.KeyManager; import eu.abc4trust.keyManager.KeyManagerException; import eu.abc4trust.ri.servicehelper.AbstractHelper; import eu.abc4trust.ri.servicehelper.FileSystem; import eu.abc4trust.ri.servicehelper.issuer.IssuanceHelper; import eu.abc4trust.xml.ApplicationData; import eu.abc4trust.xml.CredentialInPolicy; import eu.abc4trust.xml.CredentialInPolicy.IssuerAlternatives; import eu.abc4trust.xml.CredentialInPolicy.IssuerAlternatives.IssuerParametersUID; import eu.abc4trust.xml.CredentialInToken; import eu.abc4trust.xml.CredentialSpecification; import eu.abc4trust.xml.InspectorPublicKey; import eu.abc4trust.xml.IssuerParameters; import eu.abc4trust.xml.Message; import eu.abc4trust.xml.ObjectFactory; import eu.abc4trust.xml.PresentationPolicy; import eu.abc4trust.xml.PresentationPolicyAlternatives; import eu.abc4trust.xml.PresentationToken; import eu.abc4trust.xml.RevocationAuthorityParameters; import eu.abc4trust.xml.RevocationInformation; import eu.abc4trust.xml.SystemParameters; import eu.abc4trust.xml.util.XmlUtils; import eu.abc4trust.xml.VerifierParameters; /** * @author hgk * */ public class VerificationHelper extends AbstractHelper { static Logger log = Logger.getLogger(IssuanceHelper.class.getName()); private static VerificationHelper instance; public VerifierAbcEngine engine; private Random random; private TokenStorage tokenStorage; private VerifierParameters verifierParameters = null; /** * holds map resources by filename (without path) and the bytes of resource */ private final Map<String, byte[]> policyResourceMap = new HashMap<String, byte[]>(); private final ObjectFactory of = new ObjectFactory(); /** * Used when creating a presentationPolicy to be later fetched when verifying a presentation * token. */ Map<URI, RevocationInformation> revocationInformationStore = new HashMap<URI, RevocationInformation>(); public static boolean cacheRevocationInformation = false; public static long REVOCATION_INFORMATION_MAX_TIME_TO_EXPIRE_IN_MINUTTES = 60; private static Map<URI, RevocationInformation> revocationInformationCache = new HashMap<URI, RevocationInformation>(); /** * Private constructor - initializes ABCE * * @param fileStoragePrefix * @throws URISyntaxException */ private VerificationHelper() throws URISyntaxException { log.info("VerificationHelper : create instance.."); try { Injector injector = Guice.createInjector(ProductionModuleFactory.newModule()); VerifierAbcEngine e = injector.getInstance(VerifierAbcEngine.class); this.engine = new SynchronizedVerifierAbcEngineImpl(e); this.keyManager = injector.getInstance(KeyManager.class); this.random = injector.getInstance(Random.class); // this.tokenStorage = injector.getInstance(TokenStorage.class); } catch (Exception e) { System.err.println("Init Failed"); e.printStackTrace(); throw new IllegalStateException("Could not setup Verifier !", e); } } public static synchronized VerificationHelper initInstance() throws URISyntaxException{ if (instance != null) { throw new IllegalStateException("initInstance can only be called once!"); } log.info("VerificationHelper.initInstance ([])"); instance = new VerificationHelper(); return instance; } /** * @param credSpecResourceList * @param inspectorPublicKeyResourceList * @param fileStoragePrefix this prefix will be prepended on storage files needed by the * VerifierAbcEnginge * @param presentationPolicyResourceList initialize helper with list of resources of * PresentationPolicyAlternatives * @throws URISyntaxException */ public static synchronized VerificationHelper initInstance(SystemParameters systemParams, List<IssuerParameters> issuerParamsList, List<CredentialSpecification> credSpecList, List<InspectorPublicKey> inspectorPublicKeyList, List<RevocationAuthorityParameters> revocationAuthorityParametersList, String fileStoragePrefix, String... presentationPolicyResourceList) throws URISyntaxException { if (instance != null) { throw new IllegalStateException("initInstance can only be called once!"); } log.info("VerificationHelper.initInstance ([])"); instance = new VerificationHelper(); // instance.setSystemParams(systemParams); // instance.addCredentialSpecifications(credSpecList); instance.addIssuerParameters(issuerParamsList); instance.addInspectorPublicKeys(inspectorPublicKeyList); // instance.addPresentationPolicy(presentationPolicyResourceList); // instance.addRevocationAuthorities(instance.keyManager, revocationAuthorityParametersList); log.info("VerificationHelper.initInstance : DONE"); return instance; } /** * @return true if VerificationHelper has been initialized */ public static synchronized boolean isInit() { return instance != null; } /** * Only used in test - can reset static instance */ public static synchronized void resetInstance() { System.err.println("WARNING VerificationHelper.resetInstance : " + instance); instance = null; } /** * @return initialized instance of VerificationHelper */ public static synchronized VerificationHelper getInstance() { log.info("VerificationHelper.getInstance : " + instance); if (instance == null) { throw new IllegalStateException("initInstance not called before using VerificationHelper!"); } return instance; } /** * Adds extra policy resorces to VerificationHelper * * @param presentationPolicyResourceList */ public void addPresentationPolicy(String[] presentationPolicyResourceList) { log.info("VerificationHelper addPresentationPolicy from resources : " + presentationPolicyResourceList); ArrayList<String> list = new ArrayList<String>(); for (String r : presentationPolicyResourceList) { list.add(r); } this.addPresentationPolicy(list); } /** * Adds extra policy resorces to VerificationHelper * * @param presentationPolicyResourceList */ public void addPresentationPolicy(ArrayList<String> presentationPolicyResourceList) { log.info("VerificationHelper addPresentationPolicy from resoucres : " + presentationPolicyResourceList); String current = null; try { for (String resource : presentationPolicyResourceList) { current = resource; int ix = resource.lastIndexOf("/"); if (ix == -1) { ix = resource.lastIndexOf("\\"); } String key = resource; if (ix != -1) { key = resource.substring(ix + 1); } log.info(" - add policy : " + key + " - resource : " + resource); byte[] b = new byte[1]; InputStream is = FileSystem.getInputStream(resource); ByteArrayOutputStream os = new ByteArrayOutputStream(); while (is.read(b) != -1) { os.write(b); } byte[] policyBytes = os.toByteArray(); @SuppressWarnings("unused") PresentationPolicyAlternatives presentationPolicy = (PresentationPolicyAlternatives) XmlUtils.getObjectFromXML(new ByteArrayInputStream( policyBytes), false); // ok... this.policyResourceMap.put(key, policyBytes); } } catch (Exception e) { log.log(Level.SEVERE, "Init Failed - policy : " + current, e); throw new IllegalStateException("Init Failed - policy ! : " + current, e); } } public PresentationPolicyAlternatives modifyPresentationPolicy( PresentationPolicyAlternatives ppa, byte[] nonce, String applicationData, Map<URI, URI> revInfoUIDs) throws Exception { this.modifyPPA(ppa, applicationData, nonce, revInfoUIDs); return ppa; } /** * @param policyName name of policy resource (without path) * @param applicationData if present - will be inserted on all presentation policies * @param revInfoUIDs if present - will try to fetch revocation information based on the uids. * @return PresentationPolicyAlternatives - patched with applicationData * @throws Exception */ public PresentationPolicyAlternatives createPresentationPolicy(String policyName, byte[] nonce, String applicationData, Map<URI, URI> revInfoUIDs) throws Exception { log.info("VerificationHelper - create policy : " + policyName + " - data : " + applicationData); PresentationPolicyAlternatives pp_alternatives; byte[] policyBytes = this.policyResourceMap.get(policyName); if (policyBytes == null) { log.warning(" - policy not found : " + policyName + " - map : " + this.policyResourceMap); throw new IllegalStateException("PresentationPolicy not found : " + policyName); } try { pp_alternatives = (PresentationPolicyAlternatives) XmlUtils.getObjectFromXML(new ByteArrayInputStream( policyBytes), false); } catch (Exception e) { log.log(Level.SEVERE, " - could init sample XML - create default", e); throw new IllegalStateException( "Could not init PresentationPolicy - event though it should have been verifed..."); } this.modifyPPA(pp_alternatives, applicationData, nonce, revInfoUIDs); return pp_alternatives; } private PresentationPolicyAlternatives modifyPPA(PresentationPolicyAlternatives pp_alternatives, String applicationData, byte[] nonce, Map<URI, URI> revInfoUIDs) throws Exception { // try to make sure that RevocationInformation is only fetch once per RevAuth Map<URI, RevocationInformation> revocationInformationMap = new HashMap<URI, RevocationInformation>(); for (PresentationPolicy pp : pp_alternatives.getPresentationPolicy()) { Message message = pp.getMessage(); // set nonce message.setNonce(nonce); // set application data if (applicationData != null) { log.fine(" - SET APPLICATION DATA : " + applicationData); // message.setApplicationData(applicationData.getBytes()); ApplicationData a = message.getApplicationData(); a.getContent().clear(); a.getContent().add(applicationData); } // REVOCATION! for (CredentialInPolicy cred : pp.getCredential()) { List<URI> credSpecURIList = cred.getCredentialSpecAlternatives().getCredentialSpecUID(); boolean containsRevoceableCredential = false; CredentialSpecification credSpec = null; for (URI uri : credSpecURIList) { try { credSpec = this.keyManager.getCredentialSpecification(uri); if (credSpec== null) { throw new IllegalStateException("CredentialSpecification : " + uri + " - is in policy BUT is not registered in ABCE"); } if (credSpec.isRevocable()) { containsRevoceableCredential = true; break; } } catch (KeyManagerException ignore) {} } if (containsRevoceableCredential) { IssuerAlternatives ia = cred.getIssuerAlternatives(); log.info("WE HAVE REVOCEABLE CREDENTIAL : " + ia); for (IssuerParametersUID ipUid : ia.getIssuerParametersUID()) { IssuerParameters ip = this.keyManager.getIssuerParameters(ipUid.getValue()); if ((ip != null) && (ip.getRevocationParametersUID() != null)) { // issuer params / credspec has revocation... RevocationInformation ri; log.info("revInfoUIDs: " + revInfoUIDs); if (revInfoUIDs != null) { log.info("Trying to get revInfo under " + credSpec.getSpecificationUID()); URI revInformationUid = revInfoUIDs.get(credSpec.getSpecificationUID()); ri = this.revocationInformationStore.get(revInformationUid); if (ri != null) { log.info("Got revInfo: " + ri.getRevocationInformationUID() + ", which should be the same as: " + revInformationUid); } else { log.info("Revocation information is not there"); } } else { ri = revocationInformationMap.get(ip.getRevocationParametersUID()); if(ri != null) { log.info(" - revocationInformation found in (reuse) map"); } } log.info("RevocationInformation : " + ri); if((ri==null) && cacheRevocationInformation) { ri = revocationInformationCache.get(ip.getRevocationParametersUID()); if(ri!=null) { Calendar now = Calendar.getInstance(); if(now.getTimeInMillis() > ri.getExpires().getTimeInMillis()) { log.info(" - revocationInformation has expired! - now : " + now.getTime() + " - created : " + ri.getCreated().getTime() + " : - expires : " + ri.getExpires().getTime() ); } else if(now.getTimeInMillis() > (ri.getExpires().getTimeInMillis()- (REVOCATION_INFORMATION_MAX_TIME_TO_EXPIRE_IN_MINUTTES * 60 * 1000))) { long millis_to_expiration = (ri.getExpires().getTimeInMillis() - (REVOCATION_INFORMATION_MAX_TIME_TO_EXPIRE_IN_MINUTTES * 60 * 1000)) - now.getTimeInMillis(); log.info(" - revocationInformation was invalidated ! - now : " + now.getTime() + " - created : " + ri.getCreated().getTime() + " : - expires : " + ri.getExpires().getTime() + " MILLIS TO EXPIRE " + millis_to_expiration); ri = null; } else { long millis_to_expiration = (ri.getExpires().getTimeInMillis() - (REVOCATION_INFORMATION_MAX_TIME_TO_EXPIRE_IN_MINUTTES * 60 * 1000)) - now.getTimeInMillis(); log.info(" - valid revocationInformation found in Cache - now : " + now.getTime() + " - created : " + ri.getCreated().getTime() + " : - expires : " + ri.getExpires().getTime() + " MILLES TO EXPIRE : " + millis_to_expiration); revocationInformationMap.put(ip.getRevocationParametersUID(), ri); } } } if (ri == null) { log.info("Getting rev parameters uid information: " + ip.getRevocationParametersUID()); log.info("Getting rev parameters uid information: " + keyManager.getRevocationAuthorityParameters(ip.getRevocationParametersUID())); ri = this.keyManager.getLatestRevocationInformation(ip.getRevocationParametersUID()); revocationInformationMap.put(ip.getRevocationParametersUID(), ri); this.revocationInformationStore.put(ri.getRevocationInformationUID(), ri); if(cacheRevocationInformation) { log.info(" - storage RevocationInformation in cache : " + ip.getRevocationParametersUID()); revocationInformationCache.put(ip.getRevocationParametersUID(), ri); } } log.info("RevocationInformation : " + ri.getRevocationInformationUID()); URI revInfoUid = ri.getRevocationInformationUID(); ipUid.setRevocationInformationUID(revInfoUid); } } } } } if(pp_alternatives.getVerifierParameters()==null) { pp_alternatives.setVerifierParameters(getVerifierParameters()); } log.fine(" - presentationPolicy created"); return pp_alternatives; } /** * TODO : Remove when ABCE-Components can properly compare XML Private Helper * * @param orig XML as string * @return patched XML as JaxB * @throws Exception */ private PresentationToken getPatchedPresetationToken(String orig) throws Exception { String patched = orig.replace("ConstantValue xmlns=\"http://abc4trust.eu/wp2/abcschemav1.0\"", "ConstantValue"); patched = patched.replace(" xmlns=\"\"", ""); patched = patched.replace("xmlns:ns2=\"http://abc4trust.eu/wp2/abcschemav1.0\"", ""); return (PresentationToken) XmlUtils.getObjectFromXML( new ByteArrayInputStream(patched.getBytes()), true); } /** * @param policyName name of policy resource (without path) * @param applicationData if present - will be inserted on all presentation policies - must match * application data supplied when creating Policy * @param presentationToken * @return * @throws Exception */ public boolean verifyToken(String policyName, byte[] nonce, String applicationData, PresentationToken presentationToken) throws Exception { log.info("VerificationHelper - verify token : " + policyName + " - applicationData : " + applicationData); String orig = XmlUtils.toXml(this.of.createPresentationToken(presentationToken)); presentationToken = this.getPatchedPresetationToken(orig); Map<URI, URI> revInfoUIDs = this.extractRevInfoUIDs(presentationToken); PresentationPolicyAlternatives pp = this.createPresentationPolicy(policyName, nonce, applicationData, revInfoUIDs); return this.verifyToken(pp, presentationToken); } private Map<URI, URI> extractRevInfoUIDs(PresentationToken pt) { Map<URI, URI> revInfoUIDs = null; for (CredentialInToken cred : pt.getPresentationTokenDescription().getCredential()) { boolean containsRevoceableCredential = false; try { CredentialSpecification credSpec = this.keyManager.getCredentialSpecification(cred.getCredentialSpecUID()); if (credSpec.isRevocable()) { containsRevoceableCredential = true; } } catch (KeyManagerException ignore) {} if (containsRevoceableCredential) { if (revInfoUIDs == null) { revInfoUIDs = new HashMap<URI, URI>(); } revInfoUIDs.put(cred.getCredentialSpecUID(), cred.getRevocationInformationUID()); } } return revInfoUIDs; } /** * @param ppXml PresentationPolicyAlternatives as String * @param presentationTokenXml as String * @return * @throws Exception */ public boolean verifyToken_String(String ppaXml, String presentationTokenXml) throws Exception { PresentationPolicyAlternatives ppa = (PresentationPolicyAlternatives) XmlUtils.getObjectFromXML( new ByteArrayInputStream(ppaXml.getBytes()), true); return this.verifyToken(ppa, this.getPatchedPresetationToken(presentationTokenXml)); } /** * @param ppa PresentationPolicyAlternatives * @param presentationToken * @return * @throws Exception */ public boolean verifyToken(PresentationPolicyAlternatives ppa, PresentationToken presentationToken) throws Exception { try { // verify in ABCE this.engine.verifyTokenAgainstPolicy(ppa, presentationToken, true); log.info(" - OK!"); return true; } catch (TokenVerificationException e) { log.warning(" - TokenVerificationException : " + e); throw e; } catch (Exception e) { log.log(Level.SEVERE, " - Failed verifying token : ", e); return false; } } public byte[] generateNonce() { // TODO : is 10 bytes correct ? byte[] nonceBytes = new byte[10]; this.random.nextBytes(nonceBytes); return nonceBytes; } public void registerSmartcardScopeExclusivePseudonym(BigInteger pse) throws IOException { String primaryKey = DatatypeConverter.printBase64Binary(pse.toByteArray()); this.registerSmartcardScopeExclusivePseudonym(primaryKey); } public void registerSmartcardScopeExclusivePseudonym(byte[] pseValueAsBytes) throws IOException { String primaryKey = DatatypeConverter.printBase64Binary(pseValueAsBytes); this.registerSmartcardScopeExclusivePseudonym(primaryKey); } public void registerSmartcardScopeExclusivePseudonym(String b64Encoded_pseudonymValue) throws IOException { String primaryKey = b64Encoded_pseudonymValue; if (!this.tokenStorage.checkForPseudonym(primaryKey)) { log.info("registerSmartcardScopeExclusivePseudonym - register new pseudonym - PseudonymPrimaryKey : " + primaryKey); this.tokenStorage.addPseudonymPrimaryKey(primaryKey); } else { log.info("registerSmartcardScopeExclusivePseudonym - already registered"); } } @Override public void addIssuerParameters(List<IssuerParameters> ips){ super.addIssuerParameters(ips); } @Override public void addCredentialSpecifications(List<CredentialSpecification> credSpecs){ super.addCredentialSpecifications(credSpecs); } @Override public void setSystemParams(SystemParameters syspar){ super.setSystemParams(syspar); } public VerifierParameters getVerifierParameters() throws Exception { if(verifierParameters == null) { try { SystemParameters sp = keyManager.getSystemParameters(); verifierParameters = engine.createVerifierParameters(sp); } catch(Exception e) { log.log(Level.WARNING, "Failed to generate VerifierParameters : " + e.getMessage()); } } return verifierParameters; } }