-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 07e840d
Showing
17 changed files
with
780 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Project exclude paths | ||
/.gradle/ | ||
**/build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
79 changes: 79 additions & 0 deletions
79
digital-signatures-cli/src/main/java/com/transferwise/digitalsignatures/cli/Main.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
digital-signatures-cli/src/test/java/com/transferwise/digitalsignatures/cli/MainTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
27
digital-signatures-cli/src/test/resources/keys/private.pem
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
} |
Oops, something went wrong.