package org.ovirt.engine.core.uutils.crypto.ticket;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import org.apache.commons.codec.binary.Base64;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.ovirt.engine.core.uutils.crypto.CertificateChain;
public class TicketDecoder {
private static final String DATE_FORMAT = "yyyyMMddHHmmss";
private final Set<TrustAnchor> trustAnchors;
private final String eku;
private final Certificate peer;
private final int tollerance;
public TicketDecoder(KeyStore trustStore, String eku, Certificate peer, int tollerance) throws KeyStoreException {
if (trustStore == null) {
trustAnchors = null;
} else {
trustAnchors = CertificateChain.keyStoreToTrustAnchors(trustStore);
}
this.eku = eku;
this.peer = peer;
this.tollerance = tollerance;
}
public TicketDecoder(KeyStore trustStore, String eku, Certificate peer) throws KeyStoreException {
this(trustStore, eku, peer, 0);
}
public TicketDecoder(KeyStore trustStore, String eku, int tollerance) throws KeyStoreException {
this(trustStore, eku, null, tollerance);
}
public TicketDecoder(Certificate peer, int tollerance) throws KeyStoreException {
this(null, null, peer, tollerance);
}
public TicketDecoder(KeyStore trustStore, String eku) throws KeyStoreException {
this(trustStore, eku, null);
}
public TicketDecoder(Certificate peer) throws KeyStoreException {
this(null, null, peer);
}
public String decode(String ticket)
throws GeneralSecurityException, IOException {
Certificate cert;
Map<String, String> map = new ObjectMapper().readValue(
Base64.decodeBase64(ticket),
TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, String.class)
);
if (peer != null) {
cert = peer;
} else {
try (InputStream is = new ByteArrayInputStream(map.get("certificate").getBytes(StandardCharsets.UTF_8))) {
cert = CertificateFactory.getInstance("X.509").generateCertificate(is);
}
}
if (trustAnchors != null) {
CertificateChain.buildCertPath(Arrays.asList(cert), trustAnchors);
}
if (eku != null) {
if (!((X509Certificate)cert).getExtendedKeyUsage().contains(eku)) {
throw new GeneralSecurityException("Certificate is not authorized for action");
}
}
List<String> signedFields = Arrays.asList(map.get("signedFields").trim().split("\\s*,\\s*"));
if (!signedFields.containsAll(Arrays.asList("salt", "data"))) {
throw new GeneralSecurityException("Invalid ticket");
}
Signature sig = Signature.getInstance(String.format("%swith%s", map.get("digest"), cert.getPublicKey().getAlgorithm()));
sig.initVerify(cert.getPublicKey());
for (String field : signedFields) {
byte[] buf = map.get(field).getBytes(StandardCharsets.UTF_8);
sig.update(buf);
}
if (!sig.verify(Base64.decodeBase64(map.get("signature")))) {
throw new GeneralSecurityException("Invalid ticket signature");
}
try {
DateFormat df = new SimpleDateFormat(DATE_FORMAT);
df.setTimeZone(TimeZone.getTimeZone("UTC"));
Date validFrom = df.parse(map.get("validFrom"));
Date validTo = df.parse(map.get("validTo"));
Date now = new Date();
if (! (validFrom.getTime() - tollerance <= now.getTime() && now.getTime() <= validTo.getTime() + tollerance)) {
throw new GeneralSecurityException("Ticket lifetime expired");
}
} catch (ParseException e) {
throw new GeneralSecurityException(e);
}
return map.get("data");
}
}