From 5f6aff38ce1a57f20a3230fe16465cf1a2230bbf Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 20 May 2023 19:07:47 +0800 Subject: [PATCH] feat: server/client ca --- yubikey-ca-java/README.md | 24 +++++- .../hatter/tools/yubikeyca/YubikeyCaArgs.java | 22 +++-- .../hatter/tools/yubikeyca/YubikeyCaMain.java | 86 ++++++++++++++++--- 3 files changed, 108 insertions(+), 24 deletions(-) diff --git a/yubikey-ca-java/README.md b/yubikey-ca-java/README.md index fe320c4..361f754 100644 --- a/yubikey-ca-java/README.md +++ b/yubikey-ca-java/README.md @@ -12,7 +12,7 @@ $ java -jar yubikey-ca-java.jar --generate-keypair --keypair-type secp256r1 # Issue ROOT CA ```shell -$ java -jar yubikey-ca-java.jar --generate-root-ca \ +$ java -jar yubikey-ca-java.jar --issue-root-ca \ --sign-slot 88 --subject 'CN=Hatter Yubikey EC Root CA' \ --pin ****** \ [--add-to-remote] @@ -21,10 +21,30 @@ $ java -jar yubikey-ca-java.jar --generate-root-ca \ # Issue Intermediate CA ```shell -$ java -jar yubikey-ca-java.jar --generate-intermediate-ca \ +$ java -jar yubikey-ca-java.jar --issue-intermediate-ca \ --sign-slot 88 --subject 'CN=Hatter Yubikey EC Intermediate CA' \ --cert-slot 89 --root-ca-id 39 \ --pin ****** \ [--add-to-remote] ``` +# Issue Server CA + +```shell +$ java -jar yubikey-ca-java.jar --issue-server-ca \ + --sign-slot 89 --subject 'CN=hatter-test' \ + --intermediate-ca-id 40 --keypair-type secp256r1 \ + --dns-name a.example.com --dns-name b.example.com \ + --pin 123456 \ + [--add-to-remote] +``` + +# Issue Client CA + +```shell +$ java -jar yubikey-ca-java.jar --issue-client-ca \ + --sign-slot 89 --subject 'CN=hatter-test' \ + --intermediate-ca-id 40 --keypair-type secp256r1 \ + --pin 123456 \ + [--add-to-remote] +``` diff --git a/yubikey-ca-java/src/main/java/me/hatter/tools/yubikeyca/YubikeyCaArgs.java b/yubikey-ca-java/src/main/java/me/hatter/tools/yubikeyca/YubikeyCaArgs.java index a8a3dbd..10b8b6c 100644 --- a/yubikey-ca-java/src/main/java/me/hatter/tools/yubikeyca/YubikeyCaArgs.java +++ b/yubikey-ca-java/src/main/java/me/hatter/tools/yubikeyca/YubikeyCaArgs.java @@ -11,17 +11,17 @@ public class YubikeyCaArgs { @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 = {"--issue-root-ca"}, description = "Issue root CA") + boolean issueRootCa = false; - @CommandLine.Option(names = {"--generate-intermediate-ca"}, description = "Generate intermediate CA") - boolean generateIntermediateCa = false; + @CommandLine.Option(names = {"--issue-intermediate-ca"}, description = "Issue intermediate CA") + boolean issueIntermediateCa = false; - @CommandLine.Option(names = {"--generate-server-ca"}, description = "Generate server CA") - boolean generateServerCa = false; + @CommandLine.Option(names = {"--issue-server-ca"}, description = "Issue server CA") + boolean issueServerCa = false; - @CommandLine.Option(names = {"--generate-client-ca"}, description = "Generate client CA") - boolean generateClientCa = false; + @CommandLine.Option(names = {"--issue-client-ca"}, description = "Issue client CA") + boolean issueClientCa = false; @CommandLine.Option(names = {"--subject"}, description = "Certificate subject") String subject; @@ -29,6 +29,12 @@ public class YubikeyCaArgs { @CommandLine.Option(names = {"--root-ca-id"}, description = "Root certificate ID") String rootCaId; + @CommandLine.Option(names = {"--intermediate-ca-id"}, description = "Intermediate certificate ID") + String intermediateCaId; + + @CommandLine.Option(names = {"--dns-name"}, description = "DNS name (Subject alt name)") + String[] dnsNames; + @CommandLine.Option(names = {"--keypair-type"}, description = "Keypair type, e.g." + " RSA1024, RSA2048, RSA3072, RSA4096," + " secp192k1, secp192r1, secp224k1, secp256k1," + diff --git a/yubikey-ca-java/src/main/java/me/hatter/tools/yubikeyca/YubikeyCaMain.java b/yubikey-ca-java/src/main/java/me/hatter/tools/yubikeyca/YubikeyCaMain.java index 26fc15a..fb61bdf 100644 --- a/yubikey-ca-java/src/main/java/me/hatter/tools/yubikeyca/YubikeyCaMain.java +++ b/yubikey-ca-java/src/main/java/me/hatter/tools/yubikeyca/YubikeyCaMain.java @@ -37,19 +37,72 @@ public class YubikeyCaMain { generateKeyPair(args); return; } - if (args.generateRootCa) { - generateRootCa(args); + if (args.issueRootCa) { + issueRootCa(args); return; } - if (args.generateIntermediateCa) { - generateIntermediateCa(args); + if (args.issueIntermediateCa) { + issueIntermediateCa(args); + return; + } + if (args.issueServerCa || args.issueClientCa) { + issueServerClientCa(args); return; } log.error("Unknown command, use --help for help"); } - private static void generateIntermediateCa(YubikeyCaArgs args) { + private static void issueServerClientCa(YubikeyCaArgs args) { + if (checkCertificateArgs(args)) return; + if (StringUtil.isEmpty(args.intermediateCaId)) { + log.error("Intermediate CA id is required."); + return; + } + if (StringUtil.isEmpty(args.keypairType)) { + log.error("Keypair type is required."); + return; + } + if (args.issueServerCa && (args.dnsNames == null || args.dnsNames.length == 0)) { + log.error("DNS name is required."); + return; + } + + + final PKType pkType = getPkTypeFromArgs(args); + if (pkType == null) return; + + final X509Certificate intermediateCertificate = CertificateUtil.getCertificate(args.pin, args.intermediateCaId); + + final JSONObject signPivMetaJsonObject = CardCliUtil.getPivMeta(args.signSlot); + final String signAlgorithm = signPivMetaJsonObject.getString("algorithm"); + + final KeyPair keyPair = KeyPairTool.instance(pkType).generateKeyPair().getKeyPair(); + + final String cardCliCmd = CardCliUtil.getCardCliCmd(); + final CertificateAuthority ca = CertificateAuthority.instance() + .subject(args.subject) + .signCert(intermediateCertificate) + .certPubKey(keyPair.getPublic()) + .validYears(2) + .customerSigner(new CardCliPivCustomerSigner(args.pin, args.signSlot, signAlgorithm, cardCliCmd)); + + final X509Certificate cert; + if (args.issueServerCa) { + cert = ca.createServerCert(Arrays.asList(args.dnsNames)); + } else { + cert = ca.createClientCert(); + } + final String certPem = X509CertUtil.serializeX509CertificateToPEM(cert); + final String privateKeyPem = KeyUtil.serializePrivateKeyToPEM(keyPair.getPrivate()); + + log.info("Issued CA: " + certPem); + if (args.addToRemote) { + CertificateUtil.addCertificate(args.pin, args.intermediateCaId, args.memo, certPem, privateKeyPem); + } + } + + private static void issueIntermediateCa(YubikeyCaArgs args) { if (checkCertificateArgs(args)) return; if (StringUtil.isEmpty(args.rootCaId)) { log.error("Root CA id is required."); @@ -83,7 +136,7 @@ public class YubikeyCaMain { } } - private static void generateRootCa(YubikeyCaArgs args) { + private static void issueRootCa(YubikeyCaArgs args) { if (checkCertificateArgs(args)) return; final JSONObject signPivMetaJsonObject = CardCliUtil.getPivMeta(args.signSlot); @@ -123,22 +176,27 @@ public class YubikeyCaMain { } private static void generateKeyPair(YubikeyCaArgs args) { + final PKType pkType = getPkTypeFromArgs(args); + if (pkType == null) return; + + final KeyPair keyPair = KeyPairTool.instance(pkType).generateKeyPair().getKeyPair(); + + System.out.println("Private key:\n" + KeyUtil.serializePrivateKeyToPEM(keyPair.getPrivate()) + "\n"); + System.out.println("Public key: \n" + KeyUtil.serializePublicKeyToPEM(keyPair.getPublic()) + "\n"); + } + + private static PKType getPkTypeFromArgs(YubikeyCaArgs args) { if (StringUtil.isEmpty(args.keypairType)) { log.error("Keypair type is required."); - return; + return null; } Optional pkTypeOpt = Arrays.stream(PKType.values()) .filter(t -> t.name().equalsIgnoreCase(args.keypairType)) .findFirst(); if (!pkTypeOpt.isPresent()) { log.error("Invalid keypair type: " + args.keypairType); - return; + return null; } - - final KeyPair keyPair = KeyPairTool.instance(pkTypeOpt.get()) - .generateKeyPair().getKeyPair(); - - System.out.println("Private key:\n" + KeyUtil.serializePrivateKeyToPEM(keyPair.getPrivate()) + "\n"); - System.out.println("Public key: \n" + KeyUtil.serializePublicKeyToPEM(keyPair.getPublic()) + "\n"); + return pkTypeOpt.get(); } }