Adding utility methods for generating x509 certificates for testing purposes.

Adding jacoco test coverage report plugin.
Adding and improving tests for X509SvidValidator and CertificateUtils.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
Max Lambrecht 2020-05-22 09:31:32 -03:00
parent ef4dbf86c5
commit ef2cdafab9
10 changed files with 271 additions and 95 deletions

View File

@ -3,6 +3,7 @@ subprojects {
version '0.6.0'
apply plugin: 'java-library'
apply plugin: 'jacoco'
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
@ -13,6 +14,28 @@ subprojects {
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
jacocoTestReport {
dependsOn test // tests are required to run before generating the report
reports {
xml.enabled false
csv.enabled false
html.destination file("${buildDir}/jacocoHtml")
}
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: ['**/internal/**', '**/exception/**'])
}))
}
}
jacoco {
toolVersion = "0.8.5"
reportsDir = file("$buildDir/customJacocoReportDir")
}
dependencies {

View File

@ -56,5 +56,8 @@ dependencies {
// library for processing JWT tokens and JOSE JWK bundles
implementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '5.7'
// using bouncy castle for generating x509 certs for testing purposes
testImplementation group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.65'
}

View File

@ -18,6 +18,7 @@ import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import static java.util.Collections.EMPTY_LIST;
import static org.apache.commons.lang3.StringUtils.startsWith;
/**
@ -209,6 +210,9 @@ public class CertificateUtils {
}
private static List<String> getSpiffeIds(X509Certificate certificate) throws CertificateParsingException {
if (certificate.getSubjectAlternativeNames() == null) {
return EMPTY_LIST;
}
return certificate.getSubjectAlternativeNames()
.stream()
.map(san -> (String) san.get(SAN_VALUE_INDEX))

View File

@ -3,19 +3,26 @@ package spiffe.internal;
import lombok.val;
import org.junit.jupiter.api.Test;
import spiffe.spiffeid.SpiffeId;
import spiffe.spiffeid.TrustDomain;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.*;
import static spiffe.utils.X509CertificateTestUtils.createCertificate;
import static spiffe.utils.X509CertificateTestUtils.createRootCA;
public class CertificateUtilsTest {
@ -24,7 +31,7 @@ public class CertificateUtilsTest {
val path = Paths.get(toUri("testdata/internal/cert.pem"));
val certBytes = Files.readAllBytes(path);
List<X509Certificate> x509CertificateList = null;
List<X509Certificate> x509CertificateList;
SpiffeId spiffeId = null;
try {
x509CertificateList = CertificateUtils.generateCertificates(certBytes);
@ -55,7 +62,59 @@ public class CertificateUtilsTest {
}
}
@Test
void testGeneratePrivateKey() throws URISyntaxException, IOException {
val keyPath = Paths.get(toUri("testdata/internal/privateKeyRsa.pem"));
val keyBytes = Files.readAllBytes(keyPath);
try {
PrivateKey privateKey = CertificateUtils.generatePrivateKey(keyBytes);
assertNotNull(privateKey);
assertEquals("RSA", privateKey.getAlgorithm());
} catch (InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException e) {
fail("Should have generated key", e);
}
}
@Test
void testGetSpiffeId() throws Exception {
val rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://domain.test" );
val leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://domain.test/workload", rootCa, false);
SpiffeId spiffeId = CertificateUtils.getSpiffeId(leaf.getCertificate());
assertEquals(SpiffeId.parse("spiffe://domain.test/workload"), spiffeId);
}
@Test
void testGetSpiffeId_certNotContainSpiffeId_throwsCertificateException() throws Exception {
val rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://domain.test" );
val leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "", rootCa, false);
try {
CertificateUtils.getSpiffeId(leaf.getCertificate());
fail("exception is expected");
} catch (CertificateException e) {
assertEquals("Certificate does not contain SPIFFE ID in the URI SAN", e.getMessage());
}
}
@Test
void testGetTrustDomain() throws Exception {
val rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://domain.test" );
val intermediate = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://domain.test/host", rootCa, true);
val leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://domain.test/workload", intermediate, false);
val chain = Arrays.asList(leaf.getCertificate(), intermediate.getCertificate());
try {
TrustDomain trustDomain = CertificateUtils.getTrustDomain(chain);
assertNotNull(trustDomain);
assertEquals(TrustDomain.of("domain.test"), trustDomain);
} catch (CertificateException e) {
fail(e);
}
}
private URI toUri(String path) throws URISyntaxException {
return getClass().getClassLoader().getResource(path).toURI();
}
}

View File

@ -3,88 +3,79 @@ package spiffe.svid.x509svid;
import lombok.val;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import spiffe.bundle.x509bundle.X509Bundle;
import spiffe.bundle.x509bundle.X509BundleSource;
import spiffe.exception.BundleNotFoundException;
import spiffe.internal.CertificateUtils;
import spiffe.spiffeid.SpiffeId;
import spiffe.spiffeid.TrustDomain;
import spiffe.utils.X509CertificateTestUtils.CertAndKeyPair;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static java.util.Collections.EMPTY_LIST;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.when;
import static spiffe.utils.X509CertificateTestUtils.createCertificate;
import static spiffe.utils.X509CertificateTestUtils.createRootCA;
public class X509SvidValidatorTest {
@Mock
X509BundleSource bundleSourceMock;
List<X509Certificate> chain;
CertAndKeyPair rootCa;
CertAndKeyPair otherRootCa;
CertAndKeyPair leaf;
@BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
void setUp() throws Exception {
rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://example.org" );
val intermediate1 = createCertificate("C = US, O = SPIRE", "C = US, O = SPIFFE", "spiffe://example.org/host", rootCa, true);
val intermediate2 = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://example.org/host2", intermediate1, true);
leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://example.org/test", intermediate2, false);
chain = Arrays.asList(leaf.getCertificate(), intermediate2.getCertificate(), intermediate1.getCertificate());
otherRootCa = createRootCA("C = US, O = SPIFFE", "spiffe://example.org" );
}
@Test
void verifyChain_certificateExpired_throwsCertificateException() throws IOException, CertificateException, BundleNotFoundException, URISyntaxException {
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
val certBytes = Files.readAllBytes(certPath);
val chain = CertificateUtils.generateCertificates(certBytes);
void testVerifyChain_chainCanBeVerifiedWithAuthorityInBundle() throws Exception {
HashSet<X509Certificate> x509Authorities = new HashSet<>();
x509Authorities.add(rootCa.getCertificate());
x509Authorities.add(otherRootCa.getCertificate());
val bundlePath = Paths.get(toUri("testdata/x509svid/bundle.pem"));
X509Bundle x509Bundle=
X509Bundle.load(
TrustDomain.of("example.org"),
bundlePath
);
val x509Bundle = new X509Bundle(TrustDomain.of("example.org"), x509Authorities);
X509SvidValidator.verifyChain(chain, x509Bundle);
}
when(bundleSourceMock
.getX509BundleForTrustDomain(
TrustDomain.of("example.org")))
.thenReturn(x509Bundle);
@Test
void testVerifyChain_chainCannotBeVerifiedWithAuthorityInBundle_throwsCertificateException() throws Exception {
HashSet<X509Certificate> x509Authorities = new HashSet<>();
x509Authorities.add(otherRootCa.getCertificate());
val x509Bundle = new X509Bundle(TrustDomain.of("example.org"), x509Authorities);
try {
X509SvidValidator.verifyChain(chain, bundleSourceMock);
fail("Verify chain should have thrown validation exception");
X509SvidValidator.verifyChain(chain, x509Bundle);
fail("exception is expected");
} catch (CertificateException e) {
assertEquals("Cert chain cannot be verified", e.getMessage());
}
}
@Test
void verifyChain_noBundleForTrustDomain_throwsBundleNotFoundException() throws IOException, CertificateException, BundleNotFoundException, URISyntaxException {
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
val certBytes = Files.readAllBytes(certPath);
val chain = CertificateUtils.generateCertificates(certBytes);
void verifyChain_noBundleForTrustDomain_throwsBundleNotFoundException() throws Exception {
HashSet<X509Certificate> x509Authorities = new HashSet<>();
x509Authorities.add(otherRootCa.getCertificate());
val bundlePath = Paths.get(toUri("testdata/x509svid/bundle.pem"));
X509Bundle x509Bundle=
X509Bundle.load(
TrustDomain.of("example.org"),
bundlePath
);
when(bundleSourceMock
.getX509BundleForTrustDomain(
TrustDomain.of("example.org")))
.thenThrow(new BundleNotFoundException("No bundle found"));
val x509Bundle = new X509Bundle(TrustDomain.of("other.org"), x509Authorities);
try {
X509SvidValidator.verifyChain(chain, bundleSourceMock);
X509SvidValidator.verifyChain(chain, x509Bundle);
fail("Verify chain should have thrown validation exception");
} catch (BundleNotFoundException e) {
assertEquals("No bundle found", e.getMessage());
assertEquals("No X509 bundle found for trust domain example.org", e.getMessage());
}
}
@ -93,13 +84,9 @@ public class X509SvidValidatorTest {
val spiffeId1 = SpiffeId.parse("spiffe://example.org/test");
val spiffeId2 = SpiffeId.parse("spiffe://example.org/test2");
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
val certBytes = Files.readAllBytes(certPath);
val x509Certificate = CertificateUtils.generateCertificates(certBytes);
val spiffeIdList = Arrays.asList(spiffeId1, spiffeId2);
X509SvidValidator.verifySpiffeId(x509Certificate.get(0), () -> spiffeIdList);
X509SvidValidator.verifySpiffeId(leaf.getCertificate(), () -> spiffeIdList);
}
@Test
@ -108,12 +95,8 @@ public class X509SvidValidatorTest {
val spiffeId2 = SpiffeId.parse("spiffe://example.org/other2");
List<SpiffeId> spiffeIdList = Arrays.asList(spiffeId1, spiffeId2);
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
val certBytes = Files.readAllBytes(certPath);
val x509Certificate = CertificateUtils.generateCertificates(certBytes);
try {
X509SvidValidator.verifySpiffeId(x509Certificate.get(0), () -> spiffeIdList);
X509SvidValidator.verifySpiffeId(leaf.getCertificate(), () -> spiffeIdList);
fail("Should have thrown CertificateException");
} catch (CertificateException e) {
assertEquals("SPIFFE ID spiffe://example.org/test in X.509 certificate is not accepted", e.getMessage());
@ -133,10 +116,7 @@ public class X509SvidValidatorTest {
@Test
void checkSpiffeId_nullAcceptedSpiffeIdsSuppplier_throwsNullPointerException() throws CertificateException, URISyntaxException, IOException {
try {
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
val certBytes = Files.readAllBytes(certPath);
val x509Certificate = CertificateUtils.generateCertificates(certBytes);
X509SvidValidator.verifySpiffeId(x509Certificate.get(0), null);
X509SvidValidator.verifySpiffeId(leaf.getCertificate(), null);
fail("should have thrown an exception");
} catch (NullPointerException e) {
assertEquals("acceptedSpiffedIdsSupplier is marked non-null but is null", e.getMessage());
@ -146,7 +126,7 @@ public class X509SvidValidatorTest {
@Test
void verifyChain_nullChain_throwsNullPointerException() throws CertificateException, BundleNotFoundException {
try {
X509SvidValidator.verifyChain(null, bundleSourceMock);
X509SvidValidator.verifyChain(null, new X509Bundle(TrustDomain.of("example.org")));
fail("should have thrown an exception");
} catch (NullPointerException e) {
assertEquals("chain is marked non-null but is null", e.getMessage());
@ -156,17 +136,10 @@ public class X509SvidValidatorTest {
@Test
void verifyChain_nullBundleSource_throwsNullPointerException() throws CertificateException, BundleNotFoundException, URISyntaxException, IOException {
try {
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
val certBytes = Files.readAllBytes(certPath);
val chain = CertificateUtils.generateCertificates(certBytes);
X509SvidValidator.verifyChain(chain, null);
fail("should have thrown an exception");
} catch (NullPointerException e) {
assertEquals("x509BundleSource is marked non-null but is null", e.getMessage());
}
}
private URI toUri(String path) throws URISyntaxException {
return getClass().getClassLoader().getResource(path).toURI();
}
}

View File

@ -0,0 +1,125 @@
package spiffe.utils;
import lombok.Value;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
public class X509CertificateTestUtils {
/**
* Creates a self-signed Root CA certificate
*/
public static CertAndKeyPair createRootCA(String subject, String spiffeId) throws Exception {
KeyPair certKeyPair = generateKeyPair();
JcaX509v3CertificateBuilder builder = getCertificateBuilder(certKeyPair, subject, subject);
addCAExtensions(builder, certKeyPair, spiffeId);
// self signed
X509Certificate cert = getSignedX509Certificate(certKeyPair.getPrivate(), builder);
return new CertAndKeyPair(cert, certKeyPair);
}
/**
* Creates a certificate signed with the private key of the issuer. The generated cert can be an intermediate CA or
* a leaf certificate.
*/
public static CertAndKeyPair createCertificate(String subject, String issuerSubject, String spiffeId, CertAndKeyPair issuer, boolean isCa) throws Exception {
KeyPair certKeyPair = generateKeyPair();
PrivateKey issuerKey = issuer.keyPair.getPrivate();
JcaX509v3CertificateBuilder builder = getCertificateBuilder(certKeyPair, subject, issuerSubject);
addCertExtensions(builder, spiffeId, isCa);
X509Certificate cert = getSignedX509Certificate(issuerKey, builder);
return new CertAndKeyPair(cert, certKeyPair);
}
@Value
public final static class CertAndKeyPair {
private final KeyPair keyPair;
private final X509Certificate certificate;
public CertAndKeyPair(X509Certificate certificate, KeyPair keyPair) {
this.keyPair = keyPair;
this.certificate = certificate;
}
}
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
return keyGen.generateKeyPair();
}
private static void addCertExtensions(JcaX509v3CertificateBuilder builder, String spiffeId, boolean isCa) throws CertIOException {
builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(isCa));
if (isCa) {
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.cRLSign);
builder.addExtension(Extension.keyUsage, true, usage);
} else {
KeyUsage usage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.keyAgreement);
builder.addExtension(Extension.keyUsage, true, usage);
ASN1EncodableVector purposes = new ASN1EncodableVector();
purposes.add(KeyPurposeId.id_kp_serverAuth);
purposes.add(KeyPurposeId.id_kp_clientAuth);
builder.addExtension(Extension.extendedKeyUsage, false, new DERSequence(purposes));
}
if (StringUtils.isNotBlank(spiffeId)) {
builder.addExtension(Extension.subjectAlternativeName, false,
new GeneralNames(new GeneralName(GeneralName.uniformResourceIdentifier, spiffeId )));
}
}
private static void addCAExtensions(JcaX509v3CertificateBuilder builder, KeyPair certKeyPair, String spiffeId) throws CertIOException {
builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.cRLSign);
builder.addExtension(Extension.keyUsage, true, usage);
builder.addExtension(Extension.subjectAlternativeName, true,
new GeneralNames(new GeneralName(GeneralName.uniformResourceIdentifier, spiffeId)));
builder.addExtension(Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(certKeyPair.getPublic().getEncoded()));
}
private static JcaX509v3CertificateBuilder getCertificateBuilder(KeyPair certKeyPair, String subject, String issuerSubject) {
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
Instant validFrom = Instant.now().minus(5, ChronoUnit.DAYS);
Instant validUntil = validFrom.plus(30 , ChronoUnit.DAYS);
X500Name name = new X500Name(subject);
X500Name issuerName = new X500Name(issuerSubject);
return new JcaX509v3CertificateBuilder(
issuerName,
serialNumber,
Date.from(validFrom), Date.from(validUntil),
name, certKeyPair.getPublic());
}
private static X509Certificate getSignedX509Certificate(PrivateKey issuerKey, JcaX509v3CertificateBuilder builder) throws OperatorCreationException, CertificateException {
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA").build(issuerKey);
X509CertificateHolder certHolder = builder.build(signer);
return new JcaX509CertificateConverter().getCertificate(certHolder);
}
}

View File

@ -0,0 +1,13 @@
-----BEGIN PRIVATE KEY-----
MIIB5QIBADANBgkqhkiG9w0BAQEFAASCAc8wggHLAgEAAmEA0EdYWa7LZ/ZA2sKk
JsREA2TkXDIq01JIRsVetPFRqRW5GEmkwwk0qThebp/ofZt8oLkMwp06BLaQC3tH
QGkK31jh5Djd1NHA8CpU4ByObgGeY75dhwFEzI4YwXM+e0n/AgMBAAECYFD4S4qh
/4WtIE1refFwP5iqMnT9M9TvmhWZSVZCsqJvRYQBrUH9ZDGdLmkHVZTvSvKKmkoZ
VvXDlpmW4Eaed8xXqsLYplMrVo6WkvdtvlvfIwP69PGFmWwKgFBe2aLHsQIxAOoT
dwmlr/dNNu2MjyjcvTK0lCn6vexp6k8MaXTEsTvG0kBmVDZGuSXcKzbBoAZLxQIx
AOPJU65HnDpcOM+qLH3jahTnbrg4C0BO0mj1OusLcSUnA6bFP2NkZ9LyWfMerbvG
8wIxAI7Iyt8mo50+C5iCGj250OtiPdMRsdLJlPUdRCLHbLljAZPpF8t3/q66i929
5MiSZQIwE3wXQmMxw/Q7j9f4slQPsPYTDIMOw1N6wCup/I0gApORxmQ9Bd2C3BKL
CzbmmZdtAjEA2v1fSN4DPcQW2bgmoE0GoNEMYGfSza7jBGOiKkqm4p2hAjaur174
U2t9BPJHk+Xh
-----END PRIVATE KEY-----

View File

@ -1,12 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBzDCCAVGgAwIBAgIBADAKBggqhkjOPQQDAzAeMQswCQYDVQQGEwJVUzEPMA0G
A1UEChMGU1BJRkZFMB4XDTIwMDMxNjE4MDQ1N1oXDTIwMDMyMzE4MDUwN1owHjEL
MAkGA1UEBhMCVVMxDzANBgNVBAoTBlNQSUZGRTB2MBAGByqGSM49AgEGBSuBBAAi
A2IABOStlk5qbbCuuRqKitTqTzQf/UnlljO2bHAekgz+0na4oJhcsOYhRLbKQVcr
U5jf7BpcT7nYHyLMv79Cy+Xa1snzMCGhx1obgt8gAXb4b4GwyAiNzaq7ytX4SAZB
CUXp36NjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFCMzt4WGhen9N2N+MwzUkwEtG6YLMB8GA1UdEQQYMBaGFHNwaWZmZTovL2V4
YW1wbGUub3JnMAoGCCqGSM49BAMDA2kAMGYCMQDJSIl+8jQek7tHRSfU2vYLQ6sy
2t4NJcc5zeSjNxFIajUogl7L8T1QNzrKpXm/ZbQCMQDU54VnA1Awq0Qq2pf4zQ1C
KOpj4PguMpUmulRVrzD2KNQz+FUKsHgL/mIZSfN5g1M=
-----END CERTIFICATE-----

View File

@ -1,13 +0,0 @@
-----BEGIN CERTIFICATE-----
MIICADCCAYagAwIBAgIQJqqLnXOR3tjUV43PpGW4YDAKBggqhkjOPQQDAzAeMQsw
CQYDVQQGEwJVUzEPMA0GA1UEChMGU1BJRkZFMB4XDTIwMDMxODE5MDc1MloXDTIw
MDMyMzE4MDUwN1owHTELMAkGA1UEBhMCVVMxDjAMBgNVBAoTBVNQSVJFMFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAE4/NA2umeK9j1U5+egPqfzomjnpnLz68jNvUN
tdA0Lg6E7/nsmvqoNbVVbaD84Jplfg6/6HWSnoO7K7A+oZ1g+qOBpjCBozAOBgNV
HQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud
EwEB/wQCMAAwHQYDVR0OBBYEFPLf59AkFCva6ehKx8L4i+pjCU1CMB8GA1UdIwQY
MBaAFCMzt4WGhen9N2N+MwzUkwEtG6YLMCQGA1UdEQQdMBuGGXNwaWZmZTovL2V4
YW1wbGUub3JnL3Rlc3QwCgYIKoZIzj0EAwMDaAAwZQIwRzrN6Rh8X28UYJEuql/1
GZEeto7zzj0UtjZFwQy2ODl48nFFRGKUnq8mc4cIMI/kAjEAlixJBUHqb4ty8Ff+
d0XHzA5duE1hzFxd2feRppjqiOHKJu7Rh2dzZd3rZhMZWrrd
-----END CERTIFICATE-----

1
lombok.config Normal file
View File

@ -0,0 +1 @@
lombok.addLombokGeneratedAnnotation = true