feat: root ca sign
This commit is contained in:
@@ -13,8 +13,8 @@
|
||||
"repo": {
|
||||
"dependencies": [
|
||||
"info.picocli:picocli:4.6.1",
|
||||
"me.hatter:commons:3.66",
|
||||
"me.hatter:crypto:1.10"
|
||||
"me.hatter:commons:3.67",
|
||||
"me.hatter:crypto:1.12"
|
||||
],
|
||||
"testDependencies": [
|
||||
"junit:junit:4.12"
|
||||
|
||||
@@ -8,15 +8,33 @@ import picocli.CommandLine;
|
||||
"Argument details:")
|
||||
public class YubikeyCaArgs {
|
||||
|
||||
@CommandLine.Option(names = {"-k", "--generate-keypair"}, description = "Generate keypair")
|
||||
@CommandLine.Option(names = {"--generate-keypair"}, description = "Generate keypair")
|
||||
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." +
|
||||
" RSA1024, RSA2048, RSA3072, RSA4096," +
|
||||
" secp192k1, secp192r1, secp224k1, secp256k1," +
|
||||
" secp224r1, secp256r1, secp384r1, secp521r1;")
|
||||
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")
|
||||
boolean helpRequested = false;
|
||||
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
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.LogTool;
|
||||
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.KeyUtil;
|
||||
import me.hatter.tools.commons.security.key.PKType;
|
||||
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.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -29,10 +37,47 @@ public class YubikeyCaMain {
|
||||
generateKeyPair(args);
|
||||
return;
|
||||
}
|
||||
if (args.generateRootCa) {
|
||||
generateRootCa(args);
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (StringUtil.isEmpty(args.keypairType)) {
|
||||
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