diff --git a/flake.nix b/flake.nix index 26ef9f6a0aa..8cc9b7bf231 100644 --- a/flake.nix +++ b/flake.nix @@ -161,6 +161,8 @@ cargo-deadlinks clang-tools hadolint + ktfmt + ktlint nixpkgs-fmt nodePackages.markdownlint-cli shellcheck diff --git a/java/src/main/java/com/google/oak/crypto/DecryptionResult.kt b/java/src/main/java/com/google/oak/crypto/DecryptionResult.kt index 1a9d7f3c87d..f11be206430 100644 --- a/java/src/main/java/com/google/oak/crypto/DecryptionResult.kt +++ b/java/src/main/java/com/google/oak/crypto/DecryptionResult.kt @@ -14,14 +14,12 @@ // limitations under the License. // -package com.google.oak.crypto; +package com.google.oak.crypto class DecryptionResult( - // This is needed because the Java code reference backing fields directly. - // By default, Kotlin properties generate getter/setter methods, and does not - // expose the backing fields directly. - @JvmField - val plaintext: ByteArray, - @JvmField - val associatedData: ByteArray, + // This is needed because the Java code reference backing fields directly. + // By default, Kotlin properties generate getter/setter methods, and does not + // expose the backing fields directly. + @JvmField val plaintext: ByteArray, + @JvmField val associatedData: ByteArray, ) diff --git a/java/src/main/java/com/google/oak/verification/LogEntryVerifier.kt b/java/src/main/java/com/google/oak/verification/LogEntryVerifier.kt index 13d67b00615..3d0ca279323 100644 --- a/java/src/main/java/com/google/oak/verification/LogEntryVerifier.kt +++ b/java/src/main/java/com/google/oak/verification/LogEntryVerifier.kt @@ -70,9 +70,9 @@ object LogEntryVerifier { * @return empty if the verification succeeds, or a failure otherwise */ fun verify( - logEntry: RekorLogEntry, - publicKeyBytes: ByteArray, - endorsementBytes: ByteArray + logEntry: RekorLogEntry, + publicKeyBytes: ByteArray, + endorsementBytes: ByteArray, ): Optional { val rekorSigVer = verifyRekorSignature(logEntry, publicKeyBytes) return if (rekorSigVer.isPresent()) { @@ -108,13 +108,14 @@ object LogEntryVerifier { fun verifyRekorBody(body: RekorLogEntry.Body, contentBytes: ByteArray): Optional { if (body.spec.signature.format != "x509") { return failure( - "unsupported signature format: ${body.spec.signature.format} only x509 is supported") + "unsupported signature format: ${body.spec.signature.format} only x509 is supported" + ) } // For now, we only support `sha256` as the hashing algorithm. if (body.spec.data.hash.algorithm != "sha256") { return failure( - "unsupported hash algorithm: ${body.spec.data.hash.algorithm} only sha256 is supported", + "unsupported hash algorithm: ${body.spec.data.hash.algorithm} only sha256 is supported" ) } @@ -122,14 +123,16 @@ object LogEntryVerifier { val digest = sha256Hex(contentBytes) if (body.spec.data.hash.value != digest) { return failure( - "SHA2-256 digest of contents ($digest) differs from that in Rekor entry body (${body.spec.data.hash.value})") + "SHA2-256 digest of contents ($digest) differs from that in Rekor entry body (${body.spec.data.hash.value})" + ) } val signatureBytes = Base64.getDecoder().decode(body.spec.signature.content) val publicKeyBytes = - SignatureVerifier.convertPemToRaw( - Base64.getDecoder() - .decode(body.spec.signature.publicKey.content) - .toString(StandardCharsets.UTF_8)) + SignatureVerifier.convertPemToRaw( + Base64.getDecoder() + .decode(body.spec.signature.publicKey.content) + .toString(StandardCharsets.UTF_8) + ) return SignatureVerifier.verify(signatureBytes, publicKeyBytes, contentBytes) } diff --git a/java/src/main/java/com/google/oak/verification/RekorLogEntry.kt b/java/src/main/java/com/google/oak/verification/RekorLogEntry.kt index 845748445ac..b47de9d1712 100644 --- a/java/src/main/java/com/google/oak/verification/RekorLogEntry.kt +++ b/java/src/main/java/com/google/oak/verification/RekorLogEntry.kt @@ -27,8 +27,8 @@ import java.util.Base64 * https://github.com/sigstore/rekor/blob/2978cdc26fdf8f5bfede8459afd9735f0f231a2a/pkg/generated/models/log_entry.go#L89 */ data class RekorLogEntry( - // package-private for testing - val logEntry: LogEntry + // package-private for testing + val logEntry: LogEntry ) { // The following nested classes represent a subset of Rekor types defined in // . @@ -37,23 +37,23 @@ data class RekorLogEntry( // clients are not expected to instantiate them directly. The fields are not // explicitly made final to allow instantiation with Gson. data class LogEntry( - /** We cannot directly use the type `Body` here, since body is Base64-encoded. */ - val body: String, - - /** - * Unmarshaled body of this LogEntry. It is declared as a transient field, so that it is - * excluded when serializing and deserializing instances of LogEntry. - */ - val integratedTime: Long, - - /** - * The SHA2-256 hash of the DER-encoded public key for the log at the time the entry was - * included in the log. Pattern: ^[0-9a-fA-F]{64}$ - */ - val logID: String, - - /** Minimum: 0 */ - var logIndex: Long, + /** We cannot directly use the type `Body` here, since body is Base64-encoded. */ + val body: String, + + /** + * Unmarshaled body of this LogEntry. It is declared as a transient field, so that it is + * excluded when serializing and deserializing instances of LogEntry. + */ + val integratedTime: Long, + + /** + * The SHA2-256 hash of the DER-encoded public key for the log at the time the entry was + * included in the log. Pattern: ^[0-9a-fA-F]{64}$ + */ + val logID: String, + + /** Minimum: 0 */ + var logIndex: Long, ) { @kotlin.jvm.Transient var bodyObject: Body? = null @@ -101,14 +101,14 @@ data class RekorLogEntry( * //github.com/sigstore/rekor/blob/2978cdc26fdf8f5bfede8459afd9735f0f231a2a/pkg/generated/models/rekord_v001_schema.go#L383> */ data class GenericSignature( - /** Base64 content that is signed. */ - val content: String, + /** Base64 content that is signed. */ + val content: String, - /** Signature format, e.g., x509. */ - val format: String, + /** Signature format, e.g., x509. */ + val format: String, - /** Public key associated with the signing key that generated this signature. */ - val publicKey: PublicKey, + /** Public key associated with the signing key that generated this signature. */ + val publicKey: PublicKey, ) /** @@ -118,8 +118,8 @@ data class RekorLogEntry( * //github.com/sigstore/rekor/blob/2978cdc26fdf8f5bfede8459afd9735f0f231a2a/pkg/generated/models/rekord_v001_schema.go#L551.> */ data class PublicKey( - /** Base64 content of a public key. */ - val content: String + /** Base64 content of a public key. */ + val content: String ) /** @@ -131,8 +131,8 @@ data class RekorLogEntry( * //github.com/sigstore/rekor/blob/2978cdc26fdf8f5bfede8459afd9735f0f231a2a/pkg/generated/models/log_entry.go#L341>. */ internal data class LogEntryVerification( - /** Base64-encoded signature over the body, integratedTime, logID, and logIndex. */ - val signedEntryTimestamp: String + /** Base64-encoded signature over the body, integratedTime, logID, and logIndex. */ + val signedEntryTimestamp: String ) val body: Body? @@ -155,11 +155,11 @@ data class RekorLogEntry( // Use a default Gson instance to parse JSON strings into Java objects. val gson: Gson = GsonBuilder().create() val entryMap: Map = - try { - gson.fromJson(json, object : TypeToken?>() {}.getType()) - } catch (e: JsonSyntaxException) { - throw IllegalArgumentException(e) - } + try { + gson.fromJson(json, object : TypeToken?>() {}.getType()) + } catch (e: JsonSyntaxException) { + throw IllegalArgumentException(e) + } require(entryMap.size == 1) { "Expected exactly one entry in the json-formatted Rekor log entry, found ${ diff --git a/java/src/main/java/com/google/oak/verification/RekorSignatureBundle.kt b/java/src/main/java/com/google/oak/verification/RekorSignatureBundle.kt index d6f46e66a2b..7b620e9f613 100644 --- a/java/src/main/java/com/google/oak/verification/RekorSignatureBundle.kt +++ b/java/src/main/java/com/google/oak/verification/RekorSignatureBundle.kt @@ -24,14 +24,14 @@ import com.google.gson.GsonBuilder * the /api/v1/log/publicKey Rest API. For [sigstore.dev], it is a PEM-encoded x509/PKIX public key. */ data class RekorSignatureBundle( - /** - * Canonicalized JSON representation, based on RFC 8785 rules, of a subset of a Rekor LogEntry - * fields that are signed to generate `signedEntryTimestamp` (also a field in the Rekor - * LogEntry). These fields include body, integratedTime, logID and logIndex. - */ - val canonicalized: String, - /** Base64-encoded signature over the canonicalized JSON document. */ - val base64Signature: String, + /** + * Canonicalized JSON representation, based on RFC 8785 rules, of a subset of a Rekor LogEntry + * fields that are signed to generate `signedEntryTimestamp` (also a field in the Rekor LogEntry). + * These fields include body, integratedTime, logID and logIndex. + */ + val canonicalized: String, + /** Base64-encoded signature over the canonicalized JSON document. */ + val base64Signature: String, ) { companion object { diff --git a/java/src/main/java/com/google/oak/verification/SignatureVerifier.kt b/java/src/main/java/com/google/oak/verification/SignatureVerifier.kt index 182722f7705..5fe6fcba8e6 100644 --- a/java/src/main/java/com/google/oak/verification/SignatureVerifier.kt +++ b/java/src/main/java/com/google/oak/verification/SignatureVerifier.kt @@ -65,9 +65,9 @@ object SignatureVerifier { * @return empty if the verification succeeds, or a failure otherwise */ fun verify( - signatureBytes: ByteArray, - publicKeyBytes: ByteArray, - contentBytes: ByteArray + signatureBytes: ByteArray, + publicKeyBytes: ByteArray, + contentBytes: ByteArray, ): Optional { if (signatureBytes.isEmpty()) { return failure("empty signature") @@ -100,12 +100,12 @@ object SignatureVerifier { /** Makes a plausible guess whether the public key is in PEM format. */ fun looksLikePem(maybePem: String): Boolean = - maybePem.trim().run { startsWith(PEM_HEADER) && endsWith(PEM_FOOTER) } + maybePem.trim().run { startsWith(PEM_HEADER) && endsWith(PEM_FOOTER) } /** Converts a public key from PEM (on disk format) to raw binary format. */ fun convertPemToRaw(pem: String): ByteArray { val trimmed: String = - pem.replace(PEM_HEADER, "").replace(PEM_FOOTER, "").replace("\n", "").trim() + pem.replace(PEM_HEADER, "").replace(PEM_FOOTER, "").replace("\n", "").trim() return Base64.getDecoder().decode(trimmed) } } diff --git a/java/src/test/java/com/google/oak/verification/LogEntryVerifierTest.kt b/java/src/test/java/com/google/oak/verification/LogEntryVerifierTest.kt index a7c74c7ae50..87530de6b9a 100644 --- a/java/src/test/java/com/google/oak/verification/LogEntryVerifierTest.kt +++ b/java/src/test/java/com/google/oak/verification/LogEntryVerifierTest.kt @@ -16,16 +16,15 @@ package com.google.oak.verification import java.io.File -import org.junit.Test import kotlin.test.assertFalse import kotlin.test.assertTrue +import org.junit.Test class LogEntryVerifierTest { private val logEntryBytes = File(LOG_ENTRY_PATH).readBytes() private val logEntry = RekorLogEntry.createFromJson(logEntryBytes) - private val publicKeyBytes = SignatureVerifier.convertPemToRaw( - File(REKOR_PUBLIC_KEY_PATH).readText() - ) + private val publicKeyBytes = + SignatureVerifier.convertPemToRaw(File(REKOR_PUBLIC_KEY_PATH).readText()) private val endorsementBytes = File(ENDORSEMENT_PATH).readBytes() @Test diff --git a/java/src/test/java/com/google/oak/verification/RekorLogEntryTest.kt b/java/src/test/java/com/google/oak/verification/RekorLogEntryTest.kt index 8aae2baf0e0..373bde35779 100644 --- a/java/src/test/java/com/google/oak/verification/RekorLogEntryTest.kt +++ b/java/src/test/java/com/google/oak/verification/RekorLogEntryTest.kt @@ -15,8 +15,8 @@ // package com.google.oak.verification -import kotlin.test.assertEquals import java.io.File +import kotlin.test.assertEquals import kotlin.test.assertTrue import org.junit.Test diff --git a/java/src/test/java/com/google/oak/verification/SignatureVerifierTest.kt b/java/src/test/java/com/google/oak/verification/SignatureVerifierTest.kt index fabfa9f4199..faf8da81a72 100644 --- a/java/src/test/java/com/google/oak/verification/SignatureVerifierTest.kt +++ b/java/src/test/java/com/google/oak/verification/SignatureVerifierTest.kt @@ -27,14 +27,14 @@ class SignatureVerifierTest { @Test fun testVerifySucceeds() { - val failure = SignatureVerifier.verify(signatureBytes, publicKeyBytes, contentBytes) + val failure = SignatureVerifier.verify(signatureBytes, publicKeyBytes, contentBytes) assertFalse(failure.isPresent()) } @Test fun testVerifyFailsWithManipulatedSignature() { signatureBytes[signatureBytes.size / 2]++ - val failure = SignatureVerifier.verify(signatureBytes, publicKeyBytes, contentBytes) + val failure = SignatureVerifier.verify(signatureBytes, publicKeyBytes, contentBytes) assertTrue(failure.isPresent()) } diff --git a/linter/BUILD b/linter/BUILD index 2e6774b3d05..5ae793f564e 100644 --- a/linter/BUILD +++ b/linter/BUILD @@ -52,6 +52,7 @@ rust_library( "tools/buildifier.rs", "tools/clang_format.rs", "tools/hadolint.rs", + "tools/ktfmt.rs", "tools/lib.rs", "tools/markdownlint.rs", "tools/prettier.rs", diff --git a/linter/main.rs b/linter/main.rs index b9646faa167..28cb78f0d09 100644 --- a/linter/main.rs +++ b/linter/main.rs @@ -73,6 +73,7 @@ fn main() { counts += context.lint(tools::buildifier::BuildifierTool {}); counts += context.lint(tools::clang_format::ClangFormatTool {}); counts += context.lint(tools::hadolint::HadolintTool {}); + counts += context.lint(tools::ktfmt::KtfmtTool {}); counts += context.lint(tools::prettier::PrettierTool {}); counts += context.lint(tools::shell_check::ShellCheckTool {}); counts += context.lint(tools::rustfmt::RustfmtTool {}); diff --git a/linter/tools/buildifier.rs b/linter/tools/buildifier.rs index b8a31199c89..87b84c2a685 100644 --- a/linter/tools/buildifier.rs +++ b/linter/tools/buildifier.rs @@ -32,6 +32,6 @@ impl linter::LinterTool for BuildifierTool { } fn fix(&self, path: &Path) -> anyhow::Result { - super::linter_command("buildifier", &["-mode=fix", "-lint=fix", "-v"], path) + super::linter_command("buildifier", &["-mode=fix", "-lint=fix"], path) } } diff --git a/linter/tools/ktfmt.rs b/linter/tools/ktfmt.rs new file mode 100644 index 00000000000..3622195791d --- /dev/null +++ b/linter/tools/ktfmt.rs @@ -0,0 +1,41 @@ +// +// Copyright 2024 The Project Oak Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +use std::path::Path; + +use super::{has_extension, QuietSuccess}; + +pub struct KtfmtTool {} + +impl linter::LinterTool for KtfmtTool { + const NAME: &'static str = "Kotlin Format"; + const SUPPORTS_FIX: bool = true; + + fn accept(&self, path: &Path) -> anyhow::Result { + Ok(has_extension(path, &["kt"])) + } + + fn check(&self, path: &Path) -> anyhow::Result { + super::linter_command("ktfmt", &["--google-style", "--dry-run"], path) + } + + fn fix(&self, path: &Path) -> anyhow::Result { + super::linter_command("ktfmt", &["--google-style"], path) + // Ktfmt is noisy when fixing, outputting filenames whether something was done or not, + // with no useful info. + .map(|outcome| outcome.quiet_success()) + } +} diff --git a/linter/tools/lib.rs b/linter/tools/lib.rs index 73b9e122d42..3efff753d0d 100644 --- a/linter/tools/lib.rs +++ b/linter/tools/lib.rs @@ -19,6 +19,7 @@ pub mod build_license; pub mod buildifier; pub mod clang_format; pub mod hadolint; +pub mod ktfmt; pub mod markdownlint; pub mod prettier; pub mod rustfmt; @@ -50,3 +51,16 @@ fn contents_starts_with(path: &Path, bytes: &[u8]) -> anyhow::Result { fn linter_command(command: &str, args: &[&str], path: &Path) -> anyhow::Result { Command::new(command).args(args).arg(path.to_str().unwrap()).try_into() } + +trait QuietSuccess { + fn quiet_success(self) -> linter::Outcome; +} + +impl QuietSuccess for linter::Outcome { + fn quiet_success(self) -> linter::Outcome { + match self { + linter::Outcome::Success(_) => linter::Outcome::Success("".to_string()), + x => x, + } + } +}