Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AG-112 API overovanie #352

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,21 @@ public synchronized static SignatureValidator getInstance() {
return instance;
}

public synchronized Reports validate(SignedDocumentValidator docValidator) {
private synchronized Reports validate(SignedDocumentValidator docValidator) {
docValidator.setCertificateVerifier(verifier);

// TODO: do not print stack trace inside DSS
return docValidator.validateDocument();
}

public synchronized Reports validate(DSSDocument document) {
var documentValidator = createDocumentValidator(document);
if (documentValidator == null)
return null;

return validate(documentValidator);
}

public synchronized void refresh() {
validationJob.offlineRefresh();
}
Expand Down Expand Up @@ -126,11 +134,7 @@ private CertificateSource getJournalCertificateSource() throws AssertionError {
}

public synchronized ValidationReports getSignatureValidationReport(SigningJob job) {
var documentValidator = createDocumentValidator(job.getDocument());
if (documentValidator == null)
return new ValidationReports(null, job);

return new ValidationReports(validate(documentValidator), job);
return new ValidationReports(validate(job.getDocument()), job);
}

public static String getSignatureValidationReportHTML(Reports signatureValidationReport) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ public static boolean validateXmlContentAgainstXsd(String xmlContent, String xsd
return true;

try {
if (!xsdSchema.isEmpty() && xsdSchema.charAt(0) == '\uFEFF')
xsdSchema = xsdSchema.substring(1);

var schema = XMLUtils.getSecureSchemaFactory().newSchema(new StreamSource(new StringReader(xsdSchema)));
var validator = schema.newValidator();
validator.validate(new StreamSource(new StringReader(xmlContent)));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package digital.slovensko.autogram.core.errors;

public class DocumentNotSignedYetException extends AutogramException {
public DocumentNotSignedYetException() {
super("Document not signed", "Document is not signed yet", "The provided document is not eligible for signature validation because the document is not signed yet.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public void start() {
server.createContext("/batch", new BatchEndpoint(autogram)).getFilters()
.add(new AutogramCorsFilter(List.of("POST", "DELETE")));

// Validate
server.createContext("/validate", new ValidationEndpoint()).getFilters()
.add(new AutogramCorsFilter(List.of("POST")));

// Start server
server.start();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package digital.slovensko.autogram.server;

import com.google.gson.JsonSyntaxException;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import digital.slovensko.autogram.core.SignatureValidator;
import digital.slovensko.autogram.core.errors.DocumentNotSignedYetException;
import digital.slovensko.autogram.server.dto.ErrorResponse;
import digital.slovensko.autogram.server.dto.ValidationRequestBody;
import digital.slovensko.autogram.server.dto.ValidationResponseBody;
import digital.slovensko.autogram.server.errors.MalformedBodyException;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.InMemoryDocument;

import java.io.IOException;
import java.util.Base64;

public class ValidationEndpoint implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
DSSDocument document = null;
try {
var body = EndpointUtils.loadFromJsonExchange(exchange, ValidationRequestBody.class);
if (body.dataB64() == null)
throw new IllegalArgumentException("Document to validate is not provided.");

document = new InMemoryDocument(Base64.getDecoder().decode(body.dataB64()));
if (document == null || document.openStream().readAllBytes().length < 1)
throw new IllegalArgumentException("Document to validate is null.");
} catch (JsonSyntaxException | IOException | IllegalArgumentException e) {
var response = ErrorResponse.buildFromException(new MalformedBodyException(e.getMessage(), e));
EndpointUtils.respondWithError(response, exchange);
}

try {
var reports = SignatureValidator.getInstance().validate(document);
if (reports == null) {
EndpointUtils.respondWithError(ErrorResponse.buildFromException(new DocumentNotSignedYetException()), exchange);
return;
}

try {
EndpointUtils.respondWith(ValidationResponseBody.build(reports, document), exchange);
} catch (Exception e) {
EndpointUtils.respondWithError(ErrorResponse.buildFromException(e), exchange);
}

} catch (Exception e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public static ErrorResponse buildFromException(Exception e) {
case "BatchNotStartedException" -> new ErrorResponse(400, "BATCH_NOT_STARTED", (AutogramException) e);
case "BatchInvalidIdException" -> new ErrorResponse(404, "BATCH_NOT_FOUND", (AutogramException) e);
case "BatchConflictException" -> new ErrorResponse(400, "BATCH_CONFLICT", (AutogramException) e);
case "DocumentNotSignedYetException" -> new ErrorResponse(422, "DOCUMENT_NOT_SIGNED", (AutogramException) e);
default -> new ErrorResponse(500, "INTERNAL_ERROR", "Unexpected exception signing document", e.getMessage());
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package digital.slovensko.autogram.server.dto;

public record ValidationRequestBody(String dataB64) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package digital.slovensko.autogram.server.dto;

import digital.slovensko.autogram.core.errors.DocumentNotSignedYetException;
import eu.europa.esig.dss.asic.cades.ASiCWithCAdESContainerExtractor;
import eu.europa.esig.dss.asic.common.AbstractASiCContainerExtractor;
import eu.europa.esig.dss.asic.xades.ASiCWithXAdESContainerExtractor;
import eu.europa.esig.dss.diagnostic.SignerDataWrapper;
import eu.europa.esig.dss.enumerations.MimeTypeEnum;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.validation.reports.Reports;

import javax.security.auth.x500.X500Principal;
import java.text.SimpleDateFormat;
import java.util.List;

public record ValidationResponseBody(String fileFormat, List<Signature> signatures, List<SignedObject> signedObjects,
List<UnsignedObject> unsignedObjects) {
private static final SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss Z");

public static ValidationResponseBody build(Reports reports, DSSDocument document) throws DocumentNotSignedYetException {
var sr = reports.getSimpleReport();
var dd = reports.getDiagnosticData();
var dr = reports.getDetailedReport();

if (sr.getSignatureIdList().isEmpty())
throw new DocumentNotSignedYetException();

var f = sr.getContainerType();
var fileFormat = f == null ? sr.getSignatureFormat(sr.getSignatureIdList().get(0)).getSignatureForm().name() : f.name();

List<Signature> signatures = dr.getSignatures().stream().map((e) -> {
var conclusion = e.getConclusion();
var timestamps = e.getTimestamps();
var signingCertificate = dd.getCertificateById(dd.getSigningCertificateId(e.getId()));

return new Signature(
new Result(
conclusion.getIndication().ordinal(),
conclusion.getIndication().name()
),
new SignatureInfo(
sr.getSignatureFormat(e.getId()).name(),
format.format(sr.getSigningTime(e.getId())),
!timestamps.isEmpty(),
timestamps.isEmpty() ? null : format.format(sr.getProductionTime(timestamps.get(0).getId())),
new CertificateInfo(
new X500Principal(signingCertificate.getCertificateIssuerDN()).getName(X500Principal.RFC1779),
new X500Principal(signingCertificate.getCertificateDN()).getName(X500Principal.RFC1779),
signingCertificate.getSerialNumber(),
format.format(sr.getSigningTime(e.getId())),
format.format(signingCertificate.getNotBefore()),
format.format(signingCertificate.getNotAfter()),
new Result(
sr.getSignatureQualification(e.getId()).ordinal(),
sr.getSignatureQualification(e.getId()).getReadable()
)
),
timestamps.isEmpty() ? null : timestamps.stream().map((t) -> {
var tsCertificate = dd.getCertificateById(dd.getTimestampSigningCertificateId(t.getId()));

return new TimestampCertificateInfo(
new X500Principal(tsCertificate.getCertificateIssuerDN()).getName(X500Principal.RFC1779),
new X500Principal(tsCertificate.getCertificateDN()).getName(X500Principal.RFC1779),
tsCertificate.getSerialNumber(),
format.format(sr.getProductionTime(t.getId())),
format.format(tsCertificate.getNotBefore()),
format.format(tsCertificate.getNotAfter()),
new Result(
sr.getTimestampQualification(t.getId()).ordinal(),
sr.getTimestampQualification(t.getId()).getReadable()
),
dd.getTimestampType(t.getId()).name()
);
}).toList(),
dd.getSignerDocuments(e.getId()).stream().map(SignerDataWrapper::getId).toList()
));
}).toList();

List<SignedObject> signedObjects = null;
List<UnsignedObject> unsignedObjects = null;
AbstractASiCContainerExtractor extractor = null;

switch (sr.getSignatureFormat(sr.getSignatureIdList().get(0)).getSignatureForm()) {
case PAdES: {
signedObjects = dd.getAllSignerDocuments().stream().map((d) -> new SignedObject(
d.getId(),
MimeTypeEnum.PDF.getMimeTypeString(),
d.getReferencedName()
)).toList();
break;
}
case XAdES: {
extractor = new ASiCWithXAdESContainerExtractor(document);
break;
}
case CAdES: {
extractor = new ASiCWithCAdESContainerExtractor(document);
break;
}
default:
}

if (extractor != null) {
var allObjects = extractor.extract().getSignedDocuments();
signedObjects = getSignedObjects(allObjects, dd.getAllSignerDocuments());
unsignedObjects = getUnsignedObjects(allObjects, dd.getAllSignerDocuments());
}

return new ValidationResponseBody(fileFormat, signatures, signedObjects, unsignedObjects);
}

private static List<SignedObject> getSignedObjects(List<DSSDocument> docs, List<SignerDataWrapper> signedObjects) {
return signedObjects.stream().map((signedObject) -> {
var r = docs.stream().filter((doc) -> doc.getName().equals(signedObject.getReferencedName())).toList();
if (r.isEmpty())
return null;

return new SignedObject(
signedObject.getId(),
r.get(0).getMimeType().getMimeTypeString(),
signedObject.getReferencedName()
);
}).toList();
}

private static List<UnsignedObject> getUnsignedObjects(List<DSSDocument> docs, List<SignerDataWrapper> signedObjects) {
return docs.stream().filter((o) -> signedObjects.stream().filter((s) -> o.getName().equals(s.getReferencedName())).toList().isEmpty()).map((generalObject) ->
new UnsignedObject(
generalObject.getMimeType().getMimeTypeString(),
generalObject.getName()
)
).toList();
}

record Signature(Result validationResult, SignatureInfo signatureInfo) {
}

record Result(int code, String description) {
}

record SignatureInfo(String level, String claimedSigningTime, boolean isTimestamped, String timestampSigningTime,
CertificateInfo signingCertificate, List<TimestampCertificateInfo> timestamps,
List<String> signedObjectsIds) {
}

record CertificateInfo(String issuerDN, String subjectDN, String serialNumber, String productionTime,
String notBefore,
String notAfter, Result qualification) {
}

record TimestampCertificateInfo(String issuerDN, String subjectDN, String serialNumber, String productionTime,
String notBefore,
String notAfter, Result qualification, String timestampType) {
}

record SignedObject(String id, String mimeType, String filename) {
}

record UnsignedObject(String mimeType, String filename) {
}
}
Loading