feat: root ca sign
This commit is contained in:
@@ -13,8 +13,8 @@
|
|||||||
"repo": {
|
"repo": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"info.picocli:picocli:4.6.1",
|
"info.picocli:picocli:4.6.1",
|
||||||
"me.hatter:commons:3.66",
|
"me.hatter:commons:3.67",
|
||||||
"me.hatter:crypto:1.10"
|
"me.hatter:crypto:1.12"
|
||||||
],
|
],
|
||||||
"testDependencies": [
|
"testDependencies": [
|
||||||
"junit:junit:4.12"
|
"junit:junit:4.12"
|
||||||
|
|||||||
@@ -8,15 +8,33 @@ import picocli.CommandLine;
|
|||||||
"Argument details:")
|
"Argument details:")
|
||||||
public class YubikeyCaArgs {
|
public class YubikeyCaArgs {
|
||||||
|
|
||||||
@CommandLine.Option(names = {"-k", "--generate-keypair"}, description = "Generate keypair")
|
@CommandLine.Option(names = {"--generate-keypair"}, description = "Generate keypair")
|
||||||
boolean generateKeypair = false;
|
boolean generateKeypair = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--generate-root-ca"}, description = "Generate root CA")
|
||||||
|
boolean generateRootCa = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--generate-intermediate-ca"}, description = "Generate intermediate CA")
|
||||||
|
boolean generateIntermediateCa = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--generate-server-ca"}, description = "Generate server CA")
|
||||||
|
boolean generateServerCa = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--generate-client-ca"}, description = "Generate client CA")
|
||||||
|
boolean generateClientCa = false;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"--keypair-type"}, description = "Keypair type, e.g." +
|
@CommandLine.Option(names = {"--keypair-type"}, description = "Keypair type, e.g." +
|
||||||
" RSA1024, RSA2048, RSA3072, RSA4096," +
|
" RSA1024, RSA2048, RSA3072, RSA4096," +
|
||||||
" secp192k1, secp192r1, secp224k1, secp256k1," +
|
" secp192k1, secp192r1, secp224k1, secp256k1," +
|
||||||
" secp224r1, secp256r1, secp384r1, secp521r1;")
|
" secp224r1, secp256r1, secp384r1, secp521r1;")
|
||||||
String keypairType;
|
String keypairType;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--sign-slot"}, description = "Slot for sign")
|
||||||
|
String signSlot;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--pin"}, description = "Yubikey PIV PIN")
|
||||||
|
String pin;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display a help message")
|
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display a help message")
|
||||||
boolean helpRequested = false;
|
boolean helpRequested = false;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
package me.hatter.tools.yubikeyca;
|
package me.hatter.tools.yubikeyca;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import me.hatter.tools.commons.bytes.Bytes;
|
||||||
import me.hatter.tools.commons.log.LogConfig;
|
import me.hatter.tools.commons.log.LogConfig;
|
||||||
import me.hatter.tools.commons.log.LogTool;
|
import me.hatter.tools.commons.log.LogTool;
|
||||||
import me.hatter.tools.commons.log.LogTools;
|
import me.hatter.tools.commons.log.LogTools;
|
||||||
|
import me.hatter.tools.commons.security.cert.X509CertUtil;
|
||||||
import me.hatter.tools.commons.security.key.KeyPairTool;
|
import me.hatter.tools.commons.security.key.KeyPairTool;
|
||||||
import me.hatter.tools.commons.security.key.KeyUtil;
|
import me.hatter.tools.commons.security.key.KeyUtil;
|
||||||
import me.hatter.tools.commons.security.key.PKType;
|
import me.hatter.tools.commons.security.key.PKType;
|
||||||
import me.hatter.tools.commons.string.StringUtil;
|
import me.hatter.tools.commons.string.StringUtil;
|
||||||
|
import me.hatter.tools.crypto.ca.CertificateAuthority;
|
||||||
|
import me.hatter.tools.crypto.piv.PivCustomerSigner;
|
||||||
|
import me.hatter.tools.yubikeyca.cardcli.CardCliUtil;
|
||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -29,10 +37,47 @@ public class YubikeyCaMain {
|
|||||||
generateKeyPair(args);
|
generateKeyPair(args);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (args.generateRootCa) {
|
||||||
|
generateRootCa(args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
log.error("Unknown command, use --help for help");
|
log.error("Unknown command, use --help for help");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void generateRootCa(YubikeyCaArgs args) {
|
||||||
|
final String signSlot = args.signSlot;
|
||||||
|
if (StringUtil.isEmpty(signSlot)) {
|
||||||
|
log.error("Sign slot is required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StringUtil.isEmpty(args.pin)) {
|
||||||
|
log.error("PIV PIN is required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final JSONObject signPivMetaJsonObject = CardCliUtil.getPivMeta(signSlot);
|
||||||
|
|
||||||
|
final String algorithm = signPivMetaJsonObject.getString("algorithm");
|
||||||
|
final String publicKeyPem = signPivMetaJsonObject.getString("public_key_pem");
|
||||||
|
final PublicKey publicKey = KeyUtil.parsePublicKeyPEM(publicKeyPem);
|
||||||
|
|
||||||
|
final String cardCliCmd = CardCliUtil.getCardCliCmd();
|
||||||
|
|
||||||
|
final X509Certificate rootCa = CertificateAuthority.instance()
|
||||||
|
.subject("CN=HatterYubikeyRootCA")
|
||||||
|
.certPubKey(publicKey)
|
||||||
|
.validYears(30)
|
||||||
|
.customerSigner(new PivCustomerSigner(signSlot, algorithm, cardCliCmd) {
|
||||||
|
@Override
|
||||||
|
protected byte[] signWithPiv(String slot, String algorithm, Bytes digest) {
|
||||||
|
return CardCliUtil.signPiv(args.pin, slot, algorithm, digest);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.createCA();
|
||||||
|
|
||||||
|
log.info("Issued root CA: " + X509CertUtil.serializeX509CertificateToPEM(rootCa));
|
||||||
|
}
|
||||||
|
|
||||||
private static void generateKeyPair(YubikeyCaArgs args) {
|
private static void generateKeyPair(YubikeyCaArgs args) {
|
||||||
if (StringUtil.isEmpty(args.keypairType)) {
|
if (StringUtil.isEmpty(args.keypairType)) {
|
||||||
log.error("Keypair type is required.");
|
log.error("Keypair type is required.");
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package me.hatter.tools.yubikeyca.cardcli;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import me.hatter.tools.commons.assertion.AssertUtil;
|
||||||
|
import me.hatter.tools.commons.bytes.Bytes;
|
||||||
|
import me.hatter.tools.commons.collection.CollectionUtil;
|
||||||
|
import me.hatter.tools.commons.io.IOUtil;
|
||||||
|
import me.hatter.tools.commons.log.LogTool;
|
||||||
|
import me.hatter.tools.commons.log.LogTools;
|
||||||
|
import me.hatter.tools.commons.string.StringUtil;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CardCliUtil {
|
||||||
|
private static final LogTool log = LogTools.getLogTool(CardCliUtil.class);
|
||||||
|
|
||||||
|
public static byte[] signPiv(String pin, String slot, String algorithm, Bytes digest) {
|
||||||
|
final String digestHex = digest.asHex();
|
||||||
|
final JSONObject signJsonObject = runCardCliAsJsonObject(Arrays.asList(
|
||||||
|
"piv-ecsign",
|
||||||
|
"--slot", slot,
|
||||||
|
"--algorithm", algorithm,
|
||||||
|
"--hash-hex", digestHex,
|
||||||
|
"--pin", pin,
|
||||||
|
"--json"
|
||||||
|
));
|
||||||
|
return Bytes.fromBase64(signJsonObject.getString("signed_data_base64")).bytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject getPivMeta(String slot) {
|
||||||
|
AssertUtil.notEmpty(slot, "Slot cannot be empty.");
|
||||||
|
return runCardCliAsJsonObject(Arrays.asList(
|
||||||
|
"piv-meta",
|
||||||
|
"--slot", slot,
|
||||||
|
"--json"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCardCliCmd() {
|
||||||
|
final String cardCliCmdFromEnv = System.getenv("CARD_CLI");
|
||||||
|
final String cardCliCmdFromProperties = System.getProperty("card.cli");
|
||||||
|
return StringUtil.def(cardCliCmdFromEnv,
|
||||||
|
cardCliCmdFromProperties,
|
||||||
|
"card-cli");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static JSONObject runCardCliAsJsonObject(List<String> arguments) {
|
||||||
|
return JSON.parseObject(runCardCli(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String runCardCli(List<String> arguments) {
|
||||||
|
final List<String> commands = new ArrayList<>();
|
||||||
|
commands.add(getCardCliCmd());
|
||||||
|
if (CollectionUtil.isNotEmpty(arguments)) {
|
||||||
|
commands.addAll(arguments);
|
||||||
|
}
|
||||||
|
return runProcess(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String runProcess(List<String> commands) {
|
||||||
|
return runProcess(new ProcessBuilder().command(commands));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String runProcess(ProcessBuilder pb) {
|
||||||
|
final String outputs;
|
||||||
|
final String errorOutputs;
|
||||||
|
try {
|
||||||
|
log.info("Run command: " + pb.command());
|
||||||
|
final Process p = pb.start();
|
||||||
|
final byte[] outputsBytes = IOUtil.readToBytes(p.getInputStream());
|
||||||
|
final byte[] errorOutputsByes = IOUtil.readToBytes(p.getErrorStream());
|
||||||
|
outputs = new String(outputsBytes, StandardCharsets.UTF_8);
|
||||||
|
errorOutputs = new String(errorOutputsByes, StandardCharsets.UTF_8);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error in run command.", e);
|
||||||
|
}
|
||||||
|
if (StringUtil.isEmpty(StringUtil.trim(outputs))) {
|
||||||
|
if (StringUtil.isNotEmpty(StringUtil.trim(errorOutputs))) {
|
||||||
|
log.error("Error outputs: " + errorOutputs);
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Outputs is empty!");
|
||||||
|
}
|
||||||
|
return outputs;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user