mirror of
https://github.com/Graylog2/graylog2-server.git
synced 2026-03-13 09:32:21 +08:00
Fix y10k problem in certificate signing (#24604)
* fix y10k problem in certificate signing * added changelog
This commit is contained in:
5
changelog/unreleased/pr-24604.toml
Normal file
5
changelog/unreleased/pr-24604.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
type = "f"
|
||||
message = "Fix y10k problem in certificates signing"
|
||||
|
||||
pulls = ["24604"]
|
||||
issues = []
|
||||
@@ -40,6 +40,8 @@ import java.security.cert.X509Certificate;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
@@ -58,6 +60,9 @@ public class CsrSigner {
|
||||
new GeneralName(iPAddress, "127.0.0.1"),
|
||||
new GeneralName(iPAddress, "0:0:0:0:0:0:0:1")
|
||||
);
|
||||
public static final Instant Y10K = LocalDate.of(9999, 1, 1) // Let's not try 31.12, who wants to deal with timezones and DST in year 9999?!
|
||||
.atStartOfDay(ZoneOffset.UTC)
|
||||
.toInstant();
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
@@ -83,7 +88,7 @@ public class CsrSigner {
|
||||
public X509Certificate sign(PrivateKey caPrivateKey, X509Certificate caCertificate, PKCS10CertificationRequest csr, @NotNull Duration certificateLifetime) throws Exception {
|
||||
|
||||
final boolean keysMatching = KeystoreUtils.matchingKeys(caPrivateKey, caCertificate.getPublicKey());
|
||||
if(!keysMatching) {
|
||||
if (!keysMatching) {
|
||||
throw new IllegalArgumentException("Provided CA private key doesn't correspond to provided CA certificate!");
|
||||
}
|
||||
|
||||
@@ -109,7 +114,7 @@ public class CsrSigner {
|
||||
var builder = new X509v3CertificateBuilder(
|
||||
issuerName,
|
||||
serialNumber,
|
||||
Date.from(validFrom), Date.from(validUntil),
|
||||
Date.from(validFrom), fixY10kProblem(validUntil),
|
||||
csr.getSubject(), csr.getSubjectPublicKeyInfo());
|
||||
|
||||
var altNames = Optional.ofNullable(csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest))
|
||||
@@ -133,6 +138,13 @@ public class CsrSigner {
|
||||
return new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||
}
|
||||
|
||||
private Date fixY10kProblem(Instant validUntil) {
|
||||
if (validUntil.isAfter(Y10K)) {
|
||||
return Date.from(Y10K);
|
||||
}
|
||||
return Date.from(validUntil);
|
||||
}
|
||||
|
||||
private Stream<? extends GeneralName> resolveDNSName(GeneralName name) {
|
||||
final var hostname = name.getName().toString();
|
||||
try {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.graylog2.bootstrap.preflight.web.resources;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
@@ -49,7 +50,16 @@ public class CertificateRenewalPolicyResource {
|
||||
@POST
|
||||
@RequiresPermissions(PreflightWebModule.PERMISSION_PREFLIGHT_ONLY)
|
||||
@NoAuditEvent("No Auditing during preflight")
|
||||
public void set(@NotNull RenewalPolicy renewalPolicy) {
|
||||
this.clusterConfigService.write(renewalPolicy);
|
||||
public void set(@NotNull @Valid RenewalPolicy renewalPolicy) {
|
||||
this.clusterConfigService.write(validatePolicy(renewalPolicy));
|
||||
}
|
||||
|
||||
private RenewalPolicy validatePolicy(@NotNull @Valid RenewalPolicy renewalPolicy) {
|
||||
try {
|
||||
renewalPolicy.parsedCertificateLifetime();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Invalid certificate lifetime value: " + renewalPolicy.certificateLifetime(), e);
|
||||
}
|
||||
return renewalPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.security.cert.X509Certificate;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -109,6 +110,23 @@ class CsrSignerTest {
|
||||
assertThat(result.getNotAfter()).isEqualTo(fixedInstant.plus(180, ChronoUnit.DAYS));
|
||||
}
|
||||
|
||||
/**
|
||||
* X509 certificates don't handle Y10K problem correctly, failing to parse
|
||||
* any date after 9999-12-31. Let's make sure we limit cert validity in a way
|
||||
* that prevents this.
|
||||
*/
|
||||
@Test
|
||||
void testSigningCertY10k() throws Exception {
|
||||
var result = sign("P9999999D");
|
||||
assertThat(result).isNotNull();
|
||||
|
||||
final Instant y10k = LocalDate.of(10000, 1, 1)
|
||||
.atStartOfDay(UTC)
|
||||
.toInstant();
|
||||
|
||||
assertThat(result.getNotAfter()).isBefore(y10k);
|
||||
}
|
||||
|
||||
private PKCS10CertificationRequest createCSR(KeyPair keyPair) throws OperatorCreationException {
|
||||
var contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate());
|
||||
var pkcs10Builder = new PKCS10CertificationRequestBuilder(subjectName, SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Graylog, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*/
|
||||
package org.graylog2.bootstrap.preflight.web.resources;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.graylog.security.certutil.InMemoryClusterConfigService;
|
||||
import org.graylog2.plugin.certificates.RenewalPolicy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CertificateRenewalPolicyResourceTest {
|
||||
|
||||
@Test
|
||||
void testCertificateLifetimeValidation() {
|
||||
final CertificateRenewalPolicyResource resource = new CertificateRenewalPolicyResource(new InMemoryClusterConfigService());
|
||||
Assertions.assertThatThrownBy(() -> resource.set(new RenewalPolicy(RenewalPolicy.Mode.AUTOMATIC, "10nonsense")))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Invalid certificate lifetime value: 10nonsense");
|
||||
|
||||
Assertions.assertThatNoException()
|
||||
.isThrownBy(() -> resource.set(new RenewalPolicy(RenewalPolicy.Mode.AUTOMATIC, "P30D")));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user