package org.mitre.jose.jwk;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.io.IOUtils;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.ECKey.Curve;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.KeyUse;
/**
* Small Helper App to generate Json Web Keys
*/
public class Launcher {
private static Options options;
public static void main(String[] args) {
options = new Options();
options.addOption("t", true, "Key Type, one of: " + KeyType.RSA.getValue() + ", " + KeyType.OCT.getValue() + ", " +
KeyType.EC.getValue());
options.addOption("s", true, "Key Size in bits, required for RSA and oct key types. Must be an integer divisible by 8");
options.addOption("u", true, "Usage, one of: enc, sig (optional)");
options.addOption("a", true, "Algorithm (optional)");
options.addOption("i", true, "Key ID (optional), one will be generated if not defined");
options.addOption("I", false, "Don't generate a Key ID if none defined");
options.addOption("p", false, "Display public key separately");
options.addOption("c", true, "Key Curve, required for EC key type. Must be one of " + Curve.P_256 + ", " + Curve.P_384
+ ", " + Curve.P_521);
options.addOption("S", false, "Wrap the generated key in a KeySet");
options.addOption("o", true, "Write output to file (will append to existing KeySet if -S is used), No Display of Key "
+ "Material");
CommandLineParser parser = new PosixParser();
try {
CommandLine cmd = parser.parse(options, args);
String kty = cmd.getOptionValue("t");
String size = cmd.getOptionValue("s");
String use = cmd.getOptionValue("u");
String alg = cmd.getOptionValue("a");
String kid = cmd.getOptionValue("i");
String crv = cmd.getOptionValue("c");
boolean keySet = cmd.hasOption("S");
boolean pubKey = cmd.hasOption("p");
boolean doNotGenerateKid = cmd.hasOption("I");
String outFile = cmd.getOptionValue("o");
// check for required fields
if (kty == null) {
printUsageAndExit("Key type must be supplied.");
}
// parse out the important bits
KeyType keyType = KeyType.parse(kty);
KeyUse keyUse = null;
if (use != null) {
if (use.equals("sig")) {
keyUse = KeyUse.SIGNATURE;
} else if (use.equals("enc")) {
keyUse = KeyUse.ENCRYPTION;
} else {
printUsageAndExit("Invalid key usage, must be 'sig' or 'enc', got " + use);
}
}
if (Strings.isNullOrEmpty(kid)) {
kid = doNotGenerateKid ? null : generateKid(keyUse);
}
Algorithm keyAlg = null;
if (!Strings.isNullOrEmpty(alg)) {
keyAlg = JWSAlgorithm.parse(alg);
}
JWK jwk = null;
if (keyType.equals(KeyType.RSA)) {
// surrounding try/catch catches numberformatexception from this
if (Strings.isNullOrEmpty(size)) {
printUsageAndExit("Key size (in bits) is required for key type " + keyType);
}
Integer keySize = Integer.decode(size);
if (keySize % 8 != 0) {
printUsageAndExit("Key size (in bits) must be divisible by 8, got " + keySize);
}
jwk = RSAKeyMaker.make(keySize, keyUse, keyAlg, kid);
} else if (keyType.equals(KeyType.OCT)) {
// surrounding try/catch catches numberformatexception from this
if (Strings.isNullOrEmpty(size)) {
printUsageAndExit("Key size (in bits) is required for key type " + keyType);
}
Integer keySize = Integer.decode(size);
if (keySize % 8 != 0) {
printUsageAndExit("Key size (in bits) must be divisible by 8, got " + keySize);
}
jwk = OctetSequenceKeyMaker.make(keySize, keyUse, keyAlg, kid);
} else if (keyType.equals(KeyType.EC)) {
if (Strings.isNullOrEmpty(crv)) {
printUsageAndExit("Curve is required for key type " + keyType);
}
Curve keyCurve = Curve.parse(crv);
jwk = ECKeyMaker.make(keyCurve, keyUse, keyAlg, kid);
} else {
printUsageAndExit("Unknown key type: " + keyType);
}
// round trip it through GSON to get a prettyprinter
Gson gson = new GsonBuilder().setPrettyPrinting().create();
if (outFile == null) {
System.out.println("Full key:");
printKey(keySet, jwk, gson);
if (pubKey) {
System.out.println(); // spacer
// also print public key, if possible
JWK pub = jwk.toPublicJWK();
if (pub != null) {
System.out.println("Public key:");
printKey(keySet, pub, gson);
} else {
System.out.println("No public key.");
}
}
} else {
writeKeyToFile(keySet, outFile, jwk, gson);
}
} catch (NumberFormatException e) {
printUsageAndExit("Invalid key size: " + e.getMessage());
} catch (ParseException e) {
printUsageAndExit("Failed to parse arguments: " + e.getMessage());
} catch (java.text.ParseException e) {
printUsageAndExit("Could not parse existing KeySet: " + e.getMessage());
} catch (IOException e) {
printUsageAndExit("Could not read existing KeySet: " + e.getMessage());
}
}
private static String generateKid(KeyUse keyUse) {
String prefix = keyUse == null ? "" : keyUse.identifier();
return prefix + (System.currentTimeMillis() / 1000);
}
private static void writeKeyToFile(boolean keySet, String outFile, JWK jwk, Gson gson) throws IOException,
java.text.ParseException {
JsonElement json;
File output = new File(outFile);
if (keySet) {
List<JWK> existingKeys = output.exists() ? JWKSet.load(output).getKeys() : Collections.<JWK>emptyList();
List<JWK> jwkList = new ArrayList<JWK>(existingKeys);
jwkList.add(jwk);
JWKSet jwkSet = new JWKSet(jwkList);
json = new JsonParser().parse(jwkSet.toJSONObject(false).toJSONString());
} else {
json = new JsonParser().parse(jwk.toJSONString());
}
OutputStream os = null;
try {
os = new FileOutputStream(output);
IOUtils.write(gson.toJson(json), os);
} finally {
IOUtils.closeQuietly(os);
}
}
private static void printKey(boolean keySet, JWK jwk, Gson gson) {
if (keySet) {
JWKSet jwkSet = new JWKSet(jwk);
JsonElement json = new JsonParser().parse(jwkSet.toJSONObject(false).toJSONString());
System.out.println(gson.toJson(json));
} else {
JsonElement json = new JsonParser().parse(jwk.toJSONString());
System.out.println(gson.toJson(json));
}
}
// print out a usage message and quit
private static void printUsageAndExit(String message) {
if (message != null) {
System.err.println(message);
}
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("java -jar json-web-key-generator.jar -t <keyType> [options]", options);
// kill the program
System.exit(1);
}
}