Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
234c92e0e8 | ||
|
|
7e89f0c5d3 |
No files matched your search
@@ -96,4 +96,3 @@ local.properties
|
|||||||
.scala_dependencies
|
.scala_dependencies
|
||||||
.worksheet
|
.worksheet
|
||||||
|
|
||||||
.idea
|
|
||||||
Vendored
+169
-84
@@ -1,101 +1,186 @@
|
|||||||
pipeline {
|
#!groovy
|
||||||
agent any
|
|
||||||
|
|
||||||
options {
|
// Required Jenkins plugins:
|
||||||
buildDiscarder(logRotator(numToKeepStr: '5'))
|
// https://wiki.jenkins-ci.org/display/JENKINS/Timestamper
|
||||||
disableConcurrentBuilds()
|
// https://wiki.jenkins-ci.org/display/JENKINS/Static+Code+Analysis+Plug-ins
|
||||||
skipDefaultCheckout(true)
|
// https://wiki.jenkins-ci.org/display/JENKINS/Checkstyle+Plugin ?
|
||||||
timestamps()
|
// https://wiki.jenkins-ci.org/display/JENKINS/FindBugs+Plugin
|
||||||
|
// https://wiki.jenkins-ci.org/display/JENKINS/PMD+Plugin ?
|
||||||
|
// https://wiki.jenkins-ci.org/display/JENKINS/DRY+Plugin ?
|
||||||
|
// https://wiki.jenkins-ci.org/display/JENKINS/Task+Scanner+Plugin
|
||||||
|
// https://wiki.jenkins-ci.org/display/JENKINS/Javadoc+Plugin
|
||||||
|
// https://wiki.jenkins-ci.org/display/JENKINS/JaCoCo+Plugin ?
|
||||||
|
|
||||||
|
|
||||||
|
init()
|
||||||
|
|
||||||
|
def branch_name
|
||||||
|
def branch_name_base
|
||||||
|
def build_number
|
||||||
|
def build_url
|
||||||
|
def git_commit
|
||||||
|
def job_name
|
||||||
|
def tag
|
||||||
|
def version
|
||||||
|
def build_type
|
||||||
|
def display_name
|
||||||
|
|
||||||
|
def init() {
|
||||||
|
|
||||||
|
// Keep the 5 most recent builds
|
||||||
|
properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '5']]])
|
||||||
|
|
||||||
|
build_number = env.BUILD_NUMBER
|
||||||
|
build_url = env.BUILD_URL
|
||||||
|
job_name = "${env.JOB_NAME}"
|
||||||
|
branch_name = env.BRANCH_NAME
|
||||||
|
branch_name_docker = branch_name.replaceAll(/\//,'.')
|
||||||
|
persist = "/var/lib/jenkins/PERSIST/${branch_name_docker}"
|
||||||
|
|
||||||
|
// execute the branch type specific pipeline code
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (branch_name.indexOf('release/')==0) build_type='release'
|
||||||
|
if (branch_name.indexOf('feature/')==0) build_type='feature'
|
||||||
|
if (branch_name.indexOf('develop')==0) build_type='develop'
|
||||||
|
if (branch_name.indexOf('hotfix')==0) build_type='hotfix'
|
||||||
|
if (branch_name.indexOf('bugfix')==0) build_type='bugfix'
|
||||||
|
if (branch_name.indexOf('master')==0) build_type='master'
|
||||||
|
|
||||||
|
// common pipeline elements
|
||||||
|
node('master') {
|
||||||
|
Initialize()
|
||||||
|
SetVersion(build_type)
|
||||||
|
print_vars() // after SetVersion - all variables now defined
|
||||||
|
set_result('INPROGRESS')
|
||||||
|
Build() // builds database via flyway migration
|
||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
if (branch_name.indexOf('develop')==0) {
|
||||||
stage('Checkout') {
|
node('master') {
|
||||||
steps {
|
Deploy();
|
||||||
|
}
|
||||||
|
} else if (branch_name.indexOf('release/')==0) {
|
||||||
|
node('master') {
|
||||||
|
Deploy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node('master') {
|
||||||
|
set_result('SUCCESS')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
node() {
|
||||||
|
set_result('FAILURE')
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def Build() {
|
||||||
|
stage ('build') {
|
||||||
|
mvn "install -DskipTests=true -Dbuild.revision=${git_commit}"
|
||||||
|
step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def Initialize() {
|
||||||
|
stage ('initialize') {
|
||||||
|
|
||||||
|
// get new code
|
||||||
checkout scm
|
checkout scm
|
||||||
|
|
||||||
|
git_commit = getSha1()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Set version') {
|
def Deploy() {
|
||||||
steps {
|
stage ('deploy') {
|
||||||
script {
|
mvn "deploy -DskipTests=true -Dbuild.number=${build_number} -Dbuild.revision=${git_commit}"
|
||||||
String branch = env.BRANCH_NAME
|
}
|
||||||
if (!branch) {
|
|
||||||
error('BRANCH_NAME is unavailable; this pipeline must run as a multibranch job')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
env.DEPLOY_BUILD = 'false'
|
def getSha1() {
|
||||||
|
sha1 = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
|
||||||
|
echo "sha1 is ${sha1}"
|
||||||
|
return sha1
|
||||||
|
}
|
||||||
|
|
||||||
// A release branch supplies major.minor; Jenkins supplies the patch
|
def mvn(args) {
|
||||||
// number so release/2.0 build 1 becomes 2.0.1-RELEASE.
|
withMaven(
|
||||||
def releaseMatch = branch =~ /^release\/(\d+\.\d+)$/
|
maven: 'maven-3.6.1',
|
||||||
if (releaseMatch.matches()) {
|
globalMavenSettingsConfig: 'locusworks-settings'
|
||||||
env.BUILD_VERSION = "${releaseMatch[0][1]}.${env.BUILD_NUMBER}-RELEASE"
|
) {
|
||||||
env.DEPLOY_BUILD = 'true'
|
|
||||||
} else if (branch == 'develop') {
|
sh "mvn ${args}"
|
||||||
env.BUILD_VERSION = "develop.${env.BUILD_NUMBER}-SNAPSHOT"
|
|
||||||
env.DEPLOY_BUILD = 'true'
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def mvn_initial(args) {
|
||||||
|
mvn(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_result(status) {
|
||||||
|
if ( status == 'SUCCESS' ) {
|
||||||
|
currentBuild.result = status
|
||||||
|
notify_bitbucket('SUCCESSFUL')
|
||||||
|
} else if ( status == 'FAILURE' ) {
|
||||||
|
currentBuild.result = status
|
||||||
|
notify_bitbucket('FAILED')
|
||||||
|
} else if ( status == 'INPROGRESS' ) {
|
||||||
|
notify_bitbucket('INPROGRESS')
|
||||||
} else {
|
} else {
|
||||||
String safeBranch = branch.replaceAll(/[^A-Za-z0-9._-]/, '-')
|
error ("unknown status")
|
||||||
env.BUILD_VERSION = "0.0.${env.BUILD_NUMBER}-${safeBranch}-SNAPSHOT"
|
|
||||||
}
|
|
||||||
currentBuild.displayName = env.BUILD_VERSION
|
|
||||||
}
|
}
|
||||||
|
|
||||||
withMaven(maven: 'maven-3.9.16', globalMavenSettingsConfig: 'locusworks-settings') {
|
// save in persistence file for access the status page
|
||||||
sh 'mvn -B org.codehaus.mojo:versions-maven-plugin:2.21.0:set -DgenerateBackupPoms=false -DnewVersion="$BUILD_VERSION"'
|
// make sure the directory exists first
|
||||||
|
sh "mkdir -p $persist && echo $status > $persist/build.result"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def notify_bitbucket(state) {
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_vars() {
|
||||||
|
echo "build_number = ${build_number}"
|
||||||
|
echo "build_url = ${build_url}"
|
||||||
|
echo "job_name = ${job_name}"
|
||||||
|
echo "branch_name = ${branch_name}"
|
||||||
|
echo "branch_name_base = ${branch_name_base}"
|
||||||
|
echo "build_type = ${build_type}"
|
||||||
|
echo "display_name = ${currentBuild.displayName}"
|
||||||
|
echo "version = ${version}"
|
||||||
|
echo "git_commit = ${git_commit}"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def SetVersion( v ) {
|
||||||
|
stage ('set version') {
|
||||||
|
echo "set version ${v}"
|
||||||
|
branch_name_base = (branch_name =~ /([^\/]+$)/)[0][0]
|
||||||
|
if ( v == 'release' ) {
|
||||||
|
// for release branches, where the branch is named "release/1.2.3",
|
||||||
|
// derive the version and display name derive from the numeric suffix and append the build number
|
||||||
|
// 3.2.1.100
|
||||||
|
version = branch_name_base + "." + build_number + "-RELEASE";
|
||||||
|
//version = branch_name.substring('release/'.length()) + "." + build_number
|
||||||
|
currentBuild.displayName = version
|
||||||
|
} else if (v == 'develop') {
|
||||||
|
version = branch_name_base + "." + build_number + "-SNAPSHOT";
|
||||||
|
currentBuild.displayName = version
|
||||||
|
} else {
|
||||||
|
// for all other branches the version number is 0 with an appended build number
|
||||||
|
// and for the display name use the jenkins default #n and add the branch name
|
||||||
|
// #101 - feature/user/foo
|
||||||
|
//version = '0.' + build_number
|
||||||
|
version = branch_name_base + "." + build_number
|
||||||
|
currentBuild.displayName = "#" + build_number + " - " + branch_name_base
|
||||||
|
}
|
||||||
|
display_name = currentBuild.displayName
|
||||||
|
mvn_initial "versions:set -DnewVersion=${version}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Prepare OWASP cache') {
|
return this
|
||||||
steps {
|
|
||||||
lock(resource: 'owasp-nvd-cache') {
|
|
||||||
withMaven(maven: 'maven-3.9.16', globalMavenSettingsConfig: 'locusworks-settings') {
|
|
||||||
sh 'mkdir -p "$HOME/.cache/dependency-check"'
|
|
||||||
sh 'mvn -B org.owasp:dependency-check-maven:13.0.0:update-only -DdataDirectory="$HOME/.cache/dependency-check"'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Build and test') {
|
|
||||||
steps {
|
|
||||||
withMaven(maven: 'maven-3.9.16', globalMavenSettingsConfig: 'locusworks-settings') {
|
|
||||||
sh 'mvn -B clean verify -DdataDirectory="$HOME/.cache/dependency-check" -DautoUpdate=false -Dbuild.revision="$GIT_COMMIT"'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
junit allowEmptyResults: true, testResults: '**/target/surefire-reports/*.xml'
|
|
||||||
}
|
|
||||||
success {
|
|
||||||
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
|
|
||||||
archiveArtifacts artifacts: '**/target/site/jacoco/**', fingerprint: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Deploy') {
|
|
||||||
when {
|
|
||||||
expression { env.DEPLOY_BUILD == 'true' }
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
withMaven(maven: 'maven-3.9.16', globalMavenSettingsConfig: 'locusworks-settings') {
|
|
||||||
sh 'mvn -B deploy -DskipTests -Ddependency-check.skip=true -Dbuild.number="$BUILD_NUMBER" -Dbuild.revision="$GIT_COMMIT"'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
success {
|
|
||||||
echo "Published build ${env.BUILD_VERSION} successfully"
|
|
||||||
}
|
|
||||||
failure {
|
|
||||||
echo "Build ${env.BUILD_VERSION ?: env.BUILD_NUMBER} failed"
|
|
||||||
}
|
|
||||||
cleanup {
|
|
||||||
deleteDir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,70 +19,26 @@
|
|||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<license-maven-plugin.version>4.1</license-maven-plugin.version>
|
||||||
<nexus.repo>https://nexus.locusworks.net</nexus.repo>
|
<nexus.repo>https://nexus.locusworks.net</nexus.repo>
|
||||||
<license-maven-plugin.version>5.1.2</license-maven-plugin.version>
|
|
||||||
<junit.version>6.1.3</junit.version>
|
|
||||||
<mockito.version>5.23.0</mockito.version>
|
|
||||||
<jacoco.version>0.8.15</jacoco.version>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
|
||||||
<groupId>org.jacoco</groupId>
|
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
|
||||||
<version>${jacoco.version}</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<goals>
|
|
||||||
<goal>prepare-agent</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>report</id>
|
|
||||||
<phase>verify</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>report</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>coverage-check</id>
|
|
||||||
<phase>verify</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>check</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<rules>
|
|
||||||
<rule>
|
|
||||||
<element>BUNDLE</element>
|
|
||||||
<limits>
|
|
||||||
<limit>
|
|
||||||
<counter>LINE</counter>
|
|
||||||
<value>COVEREDRATIO</value>
|
|
||||||
<minimum>0.75</minimum>
|
|
||||||
</limit>
|
|
||||||
</limits>
|
|
||||||
</rule>
|
|
||||||
</rules>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.5.6</version>
|
<version>3.0.0-M5</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<argLine>@{argLine} -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar</argLine>
|
<forkMode>always</forkMode>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.15.0</version>
|
<version>3.8.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>${maven.compiler.source}</source>
|
<source>${maven.compiler.source}</source>
|
||||||
<target>${maven.compiler.target}</target>
|
<target>${maven.compiler.target}</target>
|
||||||
@@ -95,14 +51,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.owasp</groupId>
|
<groupId>org.owasp</groupId>
|
||||||
<artifactId>dependency-check-maven</artifactId>
|
<artifactId>dependency-check-maven</artifactId>
|
||||||
<version>13.0.0</version>
|
<version>6.3.1</version>
|
||||||
<configuration>
|
|
||||||
<skipProvidedScope>true</skipProvidedScope>
|
|
||||||
<skipTestScope>true</skipTestScope>
|
|
||||||
<failOnError>true</failOnError>
|
|
||||||
<versionCheckEnabled>true</versionCheckEnabled>
|
|
||||||
<nvdApiKey>e2eb1036-9b5b-4df9-95e7-0888be947011</nvdApiKey>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<goals>
|
<goals>
|
||||||
@@ -144,7 +93,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
<version>${junit.version}</version>
|
<version>5.8.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -153,15 +102,14 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>commons-io</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
<version>2.22.0</version>
|
<version>2.11.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
|
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-codec</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>commons-codec</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
<version>1.22.1</version>
|
<version>1.15</version>
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
@@ -169,14 +117,14 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
<version>33.7.1-jre</version>
|
<version>30.1.1-jre</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>3.20.0</version>
|
<version>3.12.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -32,10 +32,14 @@ import java.io.InputStream;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.spec.EncodedKeySpec;
|
import java.security.spec.EncodedKeySpec;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import static com.google.common.collect.Iterators.get;
|
||||||
|
import static com.google.common.collect.Iterators.size;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
public class AESKeySpec extends EncodedKeySpec {
|
public class AESKeySpec extends EncodedKeySpec {
|
||||||
@@ -50,10 +54,10 @@ public class AESKeySpec extends EncodedKeySpec {
|
|||||||
try {
|
try {
|
||||||
byte[] data = this.getEncoded();
|
byte[] data = this.getEncoded();
|
||||||
InputStream stream = new ByteArrayInputStream(data);
|
InputStream stream = new ByteArrayInputStream(data);
|
||||||
String[] parts = IOUtils.toString(stream, StandardCharsets.UTF_8).split(" ");
|
Iterator<String> parts = Arrays.asList(IOUtils.toString(stream, StandardCharsets.UTF_8).split(" ")).iterator();
|
||||||
|
|
||||||
checkArgument(parts.length == 2 && AES_MARKER.equals(parts[0]), "Bad format, should be: aes-seed AAB3...");
|
checkArgument(size(parts) == 2 && AES_MARKER.equals(get(parts, 0)), "Bad format, should be: aes-seed AAB3...");
|
||||||
stream = new ByteArrayInputStream(Base64.getDecoder().decode(parts[1]));
|
stream = new ByteArrayInputStream(Base64.getDecoder().decode(String.valueOf(get(parts, 1))));
|
||||||
String marker = IOUtils.toString(stream, StandardCharsets.UTF_8);
|
String marker = IOUtils.toString(stream, StandardCharsets.UTF_8);
|
||||||
return new AESKey(marker);
|
return new AESKey(marker);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ import java.security.PublicKey;
|
|||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.KeySpec;
|
import java.security.spec.KeySpec;
|
||||||
|
|
||||||
|
import sun.security.jca.GetInstance;
|
||||||
|
import sun.security.jca.GetInstance.Instance;
|
||||||
|
|
||||||
|
@SuppressWarnings("restriction")
|
||||||
public class EncryptionKeyFactory extends KeyFactory {
|
public class EncryptionKeyFactory extends KeyFactory {
|
||||||
|
|
||||||
protected EncryptionKeyFactory(KeyFactorySpi keyFacSpi, Provider provider, String algorithm) {
|
protected EncryptionKeyFactory(KeyFactorySpi keyFacSpi, Provider provider, String algorithm) {
|
||||||
@@ -55,21 +59,8 @@ public class EncryptionKeyFactory extends KeyFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static EncryptionKeyFactory getInstance(String algorithm) throws NoSuchAlgorithmException {
|
public static EncryptionKeyFactory getInstance(String algorithm) throws NoSuchAlgorithmException {
|
||||||
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
|
Instance instance = GetInstance.getInstance("KeyFactory", KeyFactorySpi.class, algorithm);
|
||||||
Provider provider = keyFactory.getProvider();
|
return new EncryptionKeyFactory((KeyFactorySpi)instance.impl, instance.provider, algorithm);
|
||||||
Provider.Service service = provider.getService("KeyFactory", algorithm);
|
|
||||||
if (service == null) {
|
|
||||||
throw new NoSuchAlgorithmException(
|
|
||||||
String.format("KeyFactory %s is not available from provider %s", algorithm, provider.getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Object implementation = service.newInstance(null);
|
|
||||||
if (!(implementation instanceof KeyFactorySpi)) {
|
|
||||||
throw new NoSuchAlgorithmException(
|
|
||||||
String.format("Provider %s returned an invalid KeyFactory implementation for %s",
|
|
||||||
provider.getName(), algorithm));
|
|
||||||
}
|
|
||||||
return new EncryptionKeyFactory((KeyFactorySpi)implementation, provider, algorithm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
package net.locusworks.crypto;
|
package net.locusworks.crypto;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
@@ -95,16 +96,15 @@ public class KeyFile implements AutoCloseable {
|
|||||||
throw new IllegalArgumentException(String.format("Unable to find file with name %s. Please check path", fileName));
|
throw new IllegalArgumentException(String.format("Unable to find file with name %s. Please check path", fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
String contentStr = Files.readString(keyFile.toPath(), StandardCharsets.UTF_8);
|
String contentStr = IOUtils.toString(new FileInputStream(keyFile), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
boolean rsaFormat = !contentStr.startsWith("ssh-rsa") && !contentStr.startsWith("aes-seed");
|
boolean rsaFormat = !contentStr.startsWith("ssh-rsa") && !contentStr.startsWith("aes-seed");
|
||||||
if (rsaFormat) {
|
if (rsaFormat) {
|
||||||
contentStr = contentStr.replaceAll("-----BEGIN RSA .*?-----|-----END RSA .*?-----", "");
|
contentStr = contentStr.replace("-----.*", "");
|
||||||
contentStr = contentStr.replaceAll("\\s", "");
|
|
||||||
} else {
|
|
||||||
contentStr = contentStr.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentStr = contentStr.replace("\\r?\\n", "");
|
||||||
|
|
||||||
byte[] content = rsaFormat ? Base64.getDecoder().decode(contentStr): contentStr.getBytes(StandardCharsets.UTF_8);
|
byte[] content = rsaFormat ? Base64.getDecoder().decode(contentStr): contentStr.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
EncryptionKeyFactory kf = EncryptionKeyFactory.getInstance("RSA");
|
EncryptionKeyFactory kf = EncryptionKeyFactory.getInstance("RSA");
|
||||||
@@ -144,7 +144,7 @@ public class KeyFile implements AutoCloseable {
|
|||||||
dos.writeInt(item.length);
|
dos.writeInt(item.length);
|
||||||
dos.write(item);
|
dos.write(item);
|
||||||
}
|
}
|
||||||
data = String.format("ssh-rsa %s %s", dos.base64Encoded(), getDescription());
|
data = String.format("ssh-rsa", dos.base64Encoded(), this.description);
|
||||||
IOUtils.write(data, Files.newOutputStream(Paths.get(fileName)), StandardCharsets.UTF_8);
|
IOUtils.write(data, Files.newOutputStream(Paths.get(fileName)), StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import net.locusworks.crypto.KeyFile.EncryptionType;
|
|||||||
public class RSA {
|
public class RSA {
|
||||||
|
|
||||||
private static final String ENCRYPTION_TYPE = "RSA";
|
private static final String ENCRYPTION_TYPE = "RSA";
|
||||||
private static final String ENCRYPTION_ALGORITHM = "RSA/ECB/PKCS1Padding";
|
private static final String ENCRYPTION_ALGORITHM = "RSA/ECB/PKCS10PADDING";
|
||||||
private static final String PROVIDER = "SunJCE";
|
private static final String PROVIDER = "SunJCE";
|
||||||
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
|
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,14 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.security.spec.EncodedKeySpec;
|
import java.security.spec.EncodedKeySpec;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.RSAPublicKeySpec;
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import static com.google.common.collect.Iterators.get;
|
||||||
|
import static com.google.common.collect.Iterators.size;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
|
||||||
@@ -54,10 +58,10 @@ public class SSHEncodedKeySpec extends EncodedKeySpec {
|
|||||||
try {
|
try {
|
||||||
byte[] data = this.getEncoded();
|
byte[] data = this.getEncoded();
|
||||||
InputStream stream = new ByteArrayInputStream(data);
|
InputStream stream = new ByteArrayInputStream(data);
|
||||||
String[] parts = IOUtils.toString(stream, StandardCharsets.UTF_8).split(" ");
|
Iterator<String> parts = Arrays.asList(IOUtils.toString(stream, StandardCharsets.UTF_8).split(" ")).iterator();
|
||||||
|
|
||||||
checkArgument(parts.length >= 2 && SSH_MARKER.equals(parts[0]), "Bad format, should be: ssh-rsa AAB3...");
|
checkArgument(size(parts) >= 2 && SSH_MARKER.equals(get(parts, 0)), "Bad format, should be: ssh-rsa AAB3...");
|
||||||
stream = new ByteArrayInputStream(Base64.getDecoder().decode(parts[1]));
|
stream = new ByteArrayInputStream(Base64.getDecoder().decode(String.valueOf(get(parts, 1))));
|
||||||
String marker = new String(readLengthFirst(stream));
|
String marker = new String(readLengthFirst(stream));
|
||||||
checkArgument(SSH_MARKER.equals(marker), "Looking for marker %s but received %s", SSH_MARKER, marker);
|
checkArgument(SSH_MARKER.equals(marker), "Looking for marker %s but received %s", SSH_MARKER, marker);
|
||||||
BigInteger publicExponent = new BigInteger(readLengthFirst(stream));
|
BigInteger publicExponent = new BigInteger(readLengthFirst(stream));
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
* Project: Crypto, File: ConfigurationManagerTest.java
|
|
||||||
* Copyright 2019 Locusworks LLC. All rights reserved.
|
|
||||||
*/
|
|
||||||
package net.locusworks.crypto.tests;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import net.locusworks.crypto.configuration.ConfigurationManager;
|
|
||||||
import net.locusworks.crypto.configuration.PersistableRequest;
|
|
||||||
|
|
||||||
class ConfigurationManagerTest {
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void initializationCreatesConfigurationFromDefaultsAndReportsProgress() throws Exception {
|
|
||||||
TestManager manager = new TestManager();
|
|
||||||
List<String> messages = new java.util.ArrayList<>();
|
|
||||||
manager.initialize(tempDir, messages);
|
|
||||||
|
|
||||||
assertEquals("localhost", manager.getPropertyValue("dbHost"));
|
|
||||||
assertEquals("fallback", manager.getPropertyValue("missing", "fallback"));
|
|
||||||
assertEquals(null, manager.getPropertyValue("missing"));
|
|
||||||
assertEquals(4, manager.getConfiguration().size());
|
|
||||||
assertTrue(Files.exists(tempDir.resolve("test.properties")));
|
|
||||||
assertTrue(messages.stream().anyMatch(message -> message.startsWith("Loading config file:")));
|
|
||||||
assertTrue(messages.stream().anyMatch(message -> message.contains("Added new configuration items")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void saveAndReloadReconcilesAddedAndRemovedConfiguration() throws Exception {
|
|
||||||
TestManager manager = new TestManager();
|
|
||||||
List<String> messages = new java.util.ArrayList<>();
|
|
||||||
manager.initialize(tempDir, messages);
|
|
||||||
|
|
||||||
Properties changed = new Properties();
|
|
||||||
changed.setProperty("dbHost", "example.test");
|
|
||||||
changed.setProperty("obsolete", "remove me");
|
|
||||||
manager.saveToConf(changed);
|
|
||||||
|
|
||||||
assertEquals("example.test", manager.getPropertyValue("dbHost"));
|
|
||||||
assertFalse(manager.getConfiguration().containsKey("obsolete"));
|
|
||||||
assertEquals(4, manager.getConfiguration().size());
|
|
||||||
assertTrue(messages.stream().anyMatch(message -> message.startsWith("Saved config file:")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void persistableRequestUpdatesPlainAndEncryptedValues() throws Exception {
|
|
||||||
TestManager manager = new TestManager();
|
|
||||||
manager.initialize(tempDir, new java.util.ArrayList<>());
|
|
||||||
|
|
||||||
Request request = new Request("database.test", "secret", "ignored");
|
|
||||||
manager.saveConfiguration(request, Set.of("dbHost", "logLevel"), Set.of("logLevel"));
|
|
||||||
|
|
||||||
assertEquals("database.test", manager.getPropertyValue("dbHost"));
|
|
||||||
assertFalse("secret".equals(manager.getPropertyValue("logLevel")));
|
|
||||||
assertEquals("secret", manager.decrypt(manager.getPropertyValue("logLevel")));
|
|
||||||
assertThrows(IOException.class,
|
|
||||||
() -> manager.saveConfiguration(request, new HashSet<>(), null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class TestManager extends ConfigurationManager {
|
|
||||||
void initialize(Path baseDir, List<String> messages) throws IOException {
|
|
||||||
init(baseDir.toString(), "test.properties", "fixed test seed".getBytes(), messages::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
String decrypt(String value) {
|
|
||||||
return aes.decrypt(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Request implements PersistableRequest {
|
|
||||||
private final String dbHost;
|
|
||||||
private final String logLevel;
|
|
||||||
private final String ignored;
|
|
||||||
|
|
||||||
Request(String dbHost, String logLevel, String ignored) {
|
|
||||||
this.dbHost = dbHost;
|
|
||||||
this.logLevel = logLevel;
|
|
||||||
this.ignored = ignored;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
* Project: Crypto, File: KeyAndRsaTest.java
|
|
||||||
* Copyright 2019 Locusworks LLC. All rights reserved.
|
|
||||||
*/
|
|
||||||
package net.locusworks.crypto.tests;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import net.locusworks.crypto.AESKey;
|
|
||||||
import net.locusworks.crypto.AESKeySpec;
|
|
||||||
import net.locusworks.crypto.EncryptionKeyFactory;
|
|
||||||
import net.locusworks.crypto.KeyFile;
|
|
||||||
import net.locusworks.crypto.RSA;
|
|
||||||
import net.locusworks.crypto.SSHEncodedKeySpec;
|
|
||||||
import net.locusworks.crypto.utils.DataOutputStreamHelper;
|
|
||||||
|
|
||||||
class KeyAndRsaTest {
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void aesKeyAndSpecRoundTrip() throws Exception {
|
|
||||||
AESKey key = new AESKey("repeatable seed");
|
|
||||||
assertEquals("aes", key.getAlgorithm());
|
|
||||||
assertEquals("aes-seed", key.getFormat());
|
|
||||||
assertArrayEquals("repeatable seed".getBytes(StandardCharsets.UTF_8), key.getEncoded());
|
|
||||||
|
|
||||||
String encoded = "aes-seed " + Base64.getEncoder().encodeToString(key.getEncoded());
|
|
||||||
AESKey decoded = new AESKeySpec(encoded.getBytes(StandardCharsets.UTF_8)).generateKey();
|
|
||||||
assertArrayEquals(key.getEncoded(), decoded.getEncoded());
|
|
||||||
assertEquals("aes", new AESKeySpec(encoded.getBytes(StandardCharsets.UTF_8)).getFormat());
|
|
||||||
assertThrows(InvalidKeySpecException.class,
|
|
||||||
() -> new AESKeySpec("invalid".getBytes(StandardCharsets.UTF_8)).generateKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void encryptionKeyFactoryGeneratesRsaKeysAndRejectsUnknownAlgorithms() throws Exception {
|
|
||||||
KeyPair pair = RSA.generateKeyPair(1024);
|
|
||||||
EncryptionKeyFactory factory = EncryptionKeyFactory.getInstance("RSA");
|
|
||||||
assertEquals(pair.getPrivate(), factory.generatePrivateKey(
|
|
||||||
new java.security.spec.PKCS8EncodedKeySpec(pair.getPrivate().getEncoded())));
|
|
||||||
assertEquals(pair.getPublic(), factory.generatePublicKey(
|
|
||||||
new java.security.spec.X509EncodedKeySpec(pair.getPublic().getEncoded())));
|
|
||||||
assertThrows(NoSuchAlgorithmException.class,
|
|
||||||
() -> EncryptionKeyFactory.getInstance("not-an-algorithm"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void rsaEncryptsDecryptsAndValidatesKeySize() {
|
|
||||||
KeyPair pair = RSA.generateKeyPair(1024);
|
|
||||||
String encrypted = RSA.encrypt(pair.getPublic(), "hello RSA");
|
|
||||||
assertEquals("hello RSA", RSA.decrypt(pair.getPrivate(), encrypted));
|
|
||||||
assertEquals(("hello RSA".getBytes(StandardCharsets.UTF_8).length + 11) * 8,
|
|
||||||
RSA.calculateRequiredKeyLength("hello RSA"));
|
|
||||||
assertThrows(RuntimeException.class,
|
|
||||||
() -> RSA.encrypt(pair.getPublic(), "x".repeat(200)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void pemKeyFilesRoundTripAndExposeDescriptions() {
|
|
||||||
KeyPair pair = RSA.generateKeyPair(1024);
|
|
||||||
Path privateFile = tempDir.resolve("private.pem");
|
|
||||||
Path publicFile = tempDir.resolve("public.pem");
|
|
||||||
|
|
||||||
assertTrue(RSA.writePrivateKey(pair, privateFile.toString(), "PRIVATE KEY"));
|
|
||||||
assertTrue(RSA.writePublicKey(pair, publicFile.toString(), "PUBLIC KEY"));
|
|
||||||
assertEquals(pair.getPrivate(), KeyFile.read(privateFile.toString()).getKey());
|
|
||||||
assertEquals(pair.getPublic(), KeyFile.read(publicFile.toString()).getKey());
|
|
||||||
|
|
||||||
KeyFile privateKey = new KeyFile(pair.getPrivate());
|
|
||||||
KeyFile publicKey = new KeyFile(pair.getPublic(), "server key");
|
|
||||||
assertEquals("PRIVATE KEY", privateKey.getDescription());
|
|
||||||
assertEquals("server key", publicKey.getDescription());
|
|
||||||
publicKey.setDescription("changed");
|
|
||||||
assertEquals("changed", publicKey.getDescription());
|
|
||||||
assertEquals(pair.getPublic(), publicKey.getKey());
|
|
||||||
assertThrows(RuntimeException.class, () -> KeyFile.read(tempDir.resolve("missing").toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void aesAndSshKeyFilesRoundTrip() throws Exception {
|
|
||||||
AESKey aesKey = new AESKey("file seed");
|
|
||||||
Path aesFile = tempDir.resolve("aes.key");
|
|
||||||
try (KeyFile keyFile = new KeyFile(aesKey)) {
|
|
||||||
keyFile.write(aesFile.toString());
|
|
||||||
}
|
|
||||||
assertArrayEquals(aesKey.getEncoded(), KeyFile.read(aesFile.toString()).getKey().getEncoded());
|
|
||||||
|
|
||||||
KeyPair pair = RSA.generateKeyPair(1024);
|
|
||||||
Path sshFile = tempDir.resolve("id_rsa.pub");
|
|
||||||
assertTrue(RSA.writePublicKey(pair, sshFile.toString(), "test key", true));
|
|
||||||
assertEquals(pair.getPublic(), KeyFile.read(sshFile.toString()).getKey());
|
|
||||||
String sshText = Files.readString(sshFile, StandardCharsets.UTF_8);
|
|
||||||
assertTrue(sshText.startsWith("ssh-rsa "));
|
|
||||||
assertTrue(sshText.endsWith(" test key"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void dataOutputHelperProvidesBytesTextAndBase64() throws Exception {
|
|
||||||
try (DataOutputStreamHelper output = new DataOutputStreamHelper()) {
|
|
||||||
output.write("data".getBytes(StandardCharsets.UTF_8));
|
|
||||||
assertArrayEquals("data".getBytes(StandardCharsets.UTF_8), output.toByteArray());
|
|
||||||
assertEquals("data", output.toString());
|
|
||||||
assertEquals(Base64.getEncoder().encodeToString("data".getBytes(StandardCharsets.UTF_8)),
|
|
||||||
output.base64Encoded());
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
DataOutputStreamHelper output = new DataOutputStreamHelper(bytes);
|
|
||||||
output.writeByte(7);
|
|
||||||
assertFalse(output.toByteArray().length == 0);
|
|
||||||
output.close();
|
|
||||||
assertArrayEquals(new byte[0], output.toByteArray());
|
|
||||||
output.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void invalidSshKeyIsRejected() {
|
|
||||||
SSHEncodedKeySpec spec = new SSHEncodedKeySpec("invalid ssh key".getBytes(StandardCharsets.UTF_8));
|
|
||||||
assertEquals(null, spec.getFormat());
|
|
||||||
assertThrows(InvalidKeySpecException.class, spec::convertToRSAPubKeySpec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user