Package a signed JAR
- Reading time: 9 mins
- Discuss on Slack
In this section, you will deploy your CorDapp on the node you create.
For deployment, your CorDapp will be packaged as a JAR, really a fancy ZIP file. What your CorDapp needs to include is anything that is necessary for it to work, minus what is already part of the Corda platform, i.e. the Corda JARs. That is why you saw dependencies like cordaCompile
and cordaRuntime
in build.gradle
.
It also needs to exclude from packaging the other CorDapps it uses and that will be installed separately, which is why you saw the cordapp
dependency. Notice too how your project modules are declared as cordapp
so that they can be packaged independently from each other and from future hypothetical modules.
How to Build a CorDapp
To build a CorDapp, simply execute this command inside your terminal under your project’s path:
$ ./gradlew :module-name:jar
C:\020-first-token> gradlew :module-name:jar
Where you replace module-name
with your module’s name, for instance:
$ ./gradlew :contracts:jar
C:\020-first-token> gradlew :contracts:jar
Of course, you can also use the larger build
task, but keep in mind that it will also run the tests. So choose the task that serves you.
The built JAR file(s) can be found in their respective build/libs
folder.
Do check your Java version if you are facing any issue.
Keep in mind that an error like this:
> Task :workflows:compileJava FAILED
030-tokens-sdk/workflows/src/main/java/com/template/flows/IssueFlows.java:11: error: package javafx.util does not exist
import javafx.util.Pair;
Can in fact be a Java version error.
Why Sign Contract CorDapps for Production?
When you used the deployNodes
facility to create a development network, and created a state, your CorDapp’s state recorded the hash of the contracts JAR file that created it. This constraint ensures reproducibility now and in the future, at the cost of flexibility. What if you wanted to make changes to what your contract verifies? It is possible to upgrade a given state, as you will learn in a later chapter, but at the cost of a few contorsions.
Now, if you reasonably expect that your contracts CorDapp will need some upgrade in the future, to facilitate the transition, you would be well-advised to choose to sign it and enforce a signature constraint. This offers other advantages including accepting new contracts CorDapps if they have known signatories.
So, in effect, signing a contracts CorDapp jar file is a quasi-requirement of any Corda node running in production mode. If you wanted your dev nodes to run in production mode, you could have used devMode false
inside node.conf
. The node will not load any contracts CorDapp that is not signed, or that is signed by a Corda development certificate which is done by default. If you do, you’ll see something like this inside your log file:
[WARN] 2020-02-26T05:07:45,335Z [main] cordapp.JarScanningCordappLoader. - Not loading CorDapp [Your CorDapp] as it is signed by development key(s) only.
If you recall, a signed transaction is constituted of its content, its serialized bytes, which determine its id, and a signature. When a signature is added to a transaction, it is indeed added to it and the signature does not change the transaction bytes or id. Similarly here, when you sign your CorDapp, you do not modify its content, instead you affix one or more signatures to it.
Note that only contracts CorDapps ought to be signed as they are part of transactions, to be verified in the future, and they travel over the wire. There is no point in signing workflows CorDapps as they are installed on nodes by its administrator. Of course, you separated both CorDapp types into separate modules from the start.
How to Sign Your CorDapps
To create a signed JAR, is a roughly 2 step process:
- A key has to be created, or reused.
- The key has to be used to sign.
Create a key
The key type that is used to sign a CorDapp JAR has to be stored in a file of type PKCS12, in effect a container type of file. The file itself is encrypted by a password. Inside this container, each individual key is identified by an alias, which you are free to choose. In this case, you will use the alias awesome-cordapp-signer
. Each key is also encrypted by another, or same password. For simplicity, you will use the same password.
The PKCS12 can be created in 2 steps and will be eventually stored in ~/.corda-keys
. So go ahead and create the folder:
$ mkdir ~/.corda-keys
C:\020-first-token> mkdir ~\.corda-keys
Fortunately, as part of the installation of the JDK on your computer, another executable was installed: keytool
.
-
Create a private key in JKS format, another container type, in the file
jar-sign-keystore.jks
.In the line below, complete the X500 name with your parameters, and use the same
password
value for bothstorepass
andkeypass
. Don’t usepassword
itself, obviously. Remember too that to avoid having this sensitive line in the shell’s history, prefix it with a single space.$ keytool -keystore jar-sign-keystore.jks -keyalg RSA -genkey -dname "OU=, O=, L=, C=" -storepass password -keypass password -alias awesome-cordapp-signer -validity 365
C:\020-first-token> keytool -keystore jar-sign-keystore.jks -keyalg RSA -genkey -dname "OU=, O=, L=, C=" -storepass password -keypass password -alias awesome-cordapp-signer -validity 365
You should see:
Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 365 days for: OU=DevOps, O=Corda, L=London, C=UK
-
Migrate the JKS key to PKCS12 format.
You will be prompted for 2 passwords, use the same value that you used to create the JKS key for both values:
$ keytool -importkeystore -srckeystore jar-sign-keystore.jks -destkeystore ~/.corda-keys/jar-sign-keystore.pkcs12 -deststoretype pkcs12
C:\020-first-token> keytool -importkeystore -srckeystore jar-sign-keystore.jks -destkeystore ~\.corda-keys\jar-sign-keystore.pkcs12 -deststoretype pkcs12
Now erase the first file:
$ rm jar-sign-keystore.jks
C:\020-first-token> del jar-sign-keystore.jks
Take care of your ~/.corda-keys/jar-sign-keystore.pkcs12
file as you would another key file. If you chose a folder other than ~/.corda-keys/
, make sure that you do not commit it into a Git repository.
With the key created, it is time to instruct Gradle.
Discard signing for flows
Since one only signs contracts CorDapps, let’s explicitly disable signing on the workflows CorDapp. In the workflows module’s build.gradle
:
cordapp {
targetPlatformVersion corda_platform_version
minimumPlatformVersion corda_platform_version
workflow {
name "Template Flows"
vendor "My Company"
// If it's not open source, use "Proprietary"
licence "Apache License, Version 2.0"
versionId 1
}
signing {
enabled false
}
}
A local config file
Now, to the contracts CorDapp. Gradle will need to:
- Have access to the PKCS12 file.
- Know what password to use to decrypt it.
And because neither the PKCS12 file, nor the password shall be committed into the Git repository:
- You need to create an intermediate configuration file that points to the PKCS12.
- You will pass the password on the command line.
So create a signing.properties
file in your project’s folder:
$ touch signing.properties
C:\020-first-token> echo $null >> signing.properties
Then, inside it, add:
jar.sign.keystore=/absolute/path/to/.corda-keys/jar-sign-keystore.pkcs12
jar.sign.alias=awesome-cordapp-signer
Where you have replaced /absolute/path/to/
with the correct absolute path to the key, don’t use ~
. It is safe to commit this file to Git as it does not contain any private key or password. Now, it is time to direct Gradle how to sign.
Use the key to sign
Add the below code inside the build.gradle
file of each contracts CorDapp in your project. Note how it makes use of getProperty()
instead of exposing the plain text password:
cordapp {
targetPlatformVersion corda_platform_version
minimumPlatformVersion corda_platform_version
contract {
name "Template CorDapp"
vendor "Corda Open Source"
// If it's not open source, use "Proprietary"
licence "Apache License, Version 2.0"
versionId 1
}
signing {
enabled true
options {
Properties signing = new Properties()
file("$projectDir/../signing.properties").withInputStream { signing.load(it) }
keystore signing.getProperty("jar.sign.keystore")
alias signing.getProperty("jar.sign.alias")
storepass getProperty("SIGN_PASS")
keypass getProperty("SIGN_PASS")
storetype "PKCS12"
}
}
}
Now that Gradle is correctly informed, it is time to package the CorDapp again. But what is this SIGN_PASS
property? It is an argument that you pass to the command line:
$ ./gradlew -PSIGN_PASS=password :contracts:jar
C:\020-first-token> gradlew -PSIGN_PASS=password :contracts:jar
Where you have replaced password
by the actual password you used. That’s it?
Verify
How can you verify that it is signed indeed? Simple. Use jarsigner
, another tool that came with the JDK:
$ jarsigner -verify -verbose -keystore ~/.corda-keys/jar-sign-keystore.pkcs12 contracts/build/libs/contracts-0.1.jar
C:\020-first-token> jarsigner -verify -verbose -keystore ~\.corda-keys\jar-sign-keystore.pkcs12 contracts\build\libs\contracts-0.1.jar
You should obtain something like:
s 1110 Wed Jun 03 19:55:36 GET 2020 META-INF/MANIFEST.MF
1080 Wed Jun 03 19:55:38 GET 2020 META-INF/AWESOME-.SF
1186 Wed Jun 03 19:55:38 GET 2020 META-INF/AWESOME-.RSA
0 Fri Feb 01 00:00:00 GET 1980 META-INF/
0 Fri Feb 01 00:00:00 GET 1980 com/
0 Fri Feb 01 00:00:00 GET 1980 com/template/
0 Fri Feb 01 00:00:00 GET 1980 com/template/contracts/
0 Fri Feb 01 00:00:00 GET 1980 com/template/states/
sm 493 Fri Feb 01 00:00:00 GET 1980 com/template/contracts/TokenContract$Commands$Issue.class
sm 490 Fri Feb 01 00:00:00 GET 1980 com/template/contracts/TokenContract$Commands$Move.class
sm 496 Fri Feb 01 00:00:00 GET 1980 com/template/contracts/TokenContract$Commands$Redeem.class
sm 486 Fri Feb 01 00:00:00 GET 1980 com/template/contracts/TokenContract$Commands.class
sm 8262 Fri Feb 01 00:00:00 GET 1980 com/template/contracts/TokenContract.class
sm 2746 Fri Feb 01 00:00:00 GET 1980 com/template/states/TokenState.class
sm 2281 Fri Feb 01 00:00:00 GET 1980 com/template/states/TokenStateUtilities.class
s = signature was verified
m = entry is listed in manifest
k = at least one certificate was found in keystore
- Signed by "OU=, O=, L=, C="
Digest algorithm: SHA-256
Signature algorithm: SHA256withRSA, 2048-bit key
jar verified.
Warning:
This jar contains entries whose certificate chain is invalid. Reason: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
This jar contains signed entries that are not signed by alias in this keystore.
This jar contains entries whose signer certificate is self-signed.
This jar contains signatures that do not include a timestamp. Without a timestamp, users may not be able to validate this jar after any of the signer certificates expire (as early as 2021-06-03).
Re-run with the -verbose and -certs options for more details.
The signer certificate will expire on 2021-06-03.
Cleanup
What to do with the PKCS12 file? You ought to keep it on a build machine only, and not necessarily distribute it across all developer computers. On each developer computer, in order for the jar
task to succeed, each developer can create new self-signed keys by following the above steps.
If you do want to distribute the PKCS12 file so you can all sign with the same key, it’s not recommended to send the file as it is across the internet. Instead, you can encrypt it with the recipient’s public key then send it to them. A detailed article about using gpg to encrypt/decrypt files can be found here.
Conclusion
You have created a neatly signed CorDapp JAR.