diff options
author | Thomas Lenz <tlenz@iaik.tugraz.at> | 2013-08-06 18:54:17 +0200 |
---|---|---|
committer | Thomas Lenz <tlenz@iaik.tugraz.at> | 2013-08-06 18:54:17 +0200 |
commit | 317f83216d4429612f8038c8fc7d875cd5dabc75 (patch) | |
tree | a8f0a3d4f036fb8aa37d855d68b43cb484b9d2a8 /id/server/idserverlib/src/main/java/at/gv | |
parent | 328f850d0b5775bc8aed8f5ced1a6ef6269cb831 (diff) | |
parent | c80c2df4bc7fd6cc87156e1d38f5cc4a76d1ac1a (diff) | |
download | moa-id-spss-317f83216d4429612f8038c8fc7d875cd5dabc75.tar.gz moa-id-spss-317f83216d4429612f8038c8fc7d875cd5dabc75.tar.bz2 moa-id-spss-317f83216d4429612f8038c8fc7d875cd5dabc75.zip |
Merge branch 'moa2_0_tlenz' of https://gitlab.iaik.tugraz.at/afitzek/moa-idspss into moa2_0_tlenz
Diffstat (limited to 'id/server/idserverlib/src/main/java/at/gv')
6 files changed, 221 insertions, 86 deletions
diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/binding/PostBinding.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/binding/PostBinding.java index 97c5e8d20..85861297c 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/binding/PostBinding.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/binding/PostBinding.java @@ -48,7 +48,8 @@ public class PostBinding implements IDecoder, IEncoder { Credential credentials = CredentialProvider .getIDPSigningCredential(); -// VelocityEngine engine = VelocityProvider.getClassPathVelocityEngine(); + // VelocityEngine engine = + // VelocityProvider.getClassPathVelocityEngine(); VelocityEngine engine = new VelocityEngine(); engine.setProperty(RuntimeConstants.ENCODING_DEFAULT, "UTF-8"); engine.setProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8"); @@ -56,7 +57,8 @@ public class PostBinding implements IDecoder, IEncoder { engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); engine.setProperty("classpath.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); - engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.SimpleLog4JLogSystem"); + engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, + "org.apache.velocity.runtime.log.SimpleLog4JLogSystem"); engine.init(); HTTPPostEncoder encoder = new HTTPPostEncoder(engine, @@ -94,19 +96,14 @@ public class PostBinding implements IDecoder, IEncoder { .setInboundMessageTransport(new HttpServletRequestAdapter(req)); decode.setURIComparator(new MOAURICompare()); messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); - - try { - messageContext.setMetadataProvider(new MOAMetadataProvider()); - } catch (MetadataProviderException e) { - Logger.error("Failed to get Metadata Provider"); - throw new SecurityException("Failed to get Metadata Provider"); - } - + + messageContext.setMetadataProvider(MOAMetadataProvider.getInstance()); + decode.decode(messageContext); RequestAbstractType inboundMessage = (RequestAbstractType) messageContext .getInboundMessage(); - + MOARequest request = new MOARequest(inboundMessage); request.setVerified(false); request.setEntityMetadata(messageContext.getPeerEntityMetadata()); @@ -124,11 +121,11 @@ public class PostBinding implements IDecoder, IEncoder { .setInboundMessageTransport(new HttpServletRequestAdapter(req)); messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); - + decode.decode(messageContext); Response inboundMessage = (Response) messageContext.getInboundMessage(); - + MOAResponse moaResponse = new MOAResponse(inboundMessage); moaResponse.setVerified(false); moaResponse.setEntityMetadata(messageContext.getPeerEntityMetadata()); diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/binding/RedirectBinding.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/binding/RedirectBinding.java index 4e7b08b21..86801dde5 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/binding/RedirectBinding.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/binding/RedirectBinding.java @@ -39,7 +39,7 @@ public class RedirectBinding implements IDecoder, IEncoder { public void encodeRequest(HttpServletRequest req, HttpServletResponse resp, RequestAbstractType request, String targetLocation) throws MessageEncodingException, SecurityException { - //TODO: implement + // TODO: implement } public void encodeRespone(HttpServletRequest req, HttpServletResponse resp, @@ -81,12 +81,7 @@ public class RedirectBinding implements IDecoder, IEncoder { messageContext .setInboundMessageTransport(new HttpServletRequestAdapter(req)); - try { - messageContext.setMetadataProvider(new MOAMetadataProvider()); - } catch (MetadataProviderException e) { - Logger.error("Failed to get Metadata Provider"); - throw new SecurityException("Failed to get Metadata Provider"); - } + messageContext.setMetadataProvider(MOAMetadataProvider.getInstance()); SAML2HTTPRedirectDeflateSignatureRule signatureRule = new SAML2HTTPRedirectDeflateSignatureRule( TrustEngineFactory.getSignatureKnownKeysTrustEngine()); @@ -97,7 +92,7 @@ public class RedirectBinding implements IDecoder, IEncoder { policy); messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); messageContext.setSecurityPolicyResolver(resolver); - + decode.decode(messageContext); signatureRule.evaluate(messageContext); @@ -131,12 +126,9 @@ public class RedirectBinding implements IDecoder, IEncoder { messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME); messageContext.setSecurityPolicyResolver(resolver); MOAMetadataProvider provider = null; - try { - provider = new MOAMetadataProvider(); - } catch (MetadataProviderException e) { - Logger.error("Failed to get Metadata Provider"); - throw new SecurityException("Failed to get Metadata Provider"); - } + + provider = MOAMetadataProvider.getInstance(); + messageContext.setMetadataProvider(provider); decode.decode(messageContext); @@ -150,6 +142,7 @@ public class RedirectBinding implements IDecoder, IEncoder { } public boolean handleDecode(String action, HttpServletRequest req) { - return (action.equals(PVP2XProtocol.REDIRECT) && req.getMethod().equals("GET")); + return (action.equals(PVP2XProtocol.REDIRECT) && req.getMethod() + .equals("GET")); } } diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/metadata/MOAMetadataProvider.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/metadata/MOAMetadataProvider.java index b38b862ef..e70830f93 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/metadata/MOAMetadataProvider.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/metadata/MOAMetadataProvider.java @@ -1,46 +1,99 @@ package at.gv.egovernment.moa.id.protocols.pvp2x.metadata; import java.io.File; +import java.security.cert.CertificateException; import java.util.Iterator; import java.util.List; +import java.util.Timer; import javax.xml.namespace.QName; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.protocol.Protocol; import org.opensaml.saml2.metadata.EntitiesDescriptor; import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.RoleDescriptor; import org.opensaml.saml2.metadata.provider.ChainingMetadataProvider; import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider; +import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataFilter; import org.opensaml.saml2.metadata.provider.MetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.xml.XMLObject; import org.opensaml.xml.parse.BasicParserPool; -import at.gv.egovernment.moa.id.protocols.pvp2x.config.PVPConfiguration; +import at.gv.egovernment.moa.id.auth.validator.parep.client.szrgw.SZRGWSecureSocketFactory; +import at.gv.egovernment.moa.id.commons.db.ConfigurationDBRead; +import at.gv.egovernment.moa.id.commons.db.dao.config.OAPVP2; +import at.gv.egovernment.moa.id.commons.db.dao.config.OnlineApplication; +import at.gv.egovernment.moa.id.config.auth.AuthConfigurationProvider; import at.gv.egovernment.moa.id.protocols.pvp2x.verification.MetadataSignatureFilter; +import at.gv.egovernment.moa.id.util.SSLUtils; import at.gv.egovernment.moa.logging.Logger; public class MOAMetadataProvider implements MetadataProvider { + private static MOAMetadataProvider instance = null; + + private static Object mutex = new Object(); + + public static MOAMetadataProvider getInstance() { + if (instance == null) { + synchronized (mutex) { + if (instance == null) { + instance = new MOAMetadataProvider(); + } + } + } + return instance; + } + MetadataProvider internalProvider; - public MOAMetadataProvider() throws MetadataProviderException { + private MOAMetadataProvider() { ChainingMetadataProvider chainProvider = new ChainingMetadataProvider(); Logger.info("Loading metadata"); - List<String> files = PVPConfiguration.getInstance().getMetadataFiles(); - Iterator<String> fileIt = files.iterator(); - while (fileIt.hasNext()) { - String file = fileIt.next(); - Logger.info("Loading metadata file: " + file); - FilesystemMetadataProvider fsProvider = new FilesystemMetadataProvider( - new File(file)); - fsProvider.setParserPool(new BasicParserPool()); - fsProvider.setRequireValidMetadata(true); - MetadataFilter filter = new MetadataSignatureFilter(); - fsProvider.setMetadataFilter(filter); - chainProvider.addMetadataProvider(fsProvider); - fsProvider.initialize(); + List<OnlineApplication> oaList = ConfigurationDBRead + .getAllActiveOnlineApplications(); + Iterator<OnlineApplication> oaIt = oaList.iterator(); + while (oaIt.hasNext()) { + try { + OnlineApplication oa = oaIt.next(); + Logger.info("Loading metadata for: " + oa.getFriendlyName()); + OAPVP2 pvp2Config = oa.getAuthComponentOA().getOAPVP2(); + if (pvp2Config != null) { + String metadataURL = pvp2Config.getMetadataURL(); + try { + // TODO: use proper SSL checking + HTTPMetadataProvider httpProvider = new HTTPMetadataProvider( + metadataURL, 20000); + httpProvider.setParserPool(new BasicParserPool()); + httpProvider.setRequireValidMetadata(true); + MetadataFilter filter = new MetadataSignatureFilter( + metadataURL, pvp2Config.getCertificate()); + httpProvider.setMetadataFilter(filter); + chainProvider.addMetadataProvider(httpProvider); + httpProvider.initialize(); + } catch (MetadataProviderException e) { + Logger.error( + "Failed to add Metadata file for " + + oa.getFriendlyName() + "[ " + + e.getMessage() + " ]", e); + } catch (CertificateException e) { + Logger.error( + "Failed to add Metadata file for " + + oa.getFriendlyName() + "[ " + + e.getMessage() + " ]", e); + } + } else { + Logger.info(oa.getFriendlyName() + + " is not a PVP2 Application skipping"); + } + } catch (Throwable e) { + Logger.error( + "Failed to add Metadata (unhandled reason: " + + e.getMessage(), e); + } } internalProvider = chainProvider; } diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/EntityVerifier.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/EntityVerifier.java index 42282f208..b78c2f264 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/EntityVerifier.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/EntityVerifier.java @@ -1,5 +1,6 @@ package at.gv.egovernment.moa.id.protocols.pvp2x.verification; +import java.util.Iterator; import java.util.List; import org.opensaml.saml2.metadata.EntitiesDescriptor; @@ -10,13 +11,34 @@ import org.opensaml.xml.signature.SignatureValidator; import org.opensaml.xml.validation.ValidationException; import at.gv.egovernment.moa.id.MOAIDException; +import at.gv.egovernment.moa.id.commons.db.ConfigurationDBRead; +import at.gv.egovernment.moa.id.commons.db.dao.config.OAPVP2; +import at.gv.egovernment.moa.id.commons.db.dao.config.OnlineApplication; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.NoCredentialsException; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.SAMLRequestNotSignedException; import at.gv.egovernment.moa.id.protocols.pvp2x.signer.CredentialProvider; import at.gv.egovernment.moa.logging.Logger; public class EntityVerifier { - public static void verify(EntityDescriptor entityDescriptor) throws MOAIDException { + + public static byte[] fetchSavedCredential(String entityID) { + List<OnlineApplication> oaList = ConfigurationDBRead + .getAllActiveOnlineApplications(); + Iterator<OnlineApplication> oaIt = oaList.iterator(); + while (oaIt.hasNext()) { + OnlineApplication oa = oaIt.next(); + if (oa.getPublicURLPrefix().equals(entityID)) { + OAPVP2 pvp2Config = oa.getAuthComponentOA().getOAPVP2(); + if (pvp2Config != null) { + return pvp2Config.getCertificate(); + } + } + } + return null; + } + + public static void verify(EntityDescriptor entityDescriptor) + throws MOAIDException { if (entityDescriptor.getSignature() == null) { throw new SAMLRequestNotSignedException(); } @@ -28,22 +50,71 @@ public class EntityVerifier { Logger.error("Failed to validate Signature", e); throw new SAMLRequestNotSignedException(e); } - - Credential credential = CredentialProvider.getSPTrustedCredential(entityDescriptor.getEntityID()); - if(credential == null) { + + Credential credential = CredentialProvider + .getSPTrustedCredential(entityDescriptor.getEntityID()); + if (credential == null) { throw new NoCredentialsException(entityDescriptor.getEntityID()); } - + SignatureValidator sigValidator = new SignatureValidator(credential); try { - sigValidator.validate(entityDescriptor.getSignature()); + sigValidator.validate(entityDescriptor.getSignature()); + } catch (ValidationException e) { + Logger.error("Failed to verfiy Signature", e); + throw new SAMLRequestNotSignedException(e); + } + } + + public static void verify(EntityDescriptor entityDescriptor, Credential cred) + throws MOAIDException { + if (entityDescriptor.getSignature() == null) { + throw new SAMLRequestNotSignedException(); + } + + try { + SAMLSignatureProfileValidator sigValidator = new SAMLSignatureProfileValidator(); + sigValidator.validate(entityDescriptor.getSignature()); + } catch (ValidationException e) { + Logger.error("Failed to validate Signature", e); + throw new SAMLRequestNotSignedException(e); + } + + SignatureValidator sigValidator = new SignatureValidator(cred); + try { + sigValidator.validate(entityDescriptor.getSignature()); + } catch (ValidationException e) { + Logger.error("Failed to verfiy Signature", e); + throw new SAMLRequestNotSignedException(e); + } + } + + public static void verify(EntitiesDescriptor entityDescriptor, + Credential cred) throws MOAIDException { + if (entityDescriptor.getSignature() == null) { + throw new SAMLRequestNotSignedException(); + } + + try { + SAMLSignatureProfileValidator sigValidator = new SAMLSignatureProfileValidator(); + sigValidator.validate(entityDescriptor.getSignature()); + } catch (ValidationException e) { + Logger.error("Failed to validate Signature", e); + throw new SAMLRequestNotSignedException(e); + } + + SignatureValidator sigValidator = new SignatureValidator(cred); + try { + sigValidator.validate(entityDescriptor.getSignature()); + } catch (ValidationException e) { Logger.error("Failed to verfiy Signature", e); throw new SAMLRequestNotSignedException(e); } } - - public static void verify(EntitiesDescriptor entityDescriptor) throws MOAIDException { + + public static void verify(EntitiesDescriptor entityDescriptor) + throws MOAIDException { if (entityDescriptor.getSignature() == null) { throw new SAMLRequestNotSignedException(); } @@ -56,32 +127,34 @@ public class EntityVerifier { throw new SAMLRequestNotSignedException(e); } - List<EntityDescriptor> entities = entityDescriptor.getEntityDescriptors(); - + List<EntityDescriptor> entities = entityDescriptor + .getEntityDescriptors(); + if (entities.size() > 0) { - + if (entities.size() > 1) { Logger.warn("More then one EntityID in Metadatafile with Name " - + entityDescriptor.getName() + " defined. Actually only the first" + + entityDescriptor.getName() + + " defined. Actually only the first" + " entryID is used to select the certificate to perform Metadata verification."); } - - Credential credential = CredentialProvider.getSPTrustedCredential(entities.get(0).getEntityID()); - - if(credential == null) { + + Credential credential = CredentialProvider + .getSPTrustedCredential(entities.get(0).getEntityID()); + + if (credential == null) { throw new NoCredentialsException("moaID IDP"); } - + SignatureValidator sigValidator = new SignatureValidator(credential); try { sigValidator.validate(entityDescriptor.getSignature()); - + } catch (ValidationException e) { Logger.error("Failed to verfiy Signature", e); throw new SAMLRequestNotSignedException(e); - } + } } } - - + } diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/MetadataSignatureFilter.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/MetadataSignatureFilter.java index 19176af1f..36dc2442c 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/MetadataSignatureFilter.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/MetadataSignatureFilter.java @@ -1,5 +1,8 @@ package at.gv.egovernment.moa.id.protocols.pvp2x.verification; +import iaik.x509.X509Certificate; + +import java.security.cert.CertificateException; import java.util.Iterator; import org.opensaml.saml2.metadata.EntitiesDescriptor; @@ -7,13 +10,29 @@ import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.provider.FilterException; import org.opensaml.saml2.metadata.provider.MetadataFilter; import org.opensaml.xml.XMLObject; +import org.opensaml.xml.security.credential.Credential; +import org.opensaml.xml.security.x509.BasicX509Credential; import at.gv.egovernment.moa.id.MOAIDException; import at.gv.egovernment.moa.logging.Logger; public class MetadataSignatureFilter implements MetadataFilter { + private String metadataURL; + private BasicX509Credential savedCredential; + + public MetadataSignatureFilter(String url, byte[] certificate) + throws CertificateException { + this.metadataURL = url; + X509Certificate cert = new X509Certificate(certificate); + savedCredential = new BasicX509Credential(); + savedCredential.setEntityCertificate(cert); + } + public void processEntityDescriptorr(EntityDescriptor desc) throws MOAIDException { + + String entityID = desc.getEntityID(); + EntityVerifier.verify(desc); } @@ -21,7 +40,7 @@ public class MetadataSignatureFilter implements MetadataFilter { Iterator<EntitiesDescriptor> entID = desc.getEntitiesDescriptors().iterator(); if(desc.getSignature() != null) { - EntityVerifier.verify(desc); + EntityVerifier.verify(desc, this.savedCredential); } while(entID.hasNext()) { @@ -39,12 +58,15 @@ public class MetadataSignatureFilter implements MetadataFilter { try { if (metadata instanceof EntitiesDescriptor) { EntitiesDescriptor entitiesDescriptor = (EntitiesDescriptor) metadata; + if(entitiesDescriptor.getSignature() == null) { + throw new MOAIDException("Root element of metadata file has to be signed", null); + } processEntitiesDescriptor(entitiesDescriptor); - } else if (metadata instanceof EntityDescriptor) { + } /*else if (metadata instanceof EntityDescriptor) { EntityDescriptor entityDescriptor = (EntityDescriptor) metadata; processEntityDescriptorr(entityDescriptor); - } else { - throw new MOAIDException("Invalid Metadata file", null); + } */else { + throw new MOAIDException("Invalid Metadata file Root element is no EntitiesDescriptor", null); } Logger.info("Metadata Filter done OK"); } catch (MOAIDException e) { diff --git a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/TrustEngineFactory.java b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/TrustEngineFactory.java index 60de84161..f3c5ed86a 100644 --- a/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/TrustEngineFactory.java +++ b/id/server/idserverlib/src/main/java/at/gv/egovernment/moa/id/protocols/pvp2x/verification/TrustEngineFactory.java @@ -26,7 +26,7 @@ public class TrustEngineFactory { public static SignatureTrustEngine getSignatureTrustEngine() { try { MetadataPKIXValidationInformationResolver mdResolver = new MetadataPKIXValidationInformationResolver( - new MOAMetadataProvider()); + MOAMetadataProvider.getInstance()); List<KeyInfoProvider> keyInfoProvider = new ArrayList<KeyInfoProvider>(); keyInfoProvider.add(new DSAKeyValueProvider()); @@ -49,26 +49,23 @@ public class TrustEngineFactory { public static SignatureTrustEngine getSignatureKnownKeysTrustEngine() { MetadataCredentialResolver resolver; - try { - resolver = new MetadataCredentialResolver(new MOAMetadataProvider()); - List<KeyInfoProvider> keyInfoProvider = new ArrayList<KeyInfoProvider>(); - keyInfoProvider.add(new DSAKeyValueProvider()); - keyInfoProvider.add(new RSAKeyValueProvider()); - keyInfoProvider.add(new InlineX509DataProvider()); + resolver = new MetadataCredentialResolver( + MOAMetadataProvider.getInstance()); - KeyInfoCredentialResolver keyInfoResolver = new BasicProviderKeyInfoCredentialResolver( - keyInfoProvider); + List<KeyInfoProvider> keyInfoProvider = new ArrayList<KeyInfoProvider>(); + keyInfoProvider.add(new DSAKeyValueProvider()); + keyInfoProvider.add(new RSAKeyValueProvider()); + keyInfoProvider.add(new InlineX509DataProvider()); - ExplicitKeySignatureTrustEngine engine = new ExplicitKeySignatureTrustEngine( - resolver, keyInfoResolver); + KeyInfoCredentialResolver keyInfoResolver = new BasicProviderKeyInfoCredentialResolver( + keyInfoProvider); + + ExplicitKeySignatureTrustEngine engine = new ExplicitKeySignatureTrustEngine( + resolver, keyInfoResolver); + + return engine; - return engine; - } catch (MetadataProviderException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } } - + } |