Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REL-4357 - KSM Java SDK v16.6.3 #559

Merged
merged 3 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Setup, Build and Test
uses: gradle/gradle-build-action@v2
with:
gradle-version: 7.5
gradle-version: 8.4
arguments: build test
build-root-directory: ./sdk/java/core

Expand Down
4 changes: 4 additions & 0 deletions sdk/java/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ For more information see our official documentation page https://docs.keeper.io/

# Change Log

## 16.6.3
- KSM-486 - Fix security provider not supporting AES/CBC/PKCS7Padding
- KSM-473 - Make Notation function public

## 16.6.2
- KSM-452 - Java SDK broken when using Java default crypto provider.
- KSM-453 - Upgrade kotlin-stdlib-jdk8 dependency scope to api
Expand Down
59 changes: 26 additions & 33 deletions sdk/java/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,32 @@ import java.util.*
group = "com.keepersecurity.secrets-manager"

// During publishing, If version ends with '-SNAPSHOT' then it will be published to Maven snapshot repository
version = "16.6.2"
version = "16.6.3"

plugins {
`java-library`
kotlin("jvm") version "1.6.21"
kotlin("plugin.serialization") version "1.6.21"
kotlin("jvm") version "1.9.20"
kotlin("plugin.serialization") version "1.9.20"
`maven-publish`
signing
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
sourceCompatibility = JavaVersion.VERSION_1_8.toString()
targetCompatibility = JavaVersion.VERSION_1_8.toString()
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
tasks.withType<JavaCompile>().configureEach {
javaCompiler.set(javaToolchains.compilerFor {
languageVersion.set(JavaLanguageVersion.of(8))
})
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "1.8"
}
}

repositories {
Expand All @@ -35,24 +42,20 @@ repositories {

dependencies {
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.9.20"))

// Use the Kotlin JDK 8 standard library.
api("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")

implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10")

api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.20")

// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.9.20")

// Use the Kotlin JUnit integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")

testImplementation("org.bouncycastle:bc-fips:1.0.2.3")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.9.20")

testImplementation("org.bouncycastle:bc-fips:1.0.2.4")
// testImplementation("org.bouncycastle:bcprov-jdk15on:1.70")
}

Expand All @@ -73,19 +76,12 @@ ext["ossrhPassword"] = null

// Grabbing secrets from local.properties file or from environment variables, which could be used on CI
val secretPropsFile = project.rootProject.file("local.properties")

if (secretPropsFile.exists()) {

// Retrieving variables from the properties file
secretPropsFile.reader().use {
Properties().apply {
load(it)
}
}.onEach { (name, value) ->
ext[name.toString()] = value
}
val localProperties = Properties()
localProperties.load(secretPropsFile.inputStream())
localProperties.forEach { prop -> ext[prop.key.toString()] = prop.value }
} else {

// Retrieving variables from the properties file
ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID")
ext["signing.password"] = System.getenv("SIGNING_PASSWORD")
Expand Down Expand Up @@ -139,7 +135,6 @@ publishing {
name.set("Maksim Ustinov")
email.set("[email protected]")
}

}
contributors {
contributor {
Expand All @@ -154,7 +149,6 @@ publishing {
name.set("Maksim Ustinov")
url.set("https://github.com/maksimu")
}

}
scm {
connection.set("scm:git:git://github.com/Keeper-Security/secrets-manager.git")
Expand Down Expand Up @@ -184,7 +178,6 @@ publishing {
}
}


signing {
sign(publishing.publications["mavenJava"])
}
Expand All @@ -193,4 +186,4 @@ tasks.javadoc {
if (JavaVersion.current().isJava9Compatible) {
(options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
}
}
}
3 changes: 1 addition & 2 deletions sdk/java/core/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
#distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip

zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
24 changes: 15 additions & 9 deletions sdk/java/core/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/6.2.2/userguide/multi_project_builds.html
*/

rootProject.name = "core"

plugins {
id("org.gradle.toolchains.foojay-resolver") version "0.7.0"
}

@Suppress("UnstableApiUsage")
toolchainManagement {
jvm {
javaRepositories {
repository("foojay") {
resolverClass.set(org.gradle.toolchains.foojay.FoojayToolchainResolver::class.java)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,17 @@ internal fun hash(data: ByteArray, tag: String): ByteArray {
}

internal fun getCipher(mode: Int, iv: ByteArray, key: ByteArray, useCBC: Boolean = false): Cipher {
val transformation = if (useCBC) "AES/CBC/PKCS7Padding" else "AES/GCM/NoPadding"
// Some cryptographic libraries such as the SUN provider in Java indicate PKCS#5 where PKCS#7 should be used
val paddingProvider = if (KeeperCryptoParameters.provider == null) "AES/CBC/PKCS5Padding" else "AES/CBC/PKCS7Padding"
val transformation = if (useCBC) paddingProvider else "AES/GCM/NoPadding"
val cipher = if (KeeperCryptoParameters.provider == null)
Cipher.getInstance(transformation) else
Cipher.getInstance(transformation, KeeperCryptoParameters.provider)

val keySpec = SecretKeySpec(key, "AES")
val gcmParameterSpec = GCMParameterSpec(16 * 8, iv)
cipher.init(mode, keySpec, gcmParameterSpec)
val parameterSpec = if (useCBC) IvParameterSpec(iv) else GCMParameterSpec(16 * 8, iv)
cipher.init(mode, keySpec, parameterSpec)

return cipher
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ private fun getFieldJsonValue(field: KeeperRecordField): String {
}

// data class to represent parsed notation section
internal data class NotationSection(
data class NotationSection(
var section: String? = null, // section name - ex. prefix
var isPresent: Boolean = false, // presence flag
var startPos: Int = -1, // section start position in URI
Expand Down Expand Up @@ -586,7 +586,7 @@ private fun parseSection(notation: String, section: String, pos: Int): NotationS
return result
}

internal fun parseNotation(notationUri: String, legacyMode: Boolean = false): List<NotationSection> {
fun parseNotation(notationUri: String, legacyMode: Boolean = false): List<NotationSection> {
var notation = notationUri
if (notation.isEmpty())
throw Exception("Keeper notation is missing or invalid.")
Expand Down Expand Up @@ -643,3 +643,56 @@ internal fun parseNotation(notationUri: String, legacyMode: Boolean = false): Li

return listOf(prefix, record, selector, footer)
}

fun tryParseNotation(notationUri: String, legacyMode: Boolean = false): Pair<List<NotationSection>, String> {
var error = ""
val notations = try {
parseNotation(notationUri, legacyMode)
} catch (e: Exception) {
error = e.message.toString()
emptyList<NotationSection>()
}
return Pair(notations, error)
}

fun validateNotation(notationUri: String, legacyMode: Boolean = false): Boolean {
val (parsedNotation, error) = tryParseNotation(notationUri, legacyMode)
// validate syntax
if (error.isNotBlank()) return false

// check for logical errors
// prefix, record, selector, footer
if (parsedNotation.size < 3) return false

// prefix: parsedNotation[0] == "keeper://" is optional
// record: parsedNotation[1] == UID or Title
if (!parsedNotation[1].isPresent || parsedNotation[1].text == null) return false
// selector: parsedNotation[2] == type|title|notes or file|field|custom_field
if (!parsedNotation[2].isPresent || parsedNotation[2].text == null) return false
// footer: parsedNotation[3] == anything else after a valid notation (should not be present)
if (parsedNotation[3].isPresent) return false
// valid notation section names
if (parsedNotation.firstOrNull { it.section.isNullOrBlank() ||
!listOf("prefix", "record", "selector", "footer").contains(it.section!!.lowercase()) } != null)
return false

val selector = parsedNotation[2].text!!.first.lowercase() // type|title|notes or file|field|custom_field
val parameter = parsedNotation[2].parameter?.first
// short selectors don't have parameters
if (listOf("type", "title", "notes").contains(selector) && parameter != null) return false
// these selectors require a parameter
if (listOf("file", "field", "custom_field").contains(selector) && parameter == null) return false
// file selectors have a single parameter(filename) and don't use indexes
if (selector == "file" && (parsedNotation[2].index1 != null || parsedNotation[2].index2 != null))
return false

// cannot have second index without first
if (parsedNotation[2].index1 == null && parsedNotation[2].index2 != null) return false
// .../<type|label>[index1][index2], ex. /url == /url[] == /url[][] == full value
val idx = parsedNotation[2].index1?.first?.toIntOrNull() ?: -1 // -1 full value
// valid only if [] or missing - ex. /field/phone or /field/phone[]
if (idx == -1 && !(parsedNotation[2].index1?.second.isNullOrEmpty() || parsedNotation[2].index1?.second == "[]"))
return false

return true
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.util.*
import java.util.concurrent.*
import javax.net.ssl.*

const val KEEPER_CLIENT_VERSION = "mj16.6.2"
const val KEEPER_CLIENT_VERSION = "mj16.6.3"

const val KEY_HOSTNAME = "hostname" // base url for the Secrets Manager service
const val KEY_SERVER_PUBIC_KEY_ID = "serverPublicKeyId"
Expand Down
Loading