Skip to content

Commit

Permalink
Implement support for CRMF requests
Browse files Browse the repository at this point in the history
  • Loading branch information
3keyroman committed Apr 28, 2024
1 parent b5fcdc5 commit 7e26f26
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 35 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<dependency>
<groupId>com.czertainly</groupId>
<artifactId>interfaces</artifactId>
<version>2.11.0</version>
<version>2.11.1-SNAPSHOT</version>
</dependency>

<!-- <dependency>-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.czertainly.api.exception.ValidationError;
import com.czertainly.api.exception.ValidationException;
import com.czertainly.api.model.common.ErrorMessageDto;
import com.czertainly.ca.connector.ejbca.exception.CertificateRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -125,6 +126,20 @@ public ErrorMessageDto handleMessageNotReadable(CertificateOperationException ex
return ErrorMessageDto.getInstance(ex.getMessage());
}

/**
* Handler for {@link CertificateRequestException}.
*
* @return
*/
@ExceptionHandler({
CertificateRequestException.class
})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorMessageDto handleMessageNotReadable(CertificateRequestException ex) {
LOG.info("HTTP 400: {}", ex.getMessage());
return ErrorMessageDto.getInstance(ex.getMessage());
}

/**
* Handler for {@link RuntimeException}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.czertainly.ca.connector.ejbca.exception;

public class CertificateRequestException extends RuntimeException {

public CertificateRequestException(String message) {
super(message);
}

public CertificateRequestException(String message, Throwable cause) {
super(message, cause);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.czertainly.ca.connector.ejbca.request;

import com.czertainly.api.model.core.enums.CertificateRequestFormat;
import org.bouncycastle.asn1.x500.X500Name;

public interface CertificateRequest {

/**
* Get encoded request
*
* @return encoded request
*/
byte[] getEncoded();

/**
* Get subject of the request as X500Name
*
* @return subject
*/
X500Name getSubject();

/**
* Get format of the request
*
* @return format
*/
CertificateRequestFormat getFormat();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.czertainly.ca.connector.ejbca.request;

import com.czertainly.api.model.core.enums.CertificateRequestFormat;
import com.czertainly.ca.connector.ejbca.exception.CertificateRequestException;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.crmf.jcajce.JcaCertificateRequestMessage;

public class CrmfCertificateRequest implements CertificateRequest {

private final byte[] encoded;
private final JcaCertificateRequestMessage certificateRequestMessage;

public CrmfCertificateRequest(byte[] crmfRequest) throws CertificateRequestException {
this.encoded = crmfRequest;
this.certificateRequestMessage = new JcaCertificateRequestMessage(crmfRequest);
}

@Override
public byte[] getEncoded() {
return encoded;
}

@Override
public X500Name getSubject() {
return certificateRequestMessage.getCertTemplate().getSubject();
}

@Override
public CertificateRequestFormat getFormat() {
return CertificateRequestFormat.CRMF;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.czertainly.ca.connector.ejbca.request;

import com.czertainly.api.model.core.enums.CertificateRequestFormat;
import com.czertainly.ca.connector.ejbca.exception.CertificateRequestException;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;

import java.io.IOException;

public class Pkcs10CertificateRequest implements CertificateRequest {

private final byte[] encoded;
private final JcaPKCS10CertificationRequest pkcs10CertificationRequest;

public Pkcs10CertificateRequest(byte[] pkcs10Request) {
this.encoded = pkcs10Request;
try {
this.pkcs10CertificationRequest = new JcaPKCS10CertificationRequest(pkcs10Request);
} catch (IOException e) {
throw new CertificateRequestException("Cannot process PKCS#10 request", e);
}
}

@Override
public byte[] getEncoded() {
return encoded;
}

@Override
public X500Name getSubject() {
return pkcs10CertificationRequest.getSubject();
}

@Override
public CertificateRequestFormat getFormat() {
return CertificateRequestFormat.PKCS10;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.czertainly.api.model.common.NameAndIdDto;
import com.czertainly.api.model.common.attribute.v2.MetadataAttribute;
import com.czertainly.api.model.connector.v2.CertificateDataResponseDto;
import com.czertainly.api.model.core.enums.CertificateRequestFormat;
import com.czertainly.ca.connector.ejbca.EjbcaException;
import com.czertainly.ca.connector.ejbca.dto.ejbca.request.SearchCertificatesRestRequestV2;
import com.czertainly.ca.connector.ejbca.dto.ejbca.response.SearchCertificatesRestResponseV2;
Expand All @@ -23,7 +24,7 @@ public interface EjbcaService {

void renewEndEntity(String authorityUuid, String username, String password) throws NotFoundException;

CertificateDataResponseDto issueCertificate(String authorityUuid, String username, String password, String pkcs10) throws NotFoundException, CADoesntExistsException_Exception, EjbcaException_Exception, AuthorizationDeniedException_Exception, NotFoundException_Exception, CesecoreException_Exception;
CertificateDataResponseDto issueCertificate(String authorityUuid, String username, String password, String certificateRequest, CertificateRequestFormat requestFormat) throws NotFoundException, CADoesntExistsException_Exception, EjbcaException_Exception, AuthorizationDeniedException_Exception, NotFoundException_Exception, CesecoreException_Exception;

void revokeCertificate(String uuid, String issuerDn, String serialNumber, int revocationReason) throws NotFoundException, AccessDeniedException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
import com.czertainly.ca.connector.ejbca.dto.ejbca.response.CertificateRestResponseV2;
import com.czertainly.ca.connector.ejbca.dto.ejbca.response.SearchCertificatesRestResponseV2;
import com.czertainly.ca.connector.ejbca.enums.UsernameGenMethod;
import com.czertainly.ca.connector.ejbca.request.CertificateRequest;
import com.czertainly.ca.connector.ejbca.service.AuthorityInstanceService;
import com.czertainly.ca.connector.ejbca.service.CertificateEjbcaService;
import com.czertainly.ca.connector.ejbca.service.EjbcaService;
import com.czertainly.ca.connector.ejbca.util.CertificateUtil;
import com.czertainly.ca.connector.ejbca.util.CsrUtil;
import com.czertainly.ca.connector.ejbca.util.CertificateRequestUtils;
import com.czertainly.core.util.AttributeDefinitionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -76,16 +76,17 @@ public CertificateDataResponseDto issueCertificate(String uuid, CertificateSignR
String usernamePrefix = AttributeDefinitionUtils.getSingleItemAttributeContentValue(AuthorityInstanceControllerImpl.ATTRIBUTE_USERNAME_PREFIX, request.getRaProfileAttributes(), StringAttributeContent.class).getData();
String usernamePostfix = AttributeDefinitionUtils.getSingleItemAttributeContentValue(AuthorityInstanceControllerImpl.ATTRIBUTE_USERNAME_POSTFIX, request.getRaProfileAttributes(), StringAttributeContent.class).getData();

JcaPKCS10CertificationRequest csr = parseCsrToJcaObject(request.getPkcs10());
CertificateRequest certificateRequest = CertificateRequestUtils.createCertificateRequest(
Base64.getDecoder().decode(request.getRequest()), request.getFormat());

String username = generateUsername(usernameGenMethod, usernamePrefix, usernamePostfix, csr);
String subjectDn = csr.getSubject().toString();
String username = generateUsername(usernameGenMethod, usernamePrefix, usernamePostfix, certificateRequest);
String subjectDn = certificateRequest.getSubject().toString();
String password = username;

// try to create end entity and issue certificate
ejbcaService.createEndEntity(uuid, username, password, subjectDn, request.getRaProfileAttributes(), request.getAttributes());
// issue certificate
CertificateDataResponseDto certificate = ejbcaService.issueCertificate(uuid, username, password, Base64.getEncoder().encodeToString(csr.getEncoded()));
CertificateDataResponseDto certificate = ejbcaService.issueCertificate(uuid, username, password, Base64.getEncoder().encodeToString(certificateRequest.getEncoded()), request.getFormat());

certificate.setMeta(getIssueMetadata(
username,
Expand All @@ -99,7 +100,8 @@ public CertificateDataResponseDto issueCertificate(String uuid, CertificateSignR

@Override
public CertificateDataResponseDto renewCertificate(String uuid, CertificateRenewRequestDto request) throws Exception {
JcaPKCS10CertificationRequest csr = parseCsrToJcaObject(request.getPkcs10());
CertificateRequest certificateRequest = CertificateRequestUtils.createCertificateRequest(
Base64.getDecoder().decode(request.getRequest()), request.getFormat());

List<MetadataAttribute> metadata = request.getMeta();

Expand All @@ -112,7 +114,7 @@ public CertificateDataResponseDto renewCertificate(String uuid, CertificateRenew
String usernameGenMethod = AttributeDefinitionUtils.getSingleItemAttributeContentValue(AuthorityInstanceControllerImpl.ATTRIBUTE_USERNAME_GEN_METHOD, request.getRaProfileAttributes(), StringAttributeContent.class).getData();
String usernamePrefix = AttributeDefinitionUtils.getSingleItemAttributeContentValue(AuthorityInstanceControllerImpl.ATTRIBUTE_USERNAME_PREFIX, request.getRaProfileAttributes(), StringAttributeContent.class).getData();
String usernamePostfix = AttributeDefinitionUtils.getSingleItemAttributeContentValue(AuthorityInstanceControllerImpl.ATTRIBUTE_USERNAME_POSTFIX, request.getRaProfileAttributes(), StringAttributeContent.class).getData();
username = generateUsername(usernameGenMethod, usernamePrefix, usernamePostfix, csr);
username = generateUsername(usernameGenMethod, usernamePrefix, usernamePostfix, certificateRequest);
}

String password = username;
Expand All @@ -121,12 +123,12 @@ public CertificateDataResponseDto renewCertificate(String uuid, CertificateRenew
// update end entity
ejbcaService.renewEndEntity(uuid, username, password);
} catch (NotFoundException e) {
String subjectDn = csr.getSubject().toString();
String subjectDn = certificateRequest.getSubject().toString();
ejbcaService.createEndEntityWithMeta(uuid, username, password, subjectDn, request.getRaProfileAttributes(), metadata);
}

// issue certificate
CertificateDataResponseDto certificate = ejbcaService.issueCertificate(uuid, username, password, Base64.getEncoder().encodeToString(csr.getEncoded()));
CertificateDataResponseDto certificate = ejbcaService.issueCertificate(uuid, username, password, Base64.getEncoder().encodeToString(certificateRequest.getEncoded()), request.getFormat());

List<MetadataAttribute> meta = new ArrayList<>();
meta.addAll(metadata.stream().filter(e -> !e.getName().equals(META_EJBCA_USERNAME)).collect(Collectors.toList()));
Expand Down Expand Up @@ -238,20 +240,7 @@ private List<MetadataAttribute> getIssueMetadata(String username, String email,
return attributes;
}

private JcaPKCS10CertificationRequest parseCsrToJcaObject(String pkcs10) throws IOException {
JcaPKCS10CertificationRequest csr;
try {
csr = CsrUtil.csrStringToJcaObject(pkcs10);
} catch (IOException e) {
logger.debug("Failed to parse CSR, will decode and try again...");
String decodedPkcs10 = new String(Base64.getDecoder().decode(pkcs10));
csr = CsrUtil.csrStringToJcaObject(decodedPkcs10);
}
return csr;
}

private String generateUsername(String usernameGenMethod, String usernamePrefix, String usernamePostfix, JcaPKCS10CertificationRequest csr) throws Exception {
// the csr comes Base64 encoded
private String generateUsername(String usernameGenMethod, String usernamePrefix, String usernamePostfix, CertificateRequest csr) throws Exception {
String username;
if (usernameGenMethod.equals(UsernameGenMethod.RANDOM.name())) {
SecureRandom random = new SecureRandom();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import com.czertainly.api.model.common.attribute.v2.content.BooleanAttributeContent;
import com.czertainly.api.model.common.attribute.v2.content.StringAttributeContent;
import com.czertainly.api.model.connector.v2.CertificateDataResponseDto;
import com.czertainly.api.model.core.enums.CertificateRequestFormat;
import com.czertainly.ca.connector.ejbca.EjbcaException;
import com.czertainly.ca.connector.ejbca.api.CertificateControllerImpl;
import com.czertainly.ca.connector.ejbca.dto.ejbca.request.SearchCertificatesRestRequestV2;
import com.czertainly.ca.connector.ejbca.dto.ejbca.response.SearchCertificatesRestResponseV2;
import com.czertainly.ca.connector.ejbca.exception.CertificateRequestException;
import com.czertainly.ca.connector.ejbca.service.AuthorityInstanceService;
import com.czertainly.ca.connector.ejbca.service.EjbcaService;
import com.czertainly.ca.connector.ejbca.util.EjbcaUtils;
Expand Down Expand Up @@ -124,16 +126,29 @@ public void renewEndEntity(String authorityUuid, String username, String passwor
}

@Override
public CertificateDataResponseDto issueCertificate(String authorityUuid, String username, String password, String pkcs10) throws NotFoundException {
public CertificateDataResponseDto issueCertificate(String authorityUuid, String username, String password, String certificateRequest, CertificateRequestFormat requestFormat) throws NotFoundException {
EjbcaWS ejbcaWS = authorityInstanceService.getConnection(authorityUuid);

try {
CertificateResponse certificateResponse = ejbcaWS.pkcs10Request(
username,
password,
pkcs10,
null,
"PKCS7WITHCHAIN"); // constant for PKCS7 with chain
CertificateResponse certificateResponse;
if (requestFormat == CertificateRequestFormat.CRMF) {
certificateResponse = ejbcaWS.crmfRequest(
username,
password,
certificateRequest,
null,
"PKCS7WITHCHAIN");
} else if (requestFormat == CertificateRequestFormat.PKCS10) {
certificateResponse = ejbcaWS.pkcs10Request(
username,
password,
certificateRequest,
null,
"PKCS7WITHCHAIN");
} else {
// we should not get here anyway
throw new CertificateRequestException("Unsupported certificate request format");
}
CertificateDataResponseDto response = new CertificateDataResponseDto();
response.setCertificateData(new String(certificateResponse.getData(), StandardCharsets.UTF_8));
return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import java.io.IOException;

import com.czertainly.api.model.core.enums.CertificateRequestFormat;
import com.czertainly.ca.connector.ejbca.request.CertificateRequest;
import com.czertainly.ca.connector.ejbca.request.CrmfCertificateRequest;
import com.czertainly.ca.connector.ejbca.request.Pkcs10CertificateRequest;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.Extension;
Expand All @@ -16,9 +20,9 @@
import java.util.Base64;
import java.util.List;

public class CsrUtil {
public class CertificateRequestUtils {

private static final Logger logger = LoggerFactory.getLogger(CsrUtil.class);
private static final Logger logger = LoggerFactory.getLogger(CertificateRequestUtils.class);

public static JcaPKCS10CertificationRequest csrStringToJcaObject(String csr) throws IOException {
csr = csr.replace("-----BEGIN CERTIFICATE REQUEST-----", "")
Expand Down Expand Up @@ -53,4 +57,12 @@ public static List<String> extractSanFromCsr(JcaPKCS10CertificationRequest csr)
}
return sans;
}

public static CertificateRequest createCertificateRequest(byte[] csr, CertificateRequestFormat format) {
return switch (format) {
case PKCS10 -> new Pkcs10CertificateRequest(csr);
case CRMF -> new CrmfCertificateRequest(csr);
default -> throw new IllegalArgumentException("Unsupported certificate request format: " + format);
};
}
}

0 comments on commit 7e26f26

Please sign in to comment.