From edc8381c8d40bfe3895ecad7dad1eac1d50942e1 Mon Sep 17 00:00:00 2001 From: Rohan Desai Date: Thu, 14 Nov 2024 22:00:44 -0800 Subject: [PATCH 1/2] add support types for license verification This patch adds some infra to support license verification: The main change is the addition of LicenseAuthenticator and LicenseChecker. LicenseAuthenticator is responsible for taking a license document, verifying that it is signed correctly, and then returning the contained signed LicenseInfo. LicenseChecker takes a LicenseInfo and validates that the license it describes can be used. These new classes operate on the pojo types defined under the model package that define the structure of the license documents, and the key registry (SigningKeys) that's to be packaged into the jar. Finally, this patch also includes a utility class for parsing pem files, which we'll use to store public keys. I opted to write our own parser (rather than using something like bouncycastle) because the format it's simple and I'd rather not include a dependency on a foundational artifact like bouncycastle that is likely to conflict with a user's dependencies. --- .../license/LicenseAuthenticator.java | 110 ++++++++++++++++++ .../internal/license/LicenseChecker.java | 40 +++++++ .../license/PublicKeyPemFileParser.java | 56 +++++++++ .../LicenseAuthenticationException.java | 25 ++++ .../license/exception/LicenseException.java | 25 ++++ .../LicenseUseViolationException.java | 25 ++++ .../license/model/LicenseDocument.java | 40 +++++++ .../license/model/LicenseDocumentV1.java | 69 +++++++++++ .../internal/license/model/LicenseInfo.java | 38 ++++++ .../internal/license/model/LicenseType.java | 24 ++++ .../internal/license/model/SigningKeys.java | 67 +++++++++++ .../internal/license/model/TimedTrialV1.java | 44 +++++++ .../kafka/internal/utils/Utils.java | 1 - .../kafka/api/ResponsiveKafkaStreamsTest.java | 2 +- .../license/LicenseAuthenticatorTest.java | 73 ++++++++++++ .../internal/license/LicenseCheckerTest.java | 58 +++++++++ .../license/PublicKeyPemFileParserTest.java | 93 +++++++++++++++ .../stores/ResponsiveKeyValueStoreTest.java | 4 +- .../GlobalStreamThreadIntegrationTest.java | 2 +- .../license-verifier/keys/test.pem | 14 +++ .../license-test-invalid-signature.json | 8 ++ .../license-verifier/license-test.json | 8 ++ .../license-verifier/signing-keys.json | 9 ++ .../invalid-missing-footer.pem | 4 + .../invalid-missing-header.pem | 4 + .../public-key-pem-file-parser/valid-real.pem | 14 +++ .../valid-with-comment.pem | 6 + .../public-key-pem-file-parser/valid.pem | 5 + 28 files changed, 863 insertions(+), 5 deletions(-) create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/LicenseAuthenticator.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/LicenseChecker.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/PublicKeyPemFileParser.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseAuthenticationException.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseException.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseUseViolationException.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseDocument.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseDocumentV1.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseInfo.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseType.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/SigningKeys.java create mode 100644 kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/TimedTrialV1.java create mode 100644 kafka-client/src/test/java/dev/responsive/kafka/internal/license/LicenseAuthenticatorTest.java create mode 100644 kafka-client/src/test/java/dev/responsive/kafka/internal/license/LicenseCheckerTest.java create mode 100644 kafka-client/src/test/java/dev/responsive/kafka/internal/license/PublicKeyPemFileParserTest.java create mode 100644 kafka-client/src/test/resources/license-test/license-verifier/keys/test.pem create mode 100644 kafka-client/src/test/resources/license-test/license-verifier/license-test-invalid-signature.json create mode 100644 kafka-client/src/test/resources/license-test/license-verifier/license-test.json create mode 100644 kafka-client/src/test/resources/license-test/license-verifier/signing-keys.json create mode 100644 kafka-client/src/test/resources/license-test/public-key-pem-file-parser/invalid-missing-footer.pem create mode 100644 kafka-client/src/test/resources/license-test/public-key-pem-file-parser/invalid-missing-header.pem create mode 100644 kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid-real.pem create mode 100644 kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid-with-comment.pem create mode 100644 kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid.pem diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/LicenseAuthenticator.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/LicenseAuthenticator.java new file mode 100644 index 000000000..5013e8bb6 --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/LicenseAuthenticator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.responsive.kafka.internal.license.exception.LicenseAuthenticationException; +import dev.responsive.kafka.internal.license.model.LicenseDocument; +import dev.responsive.kafka.internal.license.model.LicenseDocumentV1; +import dev.responsive.kafka.internal.license.model.LicenseInfo; +import dev.responsive.kafka.internal.license.model.SigningKeys; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PSSParameterSpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Objects; + +public class LicenseAuthenticator { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final SigningKeys signingKeys; + + public LicenseAuthenticator(final SigningKeys signingKeys) { + this.signingKeys = Objects.requireNonNull(signingKeys); + } + + public LicenseInfo authenticate(final LicenseDocument license) { + if (license instanceof LicenseDocumentV1) { + return authenticateLicenseV1((LicenseDocumentV1) license); + } else { + throw new IllegalArgumentException( + "unrecognized license doc type: " + license.getClass().getName()); + } + } + + private LicenseInfo authenticateLicenseV1(final LicenseDocumentV1 license) { + final byte[] infoBytes = verifyLicenseV1Signature(license); + try { + return MAPPER.readValue(infoBytes, LicenseInfo.class); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private byte[] verifyLicenseV1Signature(final LicenseDocumentV1 license) { + if (!license.algo().equals("RSASSA_PSS_SHA_256")) { + throw new IllegalArgumentException("unrecognized license algo: " + license.algo()); + } + final PublicKey publicKey = loadPublicKey(signingKeys.lookupKey(license.key())); + final Signature signature; + try { + signature = Signature.getInstance("RSASSA-PSS"); + signature.setParameter( + new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1) + ); + signature.initVerify(publicKey); + final byte[] info = license.decodeInfo(); + signature.update(info); + if (!signature.verify(license.decodeSignature())) { + throw new LicenseAuthenticationException("license info did not match signature"); + } + return info; + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + private PublicKey loadPublicKey(final SigningKeys.SigningKey signingKey) { + final File file; + try { + file = new File(this.getClass().getClassLoader().getResource(signingKey.path()).toURI()); + } catch (final URISyntaxException e) { + throw new RuntimeException(e); + } + final byte[] publicKeyBytes = PublicKeyPemFileParser.parsePemFile(file); + final KeyFactory keyFactory; + try { + keyFactory = KeyFactory.getInstance("RSA"); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes); + try { + return keyFactory.generatePublic(keySpec); + } catch (final InvalidKeySpecException e) { + throw new RuntimeException(e); + } + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/LicenseChecker.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/LicenseChecker.java new file mode 100644 index 000000000..4913519c8 --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/LicenseChecker.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license; + +import dev.responsive.kafka.internal.license.exception.LicenseUseViolationException; +import dev.responsive.kafka.internal.license.model.LicenseInfo; +import dev.responsive.kafka.internal.license.model.TimedTrialV1; +import java.time.Instant; + +public class LicenseChecker { + public void checkLicense(final LicenseInfo licenseInfo) { + if (licenseInfo instanceof TimedTrialV1) { + verifyTimedTrialV1((TimedTrialV1) licenseInfo); + } else { + throw new IllegalArgumentException( + "unsupported license type: " + licenseInfo.getClass().getName()); + } + } + + private void verifyTimedTrialV1(final TimedTrialV1 timedTrial) { + final Instant expiresAt = Instant.ofEpochSecond(timedTrial.expiresAt()); + if (Instant.now().isAfter(expiresAt)) { + throw new LicenseUseViolationException("license expired at: " + expiresAt); + } + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/PublicKeyPemFileParser.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/PublicKeyPemFileParser.java new file mode 100644 index 000000000..1f45ffdf1 --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/PublicKeyPemFileParser.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Base64; +import java.util.List; + +public class PublicKeyPemFileParser { + private static final String HEADER_PREFIX = "-----"; + private static final String BEGIN_PUBLIC_KEY = "BEGIN PUBLIC KEY"; + private static final String END_PUBLIC_KEY = "END PUBLIC KEY"; + private static final String BEGIN_PUBLIC_KEY_HEADER + = HEADER_PREFIX + BEGIN_PUBLIC_KEY + HEADER_PREFIX; + private static final String END_PUBLIC_KEY_HEADER + = HEADER_PREFIX + END_PUBLIC_KEY + HEADER_PREFIX; + + public static byte[] parsePemFile(final File file) { + final List lines; + try { + lines = Files.readAllLines(file.toPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + final StringBuilder keyB64Builder = new StringBuilder(); + boolean foundBegin = false; + for (final String l : lines) { + if (l.equals(BEGIN_PUBLIC_KEY_HEADER)) { + foundBegin = true; + } else if (foundBegin) { + if (l.equals(END_PUBLIC_KEY_HEADER)) { + final String keyB64 = keyB64Builder.toString(); + return Base64.getDecoder().decode(keyB64); + } + keyB64Builder.append(l); + } + } + throw new IllegalArgumentException("invalid public key pem"); + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseAuthenticationException.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseAuthenticationException.java new file mode 100644 index 000000000..d4773d3a7 --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseAuthenticationException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.exception; + +public class LicenseAuthenticationException extends LicenseException { + private static final long serialVersionUID = 0L; + + public LicenseAuthenticationException(final String message) { + super(message); + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseException.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseException.java new file mode 100644 index 000000000..219b718db --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.exception; + +public class LicenseException extends RuntimeException { + private static final long serialVersionUID = 0L; + + public LicenseException(final String message) { + super(message); + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseUseViolationException.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseUseViolationException.java new file mode 100644 index 000000000..b48ae43f3 --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/exception/LicenseUseViolationException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.exception; + +public class LicenseUseViolationException extends LicenseException { + private static final long serialVersionUID = 0L; + + public LicenseUseViolationException(final String message) { + super(message); + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseDocument.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseDocument.java new file mode 100644 index 000000000..8913e2d21 --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseDocument.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "version", + visible = true +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = LicenseDocumentV1.class, name = "1") +}) +public abstract class LicenseDocument { + private final String version; + + @JsonCreator + public LicenseDocument(@JsonProperty("version") final String version) { + this.version = version; + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseDocumentV1.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseDocumentV1.java new file mode 100644 index 000000000..ea3b9598c --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseDocumentV1.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Base64; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class LicenseDocumentV1 extends LicenseDocument { + private final String info; + private final String signature; + private final String key; + private final String algo; + + @JsonCreator + public LicenseDocumentV1( + @JsonProperty("version") final String version, + @JsonProperty("info") final String info, + @JsonProperty("signature") final String signature, + @JsonProperty("key") final String key, + @JsonProperty("algo") final String algo + ) { + super(version); + this.info = info; + this.signature = signature; + this.key = key; + this.algo = algo; + } + + public String info() { + return info; + } + + public String key() { + return key; + } + + public String signature() { + return signature; + } + + public String algo() { + return algo; + } + + public byte[] decodeInfo() { + return Base64.getDecoder().decode(info); + } + + public byte[] decodeSignature() { + return Base64.getDecoder().decode(signature); + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseInfo.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseInfo.java new file mode 100644 index 000000000..3f59e6bc6 --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseInfo.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type", + visible = true +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = TimedTrialV1.class, name = "timed_trial_v1") +}) +public abstract class LicenseInfo { + private final String type; + + LicenseInfo(@JsonProperty("type") final String type) { + this.type = type; + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseType.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseType.java new file mode 100644 index 000000000..337c4a16d --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/LicenseType.java @@ -0,0 +1,24 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum LicenseType { + @JsonProperty("timed_trial") + TIMED_TRIAL +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/SigningKeys.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/SigningKeys.java new file mode 100644 index 000000000..1c302e39f --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/SigningKeys.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Objects; + +public class SigningKeys { + private final List keys; + + @JsonCreator + public SigningKeys(@JsonProperty("keys") final List keys) { + this.keys = Objects.requireNonNull(keys); + } + + public SigningKey lookupKey(final String keyId) { + return keys.stream() + .filter(k -> k.keyId.equals(keyId)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Key not found: " + keyId)); + } + + public enum KeyType { + RSA_4096 + } + + public static class SigningKey { + private final KeyType type; + private final String keyId; + private final String path; + + @JsonCreator + public SigningKey( + @JsonProperty("type") final KeyType type, + @JsonProperty("keyId") final String keyId, + @JsonProperty("path") final String path + ) { + this.type = type; + this.keyId = keyId; + this.path = path; + } + + public KeyType type() { + return type; + } + + public String path() { + return path; + } + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/TimedTrialV1.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/TimedTrialV1.java new file mode 100644 index 000000000..70178c900 --- /dev/null +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/license/model/TimedTrialV1.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; + +public class TimedTrialV1 extends LicenseInfo { + private final String email; + private final long issuedAt; + private final long expiresAt; + + @JsonCreator + public TimedTrialV1( + @JsonProperty("type") final String type, + @JsonProperty("email") final String email, + @JsonProperty("issuedAt") final long issuedAt, + @JsonProperty("expiresAt") final long expiresAt + ) { + super(type); + this.email = Objects.requireNonNull(email); + this.issuedAt = issuedAt; + this.expiresAt = expiresAt; + } + + public long expiresAt() { + return expiresAt; + } +} diff --git a/kafka-client/src/main/java/dev/responsive/kafka/internal/utils/Utils.java b/kafka-client/src/main/java/dev/responsive/kafka/internal/utils/Utils.java index 5b77bce13..37e8ec12e 100644 --- a/kafka-client/src/main/java/dev/responsive/kafka/internal/utils/Utils.java +++ b/kafka-client/src/main/java/dev/responsive/kafka/internal/utils/Utils.java @@ -121,5 +121,4 @@ public static String extractThreadIdFromThreadName(final String threadName) { LOG.warn("Unable to parse the stream thread id, falling back to thread name {}", threadName); return threadName; } - } diff --git a/kafka-client/src/test/java/dev/responsive/kafka/api/ResponsiveKafkaStreamsTest.java b/kafka-client/src/test/java/dev/responsive/kafka/api/ResponsiveKafkaStreamsTest.java index de67fb40d..cfb5aeaf6 100644 --- a/kafka-client/src/test/java/dev/responsive/kafka/api/ResponsiveKafkaStreamsTest.java +++ b/kafka-client/src/test/java/dev/responsive/kafka/api/ResponsiveKafkaStreamsTest.java @@ -125,7 +125,7 @@ public void setUp() { properties.put(DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.LongSerde.class.getName()); properties.put(RESPONSIVE_ORG_CONFIG, "responsive"); - properties.put(RESPONSIVE_ENV_CONFIG, "test"); + properties.put(RESPONSIVE_ENV_CONFIG, "license-test"); } @SuppressWarnings("resource") diff --git a/kafka-client/src/test/java/dev/responsive/kafka/internal/license/LicenseAuthenticatorTest.java b/kafka-client/src/test/java/dev/responsive/kafka/internal/license/LicenseAuthenticatorTest.java new file mode 100644 index 000000000..27592107a --- /dev/null +++ b/kafka-client/src/test/java/dev/responsive/kafka/internal/license/LicenseAuthenticatorTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.responsive.kafka.internal.license.exception.LicenseAuthenticationException; +import dev.responsive.kafka.internal.license.model.LicenseDocument; +import dev.responsive.kafka.internal.license.model.SigningKeys; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class LicenseAuthenticatorTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final LicenseAuthenticator verifier = new LicenseAuthenticator(loadSigningKeys()); + + @Test + public void shouldVerifyLicense() { + // given: + final LicenseDocument license = loadLicense("license-test.json"); + + // when/then (no throw): + verifier.authenticate(license); + } + + @Test + public void shouldThrowForFailedSignatureVerification() { + // given: + final LicenseDocument license = loadLicense("license-test-invalid-signature.json"); + + // when/then: + assertThrows( + LicenseAuthenticationException.class, + () -> verifier.authenticate(license) + ); + } + + private static LicenseDocument loadLicense(final String file) { + return loadResource(file, LicenseDocument.class); + } + + private static SigningKeys loadSigningKeys() { + return loadResource("signing-keys.json", SigningKeys.class); + } + + private static T loadResource(final String path, final Class clazz) { + final String fullPath = "license-test/license-verifier/" + path; + try { + return MAPPER.readValue( + LicenseAuthenticatorTest.class.getClassLoader().getResource(fullPath), + clazz + ); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/kafka-client/src/test/java/dev/responsive/kafka/internal/license/LicenseCheckerTest.java b/kafka-client/src/test/java/dev/responsive/kafka/internal/license/LicenseCheckerTest.java new file mode 100644 index 000000000..26d283574 --- /dev/null +++ b/kafka-client/src/test/java/dev/responsive/kafka/internal/license/LicenseCheckerTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import dev.responsive.kafka.internal.license.exception.LicenseUseViolationException; +import dev.responsive.kafka.internal.license.model.LicenseInfo; +import dev.responsive.kafka.internal.license.model.TimedTrialV1; +import java.time.Duration; +import java.time.Instant; +import org.junit.jupiter.api.Test; + +class LicenseCheckerTest { + private final LicenseChecker checker = new LicenseChecker(); + + @Test + public void shouldThrowOnExpiredTrialV1License() { + // given: + final LicenseInfo info = new TimedTrialV1( + "timed_trial_v1", + "foo@bar.com", + 0, + Instant.now().minus(Duration.ofHours(1)).getEpochSecond() + ); + + // when/then: + assertThrows(LicenseUseViolationException.class, () -> checker.checkLicense(info)); + } + + @Test + public void shouldAcceptValidTrialV1License() { + // given: + final LicenseInfo info = new TimedTrialV1( + "timed_trial_v1", + "foo@bar.com", + 0, + Instant.now().plus(Duration.ofHours(1)).getEpochSecond() + ); + + // when/then (no throw): + checker.checkLicense(info); + } +} \ No newline at end of file diff --git a/kafka-client/src/test/java/dev/responsive/kafka/internal/license/PublicKeyPemFileParserTest.java b/kafka-client/src/test/java/dev/responsive/kafka/internal/license/PublicKeyPemFileParserTest.java new file mode 100644 index 000000000..750f3050f --- /dev/null +++ b/kafka-client/src/test/java/dev/responsive/kafka/internal/license/PublicKeyPemFileParserTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2024 Responsive Computing, Inc. + * + * 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. + */ + +package dev.responsive.kafka.internal.license; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.File; +import java.net.URISyntaxException; +import org.junit.jupiter.api.Test; + +class PublicKeyPemFileParserTest { + + @Test + public void shouldParseValidPemFile() { + // given: + final File file = getPemFile("valid.pem"); + + // when: + final byte[] key = PublicKeyPemFileParser.parsePemFile(file); + + // then: + assertThat(new String(key), is("foobarbaz")); + } + + @Test + public void shouldParsePemFileWithRealKey() { + // given: + final File file = getPemFile("valid-real.pem"); + + // when: + final byte[] key = PublicKeyPemFileParser.parsePemFile(file); + + // then: + assertThat(key.length, is(550)); + } + + @Test + public void shouldParseValidPemFileWithComment() { + // given: + final File file = getPemFile("valid-with-comment.pem"); + + // when: + final byte[] key = PublicKeyPemFileParser.parsePemFile(file); + + // then: + assertThat(new String(key), is("foobarbaz")); + } + + @Test + public void shouldFailToParseInvalidPemFileWithMissingFooter() { + // given: + final File file = getPemFile("invalid-missing-footer.pem"); + + // when/then: + assertThrows(IllegalArgumentException.class, () -> PublicKeyPemFileParser.parsePemFile(file)); + } + + @Test + public void shouldFailToParseInvalidPemFileWithMissingHeader() { + // given: + final File file = getPemFile("invalid-missing-header.pem"); + + // when/then: + assertThrows(IllegalArgumentException.class, () -> PublicKeyPemFileParser.parsePemFile(file)); + } + + private File getPemFile(final String filename) { + final String path = "license-test/public-key-pem-file-parser/" + filename; + try { + return new File( + PublicKeyPemFileParserTest.class.getClassLoader().getResource(path).toURI() + ); + } catch (final URISyntaxException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/kafka-client/src/test/java/dev/responsive/kafka/internal/stores/ResponsiveKeyValueStoreTest.java b/kafka-client/src/test/java/dev/responsive/kafka/internal/stores/ResponsiveKeyValueStoreTest.java index de1db7b95..e47dff600 100644 --- a/kafka-client/src/test/java/dev/responsive/kafka/internal/stores/ResponsiveKeyValueStoreTest.java +++ b/kafka-client/src/test/java/dev/responsive/kafka/internal/stores/ResponsiveKeyValueStoreTest.java @@ -45,7 +45,7 @@ class ResponsiveKeyValueStoreTest { public void shouldPutIfAbsent() { // Given: final var store = new ResponsiveKeyValueStore( - ResponsiveKeyValueParams.keyValue("test"), + ResponsiveKeyValueParams.keyValue("license-test"), (params, ttlResolver, context, type) -> ops ); store.init((StateStoreContext) context, root); @@ -62,7 +62,7 @@ public void shouldPutIfAbsent() { public void shouldNotPutIfAbsentWhenPresent() { // Given: final var store = new ResponsiveKeyValueStore( - ResponsiveKeyValueParams.keyValue("test"), + ResponsiveKeyValueParams.keyValue("license-test"), (params, ttlResolver, context, type) -> ops ); store.init((StateStoreContext) context, root); diff --git a/kafka-client/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadIntegrationTest.java b/kafka-client/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadIntegrationTest.java index 3425f96eb..6b5c01324 100644 --- a/kafka-client/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadIntegrationTest.java +++ b/kafka-client/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadIntegrationTest.java @@ -347,7 +347,7 @@ public KeyValueStore get() { @Override public String metricsScope() { - return "test"; + return "license-test"; } } diff --git a/kafka-client/src/test/resources/license-test/license-verifier/keys/test.pem b/kafka-client/src/test/resources/license-test/license-verifier/keys/test.pem new file mode 100644 index 000000000..1f5b4d8d3 --- /dev/null +++ b/kafka-client/src/test/resources/license-test/license-verifier/keys/test.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzsXcm9qZxyeoH0MN+Pvf +2AnxXhPHB3lnJgKC/e9/j8eLHQPI4LoLcvSwB+D97XrMtgjtiMt+64Ba3YDPQzIQ +pAkYPFttW6+K8bZYmql8xMbAJLF/zeFa/gN3Pbwlc2ZGE32u/uq/0cJPtwoy22HW +kp2xITDXROCeXbRhV8aL3THCfKtCtWaS8Ms5vpEQ/qqtJ/oBTQrR9r6PwzhbZ5vT +MU8j5hCO5A2ih6shtT3Sc1yZwVM8CahoLl1XWkrT4P2VbbE120d8haSCDr+uYUNg +KxRyO56rcxfbKaoeR09+PtKrVgjzEe7v718X6GZcpcX9cXDgm1viZ4L426OZFhuq +qNtr0NYUt3FPrfj27S95RYDVe29Iovt++jIvqeb+Ffw+W1zkW/4efAJuByUse8Va +Sx/XH+QwuMc6XNNm/HcSiXq8pFuXEmMl5fXlkwBq6acEBh3GIG1MMBZzEt8j/YTB +pibYurTtPmO8k5ZC65hBeuCJLJ1dPX3BiNIUtH6EJWx7qOUP4bp9vl2aAxIhh/N9 +W29DFxXO7HDjKXK+OAeRySXg+qqQBScThUZWwUafZMRVbarQc5/E3iZKzQY9p21u +7/ZWxBCWLWi7XxYbiqlupKhG/+sSZuMHuxnP2iM0c3Yge0iPPYTU2Rpscd/92Jnb +NGz+ApGGrznw/bYk8CorwgMCAwEAAQ== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/kafka-client/src/test/resources/license-test/license-verifier/license-test-invalid-signature.json b/kafka-client/src/test/resources/license-test/license-verifier/license-test-invalid-signature.json new file mode 100644 index 000000000..ca4d8d7af --- /dev/null +++ b/kafka-client/src/test/resources/license-test/license-verifier/license-test-invalid-signature.json @@ -0,0 +1,8 @@ +{ + "info": "eyJlbWFpbCI6InJvaGFuQHJlc3BvbnNpdmUuZGV2IiwidHlwZSI6InRpbWVkX3RyaWFsX3YxIiwiaXNzdWVkQXQiOjE3MzE2NTU5MjQsImV4cGlyZXNBdCI6MjM2MjM3NTkyNH0=", + "signature": "A9qhaMdHLAga8v6fT7U7B2e61TOciEnL99ZLD/j9PtUeuzncQDXZkxornXPXwR2Pb5pQc+mlHUhOdX0vPfHtnpSNynhjCSF7CfoIJ+mgG69CBW4iyPvzURMHysTmiJcvCAUykr2rAMd76guefAUFC4HhVWVr9GJikbFkemEJAZI9tjTYNRBEEp5vg6pwsS/dg332okqb8Fpx+y02e0oKa+VKGLOFKHOxdOfewjmHLvXRXaQkoDhl3YtKk8sFfS7GrbGynAIsQ/rI2n8pTzuaEf6hQU5YSduaqFGtZtLY+AB/HgcBD0Fx9isR+N0VhmeZuS+sFUUodbK8BRrDuhQl5YqizfgrVaqr1Gi3bUQTKU+Ex4NNFOQ5NkDetiFSFJnmi50Imgh4ViirTxFR+4+PvkmKF9p173Hsmg/dWta8n1kJhMztTG5n6hAbL2R/4PX8KbtNtSwzFKll9Ytk3f0FFHYonIwPBSSF5Je/2MlK/C+M9ann+dYpA2kBDPiUNm0q853ZiP/hVWZ3kcRB65rQ0eUoZE+7ybhci+JOqUIpQxh/iLJn0rQel1pXLa6lgBeVKQlJNe3bN0t+ZEYQX7A5ZBZet7OWnNBzpOFRTg2ILRCCgB2/GK+zL/GfsETtVXWpggUgI94reAf0hhN6VnN1BGK60EtKfxFGykh8HztzF5E=", + "key": "test", + "algo": "RSASSA_PSS_SHA_256", + "messageType": "RAW", + "version": "1" +} \ No newline at end of file diff --git a/kafka-client/src/test/resources/license-test/license-verifier/license-test.json b/kafka-client/src/test/resources/license-test/license-verifier/license-test.json new file mode 100644 index 000000000..c01529dc6 --- /dev/null +++ b/kafka-client/src/test/resources/license-test/license-verifier/license-test.json @@ -0,0 +1,8 @@ +{ + "info": "eyJlbWFpbCI6InJvaGFuQHJlc3BvbnNpdmUuZGV2IiwidHlwZSI6InRpbWVkX3RyaWFsX3YxIiwiaXNzdWVkQXQiOjE3MzE2NTU5MjQsImV4cGlyZXNBdCI6MjM2MjM3NTkyNH0=", + "signature": "UqDSI9w1T35WzCRY2hdriCLlDPh3bqu24J6apLQvucXEf+VhH6Zk7Of4G8hA5w6kQUYIpoJei5UWI2FwOVdVX5kyJwdOOgp/j6I0mm5Jidv5KR6UFTd2d1n/9eg9ChFw7YspeigFL8xfns6IjipGR9mAdnRv76LEDCSsNOFUZ1YjnhM/Rxsa4c1N4K/+ZEpMYauf3A64EyJJEdUbc7y3kPlOgu8l5Dyd47M9SkVeEtTHcHR0PUODwcwRyLO9JvxwVL/65WA0fWiEkk2o0ikeKVUt4lMg9oVd32nyTNeUnG1Ak44Yt6dMlBE7BkebXLNVSNCK8AG5eE7Kkc6kZDO0KooKNe/VcD/5fLLga95gS7HiuBu2A3PB/ifovuLUKFXRP/74847kbQefVKV/HchTl2Y8Fb4/uHniF91Vapjd7sg6xc3DYrmtgv1cfIk5FSU0ag3LQEKwmfwRFL4iRVDPdtdk+CnRYbH+Ksy03VW/3YIJPWAbF4734DQncmJr3DTY2/9b5ZTk+VA2qlvQroxBi1iR0ye4hPejki5gpQBNJVEtMWFojaGpaJl+IFTFhY+cqXFxVJFKgXdz/bKUwXrzx0+u1CvPDtm3ZsPQmhVczArGNgf+eWMOfEdAr7G3+4qEeYRP9xtQ7mOduTOzven83OC8P/xHzObtfGHibm76SyU=", + "key": "test", + "algo": "RSASSA_PSS_SHA_256", + "messageType": "RAW", + "version": "1" +} diff --git a/kafka-client/src/test/resources/license-test/license-verifier/signing-keys.json b/kafka-client/src/test/resources/license-test/license-verifier/signing-keys.json new file mode 100644 index 000000000..1a016b667 --- /dev/null +++ b/kafka-client/src/test/resources/license-test/license-verifier/signing-keys.json @@ -0,0 +1,9 @@ +{ + "keys": [ + { + "type": "RSA_4096", + "keyId": "test", + "path": "license-test/license-verifier/keys/test.pem" + } + ] +} \ No newline at end of file diff --git a/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/invalid-missing-footer.pem b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/invalid-missing-footer.pem new file mode 100644 index 000000000..3eb952247 --- /dev/null +++ b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/invalid-missing-footer.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +Zm9v +YmFy +YmF6 \ No newline at end of file diff --git a/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/invalid-missing-header.pem b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/invalid-missing-header.pem new file mode 100644 index 000000000..6db873d2d --- /dev/null +++ b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/invalid-missing-header.pem @@ -0,0 +1,4 @@ +Zm9v +YmFy +YmF6 +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid-real.pem b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid-real.pem new file mode 100644 index 000000000..1f5b4d8d3 --- /dev/null +++ b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid-real.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzsXcm9qZxyeoH0MN+Pvf +2AnxXhPHB3lnJgKC/e9/j8eLHQPI4LoLcvSwB+D97XrMtgjtiMt+64Ba3YDPQzIQ +pAkYPFttW6+K8bZYmql8xMbAJLF/zeFa/gN3Pbwlc2ZGE32u/uq/0cJPtwoy22HW +kp2xITDXROCeXbRhV8aL3THCfKtCtWaS8Ms5vpEQ/qqtJ/oBTQrR9r6PwzhbZ5vT +MU8j5hCO5A2ih6shtT3Sc1yZwVM8CahoLl1XWkrT4P2VbbE120d8haSCDr+uYUNg +KxRyO56rcxfbKaoeR09+PtKrVgjzEe7v718X6GZcpcX9cXDgm1viZ4L426OZFhuq +qNtr0NYUt3FPrfj27S95RYDVe29Iovt++jIvqeb+Ffw+W1zkW/4efAJuByUse8Va +Sx/XH+QwuMc6XNNm/HcSiXq8pFuXEmMl5fXlkwBq6acEBh3GIG1MMBZzEt8j/YTB +pibYurTtPmO8k5ZC65hBeuCJLJ1dPX3BiNIUtH6EJWx7qOUP4bp9vl2aAxIhh/N9 +W29DFxXO7HDjKXK+OAeRySXg+qqQBScThUZWwUafZMRVbarQc5/E3iZKzQY9p21u +7/ZWxBCWLWi7XxYbiqlupKhG/+sSZuMHuxnP2iM0c3Yge0iPPYTU2Rpscd/92Jnb +NGz+ApGGrznw/bYk8CorwgMCAwEAAQ== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid-with-comment.pem b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid-with-comment.pem new file mode 100644 index 000000000..f105eeac9 --- /dev/null +++ b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid-with-comment.pem @@ -0,0 +1,6 @@ +# this is a comment +-----BEGIN PUBLIC KEY----- +Zm9v +YmFy +YmF6 +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid.pem b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid.pem new file mode 100644 index 000000000..bed2d8663 --- /dev/null +++ b/kafka-client/src/test/resources/license-test/public-key-pem-file-parser/valid.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +Zm9v +YmFy +YmF6 +-----END PUBLIC KEY----- \ No newline at end of file From 691014fdcce7262c952bd603f7ee8ec07e627cea Mon Sep 17 00:00:00 2001 From: Rohan Desai Date: Fri, 15 Nov 2024 09:09:52 -0800 Subject: [PATCH 2/2] fix string replace --- .../kafka/internal/stores/ResponsiveKeyValueStoreTest.java | 4 ++-- .../internals/GlobalStreamThreadIntegrationTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kafka-client/src/test/java/dev/responsive/kafka/internal/stores/ResponsiveKeyValueStoreTest.java b/kafka-client/src/test/java/dev/responsive/kafka/internal/stores/ResponsiveKeyValueStoreTest.java index e47dff600..de1db7b95 100644 --- a/kafka-client/src/test/java/dev/responsive/kafka/internal/stores/ResponsiveKeyValueStoreTest.java +++ b/kafka-client/src/test/java/dev/responsive/kafka/internal/stores/ResponsiveKeyValueStoreTest.java @@ -45,7 +45,7 @@ class ResponsiveKeyValueStoreTest { public void shouldPutIfAbsent() { // Given: final var store = new ResponsiveKeyValueStore( - ResponsiveKeyValueParams.keyValue("license-test"), + ResponsiveKeyValueParams.keyValue("test"), (params, ttlResolver, context, type) -> ops ); store.init((StateStoreContext) context, root); @@ -62,7 +62,7 @@ public void shouldPutIfAbsent() { public void shouldNotPutIfAbsentWhenPresent() { // Given: final var store = new ResponsiveKeyValueStore( - ResponsiveKeyValueParams.keyValue("license-test"), + ResponsiveKeyValueParams.keyValue("test"), (params, ttlResolver, context, type) -> ops ); store.init((StateStoreContext) context, root); diff --git a/kafka-client/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadIntegrationTest.java b/kafka-client/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadIntegrationTest.java index 6b5c01324..3425f96eb 100644 --- a/kafka-client/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadIntegrationTest.java +++ b/kafka-client/src/test/java/org/apache/kafka/streams/processor/internals/GlobalStreamThreadIntegrationTest.java @@ -347,7 +347,7 @@ public KeyValueStore get() { @Override public String metricsScope() { - return "license-test"; + return "test"; } }