Skip to content

Commit

Permalink
Version 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikolaichuk-TW committed Jan 2, 2020
0 parents commit 07e840d
Show file tree
Hide file tree
Showing 17 changed files with 780 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8

# Identation in Java and Gradle files
[*.{java, gradle}]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
29 changes: 29 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Java CI

on: [push]

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew build
- name: Create GitHub release and upload artifacts
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: digital-signatures-cli/build/libs/digital-signatures-*-all.jar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to GitHub Pages repository
if: startsWith(github.ref, 'refs/tags/')
env:
USERNAME: ${{ secrets.GITHUB_ACTOR }}
PASSWORD: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew publish --stacktrace
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Project exclude paths
/.gradle/
**/build/
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Library for signing data with private key

Provides functionality for creating RSA digital signatures.

## Requirements

* Java ≥ 8

## Generating a RSA private/public key pair

To generate a RSA key pair and store it in PEM format you can use the OpenSSL cryptography and SSL/TLS toolkit:

1. Install OpenSSL following the instructions from [its official website](https://www.openssl.org/).
2. Generate private RSA key (key length ≥ 2048 is required for sufficient cryptographic complexity):
```bash
$ openssl genrsa -out private.pem 2048
```
3. Generate public RSA key from private key:
```bash
$ openssl rsa -pubout -in private.pem -out public.pem
```

## [Library](./digital-signatures)

Contains a single utility class
[DigitalSignatures](./digital-signatures/src/main/java/com/transferwise/digitalsignatures/DigitalSignatures.java)
with straightforward usage:
```java
byte[] signature = DigitalSignatures.sign(Path privateKeyFilePath, byte[] dataToSign);
```
There are also options to provide the private key as `String` or `Reader`.
The resulting signature byte array can be encoded to [Base64](https://en.wikipedia.org/wiki/Base64) in case it is
going to be transferred over HTTP. For such cases there is a convenience method:
```java
String signatureBase64 = DigitalSignatires.encodeToBase64(byte[] bytes);
```

## [CLI tool](./digital-signatures-cli)

To allow users to sign their data via CLI there is an executable JAR:
```bash
usage: java -jar digital-signatures-cli-<version>-all.jar -d <DATA> -k <PATH>
Calculates SHA1 with RSA signature in Base64 encoding for provided data
-d,--data-to-sign <DATA> String containing data to sign
-k,--private-key-file <PATH> Path to file containing RSA private key
```

## Building

Run `./gradlew clean build`.

The CLI tool executable JAR is assembled to an extra `*-all.jar` artifact of `digital-signatures-cli` module.
38 changes: 38 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
allprojects {
group 'com.transferwise'
version '1.0'
}

subprojects {
apply plugin: 'java'
apply plugin: 'maven-publish'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testImplementation 'junit:junit:4.12'
}

publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/transferwise/digital-signatures")
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("PASSWORD")
}
}
}
publications {
gpr(MavenPublication) {
from(components.java)
}
}
}
}
20 changes: 20 additions & 0 deletions digital-signatures-cli/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id 'java'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '5.2.0'
}

dependencies {
implementation project(':digital-signatures')
implementation 'commons-cli:commons-cli:1.3.1'

testImplementation 'com.github.stefanbirkner:system-rules:1.19.0' // System.* @Rules for JUnit
}

jar {
manifest {
attributes 'Main-Class': 'com.transferwise.digitalsignatures.cli.Main'
}
}

project.tasks.assemble.dependsOn project.tasks.shadowJar
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.transferwise.digitalsignatures.cli;

import com.transferwise.digitalsignatures.DigitalSignatures;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import java.nio.file.Path;
import java.nio.file.Paths;

class Main {

private static final String CLI_UTILITY_NAME = "java -jar digital-signatures-cli-<version>-all.jar";
private static final String CLI_HELP_HEADER = "Calculates SHA1 with RSA signature in Base64 encoding (RFC 4648) for provided data";

public static void main(String[] args) {
Option privateKeyFilePathOption = Option.builder("k")
.longOpt("private-key-file")
.desc("Path to file containing RSA private key")
.hasArg(true)
.argName("PATH")
.required(true)
.build();

Option dataToSignOption = Option.builder("d")
.longOpt("data-to-sign")
.desc("String containing data to sign")
.hasArg(true)
.argName("DATA")
.required(true)
.build();

Options options = new Options();
options.addOption(privateKeyFilePathOption);
options.addOption(dataToSignOption);

CommandLineParser commandLineParser = new DefaultParser();

CommandLine commandLine = null;

try {
commandLine = commandLineParser.parse(options, args, true);
} catch (ParseException e) {
logError(e.getMessage());
new HelpFormatter().printHelp(CLI_UTILITY_NAME, CLI_HELP_HEADER, options, null, true);

System.exit(1);
}

String privateKeyFilePathString = commandLine.getOptionValue(privateKeyFilePathOption.getOpt());

byte[] dataToSign = commandLine.getOptionValue(dataToSignOption.getOpt()).getBytes();

byte[] signature = null;

try {
Path privateKeyFilePath = Paths.get(privateKeyFilePathString);
signature = DigitalSignatures.sign(privateKeyFilePath, dataToSign);
} catch (Exception e) {
logError("Failed to sign data. Cause: " + e.getMessage());

System.exit(2);
}

String signatureBase64 = DigitalSignatures.encodeToBase64(signature);

System.out.print(signatureBase64);
}

private static void logError(String errorText) {
if (errorText != null && !errorText.isEmpty()) {
System.out.println(errorText);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.transferwise.digitalsignatures.cli;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.ExpectedSystemExit;
import org.junit.contrib.java.lang.system.SystemOutRule;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNoException;
import static org.junit.Assume.assumeThat;

public class MainTest {

private static final String DATA_TO_SIGN = "65a31b86-aa2e-47fd-a7a4-3710437ba270";
private static final String SIGNATURE = "oMbriRqpykbUnoL2sIX5xCO/yhrpZFd4TDu2lWdbcHkfxoYHQIvjdm/Px9SBgO5Lc58qjPkmeJA4z8B8spOVaxLRienkzvqrT0I11OFH7jJkoMu2g8bxPe7hmnRDdTB8cLZyFYGmlYjsr3vxemTUWSYYXdrys5Dh3LuOzWZmuYQ3bOwsBPm2sl7K39QM2KqXWckyqg9xpguWIGWzO86aKc/OboWqompVYKztLtdzMwAT5WQ5tPH+AA/lpiV3VG8J9TKTYpUzcrsRjUIelY+jznOkrFtqyyQsZ6l/G7yFXYTaA55ARc+k7CJExiw4mFX8wgPUHrGt289170HS+UJZDw==";

private static final long OPENSSL_TEST_TIMEOUT_MILLISECONDS = 1000;

@Rule
public final ExpectedSystemExit exit = ExpectedSystemExit.none();

@Rule
public final SystemOutRule systemOut = new SystemOutRule();

@Test
public void exitsWithErrorCode1OnIncompleteArguments() {
exit.expectSystemExitWithStatus(1);
Main.main(new String[0]);
}

@Test
public void exitsWithErrorCode2OnInvalidPrivateKeyFilePath() {
exit.expectSystemExitWithStatus(2);
Main.main(new String[]{"-k", "not a path", "-d", "data to sign"});
}

@Test
public void printsCorrectSignatureToSystemOut() {
String testPrivateKeyFilePath = MainTest.class.getResource("/keys/private.pem").getPath();

systemOut.enableLog();
Main.main(new String[]{"-k", testPrivateKeyFilePath, "-d", DATA_TO_SIGN});
assertEquals(SIGNATURE, systemOut.getLog());
}

@Test(timeout = OPENSSL_TEST_TIMEOUT_MILLISECONDS)
public void signatureIsIdenticalToGeneratedByOpenSSL() throws InterruptedException {
String testPrivateKeyFilePath = MainTest.class.getResource("/keys/private.pem").getPath();

Process process;
try {
String command = String.format("printf '%s' | openssl sha1 -sign %s | base64 -b 0", DATA_TO_SIGN, testPrivateKeyFilePath);
process = new ProcessBuilder("/bin/sh", "-c", command).start();
} catch (Exception e) {
assumeNoException(e);

return;
}

boolean processExited = process.waitFor(OPENSSL_TEST_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
assertTrue(processExited);

int processExitCode = process.exitValue();
assumeThat(processExitCode, is(0));

String processOutput = readProcessOutput(process);
assertEquals(SIGNATURE, processOutput);
}

private static String readProcessOutput(Process process) {
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
return reader.lines().collect(Collectors.joining(System.getProperty("line.separator")));
}
}
27 changes: 27 additions & 0 deletions digital-signatures-cli/src/test/resources/keys/private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3I1g8u8307u54gh0PVTudKujVFo90500qeJcnxEycWpaPUvl
l1HbvDJiZhkq0zKq6HN560z5r2L1baJOpiUcdKx/Vg3BR7cyCU52a4ezuSY7VmV6
KL56lNK4ieUBF6ABSW2Ivpl7fhHHi6xSMpPIzyzTMDZupCQrsmePqYev2VVx7Ajs
krHK3hT/+0X3AE1NZpaW2VuxHst3ApNHq0ld6oa0Z6q2Bo697X2OYPwbrm9srN+0
S/xwRzbqBxT851MVd2/x4GqzIRIZ1ThetqhtghdXsaPONoWnCzYSgCAXvuXvfbda
NAPyNvABViTghyU843aR8Uvw2ItCIOlp3mLN8QIDAQABAoIBAQCZGRbkbERPmS+m
hQHTlUJWANNG+cGTRLxK9VQgIzrl2dK8XBQK34rt7/e4Md41byWOaKKIQQ3Nvp7p
tNJtqLNBFoDqBnBVzQhRx4KSkEekzbJA/f43jEnhRwlMx4fjk3FxPDTBQh+kWsku
3rbMXyP1FIOhIxfYnzcqB5OFNihObyTYLFMLXKYszd7BX+CPRzu7HyTZdwa815da
eoXIAnJKIW2TgiD+FyF88GVO6PWbvA263selqN68xfBjmgPVs8ceGLD4J7Wqu9Z/
ZTMLZKKZcpe7aF89Mme9sOyzEfNJCY1fGCSY+yQRTsQBGnfVaRJW9Z2WfLd1LD3u
b98MvlQBAoGBAO9kz4fsbE0r+Fy3q3xSVo2cRdxpmZjZhtYQDo3i5ExlvH41LpqX
ViR6n0E+tGQN8oRDu+f7CJ2Zs4duNFhr2INljre09+qarXU8VRjYDb/x7Zy3OJKy
vKZJgvx1+NeQt+B32qJzS1It2GFCjRvXx4rRXW4Jx3VzTIKy3s3XtXbBAoGBAOvZ
+jh15sPAmJu6UshuJBC2sDBc+aPgo9LX+ykndFg+hi4WtdzBzNWjI7TO9jQtIxVP
lWugY6aeVqALKKnAQ0F+fgHjDkVC8TRX/wSz6YKQn5SlO68t8wK5wutACq9RhRfH
50PUu64yXnhx0u2Fx/7cHckHjTogMefkOdBLRtMxAoGAErS85rEZsVoLOSt88eT5
MG2So+t4fhIZUCbHDF07W6DjfrUnJBtJNuaCBTYiIGNanO0yBKl//dihx6Zb3sDm
lTXdVguFB8b4YN3LBHr1cBc2avWCLSxcQ14hJxsMy8NaKucSpXj+3LgKXWc24YMV
64n6k/udo1bUFq5lbI47dsECgYEAgjazrm5xvMvdtcTWJbChmtSyS9FZRsAk0qjK
EzukQYArptCFEd+xzpWmhhHp3n65Ku/oaCaCPiCXZP8kMSxkNYm32iTY4SaHc0XO
F3OZTau5X2EmpZ4x1+RlmGqgO5E/cRS+OzX9dLx8afU15kuBUtWGYFIaB+h0hTn9
LWISNVECgYB+8eV5moyvrpYOogVIx5ncbrYHDSA7vlIa7A3Q3gzjWBOpFSQwzlF5
hYtTsUm4ZCVtBZVs51dcTKT35/j/fJNweQrSLsb8Xk5Mi1BKKcHsal5Gr0i6DChQ
CWwx3RnrXjutobtUteYawvdsnLlWQlfxKWRY9hFi4AIKDgc1EXnsiw==
-----END RSA PRIVATE KEY-----
4 changes: 4 additions & 0 deletions digital-signatures/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies {
implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
}
Loading

0 comments on commit 07e840d

Please sign in to comment.