From 0e74ac938e4a57aae1a983d96ba2154fa767d27e Mon Sep 17 00:00:00 2001 From: Christian Wulff Date: Tue, 23 Aug 2022 00:08:52 +0200 Subject: [PATCH 1/4] Catch exceptions in BaseCommandProcessor Otherwise the StreamCommandProcessor stops at the unhandled exception and doesn't accept new commands. --- .../org/openas2/cmd/processor/BaseCommandProcessor.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Server/src/main/java/org/openas2/cmd/processor/BaseCommandProcessor.java b/Server/src/main/java/org/openas2/cmd/processor/BaseCommandProcessor.java index 1f6a829d..592c6a61 100644 --- a/Server/src/main/java/org/openas2/cmd/processor/BaseCommandProcessor.java +++ b/Server/src/main/java/org/openas2/cmd/processor/BaseCommandProcessor.java @@ -14,6 +14,8 @@ import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Level; +import java.util.logging.Logger; public abstract class BaseCommandProcessor implements CommandProcessor, Component, HasSchedule { @@ -89,7 +91,11 @@ public void schedule(ScheduledExecutorService executor) throws OpenAS2Exception @Override public Void call() throws Exception { while (running) { - processCommand(); + try { + processCommand(); + } catch (Exception ex) { + Logger.getLogger(BaseCommandProcessor.class.getName()).log(Level.SEVERE, ex.getMessage(), ex); + } } return VOID; } From c71288a0a4d75b089440a4e4b529c00664a72657 Mon Sep 17 00:00:00 2001 From: Christian Wulff Date: Sat, 27 Aug 2022 15:56:24 +0200 Subject: [PATCH 2/4] Begin new rest api v2 --- .../cert/AliasedCertificateFactory.java | 2 + .../cert/PKCS12CertificateFactory.java | 8 + .../cmd/processor/RestCommandProcessor.java | 5 +- .../cmd/processor/restapi/ApiV2Resource.java | 462 ++++++++++++++ .../processor/restapi/apiv2/Certificate.java | 92 +++ .../restapi/apiv2/CertificateImport.java | 68 +++ .../processor/restapi/apiv2/ErrorObject.java | 31 + docs/restapi-v2.json | 566 ++++++++++++++++++ docs/restapi-v2.yaml | 362 +++++++++++ 9 files changed, 1595 insertions(+), 1 deletion(-) create mode 100644 Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java create mode 100644 Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Certificate.java create mode 100644 Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/CertificateImport.java create mode 100644 Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/ErrorObject.java create mode 100644 docs/restapi-v2.json create mode 100644 docs/restapi-v2.yaml diff --git a/Server/src/main/java/org/openas2/cert/AliasedCertificateFactory.java b/Server/src/main/java/org/openas2/cert/AliasedCertificateFactory.java index 9b9d7d1c..08a5084e 100644 --- a/Server/src/main/java/org/openas2/cert/AliasedCertificateFactory.java +++ b/Server/src/main/java/org/openas2/cert/AliasedCertificateFactory.java @@ -10,6 +10,8 @@ public interface AliasedCertificateFactory extends CertificateFactory { X509Certificate getCertificate(String alias) throws OpenAS2Exception; + boolean hasPrivateKey(String alias) throws OpenAS2Exception; + Map getCertificates() throws OpenAS2Exception; void addCertificate(String alias, X509Certificate cert, boolean overwrite) throws OpenAS2Exception; diff --git a/Server/src/main/java/org/openas2/cert/PKCS12CertificateFactory.java b/Server/src/main/java/org/openas2/cert/PKCS12CertificateFactory.java index 9bdbbd5a..d2f7fef9 100644 --- a/Server/src/main/java/org/openas2/cert/PKCS12CertificateFactory.java +++ b/Server/src/main/java/org/openas2/cert/PKCS12CertificateFactory.java @@ -52,6 +52,14 @@ public X509Certificate getCertificate(String alias) throws OpenAS2Exception { } } + public boolean hasPrivateKey(String alias) throws OpenAS2Exception { + try{ + return getKeyStore().isKeyEntry(alias); + } catch (GeneralSecurityException gse) { + throw new WrappedException(gse); + } + } + public Map getCertificates() throws OpenAS2Exception { KeyStore ks = getKeyStore(); diff --git a/Server/src/main/java/org/openas2/cmd/processor/RestCommandProcessor.java b/Server/src/main/java/org/openas2/cmd/processor/RestCommandProcessor.java index 77c73978..f4f9b78a 100644 --- a/Server/src/main/java/org/openas2/cmd/processor/RestCommandProcessor.java +++ b/Server/src/main/java/org/openas2/cmd/processor/RestCommandProcessor.java @@ -20,8 +20,10 @@ import org.openas2.cmd.Command; import org.openas2.cmd.CommandResult; import org.openas2.cmd.processor.restapi.ApiResource; +import org.openas2.cmd.processor.restapi.ApiV2Resource; import org.openas2.cmd.processor.restapi.AuthenticationRequestFilter; import org.openas2.cmd.processor.restapi.LoggerRequestFilter; +import org.openas2.partner.XMLPartnershipFactory; import java.io.IOException; import java.net.URI; @@ -96,12 +98,13 @@ public void init(Session session, Map parameters) throws OpenAS2 final String userId = parameters.getOrDefault("userid", "admin"); final String password = parameters.getOrDefault("password", "admin"); ApiResource.setProcessor(this); + ApiV2Resource.setSession(session); LoggerRequestFilter.setLogger(logger); AuthenticationRequestFilter.setCredentials(userId, password); // Now needed to define packages in Jersey 3.0 final ResourceConfig rc = new ResourceConfig(); rc.packages("org.openas2.cmd.processor.restapi"); - rc.packages("org.glassfish.jersey.jackson"); + // rc.packages("org.glassfish.jersey.jackson"); rc.register(SecurityEntityFilteringFeature.class); rc.register(EntityFilteringFeature.class); rc.register(RolesAllowedDynamicFeature.class); diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java new file mode 100644 index 00000000..862533b6 --- /dev/null +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java @@ -0,0 +1,462 @@ +package org.openas2.cmd.processor.restapi; + +import org.openas2.ComponentNotFoundException; +import org.openas2.OpenAS2Exception; +import org.openas2.Session; +import org.openas2.cert.AliasedCertificateFactory; +import org.openas2.cert.CertificateFactory; +import org.openas2.cert.CertificateNotFoundException; +import org.openas2.cmd.processor.restapi.apiv2.Certificate; +import org.openas2.cmd.processor.restapi.apiv2.CertificateImport; +import org.openas2.cmd.processor.restapi.apiv2.ErrorObject; +import org.openas2.params.InvalidParameterException; +import org.openas2.partner.Partnership; +import org.openas2.partner.XMLPartnershipFactory; +import org.openas2.util.AS2Util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.security.Key; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.annotation.security.RolesAllowed; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PATCH; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; + +@Path("api/v2/") +public class ApiV2Resource { + public ApiV2Resource() { + if (mapper == null) { + mapper = new ObjectMapper(); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + mapper.setDateFormat(df); + } + } + + private static Session session; + + public static Session getSession() { + return session; + } + + public static void setSession(Session session) { + ApiV2Resource.session = session; + } + + private static XMLPartnershipFactory partnershipFactory; + + private static XMLPartnershipFactory getPartnershipFactory() { + if (partnershipFactory == null) { + try { + partnershipFactory = (XMLPartnershipFactory) getSession().getPartnershipFactory(); + } catch (ComponentNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + return partnershipFactory; + } + + private static AliasedCertificateFactory certificateFactory; + + private static AliasedCertificateFactory getCertificateFactory() throws ComponentNotFoundException { + if (certificateFactory == null) { + CertificateFactory certFx = getSession().getCertificateFactory(); + + if (certFx instanceof AliasedCertificateFactory) { + certificateFactory = (AliasedCertificateFactory) certFx; + } + } + + return certificateFactory; + } + + private static ObjectMapper mapper; + + private Response getEmptyOkResponse() { + return Response.ok("{}").type(MediaType.APPLICATION_JSON).build(); + } + + private Response getOkResponse(Object object) throws JsonProcessingException { + return Response.ok(mapper.writeValueAsString(object)).type(MediaType.APPLICATION_JSON).build(); + } + + private Response getErrorResponse(String errorMessage, Status status) throws JsonProcessingException { + return ErrorObject.getResponse(errorMessage, status, mapper); + } + + @RolesAllowed({ "ADMIN" }) + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/partners/") + public Response getPartners() throws JsonProcessingException { + Map partners = getPartnershipFactory().getPartners(); + + List> partnerList = partners.values().stream().map(p -> (Map) p) + .collect(Collectors.toList()); + + return getOkResponse(partnerList); + } + + @RolesAllowed({ "ADMIN" }) + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/partners/{name}/") + public Response getPartner(@PathParam("name") String name) throws JsonProcessingException { + Map partner = (Map) getPartnershipFactory().getPartners().get(name); + + if (partner == null) { + return getErrorResponse("Partner not found.", Status.NOT_FOUND); + } + + return getOkResponse(partner); + } + + @RolesAllowed({ "ADMIN" }) + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/certificates/") + public Response getCertificates() throws JsonProcessingException, OpenAS2Exception { + AliasedCertificateFactory certFx = getCertificateFactory(); + + List certificates = new ArrayList(); + synchronized (certFx) { + for (Map.Entry cert : certFx.getCertificates().entrySet()) { + certificates.add(Certificate.fromX509Certificate(cert.getValue(), cert.getKey(), + certFx.hasPrivateKey(cert.getKey()))); + } + } + + return getOkResponse(certificates); + } + + @RolesAllowed({ "ADMIN" }) + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/certificates/{alias}/") + public Response getCertificate(@PathParam("alias") String alias) throws JsonProcessingException, OpenAS2Exception { + AliasedCertificateFactory certFx = getCertificateFactory(); + + Certificate certificate; + synchronized (certFx) { + X509Certificate cert = certFx.getCertificates().get(alias); + + if (cert == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + certificate = Certificate.fromX509Certificate(cert, alias, certFx.hasPrivateKey(alias)); + certificate.setPublicKey(cert.getPublicKey().getEncoded()); + } + + return getOkResponse(certificate); + } + + /** + * Returns a certificate with the private key. + * + * @param alias The certificate alias. + * @param exportPassword The export password for the private key. + * @return The Certificate with the private key. + * @throws Exception + */ + @RolesAllowed({ "ADMIN" }) + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/certificates/{alias}/privatekey/{exportpassword}/") + public Response getPrivateKey(@PathParam("alias") String alias, @PathParam("exportpassword") String exportPassword) + throws Exception { + AliasedCertificateFactory certFx = getCertificateFactory(); + + Certificate certificate; + synchronized (certFx) { + X509Certificate cert = certFx.getCertificates().get(alias); + + if (cert == null || !certFx.hasPrivateKey(alias)) { + return getErrorResponse("Certificate not found.", Status.NOT_FOUND); + } + + PrivateKey key = certFx.getPrivateKey(alias); + + KeyStore ks = AS2Util.getCryptoHelper().getKeyStore(); + ks.load(null, null); + ks.setKeyEntry(alias, key, exportPassword.toCharArray(), new java.security.cert.Certificate[] { cert }); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ks.store(os, exportPassword.toCharArray()); + + certificate = Certificate.fromX509Certificate(cert, alias, true); + certificate.setPublicKey(cert.getEncoded()); + certificate.setPkcs12Container(os.toByteArray()); + } + + return getOkResponse(certificate); + } + + /** + * Gets a list of all partners using a certificate. + * + * @param alias The certificate alias. + * @return A list of all partners using this certificate or status 404 (not + * found) if the alias is not known. + * @throws JsonProcessingException + * @throws OpenAS2Exception + */ + @RolesAllowed({ "ADMIN" }) + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/certificates/{alias}/usedby/") + public Response getCertificateUsingPartners(@PathParam("alias") String alias) + throws JsonProcessingException, OpenAS2Exception { + Map partners = getPartnershipFactory().getPartners(); + + AliasedCertificateFactory certFx = getCertificateFactory(); + if (!certFx.getCertificates().containsKey(alias)) { + return getErrorResponse("The certificate was not found", Status.NOT_FOUND); + } + + List> partnerList = partners.values().stream() + .map(p -> (Map) p) + .filter(p -> alias.equals(p.get(Partnership.PID_X509_ALIAS)) + || alias.equals(p.get(Partnership.PID_X509_ALIAS_FALLBACK))) + .collect(Collectors.toList()); + + return getOkResponse(partnerList); + } + + /** + * Adds a new certificate to the certificate store. This can be a public + * certificate or a certificate with a private key. + * + * @param certificate The certificate import request + * @return The Response with Status + * 200 (Ok) it the import was successful, + * 400 (Bad request) if the certificate request is not valid, + * 409 (Conflict) if there is already a certificate with that alias or + * 404 (Not found) if there was no certificate found in the data. + * @throws Exception + */ + @RolesAllowed({ "ADMIN" }) + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/certificates/") + public Response addCertificate(CertificateImport certificate) throws Exception { + if (certificate == null || certificate.getAlias() == null) { + return getErrorResponse(CertificateImport.certificateImportUsage, Status.BAD_REQUEST); + } + + boolean isPrivateKeyImport; + try { + isPrivateKeyImport = certificate.isPrivateKeyRequest(); + } catch (InvalidParameterException e) { + return getErrorResponse(e.getMessage(), Status.BAD_REQUEST); + } + + AliasedCertificateFactory certFx = getCertificateFactory(); + + synchronized (certFx) { + if (certFx.getCertificates().get(certificate.getAlias()) != null) { + return getErrorResponse("A certificate with that name exists.", Status.CONFLICT); + } + + try { + if (isPrivateKeyImport) { + if (importPrivateKey(certFx, certificate, false)) { + // Return the inserted certificate + return getCertificate(certificate.getAlias()); + } + } else { + if (importPublicCert(certFx, certificate, false)) { + // Return the inserted certificate + return getCertificate(certificate.getAlias()); + } + } + + return getErrorResponse("No certificate found", Status.NOT_FOUND); + } catch (Exception e) { + return getErrorResponse("Error importing certificate: " + e.getMessage(), Status.BAD_REQUEST); + } + } + } + + /** + * Replaces a certificate in the certificate store. This can be a public + * certificate or a certificate with a private key. + * + * @param certificate The certificate import request + * @return The Response with Status + * 200 (Ok) it the import was successful, + * 400 (Bad request) if the certificate request is not valid or + * 404 (Not found) if there was no certificate found in the data or + * there was no certificate with the alias in the key store. + * @throws Exception + */ + @RolesAllowed({ "ADMIN" }) + @PATCH + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/certificates/{alias}/") + public Response replaceCertificate(@PathParam("alias") String alias, CertificateImport certificate) + throws Exception { + if (certificate == null || certificate.getAlias() == null) { + return getErrorResponse(CertificateImport.certificateImportUsage, Status.BAD_REQUEST); + } + + if (!alias.equals(certificate.getAlias())) { + return getErrorResponse("The path alias doesn't match the body alias.", Status.BAD_REQUEST); + } + + boolean isPrivateKeyImport; + try { + isPrivateKeyImport = certificate.isPrivateKeyRequest(); + } catch (InvalidParameterException e) { + return ErrorObject.getResponse(e.getMessage(), Status.BAD_REQUEST, mapper); + } + + AliasedCertificateFactory certFx = getCertificateFactory(); + + synchronized (certFx) { + if (certFx.getCertificates().get(certificate.getAlias()) == null) { + return ErrorObject.getResponse("There was no certificate with the alias found.", Status.NOT_FOUND, + mapper); + } + + try { + if (isPrivateKeyImport) { + if (importPrivateKey(certFx, certificate, true)) { + // Return the inserted certificate + return getCertificate(certificate.getAlias()); + } + } else { + if (importPublicCert(certFx, certificate, true)) { + // Return the inserted certificate + return getCertificate(certificate.getAlias()); + } + } + + return ErrorObject.getResponse("No certificate found in data.", Status.NOT_FOUND, mapper); + } catch (Exception e) { + return ErrorObject.getResponse("Error importing certificate: " + e.getMessage(), Status.BAD_REQUEST, + mapper); + } + } + } + + @RolesAllowed({ "ADMIN" }) + @DELETE + @Produces(MediaType.APPLICATION_JSON) + @Path("/certificates/{alias}/") + public Response deleteCertificate(@PathParam("alias") String alias) + throws OpenAS2Exception, JsonProcessingException { + AliasedCertificateFactory certFx = getCertificateFactory(); + + synchronized (certFx) { + try { + certFx.removeCertificate(alias); + } catch (CertificateNotFoundException e) { + return ErrorObject.getResponse("certificate was not found", Status.NOT_FOUND, mapper); + } + } + + return getEmptyOkResponse(); + } + + /** + * Import a public certificate. + * + * @param certFx The certificate factory + * @param certImport The certificate import request with alias and public key. + * @param replace true if an existing certificate should be replaced, + * otherwise false. + * @return True if a certificate was found in the certificate import, otherwise + * false. + * @throws CertificateException There was an error loading the certificate. + * @throws OpenAS2Exception There was an error adding the certificate. + */ + private boolean importPublicCert(AliasedCertificateFactory certFx, CertificateImport certImport, boolean replace) + throws CertificateException, OpenAS2Exception { + ByteArrayInputStream bais = new ByteArrayInputStream(certImport.getPublicKey()); + + java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509"); + + while (bais.available() > 0) { + java.security.cert.Certificate cert = cf.generateCertificate(bais); + + if (cert instanceof X509Certificate) { + certFx.addCertificate(certImport.getAlias(), (X509Certificate) cert, replace); + + return true; + } + } + + return false; + } + + /** + * Import a private key. + * + * @param certFx The certificate factory. + * @param certImport Teh certificate import request with alias, pkcs12Container + * and password. + * @param replace true if an existing certificate should be replaced, + * otherwise false. + * @return True if a certificate was found in the certificate import, otherwise + * false. + * @throws Exception + */ + private boolean importPrivateKey(AliasedCertificateFactory certFx, CertificateImport certImport, boolean replace) + throws Exception { + KeyStore ks = AS2Util.getCryptoHelper().getKeyStore(); + ks.load(new ByteArrayInputStream(certImport.getPkcs12Container()), certImport.getPassword().toCharArray()); + + Enumeration aliases = ks.aliases(); + + while (aliases.hasMoreElements()) { + String certAlias = aliases.nextElement(); + java.security.cert.Certificate cert = ks.getCertificate(certAlias); + + if (cert instanceof X509Certificate) { + Key certKey = ks.getKey(certAlias, certImport.getPassword().toCharArray()); + + if (certKey == null) { + throw new Exception("Corresponding private key not found."); + } + + if (replace) { + certFx.removeCertificate(certImport.getAlias()); + } + + certFx.addCertificate(certImport.getAlias(), (X509Certificate) cert, replace); + certFx.addPrivateKey(certImport.getAlias(), certKey, certImport.getPassword()); + + return true; + } + } + + return false; + } +} diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Certificate.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Certificate.java new file mode 100644 index 00000000..240ba34b --- /dev/null +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Certificate.java @@ -0,0 +1,92 @@ +package org.openas2.cmd.processor.restapi.apiv2; + +import java.security.cert.X509Certificate; +import java.util.Date; + +public class Certificate { + public Certificate() { + } + + public static Certificate fromX509Certificate(X509Certificate x509, String alias, boolean hasPrivateKey) { + Certificate cert = new Certificate(); + + cert.version = x509.getVersion(); + cert.serialNumber = x509.getSerialNumber().toString(16); + cert.issuer = x509.getIssuerX500Principal().getName(); + cert.subject = x509.getSubjectX500Principal().getName(); + cert.notBefore = x509.getNotBefore(); + cert.notAfter = x509.getNotAfter(); + cert.alias = alias; + cert.hasPrivateKey = hasPrivateKey; + + return cert; + } + + private String alias; + + public String getAlias() { + return alias; + } + + private int version; + + public int getVersion() { + return version; + } + + private String serialNumber; + + public String getSerialNumber() { + return serialNumber; + } + + private String issuer; + + public String getIssuer() { + return issuer; + } + + private String subject; + + public String getSubject() { + return subject; + } + + private Date notBefore; + + public Date getNotBefore() { + return notBefore; + } + + private Date notAfter; + + public Date getNotAfter() { + return notAfter; + } + + private boolean hasPrivateKey; + + public boolean isHasPrivateKey() { + return hasPrivateKey; + } + + private byte[] publicKey; + + public byte[] getPublicKey() { + return publicKey; + } + + public void setPublicKey(byte[] publicKey) { + this.publicKey = publicKey; + } + + private byte[] pkcs12Container; + + public byte[] getPkcs12Container() { + return pkcs12Container; + } + + public void setPkcs12Container(byte[] pkcs12Container) { + this.pkcs12Container = pkcs12Container; + } +} diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/CertificateImport.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/CertificateImport.java new file mode 100644 index 00000000..5d062b47 --- /dev/null +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/CertificateImport.java @@ -0,0 +1,68 @@ +package org.openas2.cmd.processor.restapi.apiv2; + +import org.openas2.params.InvalidParameterException; + +public class CertificateImport { + public static final String certificateImportUsage = "You must send { \"alias\": \"alias of certificate\", \"publicKey\": \"base64-encoded public key\" } or { \"alias\": \"alias of certificate\", \"pkcs12Container\": \"base64-encoded pkcs12 container (.p12-file)\", \"pkcs12Container\": \"password for pkcs12Container\" }"; + + private String alias; + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + private byte[] publicKey; + + public byte[] getPublicKey() { + return publicKey; + } + + public void setPublicKey(byte[] publicKey) { + this.publicKey = publicKey; + } + + private String password; + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + private byte[] pkcs12Container; + + public byte[] getPkcs12Container() { + return pkcs12Container; + } + + public void setPkcs12Container(byte[] pkcs12Container) { + this.pkcs12Container = pkcs12Container; + } + + /** + * Returns whether this is a public key import or a pkcs12 container export with password. + * @return True if it is a pkcs12 container with password, otherwise false. + * @throws InvalidParameterException Throws if the properties are not valid. + */ + public boolean isPrivateKeyRequest() throws InvalidParameterException { + if (getPublicKey() == null) { + if (getPkcs12Container() == null || getPassword() == null) { + throw new InvalidParameterException(certificateImportUsage); + } + + return true; + } else { + if (getPkcs12Container() != null || getPassword() != null) { + throw new InvalidParameterException(certificateImportUsage); + } + + return false; + } + } +} diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/ErrorObject.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/ErrorObject.java new file mode 100644 index 00000000..e16411a1 --- /dev/null +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/ErrorObject.java @@ -0,0 +1,31 @@ +package org.openas2.cmd.processor.restapi.apiv2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; + +public class ErrorObject { + private String errorMessage; + + public ErrorObject() { + } + + public ErrorObject(String errorMessage) { + this.errorMessage = errorMessage; + } + + public static Response getResponse(String errorMessage, Status status, ObjectMapper mapper) throws JsonProcessingException { + return Response.status(status).entity(mapper.writeValueAsString(new ErrorObject(errorMessage))).type(MediaType.APPLICATION_JSON).build(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/docs/restapi-v2.json b/docs/restapi-v2.json new file mode 100644 index 00000000..b995dccf --- /dev/null +++ b/docs/restapi-v2.json @@ -0,0 +1,566 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "OpenAS2 rest api", + "version": "2.0.0" + }, + "servers": [ + { + "url": "http://localhost:8443/api/v2/", + "description": "The default rest api v2 url" + } + ], + "security": [ + { + "basicAuth": [] + } + ], + "tags": [ + { + "name": "partner", + "description": "operations with partners" + }, + { + "name": "partnership", + "description": "operations with partnerships" + }, + { + "name": "certificates", + "description": "operations with certificates" + } + ], + "paths": { + "/partners": { + "get": { + "tags": [ + "partner" + ], + "summary": "Gets a list of all partners", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArrayOfPartner" + } + } + } + } + } + } + }, + "/partners/{name}/": { + "get": { + "tags": [ + "partner" + ], + "summary": "Get a partner", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Name of partner to return", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Partner found", + "content": { + "applicaton/json": { + "schema": { + "$ref": "#/components/schemas/Partner" + } + } + } + }, + "401": { + "description": "Not authenticated" + }, + "404": { + "description": "Partner not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + } + } + } + }, + "/certificates/": { + "get": { + "tags": [ + "certificates" + ], + "summary": "Gets a list of all partners", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArrayOfCertificate" + } + } + } + }, + "401": { + "description": "Not authenticated" + } + } + }, + "post": { + "tags": [ + "certificates" + ], + "summary": "Adds a new certificate. Only set alias and publicKey or alias, pkcs12Container and password.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CertificateImport" + } + } + } + }, + "responses": { + "200": { + "description": "Certificate imported", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Certificate" + } + } + } + }, + "400": { + "description": "The import request is invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + }, + "401": { + "description": "Not authenticated" + }, + "404": { + "description": "No certificate or private key was found in the CertificateImport request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + }, + "409": { + "description": "There is already a certificate with the requested alias", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + } + } + } + }, + "/certificates/{alias}/": { + "get": { + "tags": [ + "certificates" + ], + "summary": "Get a certificate with the public key", + "parameters": [ + { + "name": "alias", + "in": "path", + "description": "The alias of the certificate to return", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Certificate found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Certificate" + } + } + } + }, + "401": { + "description": "Not authenticated" + }, + "404": { + "description": "Certificate not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + } + } + }, + "patch": { + "tags": [ + "certificates" + ], + "summary": "Replaces an existing certificate. Only set alias and publicKey or alias, pkcs12Container and password.", + "parameters": [ + { + "name": "alias", + "in": "path", + "description": "The alias of the certificate to return", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CertificateImport" + } + } + } + }, + "responses": { + "200": { + "description": "Certificate replaced", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Certificate" + } + } + } + }, + "400": { + "description": "The import request is invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + }, + "401": { + "description": "Not authenticated" + }, + "404": { + "description": "No certificate or private key was found in the CertificateImport request or there was no certificate with the alias found in OpenAS2", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + } + } + }, + "delete": { + "tags": [ + "certificates" + ], + "summary": "Delete a certificate", + "parameters": [ + { + "name": "alias", + "in": "path", + "description": "The alias of the certificate to return", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Certificate deleted", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Not authenticated" + }, + "404": { + "description": "There is no certificate with the alias in OpenAS2", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + } + } + } + }, + "/certificates/{alias}/privatekey/{exportpassword}/": { + "get": { + "tags": [ + "certificates" + ], + "summary": "Gets a certificate with the private key", + "parameters": [ + { + "name": "alias", + "in": "path", + "description": "The alias of the certificate to return", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "exportpassword", + "in": "path", + "description": "The password that is used to encrypt the pkcs12 container", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Certificate found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Certificate" + } + } + } + }, + "401": { + "description": "Not authenticated" + }, + "404": { + "description": "Certificate not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + } + } + } + }, + "/certificates/{alias}/usedby/": { + "get": { + "tags": [ + "certificates" + ], + "summary": "Gets a list of partners using this certificate", + "parameters": [ + { + "name": "alias", + "in": "path", + "description": "The alias of the certificate", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Certificate found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArrayOfPartner" + } + } + } + }, + "401": { + "description": "Not authenticated" + }, + "404": { + "description": "Certificate not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic" + } + }, + "schemas": { + "ArrayOfPartner": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Partner" + } + }, + "Partner": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "MyCompany" + }, + "as2_id": { + "type": "string", + "example": "MyCompany_OID" + }, + "x509_alias": { + "type": "string", + "example": "mycompany" + }, + "email": { + "type": "string", + "example": "as2msgs@partnera.com" + }, + "x509_alias_fallback": { + "type": "string", + "example": "fallback_certificate" + } + } + }, + "ArrayOfCertificate": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Certificate" + } + }, + "Certificate": { + "type": "object", + "properties": { + "alias": { + "type": "string", + "example": "mycompany" + }, + "version": { + "type": "integer", + "example": 3, + "description": "The X509 version of the certificate" + }, + "serialNumber": { + "type": "string", + "example": "a43fbe7b13af62f6", + "description": "The hex serial number of the certificate" + }, + "issuer": { + "type": "string", + "example": "CN=as2.mycompany.com,OU=Test Lab,O=My Company,L=New York,ST=New York,C=US" + }, + "subject": { + "type": "string", + "example": "CN=as2.mycompany.com,OU=Test Lab,O=My Company,L=New York,ST=New York,C=US" + }, + "notBefore": { + "type": "string", + "format": "date-time", + "example": "2021-10-21T13:09:04+02:00", + "description": "The date and time of the start valid time" + }, + "notAfter": { + "type": "string", + "format": "date-time", + "example": "2031-10-19T13:09:04+02:00", + "description": "The expiration date and time" + }, + "hasPrivateKey": { + "type": "boolean", + "example": true, + "description": "Whether OpenAS2 has the private key of this certificate" + }, + "publicKey": { + "type": "string", + "format": "byte", + "example": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwHfWL/WOpz3deXgsdaf93wr3SZMTza6Npvw4XtTf7bw+iaor77r8pObK4HZWY6ZkfNgXwmPexfwWf12g/nbmkxEsJQ23WIXOBuZJcqZk1SP7zJCSri3rX4hFeVKI7f0ftS22NJWPQ+ykPfBRVbon4Iy1impCUHBsc5IIzDO9ei4l5Y9LBDwI74jCnjnCwZZDYs5d92yQeZ57N0my2cegAqLiGbBkZz2XM9pD3YfHNVHermQFr6XYuPqtf0tSsCFAvAh7UBbeEboaoIwj+4DWUFTWRezgDhX4IHtZMZrl+4y42a3ADMtrtfngkflh4nOUnGjHONB3gDs4dm7AtTQmoQIDAQAB", + "description": "The base64-encoded public key. Only set when requesting this certificate, not for a list of certificates" + }, + "pkcs12container": { + "type": "string", + "format": "byte", + "example": null, + "description": "The base64-encoded pkcs12 container with the certificate and private key. Only set when explicitly requesting the private key." + } + } + }, + "CertificateImport": { + "type": "object", + "properties": { + "alias": { + "type": "string", + "example": "mycompany" + }, + "publicKey": { + "type": "string", + "format": "byte", + "example": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwHfWL/WOpz3deXgsdaf93wr3SZMTza6Npvw4XtTf7bw+iaor77r8pObK4HZWY6ZkfNgXwmPexfwWf12g/nbmkxEsJQ23WIXOBuZJcqZk1SP7zJCSri3rX4hFeVKI7f0ftS22NJWPQ+ykPfBRVbon4Iy1impCUHBsc5IIzDO9ei4l5Y9LBDwI74jCnjnCwZZDYs5d92yQeZ57N0my2cegAqLiGbBkZz2XM9pD3YfHNVHermQFr6XYuPqtf0tSsCFAvAh7UBbeEboaoIwj+4DWUFTWRezgDhX4IHtZMZrl+4y42a3ADMtrtfngkflh4nOUnGjHONB3gDs4dm7AtTQmoQIDAQAB", + "description": "The base64-encoded public key. Only set when importing only a public key" + }, + "pkcs12Container": { + "type": "string", + "format": "byte", + "example": "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", + "description": "The base64-encoded pkcs12 container. Only set when importing a certificate with private key." + }, + "password": { + "type": "string", + "example": "mypassword", + "description": "The password for the pkcs12Container. Only set when importing a certificate with private key." + } + } + }, + "ErrorObject": { + "type": "object", + "properties": { + "errorMessage": { + "type": "string", + "example": "Partner not found", + "description": "The error message" + } + } + } + } + } +} \ No newline at end of file diff --git a/docs/restapi-v2.yaml b/docs/restapi-v2.yaml new file mode 100644 index 00000000..074b81f7 --- /dev/null +++ b/docs/restapi-v2.yaml @@ -0,0 +1,362 @@ +openapi: 3.0.3 +info: + title: OpenAS2 rest api + version: 2.0.0 +servers: + - url: http://localhost:8443/api/v2/ + description: The default rest api v2 url +security: + - basicAuth: [] +tags: + - name: partner + description: operations with partners + - name: partnership + description: operations with partnerships + - name: certificates + description: operations with certificates +paths: + /partners: + get: + tags: + - partner + summary: Gets a list of all partners + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ArrayOfPartner' + /partners/{name}/: + get: + tags: + - partner + summary: Get a partner + parameters: + - name: name + in: path + description: Name of partner to return + required: true + schema: + type: string + responses: + '200': + description: Partner found + content: + applicaton/json: + schema: + $ref: '#/components/schemas/Partner' + '401': + description: Not authenticated + '404': + description: Partner not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + /certificates/: + get: + tags: + - certificates + summary: Gets a list of all partners + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ArrayOfCertificate' + '401': + description: Not authenticated + post: + tags: + - certificates + summary: Adds a new certificate. Only set alias and publicKey or alias, pkcs12Container and password. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CertificateImport' + responses: + '200': + description: Certificate imported + content: + application/json: + schema: + $ref: '#/components/schemas/Certificate' + '400': + description: The import request is invalid + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + '401': + description: Not authenticated + '404': + description: No certificate or private key was found in the CertificateImport request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + '409': + description: There is already a certificate with the requested alias + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + /certificates/{alias}/: + get: + tags: + - certificates + summary: Get a certificate with the public key + parameters: + - name: alias + in: path + description: The alias of the certificate to return + required: true + schema: + type: string + responses: + '200': + description: Certificate found + content: + application/json: + schema: + $ref: '#/components/schemas/Certificate' + '401': + description: Not authenticated + '404': + description: Certificate not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + patch: + tags: + - certificates + summary: Replaces an existing certificate. Only set alias and publicKey or alias, pkcs12Container and password. + parameters: + - name: alias + in: path + description: The alias of the certificate to return + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CertificateImport' + responses: + '200': + description: Certificate replaced + content: + application/json: + schema: + $ref: '#/components/schemas/Certificate' + '400': + description: The import request is invalid + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + '401': + description: Not authenticated + '404': + description: No certificate or private key was found in the CertificateImport request or there was no certificate with the alias found in OpenAS2 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + delete: + tags: + - certificates + summary: Delete a certificate + parameters: + - name: alias + in: path + description: The alias of the certificate to return + required: true + schema: + type: string + responses: + '200': + description: Certificate deleted + content: + application/json: + schema: + type: object + '401': + description: Not authenticated + '404': + description: There is no certificate with the alias in OpenAS2 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + /certificates/{alias}/privatekey/{exportpassword}/: + get: + tags: + - certificates + summary: Gets a certificate with the private key + parameters: + - name: alias + in: path + description: The alias of the certificate to return + required: true + schema: + type: string + - name: exportpassword + in: path + description: The password that is used to encrypt the pkcs12 container + required: true + schema: + type: string + responses: + '200': + description: Certificate found + content: + application/json: + schema: + $ref: '#/components/schemas/Certificate' + '401': + description: Not authenticated + '404': + description: Certificate not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' + /certificates/{alias}/usedby/: + get: + tags: + - certificates + summary: Gets a list of partners using this certificate + parameters: + - name: alias + in: path + description: The alias of the certificate + required: true + schema: + type: string + responses: + '200': + description: Certificate found + content: + application/json: + schema: + $ref: '#/components/schemas/ArrayOfPartner' + '401': + description: Not authenticated + '404': + description: Certificate not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorObject' +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + schemas: + ArrayOfPartner: + type: array + items: + $ref: '#/components/schemas/Partner' + Partner: + type: object + properties: + name: + type: string + example: "MyCompany" + as2_id: + type: string + example: "MyCompany_OID" + x509_alias: + type: string + example: "mycompany" + email: + type: string + example: "as2msgs@partnera.com" + x509_alias_fallback: + type: string + example: "fallback_certificate" + ArrayOfCertificate: + type: array + items: + $ref: '#/components/schemas/Certificate' + Certificate: + type: object + properties: + alias: + type: string + example: "mycompany" + version: + type: integer + example: 3 + description: The X509 version of the certificate + serialNumber: + type: string + example: "a43fbe7b13af62f6" + description: The hex serial number of the certificate + issuer: + type: string + example: "CN=as2.mycompany.com,OU=Test Lab,O=My Company,L=New York,ST=New York,C=US" + subject: + type: string + example: "CN=as2.mycompany.com,OU=Test Lab,O=My Company,L=New York,ST=New York,C=US" + notBefore: + type: string + format: date-time + example: "2021-10-21T13:09:04+02:00" + description: The date and time of the start valid time + notAfter: + type: string + format: date-time + example: "2031-10-19T13:09:04+02:00" + description: The expiration date and time + hasPrivateKey: + type: boolean + example: true + description: Whether OpenAS2 has the private key of this certificate + publicKey: + type: string + format: byte + example: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwHfWL/WOpz3deXgsdaf93wr3SZMTza6Npvw4XtTf7bw+iaor77r8pObK4HZWY6ZkfNgXwmPexfwWf12g/nbmkxEsJQ23WIXOBuZJcqZk1SP7zJCSri3rX4hFeVKI7f0ftS22NJWPQ+ykPfBRVbon4Iy1impCUHBsc5IIzDO9ei4l5Y9LBDwI74jCnjnCwZZDYs5d92yQeZ57N0my2cegAqLiGbBkZz2XM9pD3YfHNVHermQFr6XYuPqtf0tSsCFAvAh7UBbeEboaoIwj+4DWUFTWRezgDhX4IHtZMZrl+4y42a3ADMtrtfngkflh4nOUnGjHONB3gDs4dm7AtTQmoQIDAQAB" + description: The base64-encoded public key. Only set when requesting this certificate, not for a list of certificates + pkcs12container: + type: string + format: byte + example: null + description: The base64-encoded pkcs12 container with the certificate and private key. Only set when explicitly requesting the private key. + CertificateImport: + type: object + properties: + alias: + type: string + example: "mycompany" + publicKey: + type: string + format: byte + example: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwHfWL/WOpz3deXgsdaf93wr3SZMTza6Npvw4XtTf7bw+iaor77r8pObK4HZWY6ZkfNgXwmPexfwWf12g/nbmkxEsJQ23WIXOBuZJcqZk1SP7zJCSri3rX4hFeVKI7f0ftS22NJWPQ+ykPfBRVbon4Iy1impCUHBsc5IIzDO9ei4l5Y9LBDwI74jCnjnCwZZDYs5d92yQeZ57N0my2cegAqLiGbBkZz2XM9pD3YfHNVHermQFr6XYuPqtf0tSsCFAvAh7UBbeEboaoIwj+4DWUFTWRezgDhX4IHtZMZrl+4y42a3ADMtrtfngkflh4nOUnGjHONB3gDs4dm7AtTQmoQIDAQAB" + description: The base64-encoded public key. Only set when importing only a public key + pkcs12Container: + type: string + format: byte + example: "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" + description: The base64-encoded pkcs12 container. Only set when importing a certificate with private key. + password: + type: string + example: "mypassword" + description: The password for the pkcs12Container. Only set when importing a certificate with private key. + ErrorObject: + type: object + properties: + errorMessage: + type: string + example: "Partner not found" + description: The error message + \ No newline at end of file From 631ed1688a15191473b73af5cf04f9d2cf34e1d1 Mon Sep 17 00:00:00 2001 From: Christian Wulff Date: Thu, 1 Sep 2022 00:23:44 +0200 Subject: [PATCH 3/4] Move private key export password into the request body --- .../cmd/processor/restapi/ApiV2Resource.java | 38 ++++++++++--------- .../processor/restapi/apiv2/Certificate.java | 3 -- .../processor/restapi/apiv2/Pkcs12Export.java | 13 +++++++ docs/restapi-v2.json | 20 +++++++--- docs/restapi-v2.yaml | 18 ++++++--- 5 files changed, 62 insertions(+), 30 deletions(-) create mode 100644 Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Pkcs12Export.java diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java index 862533b6..c48c2e97 100644 --- a/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java @@ -9,6 +9,7 @@ import org.openas2.cmd.processor.restapi.apiv2.Certificate; import org.openas2.cmd.processor.restapi.apiv2.CertificateImport; import org.openas2.cmd.processor.restapi.apiv2.ErrorObject; +import org.openas2.cmd.processor.restapi.apiv2.Pkcs12Export; import org.openas2.params.InvalidParameterException; import org.openas2.partner.Partnership; import org.openas2.partner.XMLPartnershipFactory; @@ -22,6 +23,7 @@ import java.security.Key; import java.security.KeyStore; import java.security.PrivateKey; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; @@ -47,14 +49,6 @@ @Path("api/v2/") public class ApiV2Resource { - public ApiV2Resource() { - if (mapper == null) { - mapper = new ObjectMapper(); - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); - mapper.setDateFormat(df); - } - } - private static Session session; public static Session getSession() { @@ -94,7 +88,16 @@ private static AliasedCertificateFactory getCertificateFactory() throws Componen return certificateFactory; } - private static ObjectMapper mapper; + private static ObjectMapper mapper = getMapper(); + + private static ObjectMapper getMapper() + { + ObjectMapper mapper = new ObjectMapper(); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + mapper.setDateFormat(df); + + return mapper; + } private Response getEmptyOkResponse() { return Response.ok("{}").type(MediaType.APPLICATION_JSON).build(); @@ -157,7 +160,7 @@ public Response getCertificates() throws JsonProcessingException, OpenAS2Excepti @GET @Produces(MediaType.APPLICATION_JSON) @Path("/certificates/{alias}/") - public Response getCertificate(@PathParam("alias") String alias) throws JsonProcessingException, OpenAS2Exception { + public Response getCertificate(@PathParam("alias") String alias) throws JsonProcessingException, OpenAS2Exception, CertificateEncodingException { AliasedCertificateFactory certFx = getCertificateFactory(); Certificate certificate; @@ -169,7 +172,7 @@ public Response getCertificate(@PathParam("alias") String alias) throws JsonProc } certificate = Certificate.fromX509Certificate(cert, alias, certFx.hasPrivateKey(alias)); - certificate.setPublicKey(cert.getPublicKey().getEncoded()); + certificate.setPublicKey(cert.getEncoded()); } return getOkResponse(certificate); @@ -184,10 +187,11 @@ public Response getCertificate(@PathParam("alias") String alias) throws JsonProc * @throws Exception */ @RolesAllowed({ "ADMIN" }) - @GET + @POST + @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Path("/certificates/{alias}/privatekey/{exportpassword}/") - public Response getPrivateKey(@PathParam("alias") String alias, @PathParam("exportpassword") String exportPassword) + @Path("/certificates/{alias}/privatekey/") + public Response getPrivateKey(@PathParam("alias") String alias, Pkcs12Export export) throws Exception { AliasedCertificateFactory certFx = getCertificateFactory(); @@ -203,10 +207,10 @@ public Response getPrivateKey(@PathParam("alias") String alias, @PathParam("expo KeyStore ks = AS2Util.getCryptoHelper().getKeyStore(); ks.load(null, null); - ks.setKeyEntry(alias, key, exportPassword.toCharArray(), new java.security.cert.Certificate[] { cert }); + ks.setKeyEntry(alias, key, export.getExportPassword().toCharArray(), new java.security.cert.Certificate[] { cert }); ByteArrayOutputStream os = new ByteArrayOutputStream(); - ks.store(os, exportPassword.toCharArray()); + ks.store(os, export.getExportPassword().toCharArray()); certificate = Certificate.fromX509Certificate(cert, alias, true); certificate.setPublicKey(cert.getEncoded()); @@ -280,7 +284,7 @@ public Response addCertificate(CertificateImport certificate) throws Exception { synchronized (certFx) { if (certFx.getCertificates().get(certificate.getAlias()) != null) { - return getErrorResponse("A certificate with that name exists.", Status.CONFLICT); + return getErrorResponse("A certificate with that alias exists.", Status.CONFLICT); } try { diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Certificate.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Certificate.java index 240ba34b..2c9b1c51 100644 --- a/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Certificate.java +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Certificate.java @@ -4,9 +4,6 @@ import java.util.Date; public class Certificate { - public Certificate() { - } - public static Certificate fromX509Certificate(X509Certificate x509, String alias, boolean hasPrivateKey) { Certificate cert = new Certificate(); diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Pkcs12Export.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Pkcs12Export.java new file mode 100644 index 00000000..1d2458a7 --- /dev/null +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/apiv2/Pkcs12Export.java @@ -0,0 +1,13 @@ +package org.openas2.cmd.processor.restapi.apiv2; + +public class Pkcs12Export { + private String exportPassword; + + public String getExportPassword() { + return exportPassword; + } + + public void setExportPassword(String exportPassword) { + this.exportPassword = exportPassword; + } +} diff --git a/docs/restapi-v2.json b/docs/restapi-v2.json index b995dccf..7ee6b0e5 100644 --- a/docs/restapi-v2.json +++ b/docs/restapi-v2.json @@ -326,12 +326,13 @@ } } }, - "/certificates/{alias}/privatekey/{exportpassword}/": { - "get": { + "/certificates/{alias}/privatekey/": { + "post": { "tags": [ "certificates" ], "summary": "Gets a certificate with the private key", + "description": "This could be solved with a simple get request. But then the export password would be in the URL and appear in logs.", "parameters": [ { "name": "alias", @@ -344,11 +345,11 @@ }, { "name": "exportpassword", - "in": "path", + "in": "body", "description": "The password that is used to encrypt the pkcs12 container", "required": true, "schema": { - "type": "string" + "$ref": "#/components/schemas/Pkcs12Export" } } ], @@ -517,7 +518,7 @@ "example": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwHfWL/WOpz3deXgsdaf93wr3SZMTza6Npvw4XtTf7bw+iaor77r8pObK4HZWY6ZkfNgXwmPexfwWf12g/nbmkxEsJQ23WIXOBuZJcqZk1SP7zJCSri3rX4hFeVKI7f0ftS22NJWPQ+ykPfBRVbon4Iy1impCUHBsc5IIzDO9ei4l5Y9LBDwI74jCnjnCwZZDYs5d92yQeZ57N0my2cegAqLiGbBkZz2XM9pD3YfHNVHermQFr6XYuPqtf0tSsCFAvAh7UBbeEboaoIwj+4DWUFTWRezgDhX4IHtZMZrl+4y42a3ADMtrtfngkflh4nOUnGjHONB3gDs4dm7AtTQmoQIDAQAB", "description": "The base64-encoded public key. Only set when requesting this certificate, not for a list of certificates" }, - "pkcs12container": { + "pkcs12Container": { "type": "string", "format": "byte", "example": null, @@ -551,6 +552,15 @@ } } }, + "Pkcs12Export": { + "type": "object", + "properties": { + "exportPassword": { + "type": "string", + "description": "The password that is used to encrypt the pkcs12 container" + } + } + }, "ErrorObject": { "type": "object", "properties": { diff --git a/docs/restapi-v2.yaml b/docs/restapi-v2.yaml index 074b81f7..0a4e1f93 100644 --- a/docs/restapi-v2.yaml +++ b/docs/restapi-v2.yaml @@ -196,11 +196,13 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorObject' - /certificates/{alias}/privatekey/{exportpassword}/: - get: + /certificates/{alias}/privatekey/: + post: tags: - certificates summary: Gets a certificate with the private key + description: This could be solved with a simple get request. But then the + export password would be in the URL and appear in logs. parameters: - name: alias in: path @@ -209,11 +211,11 @@ paths: schema: type: string - name: exportpassword - in: path + in: body description: The password that is used to encrypt the pkcs12 container required: true schema: - type: string + $ref: '#/components/schemas/Pkcs12Export' responses: '200': description: Certificate found @@ -327,7 +329,7 @@ components: format: byte example: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwHfWL/WOpz3deXgsdaf93wr3SZMTza6Npvw4XtTf7bw+iaor77r8pObK4HZWY6ZkfNgXwmPexfwWf12g/nbmkxEsJQ23WIXOBuZJcqZk1SP7zJCSri3rX4hFeVKI7f0ftS22NJWPQ+ykPfBRVbon4Iy1impCUHBsc5IIzDO9ei4l5Y9LBDwI74jCnjnCwZZDYs5d92yQeZ57N0my2cegAqLiGbBkZz2XM9pD3YfHNVHermQFr6XYuPqtf0tSsCFAvAh7UBbeEboaoIwj+4DWUFTWRezgDhX4IHtZMZrl+4y42a3ADMtrtfngkflh4nOUnGjHONB3gDs4dm7AtTQmoQIDAQAB" description: The base64-encoded public key. Only set when requesting this certificate, not for a list of certificates - pkcs12container: + pkcs12Container: type: string format: byte example: null @@ -352,6 +354,12 @@ components: type: string example: "mypassword" description: The password for the pkcs12Container. Only set when importing a certificate with private key. + Pkcs12Export: + type: object + properties: + exportPassword: + type: string + description: The password that is used to encrypt the pkcs12 container ErrorObject: type: object properties: From f91a1e1bae5b12ac0862b67d5037290056a17644 Mon Sep 17 00:00:00 2001 From: Christian Wulff Date: Thu, 1 Sep 2022 12:40:45 +0200 Subject: [PATCH 4/4] Move initialization of ObjectMapper to static method --- .../java/org/openas2/cmd/processor/restapi/ApiV2Resource.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java b/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java index c48c2e97..6b39557e 100644 --- a/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java +++ b/Server/src/main/java/org/openas2/cmd/processor/restapi/ApiV2Resource.java @@ -90,8 +90,7 @@ private static AliasedCertificateFactory getCertificateFactory() throws Componen private static ObjectMapper mapper = getMapper(); - private static ObjectMapper getMapper() - { + private static ObjectMapper getMapper() { ObjectMapper mapper = new ObjectMapper(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); mapper.setDateFormat(df);