diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1a424d5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 diff --git a/.travis.yml b/.travis.yml index 211c6bc..9bc0df5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ dist: trusty jdk: oraclejdk8 script: + - ./gradlew detekt - ./gradlew test --info --build-cache before_cache: diff --git a/README.md b/README.md index 873cbaa..6aa8d58 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ It uses a combination of obfuscation techniques to do so : - secret is obfuscated using the reversible XOR operator so it never appears in plain sight, - obfuscated secret is stored in a NDK binary as an hexadecimal array, so it is really hard to spot / put together from a disassembly, - the obfuscating string is not persisted in the binary to force runtime evaluation (ie : prevent the compiler from disclosing the secret by optimizing the de-obfuscation logic), -- optionnaly, anyone can provide it's own encoding / decoding algorithm when using the plugin to add an additional security layer. +- optionally, anyone can provide its own encoding / decoding algorithm when using the plugin to add an additional security layer. This plugin is **used in production** at [Klaxit - Covoiturage quotidien](https://play.google.com/store/apps/details?id=com.wayzup.wayzupapp). Our engineering team at Klaxit will provide its best effort to maintain this project. @@ -55,7 +55,7 @@ For more details about the installation check the [plugin's page](https://plugin Obfuscate and hide your key in your project : ```shell -gradle hideSecret -Pkey=yourKeyToObfuscate [-PkeyName=YourSecretKeyName] [-Ppackage=com.your.package] +./gradlew hideSecret -Pkey=yourKeyToObfuscate [-PkeyName=YourSecretKeyName] [-Ppackage=com.your.package] ``` The parameter `keyName` is optional, by default the key name is randomly generated. The parameter `package` is optional, by default the `applicationId` of your project will be used. @@ -94,7 +94,7 @@ As an example, we will use a [rot13 algorithm](https://en.wikipedia.org/wiki/ROT After a rot13 encoding your key `yourKeyToObfuscate` becomes `lbheXrlGbBoshfpngr`. Add it in your app : ```shell -gradle hideSecret -Pkey=lbheXrlGbBoshfpngr -PkeyName=YourSecretKeyName +./gradlew hideSecret -Pkey=lbheXrlGbBoshfpngr -PkeyName=YourSecretKeyName ``` Then in `secrets.cpp` you need to add your own decoding code in `customDecode` method: @@ -126,14 +126,14 @@ Secrets().getYourSecretKeyName(packageName) ### Copy files Copy required files to your project : ```shell -gradle copyCpp -gradle copyKotlin [-Ppackage=your.package.name] +./gradlew copyCpp +./gradlew copyKotlin [-Ppackage=your.package.name] ``` ### Obfuscate Create an obfuscated key and display it : ```shell -gradle obfuscate -Pkey=yourKeyToObfuscate [-Ppackage=com.your.package] +./gradlew obfuscate -Pkey=yourKeyToObfuscate [-Ppackage=com.your.package] ``` This command can be useful if you modify your app's package name based on `buildTypes` configuration. With this command you can get the obfuscated key for a different package name and manually integrate it in another function in `secrets.cpp`. @@ -141,9 +141,12 @@ This command can be useful if you modify your app's package name based on `build Pull Requests are very welcome! -To get started, checkout the code and run `gradle build` to create the `.jar` file in `/build/libs/`. +To get started, checkout the code and run `./gradlew build` to create the `.jar` file in `/build/libs/`. -Please make sure that you have tested your code carefully before opening a PR, and make sure as well that you have no style issues. +Before opening a PR : +- make sure that you have tested your code carefully +- `/gradlew test` must succeed +- `/gradlew detekt` must succeed to avoid any style issue ## Authors diff --git a/build.gradle.kts b/build.gradle.kts index 465ac06..245d6ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("com.gradle.plugin-publish") version "0.12.0" + id("io.gitlab.arturbosch.detekt") version "1.14.2" `java-gradle-plugin` `kotlin-dsl` `maven-publish` @@ -17,8 +18,8 @@ repositories { dependencies { implementation("com.android.tools.build:gradle:4.0.0") - testImplementation("io.kotlintest:kotlintest-runner-junit5:3.1.10") - testImplementation("junit:junit:4.13") + testImplementation("io.kotlintest:kotlintest-runner-junit5:3.1.11") + testImplementation("junit:junit:4.13.1") } configure { diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml new file mode 100644 index 0000000..09e9a8c --- /dev/null +++ b/config/detekt/detekt.yml @@ -0,0 +1,681 @@ +build: + maxIssues: 0 + excludeCorrectable: false + weights: + # complexity: 2 + # LongParameterList: 1 + # style: 1 + # comments: 1 + +config: + validation: true + warningsAsErrors: false + # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' + excludes: '' + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ClassCountProcessor' + # - 'PackageCountProcessor' + # - 'KtFileCountProcessor' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + # - 'FindingsReport' + - 'FileBasedFindingsReport' + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + +comments: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + UndocumentedPublicClass: + active: false + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + UndocumentedPublicFunction: + active: false + UndocumentedPublicProperty: + active: false + +complexity: + active: true + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ComplexMethod: + active: false + threshold: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: [run, let, apply, with, also, use, forEach, isNotNull, ifNull] + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: true + threshold: 600 + LongMethod: + active: false + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotated: [] + MethodOverloading: + active: false + threshold: 6 + NestedBlockDepth: + active: true + threshold: 4 + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + RedundantSuspendModifier: + active: false + SuspendFunWithFlowReturnType: + active: false + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: false + methodNames: [toString, hashCode, equals, finalize] + InstanceOfCheckForException: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + NotImplementedDeclaration: + active: false + PrintStackTrace: + active: false + RethrowCaughtException: + active: false + ReturnFromFinally: + active: false + ignoreLabeled: false + SwallowedException: + active: false + ignoredExceptionTypes: + - InterruptedException + - NumberFormatException + - ParseException + - MalformedURLException + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: false + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptions: + - IllegalArgumentException + - IllegalStateException + - IOException + ThrowingNewInstanceOfSameException: + active: false + TooGenericExceptionCaught: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptionNames: + - ArrayIndexOutOfBoundsException + - Error + - Exception + - IllegalMonitorStateException + - NullPointerException + - IndexOutOfBoundsException + - RuntimeException + - Throwable + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - Error + - Exception + - Throwable + - RuntimeException + +formatting: + active: true + android: false + autoCorrect: true + AnnotationOnSeparateLine: + active: false + autoCorrect: true + AnnotationSpacing: + active: false + autoCorrect: true + ArgumentListWrapping: + active: false + autoCorrect: true + ChainWrapping: + active: true + autoCorrect: true + CommentSpacing: + active: true + autoCorrect: true + EnumEntryNameCase: + active: false + autoCorrect: true + Filename: + active: true + FinalNewline: + active: true + autoCorrect: true + insertFinalNewLine: true + ImportOrdering: + active: false + autoCorrect: true + layout: 'idea' + Indentation: + active: false + autoCorrect: true + indentSize: 4 + continuationIndentSize: 4 + MaximumLineLength: + active: true + maxLineLength: 120 + ModifierOrdering: + active: true + autoCorrect: true + MultiLineIfElse: + active: true + autoCorrect: true + NoBlankLineBeforeRbrace: + active: true + autoCorrect: true + NoConsecutiveBlankLines: + active: true + autoCorrect: true + NoEmptyClassBody: + active: true + autoCorrect: true + NoEmptyFirstLineInMethodBlock: + active: false + autoCorrect: true + NoLineBreakAfterElse: + active: true + autoCorrect: true + NoLineBreakBeforeAssignment: + active: true + autoCorrect: true + NoMultipleSpaces: + active: true + autoCorrect: true + NoSemicolons: + active: true + autoCorrect: true + NoTrailingSpaces: + active: true + autoCorrect: true + NoUnitReturn: + active: true + autoCorrect: true + NoUnusedImports: + active: true + autoCorrect: true + NoWildcardImports: + active: true + PackageName: + active: true + autoCorrect: true + ParameterListWrapping: + active: true + autoCorrect: true + indentSize: 4 + SpacingAroundColon: + active: true + autoCorrect: true + SpacingAroundComma: + active: true + autoCorrect: true + SpacingAroundCurly: + active: true + autoCorrect: true + SpacingAroundDot: + active: true + autoCorrect: true + SpacingAroundDoubleColon: + active: false + autoCorrect: true + SpacingAroundKeyword: + active: true + autoCorrect: true + SpacingAroundOperators: + active: true + autoCorrect: true + SpacingAroundParens: + active: true + autoCorrect: true + SpacingAroundRangeOperator: + active: true + autoCorrect: true + SpacingBetweenDeclarationsWithAnnotations: + active: false + autoCorrect: true + SpacingBetweenDeclarationsWithComments: + active: false + autoCorrect: true + StringTemplate: + active: true + autoCorrect: true + +naming: + active: true + ClassNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + EnumNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + forbiddenName: [] + FunctionMaxLength: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)' + excludeClassPattern: '$^' + ignoreOverridden: true + ignoreAnnotated: ['Composable'] + FunctionParameterNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + InvalidPackageDeclaration: + active: false + rootPackage: '' + MatchingDeclarationName: + active: true + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NonBooleanPropertyPrefixedWithIs: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + ObjectPropertyNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + maximumVariableNameLength: 64 + VariableMinLength: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + minimumVariableNameLength: 1 + VariableNaming: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + ignoreOverridden: true + +performance: + active: true + ArrayPrimitive: + active: true + ForEachOnRange: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + SpreadOperator: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + Deprecation: + active: false + DuplicateCaseInWhenExpression: + active: true + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: false + IgnoredReturnValue: + active: false + restrictToAnnotatedMethods: true + returnValueAnnotations: ['*.CheckReturnValue', '*.CheckResult'] + ImplicitDefaultLocale: + active: false + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + excludeAnnotatedProperties: [] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: false + MissingWhenCase: + active: true + NullableToStringCall: + active: false + RedundantElseInWhen: + active: true + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullOperator: + active: false + UnnecessarySafeCall: + active: false + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + UnsafeCast: + active: false + UselessPostfixExpression: + active: false + WrongEqualsTypeParameter: + active: true + +style: + active: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: 'to' + DataClassShouldBeImmutable: + active: false + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaParameter: + active: false + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenComment: + active: true + values: ['TODO:', 'FIXME:', 'STOPSHIP:'] + allowedPatterns: '' + ForbiddenImport: + active: false + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: ['kotlin.io.println', 'kotlin.io.print'] + ForbiddenPublicDataClass: + active: false + ignorePackages: ['*.internal', '*.internal.*'] + ForbiddenVoid: + active: false + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + excludedFunctions: 'describeContents' + excludeAnnotatedFunction: ['dagger.Provides'] + LibraryCodeMustSpecifyReturnType: + active: true + LibraryEntitiesShouldNotBePublic: + active: false + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreNumbers: ['-1', '0', '1', '2', '0xff'] + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + MandatoryBracesIfStatements: + active: false + MandatoryBracesLoops: + active: false + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + MayBeConst: + active: true + ModifierOrder: + active: true + NestedClassesVisibility: + active: false + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + OptionalWhenBraces: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 2 + excludedFunctions: 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: false + SpacingBetweenPackageAndImports: + active: false + ThrowsCount: + active: true + max: 2 + TrailingWhitespace: + active: false + UnderscoresInNumericLiterals: + active: false + acceptableDecimalLength: 5 + UnnecessaryAbstractClass: + active: true + excludeAnnotatedClasses: ['dagger.Module'] + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: false + UnnecessaryInheritance: + active: true + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: false + allowedNames: '(_|ignored|expected|serialVersionUID)' + UseArrayLiteralsInAnnotations: + active: false + UseCheckNotNull: + active: false + UseCheckOrError: + active: false + UseDataClass: + active: false + excludeAnnotatedClasses: [] + allowVars: false + UseEmptyCounterpart: + active: false + UseIfInsteadOfWhen: + active: false + UseRequire: + active: false + UseRequireNotNull: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: false + WildcardImport: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + excludeImports: ['java.util.*', 'kotlinx.android.synthetic.*'] diff --git a/src/main/kotlin/com/klaxit/hiddensecrets/CodeGenerator.kt b/src/main/kotlin/com/klaxit/hiddensecrets/CodeGenerator.kt index 8e67c13..bed5a0c 100644 --- a/src/main/kotlin/com/klaxit/hiddensecrets/CodeGenerator.kt +++ b/src/main/kotlin/com/klaxit/hiddensecrets/CodeGenerator.kt @@ -3,30 +3,29 @@ package com.klaxit.hiddensecrets /** * Helper to generate C++ and Kotlin code */ -class CodeGenerator { - companion object { - /** - * Return the C++ code to add another key to your project - */ - fun getCppCode(packageName: String, keyName: String, obfuscatedKey: String): String { +object CodeGenerator { - return "\nextern \"C\"\n" + - "JNIEXPORT jstring JNICALL\n" + - "Java_" + Utils.getUnderScoredPackageName(packageName) + "_Secrets_get$keyName(\n" + - " JNIEnv* pEnv,\n" + - " jobject pThis,\n" + - " jstring packageName) {\n" + - " char obfuscatedSecret[] = $obfuscatedKey;\n" + - " return getOriginalKey(obfuscatedSecret, sizeof(obfuscatedSecret), packageName, pEnv);\n" + - "}\n" - } + /** + * Return the C++ code to add another key to your project + */ + fun getCppCode(packageName: String, keyName: String, obfuscatedKey: String): String { - /** - * Kotlin code that will be added in your project - */ - fun getKotlinCode(keyName: String): String { - return "\n external fun get$keyName(packageName: String): String\n" + - "}" - } + return "\nextern \"C\"\n" + + "JNIEXPORT jstring JNICALL\n" + + "Java_" + Utils.getUnderScoredPackageName(packageName) + "_Secrets_get$keyName(\n" + + " JNIEnv* pEnv,\n" + + " jobject pThis,\n" + + " jstring packageName) {\n" + + " char obfuscatedSecret[] = $obfuscatedKey;\n" + + " return getOriginalKey(obfuscatedSecret, sizeof(obfuscatedSecret), packageName, pEnv);\n" + + "}\n" } -} \ No newline at end of file + + /** + * Kotlin code that will be added in your project + */ + fun getKotlinCode(keyName: String): String { + return "\n external fun get$keyName(packageName: String): String\n" + + "}" + } +} diff --git a/src/main/kotlin/com/klaxit/hiddensecrets/HiddenSecretsPlugin.kt b/src/main/kotlin/com/klaxit/hiddensecrets/HiddenSecretsPlugin.kt index 88ee62c..2422d9e 100644 --- a/src/main/kotlin/com/klaxit/hiddensecrets/HiddenSecretsPlugin.kt +++ b/src/main/kotlin/com/klaxit/hiddensecrets/HiddenSecretsPlugin.kt @@ -18,6 +18,7 @@ import java.nio.charset.Charset open class HiddenSecretsPlugin : Plugin { companion object { private const val APP_MAIN_FOLDER = "src/main/" + private const val DEFAULT_KEY_NAME_LENGTH = 8 //Tasks private const val TASK_UNZIP_HIDDEN_SECRETS = "unzipHiddenSecrets" @@ -27,6 +28,11 @@ open class HiddenSecretsPlugin : Plugin { private const val TASK_HIDE_SECRET = "hideSecret" private const val TASK_PACKAGE_NAME = "packageName" + //Properties + private const val KEY = "key" + private const val KEY_NAME = "keyName" + private const val PACKAGE = "package" + //Errors private const val ERROR_EMPTY_KEY = "No key provided, use argument '-Pkey=yourKey'" private const val ERROR_EMPTY_PACKAGE = "Empty package name, use argument '-Ppackage=your.package.name'" @@ -54,9 +60,9 @@ open class HiddenSecretsPlugin : Plugin { @Input fun getKeyParam(): String { val key: String - if (project.hasProperty("key")) { + if (project.hasProperty(KEY)) { //From command line - key = project.property("key") as String + key = project.property(KEY) as String } else { throw InvalidUserDataException(ERROR_EMPTY_KEY) } @@ -70,9 +76,9 @@ open class HiddenSecretsPlugin : Plugin { fun getPackageNameParam(): String { //From config var packageName: String? = null - if (project.hasProperty("package")) { + if (project.hasProperty(PACKAGE)) { //From command line - packageName = project.property("package") as String? + packageName = project.property(PACKAGE) as String? } if (packageName.isNullOrEmpty()) { //From Android app @@ -90,10 +96,11 @@ open class HiddenSecretsPlugin : Plugin { @Input fun getKeyNameParam(): String { val chars = ('a'..'Z') + ('A'..'Z') - var keyName = List(8) { chars.random() }.joinToString("") - if (project.hasProperty("keyName")) { + // Default random key name + var keyName = List(DEFAULT_KEY_NAME_LENGTH) { chars.random() }.joinToString("") + if (project.hasProperty(KEY_NAME)) { //From command line - keyName = project.property("keyName") as String + keyName = project.property(KEY_NAME) as String } else { println("Key name has been randomized, chose your own key name by adding argument '-PkeyName=yourName'") } @@ -279,4 +286,4 @@ open class HiddenSecretsPlugin : Plugin { } } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/klaxit/hiddensecrets/Utils.kt b/src/main/kotlin/com/klaxit/hiddensecrets/Utils.kt index 82b30fb..70ac150 100644 --- a/src/main/kotlin/com/klaxit/hiddensecrets/Utils.kt +++ b/src/main/kotlin/com/klaxit/hiddensecrets/Utils.kt @@ -5,64 +5,62 @@ import java.nio.charset.Charset import java.security.MessageDigest import kotlin.experimental.xor -class Utils { - companion object { +object Utils { - /** - * Transform package name com.klaxit.hidden to com_klaxit_hidden to ingrate in C++ code - */ - fun getUnderScoredPackageName(packageName: String): String { - val packageComponents = packageName.split(".") - var packageStr = "" - val iterator: Iterator = packageComponents.iterator() - while (iterator.hasNext()) { - packageStr += iterator.next() - if (iterator.hasNext()) { - packageStr += "_" - } + /** + * Transform package name com.klaxit.hidden to com_klaxit_hidden to ingrate in C++ code + */ + fun getUnderScoredPackageName(packageName: String): String { + val packageComponents = packageName.split(".") + var packageStr = "" + val iterator: Iterator = packageComponents.iterator() + while (iterator.hasNext()) { + packageStr += iterator.next() + if (iterator.hasNext()) { + packageStr += "_" } - return packageStr } + return packageStr + } - /** - * Encode string to sha256 - */ - @VisibleForTesting - fun sha256(toHash: String): String { - val bytes = toHash.toByteArray() - val md = MessageDigest.getInstance("SHA-256") - val digest = md.digest(bytes) - return digest.fold("", { str, it -> str + "%02x".format(it) }) - } + /** + * Encode string to sha256 + */ + @VisibleForTesting + fun sha256(toHash: String): String { + val bytes = toHash.toByteArray() + val md = MessageDigest.getInstance("SHA-256") + val digest = md.digest(bytes) + return digest.fold("", { str, it -> str + "%02x".format(it) }) + } - /** - * Encode a string key to and hex array using package name - */ - fun encodeSecret(key: String, packageName: String): String { - //Generate the obfuscator as the SHA256 of the app package name - val obfuscator = sha256(packageName) - val obfuscatorBytes = obfuscator.toByteArray() + /** + * Encode a string key to and hex array using package name + */ + fun encodeSecret(key: String, packageName: String): String { + //Generate the obfuscator as the SHA256 of the app package name + val obfuscator = sha256(packageName) + val obfuscatorBytes = obfuscator.toByteArray() - //Generate the obfuscated secret bytes array by applying a XOR between the secret and the obfuscator - val obfuscatedSecretBytes = arrayListOf() - var i = 0 - key.toByteArray(Charset.defaultCharset()).forEach { secretByte -> - val obfuscatorByte = obfuscatorBytes[i % obfuscatorBytes.size] - val obfuscatedByte = secretByte.xor(obfuscatorByte) - obfuscatedSecretBytes.add(obfuscatedByte) - i++ - } - var encoded = "{ " - val iterator: Iterator = obfuscatedSecretBytes.iterator() - while (iterator.hasNext()) { - val item = iterator.next() - encoded += "0x" + Integer.toHexString(item.toInt() and 0xff) - if (iterator.hasNext()) { - encoded += ", " - } + //Generate the obfuscated secret bytes array by applying a XOR between the secret and the obfuscator + val obfuscatedSecretBytes = arrayListOf() + var i = 0 + key.toByteArray(Charset.defaultCharset()).forEach { secretByte -> + val obfuscatorByte = obfuscatorBytes[i % obfuscatorBytes.size] + val obfuscatedByte = secretByte.xor(obfuscatorByte) + obfuscatedSecretBytes.add(obfuscatedByte) + i++ + } + var encoded = "{ " + val iterator: Iterator = obfuscatedSecretBytes.iterator() + while (iterator.hasNext()) { + val item = iterator.next() + encoded += "0x" + Integer.toHexString(item.toInt() and 0xff) + if (iterator.hasNext()) { + encoded += ", " } - encoded += " }" - return encoded } + encoded += " }" + return encoded } -} \ No newline at end of file +}