/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.nifi.toolkit.tls.service.server;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.input.BoundedReader;
import org.apache.nifi.security.util.CertificateUtils;
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityRequest;
import org.apache.nifi.toolkit.tls.service.dto.TlsCertificateAuthorityResponse;
import org.apache.nifi.toolkit.tls.util.TlsHelper;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.cert.X509Certificate;
/**
* Jetty service handler that validates the hmac of a CSR and issues a certificate if it checks out
*/
public class TlsCertificateAuthorityServiceHandler extends AbstractHandler {
public static final String CSR_FIELD_MUST_BE_SET = "csr field must be set";
public static final String HMAC_FIELD_MUST_BE_SET = "hmac field must be set";
public static final String FORBIDDEN = "forbidden";
private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityServiceHandler.class);
private final String signingAlgorithm;
private final int days;
private final String token;
private final X509Certificate caCert;
private final KeyPair keyPair;
private final ObjectMapper objectMapper;
public TlsCertificateAuthorityServiceHandler(String signingAlgorithm, int days, String token, X509Certificate caCert, KeyPair keyPair, ObjectMapper objectMapper) {
this.signingAlgorithm = signingAlgorithm;
this.days = days;
this.token = token;
this.caCert = caCert;
this.keyPair = keyPair;
this.objectMapper = objectMapper;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
try {
TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = objectMapper.readValue(new BoundedReader(request.getReader(), 1024 * 1024), TlsCertificateAuthorityRequest.class);
if (!tlsCertificateAuthorityRequest.hasHmac()) {
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(HMAC_FIELD_MUST_BE_SET), Response.SC_BAD_REQUEST);
return;
}
if (!tlsCertificateAuthorityRequest.hasCsr()) {
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(CSR_FIELD_MUST_BE_SET), Response.SC_BAD_REQUEST);
return;
}
JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = TlsHelper.parseCsr(tlsCertificateAuthorityRequest.getCsr());
byte[] expectedHmac = TlsHelper.calculateHMac(token, jcaPKCS10CertificationRequest.getPublicKey());
if (MessageDigest.isEqual(expectedHmac, tlsCertificateAuthorityRequest.getHmac())) {
String dn = jcaPKCS10CertificationRequest.getSubject().toString();
if (logger.isInfoEnabled()) {
logger.info("Received CSR with DN " + dn);
}
X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, jcaPKCS10CertificationRequest.getPublicKey(),
CertificateUtils.getExtensionsFromCSR(jcaPKCS10CertificationRequest), caCert, keyPair, signingAlgorithm, days);
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(TlsHelper.calculateHMac(token, caCert.getPublicKey()),
TlsHelper.pemEncodeJcaObject(x509Certificate)), Response.SC_OK);
return;
} else {
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(FORBIDDEN), Response.SC_FORBIDDEN);
return;
}
} catch (Exception e) {
throw new ServletException("Server error");
} finally {
baseRequest.setHandled(true);
}
}
private void writeResponse(ObjectMapper objectMapper, HttpServletRequest request, HttpServletResponse response, TlsCertificateAuthorityResponse tlsCertificateAuthorityResponse,
int responseCode) throws IOException {
if (logger.isInfoEnabled()) {
logger.info(new StringBuilder("Returning code:").append(responseCode).append(" payload ").append(objectMapper.writeValueAsString(tlsCertificateAuthorityResponse))
.append(" to ").append(request.getRemoteHost()).toString());
}
if (responseCode == Response.SC_OK) {
objectMapper.writeValue(response.getWriter(), tlsCertificateAuthorityResponse);
response.setStatus(responseCode);
} else {
response.setStatus(responseCode);
response.setContentType("application/json");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
objectMapper.writeValue(response.getWriter(), tlsCertificateAuthorityResponse);
}
}
}