/*
* eID Applet Project.
* Copyright (C) 2015 e-Contract.be BVBA.
*
* 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.e_contract.eid.applet.service.impl.handler;
import java.io.Serializable;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import be.e_contract.eid.applet.service.impl.BeIDContextQualifier;
import be.e_contract.eid.applet.service.impl.Handles;
import be.fedict.eid.applet.service.Address;
import be.fedict.eid.applet.service.Identity;
import be.fedict.eid.applet.service.cdi.IdentificationEvent;
import be.fedict.eid.applet.service.cdi.IdentityEvent;
import be.fedict.eid.applet.service.cdi.SecurityAuditEvent;
import be.fedict.eid.applet.service.cdi.SecurityAuditEvent.Incident;
import be.fedict.eid.applet.service.cdi.SignatureDigestEvent;
import be.fedict.eid.applet.service.impl.handler.MessageHandler;
import be.fedict.eid.applet.service.impl.tlv.TlvParser;
import be.fedict.eid.applet.service.spi.AuthorizationException;
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.TrustCertificateSecurityException;
import be.fedict.eid.applet.shared.ErrorCode;
import be.fedict.eid.applet.shared.FinishedMessage;
import be.fedict.eid.applet.shared.SignCertificatesDataMessage;
import be.fedict.eid.applet.shared.SignRequestMessage;
@Handles(SignCertificatesDataMessage.class)
public class SignCertificatesDataMessageHandler implements MessageHandler<SignCertificatesDataMessage>, Serializable {
private static final long serialVersionUID = 1L;
@Inject
private Event<IdentityEvent> identityEvent;
@Inject
private Event<IdentificationEvent> identificationEvent;
@Inject
private Event<SignatureDigestEvent> signatureDigestEvent;
@Inject
private SignatureState signatureState;
@Inject
private Event<SecurityAuditEvent> securityAuditEvent;
@Override
public Object handleMessage(SignCertificatesDataMessage message, Map<String, String> httpHeaders,
HttpServletRequest request, HttpSession session) throws ServletException {
BeIDContextQualifier contextQualifier = new BeIDContextQualifier(request);
if (null != message.identityData) {
List<X509Certificate> rrnCertificateChain = new LinkedList<X509Certificate>();
rrnCertificateChain.add(message.rrnCertificate);
rrnCertificateChain.add(message.rootCertificate);
IdentificationEvent identificationEvent = new IdentificationEvent(rrnCertificateChain);
try {
this.identificationEvent.select(contextQualifier).fire(identificationEvent);
} 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);
}
if (false == identificationEvent.isValid()) {
SecurityAuditEvent securityAuditEvent = new SecurityAuditEvent(Incident.TRUST, message.rrnCertificate);
this.securityAuditEvent.select(contextQualifier).fire(securityAuditEvent);
throw new SecurityException("invalid national registry certificate chain");
}
verifySignature(contextQualifier, message.rrnCertificate.getSigAlgName(), message.identitySignatureData,
message.rrnCertificate, request, message.identityData);
Identity identity = TlvParser.parse(message.identityData, Identity.class);
if (null != message.photoData) {
/*
* Photo integrity check.
*/
byte[] expectedPhotoDigest = identity.photoDigest;
byte[] actualPhotoDigest = digestPhoto(getDigestAlgo(expectedPhotoDigest.length), message.photoData);
if (false == Arrays.equals(expectedPhotoDigest, actualPhotoDigest)) {
SecurityAuditEvent securityAuditEvent = new SecurityAuditEvent(Incident.DATA_INTEGRITY,
message.photoData);
this.securityAuditEvent.select(contextQualifier).fire(securityAuditEvent);
throw new ServletException("photo digest incorrect");
}
}
Address address;
if (null != message.addressData) {
byte[] addressFile = trimRight(message.addressData);
verifySignature(contextQualifier, message.rrnCertificate.getSigAlgName(), message.addressSignatureData,
message.rrnCertificate, request, addressFile, message.identitySignatureData);
address = TlvParser.parse(message.addressData, Address.class);
} else {
address = null;
}
/*
* Check the validity of the identity data as good as possible.
*/
GregorianCalendar cardValidityDateEndGregorianCalendar = identity.getCardValidityDateEnd();
if (null != cardValidityDateEndGregorianCalendar) {
Date now = new Date();
Date cardValidityDateEndDate = cardValidityDateEndGregorianCalendar.getTime();
if (now.after(cardValidityDateEndDate)) {
SecurityAuditEvent securityAuditEvent = new SecurityAuditEvent(Incident.DATA_INTEGRITY,
message.identityData);
this.securityAuditEvent.select(contextQualifier).fire(securityAuditEvent);
throw new SecurityException("eID card has expired");
}
}
this.identityEvent.select(contextQualifier).fire(new IdentityEvent(identity, address, message.photoData, null));
}
SignatureDigestEvent signatureDigestEvent = new SignatureDigestEvent(message.certificateChain);
try {
this.signatureDigestEvent.select(contextQualifier).fire(signatureDigestEvent);
} catch (AuthorizationException e) {
return new FinishedMessage(ErrorCode.AUTHORIZATION);
}
String digestAlgo = signatureDigestEvent.getDigestAlgo();
boolean logoff = signatureDigestEvent.isLogoff();
boolean requireSecureReader = false;
boolean removeCard = signatureDigestEvent.isRemoveCard();
String description = signatureDigestEvent.getDescription();
byte[] digestValue = signatureDigestEvent.getDigestValue();
// required for later verification
this.signatureState.setDigestValue(digestValue);
this.signatureState.setDigestAlgo(digestAlgo);
return new SignRequestMessage(digestValue, digestAlgo, description, logoff, removeCard, requireSecureReader);
}
@Override
public void init(ServletConfig config) throws ServletException {
}
private void verifySignature(BeIDContextQualifier contextQualifier, String signAlgo, byte[] signatureData,
X509Certificate certificate, HttpServletRequest request, byte[]... data) throws ServletException {
Signature signature;
try {
signature = Signature.getInstance(signAlgo);
} catch (NoSuchAlgorithmException e) {
throw new ServletException("algo error: " + e.getMessage(), e);
}
PublicKey publicKey = certificate.getPublicKey();
try {
signature.initVerify(publicKey);
} catch (InvalidKeyException e) {
throw new ServletException("key error: " + e.getMessage(), e);
}
try {
for (byte[] dataItem : data) {
signature.update(dataItem);
}
boolean result = signature.verify(signatureData);
if (false == result) {
SecurityAuditEvent securityAuditEvent = new SecurityAuditEvent(Incident.DATA_INTEGRITY, certificate,
signatureData);
this.securityAuditEvent.select(contextQualifier).fire(securityAuditEvent);
throw new ServletException("signature incorrect");
}
} catch (SignatureException e) {
SecurityAuditEvent securityAuditEvent = new SecurityAuditEvent(Incident.DATA_INTEGRITY, certificate,
signatureData);
this.securityAuditEvent.select(contextQualifier).fire(securityAuditEvent);
throw new ServletException("signature error: " + e.getMessage(), e);
}
}
private byte[] digestPhoto(String digestAlgoName, byte[] photoFile) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance(digestAlgoName);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("digest error: " + e.getMessage(), e);
}
byte[] photoDigest = messageDigest.digest(photoFile);
return photoDigest;
}
private String getDigestAlgo(final int hashSize) throws RuntimeException {
switch (hashSize) {
case 20:
return "SHA-1";
case 28:
return "SHA-224";
case 32:
return "SHA-256";
case 48:
return "SHA-384";
case 64:
return "SHA-512";
}
throw new RuntimeException("Failed to find guess algorithm for hash size of " + hashSize + " bytes");
}
private byte[] trimRight(byte[] addressFile) {
int idx;
for (idx = 0; idx < addressFile.length; idx++) {
if (0 == addressFile[idx]) {
break;
}
}
byte[] result = new byte[idx];
System.arraycopy(addressFile, 0, result, 0, idx);
return result;
}
}