summaryrefslogtreecommitdiffstats
path: root/gui-server/src/main/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'gui-server/src/main/java/org')
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java33
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java94
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java42
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java38
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java146
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java41
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java78
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java70
8 files changed, 542 insertions, 0 deletions
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java b/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java
new file mode 100644
index 0000000..7da2dd6
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java
@@ -0,0 +1,33 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.gui.server;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan(basePackages = "org.onap.policy.gui")
+public class GuiServerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(GuiServerApplication.class, args);
+ }
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java
new file mode 100644
index 0000000..8d501d2
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java
@@ -0,0 +1,94 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.gui.server.config;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import javax.annotation.PostConstruct;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustAllStrategy;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class ClampRestTemplateConfig {
+ private static final Logger LOG = LoggerFactory.getLogger(ClampRestTemplateConfig.class);
+
+ @Value("${clamp.disable-ssl-validation:false}")
+ private boolean disableSslValidation;
+
+ @Value("${clamp.disable-ssl-hostname-check:false}")
+ private boolean disableSslHostnameCheck;
+
+ @Value("${server.ssl.trust-store:#{null}}")
+ private Resource trustStore;
+
+ @Value("${server.ssl.trust-store-password:#{null}}")
+ private char[] trustStorePassword;
+
+ @PostConstruct
+ private void validateProperties() {
+ if (trustStore == null && !disableSslValidation) {
+ throw new IllegalArgumentException("server.ssl.trust-store must be set if SSL validation is enabled");
+ }
+ if (disableSslValidation && !disableSslHostnameCheck) {
+ LOG.info("Disabling SSL hostname check as SSL validation is disabled");
+ disableSslHostnameCheck = true;
+ }
+ }
+
+ /**
+ * Returns a RestTemplate, optionally disabling SSL hostname check or disabling SSL validation entirely.
+ */
+ @Bean
+ public RestTemplate clampRestTemplate() throws GeneralSecurityException, IOException {
+ SSLContext sslContext;
+ if (disableSslValidation) {
+ sslContext = new SSLContextBuilder().loadTrustMaterial(new TrustAllStrategy()).build();
+ } else {
+ sslContext = new SSLContextBuilder().loadTrustMaterial(trustStore.getURL(), trustStorePassword).build();
+ }
+
+ HostnameVerifier hostnameVerifier;
+ if (disableSslHostnameCheck) {
+ hostnameVerifier = new NoopHostnameVerifier();
+ } else {
+ hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
+ }
+
+ var csf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
+ var httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
+ var requestFactory = new HttpComponentsClientHttpRequestFactory();
+ requestFactory.setHttpClient(httpClient);
+ return new RestTemplate(requestFactory);
+ }
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java
new file mode 100644
index 0000000..3e62237
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java
@@ -0,0 +1,42 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.gui.server.config;
+
+import org.onap.policy.gui.server.filters.ClientSslHeaderFilter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class FilterRegistrationConfig {
+
+ /**
+ * Registers ClientSslToHeaderFilter for /clamp/restservices/*.
+ */
+ @Bean
+ public FilterRegistrationBean<ClientSslHeaderFilter> clientSslHeaderFilter() {
+ FilterRegistrationBean<ClientSslHeaderFilter> registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(new ClientSslHeaderFilter());
+ registrationBean.addUrlPatterns("/clamp/restservices/*");
+ return registrationBean;
+ }
+
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java
new file mode 100644
index 0000000..479202d
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.gui.server.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class StaticContentConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addViewControllers(ViewControllerRegistry registry) {
+ registry.addViewController("/clamp").setViewName("redirect:/clamp/");
+ registry.addViewController("/clamp/").setViewName("forward:/clamp/index.html");
+ registry.addViewController("/apex-editor").setViewName("redirect:/apex-editor/");
+ registry.addViewController("/apex-editor/").setViewName("forward:/apex-editor/index.html");
+ }
+
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java b/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java
new file mode 100644
index 0000000..db8f593
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java
@@ -0,0 +1,146 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.gui.server.filters;
+
+import static org.onap.policy.gui.server.util.X509CertificateEncoder.urlEncodeCert;
+
+import java.io.IOException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/**
+ * Filter which encodes a client SSL certificate into X-SSL-Cert HTTP header.
+ * CLAMP has a corresponding filter called ClampCadiFilter which decodes the
+ * header. This is needed as CLAMP runtime uses AAF for auth, and AAF uses
+ * client cert authentication. Since REST requests from CLAMP GUI to CLAMP
+ * runtime are proxied in gui-server, the proxy needs to attach a copy of the
+ * client SSL cert, as the proxy could not know the client's private key.
+ */
+@Order(1)
+public class ClientSslHeaderFilter extends OncePerRequestFilter {
+ private static final Logger LOG = LoggerFactory.getLogger(ClientSslHeaderFilter.class);
+
+ // Name of attribute containing request SSL cert.
+ public static final String X509_ATTRIBUTE_NAME = "javax.servlet.request.X509Certificate";
+
+ // Name of header containing encoded SSL cert - also used in clamp's ClampCadiFilter.
+ public static final String SSL_CERT_HEADER_NAME = "X-SSL-Cert";
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ var wrappedRequest = new ClientSslHeaderRequestWrapper(request);
+ var certs = (X509Certificate[]) request.getAttribute(X509_ATTRIBUTE_NAME);
+ if (certs != null && certs.length > 0) {
+ try {
+ certs[0].checkValidity();
+ wrappedRequest.setSslCertHeader(urlEncodeCert(certs[0]));
+ } catch (CertificateEncodingException e) {
+ LOG.error("Error encoding client SSL cert", e);
+ } catch (CertificateExpiredException | CertificateNotYetValidException e) {
+ LOG.info("Client SSL cert expired", e);
+ }
+ }
+ filterChain.doFilter(wrappedRequest, response);
+ }
+
+ /*
+ * This class wraps a HttpServletRequest so that X-SSL-Cert header can be added.
+ */
+ private static class ClientSslHeaderRequestWrapper extends HttpServletRequestWrapper {
+ private String encodedSslCert = null;
+
+ public ClientSslHeaderRequestWrapper(HttpServletRequest request) {
+ super(request);
+ }
+
+ public void setSslCertHeader(String encodedSslCert) {
+ this.encodedSslCert = encodedSslCert;
+ }
+
+ /**
+ * Returns the value of the specified request header as a String.
+ * The header name is case insensitive.
+ */
+ @Override
+ public String getHeader(String name) {
+ if (SSL_CERT_HEADER_NAME.equalsIgnoreCase(name)) {
+ return encodedSslCert;
+ } else {
+ return super.getHeader(name);
+ }
+ }
+
+ /**
+ * Returns all the values of the specified request header as an Enumeration
+ * of String objects.
+ * Some headers, such as Accept-Language can be sent by clients as several
+ * headers each with a different value rather than sending the header as a
+ * comma separated list. The header name is case insensitive.
+ */
+ @Override
+ public Enumeration<String> getHeaders(String name) {
+ if (SSL_CERT_HEADER_NAME.equalsIgnoreCase(name)) {
+ if (encodedSslCert != null) {
+ return Collections.enumeration(Collections.singletonList(encodedSslCert));
+ } else {
+ return Collections.emptyEnumeration();
+ }
+ } else {
+ return super.getHeaders(name);
+ }
+ }
+
+ /**
+ * Returns an enumeration of all the header names this request contains.
+ * If the request has no headers, this method returns an empty enumeration.
+ */
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ names.addAll(Collections.list(super.getHeaderNames()));
+ if (encodedSslCert != null) {
+ names.add(SSL_CERT_HEADER_NAME);
+ } else {
+ // This is needed to prevent an exploit where a user passes their own
+ // X-SSL-Cert header, possibly bypassing client cert verification.
+ names.remove(SSL_CERT_HEADER_NAME);
+ }
+ return Collections.enumeration(names);
+ }
+ }
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java
new file mode 100644
index 0000000..a4b92ef
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java
@@ -0,0 +1,41 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.gui.server.rest;
+
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.ModelAndView;
+
+@RestController
+@RequestMapping("/apex-editor/policy/gui/v*/apex/editor")
+public class ApexEditorRestController {
+
+ /**
+ * Strip /apex-editor prefix from Apex Editor rest calls.
+ */
+ @RequestMapping("/**")
+ public ModelAndView forwardApexEditorRest(ModelMap model, HttpServletRequest request) {
+ String targetUrl = request.getRequestURI().replaceFirst("^/apex-editor", "");
+ return new ModelAndView("forward:" + targetUrl, model);
+ }
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java
new file mode 100644
index 0000000..1975f37
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java
@@ -0,0 +1,78 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.gui.server.rest;
+
+import java.net.URI;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@RestController
+@RequestMapping("/clamp/restservices")
+public class ClampRestController {
+
+ @Value("${clamp.url}")
+ private URI clampUrl;
+
+ @Autowired
+ @Qualifier("clampRestTemplate")
+ private RestTemplate restTemplate;
+
+ /**
+ * Proxy rest calls to clamp backend.
+ */
+ @RequestMapping("/**")
+ public ResponseEntity<String> mirrorRest(@RequestBody(required = false) String body,
+ @RequestHeader HttpHeaders headers,
+ HttpMethod method,
+ HttpServletRequest request) {
+ // Strip /clamp/ prefix from request URI.
+ String requestUri = request.getRequestURI().replaceFirst("^/clamp/", "");
+ URI uri = UriComponentsBuilder.fromUri(clampUrl)
+ .path(requestUri)
+ .query(request.getQueryString())
+ .build(true).toUri();
+
+ HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
+ try {
+ return restTemplate.exchange(uri, method, httpEntity, String.class);
+
+ } catch (HttpStatusCodeException e) {
+ // On error, return the backend error code instead of 500.
+ return ResponseEntity.status(e.getRawStatusCode())
+ .headers(e.getResponseHeaders())
+ .body(e.getResponseBodyAsString());
+ }
+ }
+
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java b/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java
new file mode 100644
index 0000000..67da719
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java
@@ -0,0 +1,70 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.gui.server.util;
+
+import java.io.ByteArrayInputStream;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+
+/**
+ * Helper methods for encoding/decoding X509Certificates from PEM strings and URL-encoded PEM strings.
+ */
+public class X509CertificateEncoder {
+ private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n";
+ private static final String END_CERT = "\n-----END CERTIFICATE-----";
+
+ private X509CertificateEncoder() {}
+
+ /**
+ * Returns a PEM string from an X509Certificate.
+ */
+ public static String getPemFromCert(X509Certificate cert) throws CertificateEncodingException {
+ return BEGIN_CERT + Base64.getEncoder().encodeToString(cert.getEncoded()) + END_CERT;
+ }
+
+ /**
+ * Returns an X509Certificate from a PEM string.
+ */
+ public static X509Certificate getCertFromPem(String pem) throws CertificateException {
+ return (X509Certificate) CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(pem.getBytes()));
+ }
+
+ /**
+ * Returns URL-encoded PEM string from an X509Certificate, suitable as a HTTP header.
+ */
+ public static String urlEncodeCert(X509Certificate cert) throws CertificateEncodingException {
+ return URLEncoder.encode(getPemFromCert(cert), StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Returns an X509Certificate from a URL-encoded PEM string.
+ */
+ public static X509Certificate urlDecodeCert(String encodedPem) throws CertificateException {
+ return getCertFromPem(URLDecoder.decode(encodedPem, StandardCharsets.UTF_8));
+ }
+}