feat: issue root/intermediate ca
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
out/
|
||||
build/
|
||||
classes/
|
||||
.DS_Store
|
||||
|
||||
30
yubikey-ca-java/README.md
Normal file
30
yubikey-ca-java/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
ENV:
|
||||
|
||||
* CARD_CLI - Card cli command or full path, default `card-cli`
|
||||
* SIGN_REQUEST_SLOT - Sign request slot, default `82`
|
||||
|
||||
# Generate Keypair
|
||||
|
||||
```shell
|
||||
$ java -jar yubikey-ca-java.jar --generate-keypair --keypair-type secp256r1
|
||||
```
|
||||
|
||||
# Issue ROOT CA
|
||||
|
||||
```shell
|
||||
$ java -jar yubikey-ca-java.jar --generate-root-ca \
|
||||
--sign-slot 88 --subject 'CN=Hatter Yubikey EC Root CA' \
|
||||
--pin ****** \
|
||||
[--add-to-remote]
|
||||
```
|
||||
|
||||
# Issue Intermediate CA
|
||||
|
||||
```shell
|
||||
$ java -jar yubikey-ca-java.jar --generate-intermediate-ca \
|
||||
--sign-slot 88 --subject 'CN=Hatter Yubikey EC Intermediate CA' \
|
||||
--cert-slot 89 --root-ca-id 39 \
|
||||
--pin ****** \
|
||||
[--add-to-remote]
|
||||
```
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"project": {
|
||||
"name": "yubikey-ca-java",
|
||||
"main": "me.hatter.tools.yubikeyca.YubikeyCaMain",
|
||||
"archiveName": "yubikey-ca-java"
|
||||
},
|
||||
"application": false,
|
||||
"java": "1.8",
|
||||
"builder": {
|
||||
"name": "gradle",
|
||||
"version": "3.1"
|
||||
},
|
||||
"repo": {
|
||||
"dependencies": [
|
||||
"info.picocli:picocli:4.6.1",
|
||||
"me.hatter:commons:3.67",
|
||||
"me.hatter:crypto:1.12"
|
||||
],
|
||||
"testDependencies": [
|
||||
"junit:junit:4.12"
|
||||
]
|
||||
}
|
||||
"project": {
|
||||
"name": "yubikey-ca-java",
|
||||
"main": "me.hatter.tools.yubikeyca.YubikeyCaMain",
|
||||
"archiveName": "yubikey-ca-java"
|
||||
},
|
||||
"application": false,
|
||||
"java": "1.8",
|
||||
"builder": {
|
||||
"name": "gradle",
|
||||
"version": "3.1"
|
||||
},
|
||||
"repo": {
|
||||
"dependencies": [
|
||||
"info.picocli:picocli:4.6.1",
|
||||
"me.hatter:commons:3.67",
|
||||
"me.hatter:crypto:1.12"
|
||||
],
|
||||
"testDependencies": [
|
||||
"junit:junit:4.12"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ public class YubikeyCaArgs {
|
||||
@CommandLine.Option(names = {"--generate-client-ca"}, description = "Generate client CA")
|
||||
boolean generateClientCa = false;
|
||||
|
||||
@CommandLine.Option(names = {"--subject"}, description = "Certificate subject")
|
||||
String subject;
|
||||
|
||||
@CommandLine.Option(names = {"--root-ca-id"}, description = "Root certificate ID")
|
||||
String rootCaId;
|
||||
|
||||
@CommandLine.Option(names = {"--keypair-type"}, description = "Keypair type, e.g." +
|
||||
" RSA1024, RSA2048, RSA3072, RSA4096," +
|
||||
" secp192k1, secp192r1, secp224k1, secp256k1," +
|
||||
@@ -32,9 +38,18 @@ public class YubikeyCaArgs {
|
||||
@CommandLine.Option(names = {"--sign-slot"}, description = "Slot for sign")
|
||||
String signSlot;
|
||||
|
||||
@CommandLine.Option(names = {"--cert-slot"}, description = "Slot for cert")
|
||||
String certSlot;
|
||||
|
||||
@CommandLine.Option(names = {"--pin"}, description = "Yubikey PIV PIN")
|
||||
String pin;
|
||||
|
||||
@CommandLine.Option(names = {"--memo"}, description = "Memo")
|
||||
String memo;
|
||||
|
||||
@CommandLine.Option(names = {"--add-to-remote"}, description = "Add certificate to remote")
|
||||
boolean addToRemote = false;
|
||||
|
||||
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display a help message")
|
||||
boolean helpRequested = false;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -11,8 +10,9 @@ 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.CardCliPivCustomerSigner;
|
||||
import me.hatter.tools.yubikeyca.cardcli.CardCliUtil;
|
||||
import me.hatter.tools.yubikeyca.hatterink.CertificateUtil;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
@@ -41,41 +41,85 @@ public class YubikeyCaMain {
|
||||
generateRootCa(args);
|
||||
return;
|
||||
}
|
||||
if (args.generateIntermediateCa) {
|
||||
generateIntermediateCa(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.");
|
||||
private static void generateIntermediateCa(YubikeyCaArgs args) {
|
||||
if (checkCertificateArgs(args)) return;
|
||||
if (StringUtil.isEmpty(args.rootCaId)) {
|
||||
log.error("Root CA id is required.");
|
||||
return;
|
||||
}
|
||||
if (StringUtil.isEmpty(args.certSlot)) {
|
||||
log.error("Cert slot is required.");
|
||||
return;
|
||||
}
|
||||
|
||||
final X509Certificate rootCertificate = CertificateUtil.getCertificate(args.pin, args.rootCaId);
|
||||
|
||||
final JSONObject signPivMetaJsonObject = CardCliUtil.getPivMeta(args.certSlot);
|
||||
final String signAlgorithm = signPivMetaJsonObject.getString("algorithm");
|
||||
final String certPublicKeyPem = signPivMetaJsonObject.getString("public_key_pem");
|
||||
final PublicKey certPublicKey = KeyUtil.parsePublicKeyPEM(certPublicKeyPem);
|
||||
|
||||
final String cardCliCmd = CardCliUtil.getCardCliCmd();
|
||||
final X509Certificate intermediateCa = CertificateAuthority.instance()
|
||||
.subject(args.subject)
|
||||
.signCert(rootCertificate)
|
||||
.certPubKey(certPublicKey)
|
||||
.validYears(10)
|
||||
.customerSigner(new CardCliPivCustomerSigner(args.pin, args.signSlot, signAlgorithm, cardCliCmd))
|
||||
.createIntermediateCert();
|
||||
final String intermediateCaPem = X509CertUtil.serializeX509CertificateToPEM(intermediateCa);
|
||||
|
||||
log.info("Issued intermediate CA: " + intermediateCaPem);
|
||||
if (args.addToRemote) {
|
||||
CertificateUtil.addCertificate(args.pin, args.rootCaId, args.memo, intermediateCaPem, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateRootCa(YubikeyCaArgs args) {
|
||||
if (checkCertificateArgs(args)) return;
|
||||
|
||||
final JSONObject signPivMetaJsonObject = CardCliUtil.getPivMeta(args.signSlot);
|
||||
final String signAlgorithm = signPivMetaJsonObject.getString("algorithm");
|
||||
final String certPublicKeyPem = signPivMetaJsonObject.getString("public_key_pem");
|
||||
final PublicKey certPublicKey = KeyUtil.parsePublicKeyPEM(certPublicKeyPem);
|
||||
|
||||
final String cardCliCmd = CardCliUtil.getCardCliCmd();
|
||||
final X509Certificate rootCa = CertificateAuthority.instance()
|
||||
.subject(args.subject)
|
||||
.certPubKey(certPublicKey)
|
||||
.validYears(40)
|
||||
.customerSigner(new CardCliPivCustomerSigner(args.pin, args.signSlot, signAlgorithm, cardCliCmd))
|
||||
.createCA();
|
||||
final String rootCaPem = X509CertUtil.serializeX509CertificateToPEM(rootCa);
|
||||
|
||||
log.info("Issued root CA: " + rootCaPem);
|
||||
if (args.addToRemote) {
|
||||
CertificateUtil.addCertificate(args.pin, null, args.memo, rootCaPem, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkCertificateArgs(YubikeyCaArgs args) {
|
||||
if (StringUtil.isEmpty(args.signSlot)) {
|
||||
log.error("Sign slot is required.");
|
||||
return true;
|
||||
}
|
||||
if (StringUtil.isEmpty(args.subject)) {
|
||||
log.error("Certificate subject is required.");
|
||||
return true;
|
||||
}
|
||||
if (StringUtil.isEmpty(args.pin)) {
|
||||
log.error("PIV PIN is required.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
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));
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void generateKeyPair(YubikeyCaArgs args) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package me.hatter.tools.yubikeyca.cardcli;
|
||||
|
||||
import me.hatter.tools.commons.bytes.Bytes;
|
||||
import me.hatter.tools.crypto.piv.PivCustomerSigner;
|
||||
|
||||
public class CardCliPivCustomerSigner extends PivCustomerSigner {
|
||||
private final String pin;
|
||||
|
||||
public CardCliPivCustomerSigner(String pin, String slot, String algorithm, String cardCli) {
|
||||
super(slot, algorithm, cardCli);
|
||||
this.pin = pin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] signWithPiv(String slot, String algorithm, Bytes digest) {
|
||||
return CardCliUtil.signPiv(pin, slot, algorithm, digest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,12 +40,16 @@ public class CardCliUtil {
|
||||
));
|
||||
}
|
||||
|
||||
public static String getSignRequestSlot() {
|
||||
final String cardCliCmdFromEnv = System.getenv("SIGN_REQUEST_SLOT");
|
||||
final String cardCliCmdFromProperties = System.getProperty("sign.request.slot");
|
||||
return StringUtil.def(cardCliCmdFromEnv, cardCliCmdFromProperties, "82");
|
||||
}
|
||||
|
||||
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");
|
||||
return StringUtil.def(cardCliCmdFromEnv, cardCliCmdFromProperties, "card-cli");
|
||||
}
|
||||
|
||||
protected static JSONObject runCardCliAsJsonObject(List<String> arguments) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package me.hatter.tools.yubikeyca.hatterink;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import me.hatter.tools.commons.bytes.Bytes;
|
||||
import me.hatter.tools.commons.log.LogTool;
|
||||
import me.hatter.tools.commons.log.LogTools;
|
||||
import me.hatter.tools.commons.network.HttpRequest;
|
||||
import me.hatter.tools.commons.security.cert.X509CertUtil;
|
||||
import me.hatter.tools.commons.security.digest.Digests;
|
||||
import me.hatter.tools.commons.string.StringUtil;
|
||||
import me.hatter.tools.yubikeyca.cardcli.CardCliUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
public class CertificateUtil {
|
||||
private static final LogTool log = LogTools.getLogTool(CertificateUtil.class);
|
||||
|
||||
public static void addCertificate(String pin, String parentId, String memo, String certificatePem, String privateKeyPem) {
|
||||
final String authBeforeMillis = String.valueOf(System.currentTimeMillis() + Duration.ofMinutes(5).toMillis());
|
||||
memo = StringUtil.def(memo, "Added at: " + new Date());
|
||||
final String tobeSigned = StringUtil.join(Arrays.asList(
|
||||
authBeforeMillis, parentId, certificatePem, privateKeyPem, memo), ";");
|
||||
final Bytes signBytes = signRequest(pin, tobeSigned);
|
||||
log.info("Auth sign: " + signBytes.asBase64());
|
||||
Bytes response = HttpRequest.fromUrl("https://hatter.ink/ca/add_certificate.json")
|
||||
.postKeyValues()
|
||||
.kv("parentId", parentId, parentId != null)
|
||||
.kv("certificatePem", certificatePem)
|
||||
.kv("privateKeyPem", privateKeyPem, privateKeyPem != null)
|
||||
.kv("memo", memo)
|
||||
.kv("__auth_before", authBeforeMillis)
|
||||
.kv("__auth_keys", "parentId,certificatePem,privateKeyPem,memo")
|
||||
.kv("__auth_sign", signBytes.asBase64())
|
||||
.kv("pretty", "1")
|
||||
.post();
|
||||
final JSONObject responseJsonObject = response.asJSON();
|
||||
final int status = responseJsonObject.getInteger("status");
|
||||
if (status != 200) {
|
||||
throw new RuntimeException("Add certificate failed: " + status + " - " + responseJsonObject.getString("message"));
|
||||
}
|
||||
log.info("Add certificate succeed: " + responseJsonObject.getJSONObject("data"));
|
||||
}
|
||||
|
||||
public static X509Certificate getCertificate(String pin, String id) {
|
||||
final String authBeforeMillis = String.valueOf(System.currentTimeMillis() + Duration.ofMinutes(5).toMillis());
|
||||
final String tobeSigned = authBeforeMillis + ";" + id;
|
||||
|
||||
final Bytes signBytes = signRequest(pin, tobeSigned);
|
||||
log.info("Auth sign: " + signBytes.asBase64());
|
||||
Bytes response = HttpRequest.fromUrl("https://hatter.ink/ca/get_certificate.json")
|
||||
.postKeyValues()
|
||||
.kv("id", id)
|
||||
.kv("__auth_before", authBeforeMillis)
|
||||
.kv("__auth_keys", "id")
|
||||
.kv("__auth_sign", signBytes.asBase64())
|
||||
.kv("pretty", "1")
|
||||
.post();
|
||||
log.info("Got certificate: " + response);
|
||||
final JSONObject responseJsonObject = response.asJSON();
|
||||
final int status = responseJsonObject.getInteger("status");
|
||||
if (status != 200) {
|
||||
throw new RuntimeException("Get certificate failed: " + status + " - " + responseJsonObject.getString("message"));
|
||||
}
|
||||
final String certificatePem = responseJsonObject.getJSONObject("data").getString("certificate");
|
||||
return X509CertUtil.parseX509Certificate(certificatePem);
|
||||
}
|
||||
|
||||
private static Bytes signRequest(String pin, String tobeSigned) {
|
||||
final String signRequestSlot = CardCliUtil.getSignRequestSlot();
|
||||
final Bytes tobeSignedDigestBytes = Digests.sha256().digest(tobeSigned.getBytes(StandardCharsets.UTF_8));
|
||||
final byte[] sign = CardCliUtil.signPiv(pin, signRequestSlot, "p256", tobeSignedDigestBytes);
|
||||
return Bytes.from(sign);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user