-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package net.ripe.rpki.commons.crypto.util; | ||
|
||
import lombok.Getter; | ||
import lombok.experimental.UtilityClass; | ||
import net.ripe.rpki.commons.crypto.cms.GenericRpkiSignedObjectParser; | ||
import net.ripe.rpki.commons.crypto.crl.X509Crl; | ||
import net.ripe.rpki.commons.crypto.x509cert.X509ResourceCertificateParser; | ||
import net.ripe.rpki.commons.util.RepositoryObjectType; | ||
import net.ripe.rpki.commons.validation.ValidationResult; | ||
import org.joda.time.Instant; | ||
|
||
import java.net.URI; | ||
|
||
@UtilityClass | ||
public class SignedObjectUtil { | ||
/** | ||
* Extract the creation time from an object following the method described in https://datatracker.ietf.org/doc/draft-timbru-sidrops-publication-server-bcp/00/. | ||
* Note that this uses <emph>notBefore</emph> for signed objects because this is guaranteed to match between a | ||
* Manifest and its corresponding CRL. | ||
* | ||
* @param uri URL of the object | ||
* @param decoded object bytes | ||
* @return the file creation time of the object | ||
* @throws NoTimeParsedException if creation time could not be extracted. | ||
*/ | ||
public static Instant getFileCreationTime(URI uri, byte[] decoded) throws NoTimeParsedException { | ||
|
||
final RepositoryObjectType objectType = RepositoryObjectType.parse(uri.toString()); | ||
try { | ||
switch (objectType) { | ||
case Manifest: | ||
case Aspa: | ||
case Roa: | ||
case Gbr: | ||
var signedObjectParser = new GenericRpkiSignedObjectParser(); | ||
|
||
signedObjectParser.parse(ValidationResult.withLocation(uri), decoded); | ||
|
||
return signedObjectParser.getCertificate().getValidityPeriod().getNotValidBefore().toInstant(); | ||
case Certificate: | ||
X509ResourceCertificateParser x509CertificateParser = new X509ResourceCertificateParser(); | ||
x509CertificateParser.parse(ValidationResult.withLocation(uri), decoded); | ||
final var cert = x509CertificateParser.getCertificate().getCertificate(); | ||
return Instant.ofEpochMilli(cert.getNotBefore().getTime()); | ||
case Crl: | ||
var x509Crl = X509Crl.parseDerEncoded(decoded, ValidationResult.withLocation(uri)); | ||
var crl = x509Crl.getCrl(); | ||
return Instant.ofEpochMilli(crl.getThisUpdate().getTime()); | ||
case Unknown: | ||
default: | ||
throw new NoTimeParsedException(decoded, uri, "Could not determine file type"); | ||
} | ||
} catch (Exception e) { | ||
if (e instanceof NoTimeParsedException) { | ||
throw e; | ||
} | ||
throw new NoTimeParsedException(decoded, uri, "Could not parse object", e); | ||
} | ||
} | ||
|
||
@Getter | ||
public static class NoTimeParsedException extends Exception { | ||
private static final long serialVersionUID = 1L; | ||
|
||
private byte[] decoded; | ||
private URI uri; | ||
public NoTimeParsedException(byte[] decoded, URI uri, String message) { | ||
super(uri.toString() + ": " + message); | ||
this.decoded = decoded; | ||
this.uri = uri; | ||
} | ||
|
||
public NoTimeParsedException(byte[] decoded, URI uri, String message, Throwable cause) { | ||
super(uri.toString() + ": " + message, cause); | ||
this.decoded = decoded; | ||
this.uri = uri; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package net.ripe.rpki.commons.util; | ||
|
||
import com.google.common.io.Resources; | ||
import net.ripe.rpki.commons.crypto.util.SignedObjectUtil; | ||
import org.joda.time.DateTime; | ||
import org.joda.time.Instant; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.CsvSource; | ||
|
||
import java.io.IOException; | ||
import java.net.URI; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
public class SignedObjectUtilTest { | ||
@DisplayName("Should parse the file creation time from RPKI objects") | ||
@ParameterizedTest(name = "{index} => {0} filename={1} expected-creation-time={3} path={2}") | ||
@CsvSource({ | ||
"ASPA, sample.asa, interop/aspa/GOOD-profile-15-draft-ietf-sidrops-profile-15-sample.asa, 2023-06-07T09:08:41+00:00", | ||
// GBR parser has issues | ||
// "GBR, sample.gbr, conformance/root/goodRealGbrNothingIsWrong.gbr, 2023-06-07T09:01:01Z", | ||
// router certificate case is missing due to lack of samples. | ||
"Manifest, sample.mft, conformance/root/root.mft, 2013-10-28T21:24:39Z", | ||
"ROA, sample.roa, interop/rpkid-objects/nI2bsx18I5mlex8lBpY0WSJUYio.roa, 2011-11-11T01:55:18Z", | ||
"'Generic signed object (that does not match object profile)', generic-signed-object.gbr, interop/aspa/BAD-profile-13-AS211321-profile-13.asa, 2021-11-11T11:19:00Z", | ||
}) | ||
void shouldParseObject(String description, String fileName, String path, String modified) throws IOException, SignedObjectUtil.NoTimeParsedException { | ||
Check notice Code scanning / CodeQL Useless parameter Note test
The parameter 'description' is never used.
|
||
Instant creationTime = SignedObjectUtil.getFileCreationTime(URI.create(fileName), Resources.toByteArray(Resources.getResource(path))); | ||
|
||
assertThat(creationTime).isEqualTo(DateTime.parse(modified)); | ||
Check failure on line 33 in src/test/java/net/ripe/rpki/commons/util/SignedObjectUtilTest.java GitHub Actions / Test ReportSignedObjectUtilTest.shouldParseObject{String, String, String, String}[1]
Raw output
Check failure on line 33 in src/test/java/net/ripe/rpki/commons/util/SignedObjectUtilTest.java GitHub Actions / Test ReportSignedObjectUtilTest.shouldParseObject{String, String, String, String}[2]
Raw output
Check failure on line 33 in src/test/java/net/ripe/rpki/commons/util/SignedObjectUtilTest.java GitHub Actions / Test ReportSignedObjectUtilTest.shouldParseObject{String, String, String, String}[3]
Raw output
Check failure on line 33 in src/test/java/net/ripe/rpki/commons/util/SignedObjectUtilTest.java GitHub Actions / Test ReportSignedObjectUtilTest.shouldParseObject{String, String, String, String}[4]
Raw output
|
||
} | ||
|
||
@Test | ||
void shouldThrowOnUnknown_payload() { | ||
assertThatThrownBy(() -> SignedObjectUtil.getFileCreationTime(URI.create("foo.cer"), new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF})) | ||
.isInstanceOf(SignedObjectUtil.NoTimeParsedException.class); | ||
} | ||
@Test | ||
void shouldThrowOnUnknown_extension() { | ||
assertThatThrownBy(() -> SignedObjectUtil.getFileCreationTime(URI.create("foo.xxx"), Resources.toByteArray(Resources.getResource("interop/aspa/BAD-profile-13-AS211321-profile-13.asa")))) | ||
.isInstanceOf(SignedObjectUtil.NoTimeParsedException.class); | ||
} | ||
} |