/* * eID Applet Project. * Copyright (C) 2008-2009 FedICT. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version * 3.0 as published by the Free Software Foundation. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, see * http://www.gnu.org/licenses/. */ package be.fedict.eid.applet.service.impl.handler; import java.io.ByteArrayOutputStream; import java.lang.reflect.Method; import java.security.PublicKey; import java.security.Signature; import java.security.cert.X509Certificate; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PSSParameterSpec; import java.util.List; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bouncycastle.jce.provider.BouncyCastleProvider; import be.fedict.eid.applet.service.impl.ServiceLocator; import be.fedict.eid.applet.service.impl.UserIdentifierUtil; import be.fedict.eid.applet.service.spi.AuditService; import be.fedict.eid.applet.service.spi.CertificateSecurityException; import be.fedict.eid.applet.service.spi.ExpiredCertificateSecurityException; import be.fedict.eid.applet.service.spi.RevokedCertificateSecurityException; import be.fedict.eid.applet.service.spi.SignatureService; import be.fedict.eid.applet.service.spi.TrustCertificateSecurityException; import be.fedict.eid.applet.shared.ErrorCode; import be.fedict.eid.applet.shared.FinishedMessage; import be.fedict.eid.applet.shared.SignatureDataMessage; /** * Signature data message protocol handler. * * @author Frank Cornelis * */ @HandlesMessage(SignatureDataMessage.class) public class SignatureDataMessageHandler implements MessageHandler<SignatureDataMessage> { private static final Log LOG = LogFactory.getLog(SignatureDataMessageHandler.class); public static final byte[] SHA1_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; public static final byte[] SHA224_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c }; public static final byte[] SHA256_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 }; public static final byte[] SHA384_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 }; public static final byte[] SHA512_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 }; public static final byte[] RIPEMD160_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; public static final byte[] RIPEMD128_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x1d, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x02, 0x05, 0x00, 0x04, 0x10 }; public static final byte[] RIPEMD256_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x2d, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x03, 0x05, 0x00, 0x04, 0x20 }; @InitParam(HelloMessageHandler.SIGNATURE_SERVICE_INIT_PARAM_NAME) private ServiceLocator<SignatureService> signatureServiceLocator; @InitParam(AuthenticationDataMessageHandler.AUDIT_SERVICE_INIT_PARAM_NAME) private ServiceLocator<AuditService> auditServiceLocator; public static final String DIGEST_VALUE_SESSION_ATTRIBUTE = SignatureDataMessageHandler.class.getName() + ".digestValue"; public static final String DIGEST_ALGO_SESSION_ATTRIBUTE = SignatureDataMessageHandler.class.getName() + ".digestAlgo"; public Object handleMessage(SignatureDataMessage message, Map<String, String> httpHeaders, HttpServletRequest request, HttpSession session) throws ServletException { LOG.debug("signature data message received"); byte[] signatureValue = message.signatureValue; List<X509Certificate> certificateChain = message.certificateChain; if (certificateChain.isEmpty()) { throw new ServletException("certificate chain is empty"); } X509Certificate signingCertificate = certificateChain.get(0); if (null == signingCertificate) { throw new ServletException("non-repudiation certificate missing"); } LOG.debug("non-repudiation signing certificate: " + signingCertificate.getSubjectX500Principal()); PublicKey signingPublicKey = signingCertificate.getPublicKey(); /* * Verify the signature. */ String digestAlgo = SignatureDataMessageHandler.getDigestAlgo(session); byte[] expectedDigestValue = SignatureDataMessageHandler.getDigestValue(session); if (digestAlgo.endsWith("-PSS")) { LOG.debug("verifying RSA/PSS signature"); try { Signature signature = Signature.getInstance("RAWRSASSA-PSS", BouncyCastleProvider.PROVIDER_NAME); if ("SHA-256-PSS".equals(digestAlgo)) { LOG.debug("RSA/PSS SHA256"); signature.setParameter( new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 32, 1)); } signature.initVerify(signingPublicKey); signature.update(expectedDigestValue); boolean result = signature.verify(signatureValue); if (false == result) { throw new SecurityException("signature incorrect"); } } catch (Exception e) { LOG.debug("signature verification error: " + e.getMessage(), e); throw new ServletException("signature verification error: " + e.getMessage(), e); } } else { try { Signature signature = Signature.getInstance("RawRSA", BouncyCastleProvider.PROVIDER_NAME); signature.initVerify(signingPublicKey); ByteArrayOutputStream digestInfo = new ByteArrayOutputStream(); if ("SHA-1".equals(digestAlgo) || "SHA1".equals(digestAlgo)) { digestInfo.write(SHA1_DIGEST_INFO_PREFIX); } else if ("SHA-224".equals(digestAlgo)) { digestInfo.write(SHA224_DIGEST_INFO_PREFIX); } else if ("SHA-256".equals(digestAlgo)) { digestInfo.write(SHA256_DIGEST_INFO_PREFIX); } else if ("SHA-384".equals(digestAlgo)) { digestInfo.write(SHA384_DIGEST_INFO_PREFIX); } else if ("SHA-512".equals(digestAlgo)) { digestInfo.write(SHA512_DIGEST_INFO_PREFIX); } else if ("RIPEMD160".equals(digestAlgo)) { digestInfo.write(RIPEMD160_DIGEST_INFO_PREFIX); } else if ("RIPEMD128".equals(digestAlgo)) { digestInfo.write(RIPEMD128_DIGEST_INFO_PREFIX); } else if ("RIPEMD256".equals(digestAlgo)) { digestInfo.write(RIPEMD256_DIGEST_INFO_PREFIX); } digestInfo.write(expectedDigestValue); signature.update(digestInfo.toByteArray()); boolean result = signature.verify(signatureValue); if (false == result) { AuditService auditService = this.auditServiceLocator.locateService(); if (null != auditService) { String remoteAddress = request.getRemoteAddr(); auditService.signatureError(remoteAddress, signingCertificate); } throw new SecurityException("signature incorrect"); } } catch (Exception e) { LOG.debug("signature verification error: " + e.getMessage()); throw new ServletException("signature verification error: " + e.getMessage(), e); } } AuditService auditService = this.auditServiceLocator.locateService(); if (null != auditService) { String userId = UserIdentifierUtil.getUserId(signingCertificate); auditService.signed(userId); } SignatureService signatureService = this.signatureServiceLocator.locateService(); try { signatureService.postSign(signatureValue, certificateChain); } catch (ExpiredCertificateSecurityException e) { return new FinishedMessage(ErrorCode.CERTIFICATE_EXPIRED); } catch (RevokedCertificateSecurityException e) { return new FinishedMessage(ErrorCode.CERTIFICATE_REVOKED); } catch (TrustCertificateSecurityException e) { return new FinishedMessage(ErrorCode.CERTIFICATE_NOT_TRUSTED); } catch (CertificateSecurityException e) { return new FinishedMessage(ErrorCode.CERTIFICATE); } catch (Exception e) { /* * We don't want to depend on the full JavaEE profile in this * artifact. */ if ("javax.ejb.EJBException".equals(e.getClass().getName())) { Exception exception; try { Method getCausedByExceptionMethod = e.getClass().getMethod("getCausedByException", new Class[] {}); exception = (Exception) getCausedByExceptionMethod.invoke(e, new Object[] {}); } catch (Exception e2) { LOG.debug("error: " + e.getMessage(), e); throw new SecurityException("error retrieving the root cause: " + e2.getMessage()); } if (exception instanceof ExpiredCertificateSecurityException) { return new FinishedMessage(ErrorCode.CERTIFICATE_EXPIRED); } if (exception instanceof RevokedCertificateSecurityException) { return new FinishedMessage(ErrorCode.CERTIFICATE_REVOKED); } if (exception instanceof TrustCertificateSecurityException) { return new FinishedMessage(ErrorCode.CERTIFICATE_NOT_TRUSTED); } if (exception instanceof CertificateSecurityException) { return new FinishedMessage(ErrorCode.CERTIFICATE); } } throw new SecurityException("signature service error: " + e.getMessage(), e); } return new FinishedMessage(); } public void init(ServletConfig config) throws ServletException { // empty } public static byte[] getDigestValue(HttpSession session) { return (byte[]) session.getAttribute(DIGEST_VALUE_SESSION_ATTRIBUTE); } public static void setDigestValue(byte[] digestValue, String digestAlgo, HttpSession session) { session.setAttribute(DIGEST_VALUE_SESSION_ATTRIBUTE, digestValue); session.setAttribute(DIGEST_ALGO_SESSION_ATTRIBUTE, digestAlgo); } public static String getDigestAlgo(HttpSession session) { String digestAlgo = (String) session.getAttribute(DIGEST_ALGO_SESSION_ATTRIBUTE); return digestAlgo; } }