Skip to content

Commit

Permalink
Add signed object file-modification time utility
Browse files Browse the repository at this point in the history
  • Loading branch information
ties committed Nov 1, 2023
1 parent d73c7cf commit 3677313
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 3 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@

<net.ripe.ipresource.version>1.52</net.ripe.ipresource.version>
<bouncycastle.version>1.74</bouncycastle.version>
<guava.version>32.0.0-jre</guava.version>
<guava.version>32.1.2-jre</guava.version>
<joda-time.version>2.10.13</joda-time.version>
<xstream.version>1.4.20</xstream.version>
<commons-io.version>2.11.0</commons-io.version>
<lombok.version>1.18.22</lombok.version>
<lombok.version>1.18.30</lombok.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -138,7 +138,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.2.0</version>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
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;
}
}
}
46 changes: 46 additions & 0 deletions src/test/java/net/ripe/rpki/commons/util/SignedObjectUtilTest.java
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

View workflow job for this annotation

GitHub Actions / Test Report

SignedObjectUtilTest.shouldParseObject{String, String, String, String}[1]

expected: 2023-06-07T09:08:41.000Z but was: 2023-06-07T09:08:14.000Z
Raw output
org.opentest4j.AssertionFailedError: 

expected: 2023-06-07T09:08:41.000Z
 but was: 2023-06-07T09:08:14.000Z
	at net.ripe.rpki.commons.util.SignedObjectUtilTest.shouldParseObject(SignedObjectUtilTest.java:33)

Check failure on line 33 in src/test/java/net/ripe/rpki/commons/util/SignedObjectUtilTest.java

View workflow job for this annotation

GitHub Actions / Test Report

SignedObjectUtilTest.shouldParseObject{String, String, String, String}[2]

expected: 2013-10-28T21:24:39.000Z but was: 2011-04-11T18:57:28.000Z
Raw output
org.opentest4j.AssertionFailedError: 

expected: 2013-10-28T21:24:39.000Z
 but was: 2011-04-11T18:57:28.000Z
	at net.ripe.rpki.commons.util.SignedObjectUtilTest.shouldParseObject(SignedObjectUtilTest.java:33)

Check failure on line 33 in src/test/java/net/ripe/rpki/commons/util/SignedObjectUtilTest.java

View workflow job for this annotation

GitHub Actions / Test Report

SignedObjectUtilTest.shouldParseObject{String, String, String, String}[3]

expected: 2011-11-11T01:55:18.000Z but was: 2011-11-11T01:55:17.000Z
Raw output
org.opentest4j.AssertionFailedError: 

expected: 2011-11-11T01:55:18.000Z
 but was: 2011-11-11T01:55:17.000Z
	at net.ripe.rpki.commons.util.SignedObjectUtilTest.shouldParseObject(SignedObjectUtilTest.java:33)

Check failure on line 33 in src/test/java/net/ripe/rpki/commons/util/SignedObjectUtilTest.java

View workflow job for this annotation

GitHub Actions / Test Report

SignedObjectUtilTest.shouldParseObject{String, String, String, String}[4]

expected: 2021-11-11T11:19:00.000Z but was: 2021-11-11T11:14:00.000Z
Raw output
org.opentest4j.AssertionFailedError: 

expected: 2021-11-11T11:19:00.000Z
 but was: 2021-11-11T11:14:00.000Z
	at net.ripe.rpki.commons.util.SignedObjectUtilTest.shouldParseObject(SignedObjectUtilTest.java:33)
}

@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);
}
}

0 comments on commit 3677313

Please sign in to comment.