All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xipki.security.shell.P12Actions Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2013-2024 xipki. All rights reserved.
// License Apache License 2.0

package org.xipki.security.shell;

import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Completion;
import org.apache.karaf.shell.api.action.Option;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.karaf.shell.support.completers.FileCompleter;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.xipki.password.PasswordResolverException;
import org.xipki.security.ConcurrentContentSigner;
import org.xipki.security.EdECConstants;
import org.xipki.security.SignatureAlgoControl;
import org.xipki.security.SignerConf;
import org.xipki.security.X509Cert;
import org.xipki.security.XiSecurityException;
import org.xipki.security.pkcs12.KeyStoreWrapper;
import org.xipki.security.pkcs12.KeypairWithCert;
import org.xipki.security.pkcs12.KeystoreGenerationParameters;
import org.xipki.security.pkcs12.P12KeyGenerator;
import org.xipki.security.shell.Actions.CsrGenAction;
import org.xipki.security.shell.Actions.SecurityAction;
import org.xipki.security.util.AlgorithmUtil;
import org.xipki.security.util.KeyUtil;
import org.xipki.security.util.X509Util;
import org.xipki.shell.CmdFailure;
import org.xipki.shell.Completers;
import org.xipki.shell.IllegalCmdParamException;
import org.xipki.util.Args;
import org.xipki.util.ConfPairs;
import org.xipki.util.IoUtil;
import org.xipki.util.PemEncoder;
import org.xipki.util.PemEncoder.PemLabel;
import org.xipki.util.StringUtil;
import org.xipki.util.exception.ObjectCreationException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

/**
 * Actions for PKCS#12 security.
 *
 * @author Lijun Liao (xipki)
 */

public class P12Actions {

  @Command(scope = "xi", name = "secretkey-p12", description = "generate secret key in JCEKS (not PKCS#12) keystore")
  @Service
  public static class SecretkeyP12 extends P12KeyGenAction {

    @Option(name = "--key-type", required = true,
        description = "keytype, current only AES, DES3 and GENERIC are supported")
    @Completion(SecurityCompleters.SecretKeyTypeCompleter.class)
     private String keyType;

    @Option(name = "--key-size", required = true, description = "keysize in bit")
    private Integer keysize;

    @Override
    protected Object execute0() throws Exception {
      if (!StringUtil.orEqualsIgnoreCase(keyType, "AES", "DES3", "GENERIC")) {
        throw new IllegalCmdParamException("invalid keyType " + keyType);
      }

      KeyStoreWrapper key = new P12KeyGenerator().generateSecretKey(
          keyType.toUpperCase(), keysize, getKeyGenParameters());
      saveKey(key);

      return null;
    }

  } // class SecretkeyP12

  @Command(scope = "xi", name = "export-cert-p12", description = "export certificate from PKCS#12 keystore")
  @Service
  public static class ExportCertP12 extends P12SecurityAction {

    @Option(name = "--outform", description = "output format of the certificate")
    @Completion(Completers.DerPemCompleter.class)
    protected String outform = "der";

    @Option(name = "--out", aliases = "-o", required = true, description = "where to save the certificate")
    @Completion(FileCompleter.class)
    private String outFile;

    @Override
    protected Object execute0() throws Exception {
      KeyStore ks = getInKeyStore();

      String keyname = null;
      Enumeration aliases = ks.aliases();
      while (aliases.hasMoreElements()) {
        String alias = aliases.nextElement();
        if (ks.isKeyEntry(alias)) {
          keyname = alias;
          break;
        }
      }

      if (keyname == null) {
        throw new CmdFailure("could not find private key");
      }

      X509Certificate cert = (X509Certificate) ks.getCertificate(keyname);
      saveVerbose("saved certificate to file", outFile, encodeCert(cert.getEncoded(), outform));

      return null;
    }

  } // class ExportCertP12

  @Command(scope = "xi", name = "update-cert-p12", description = "update certificate in PKCS#12 keystore")
  @Service
  public static class UpdateCertP12 extends P12SecurityAction {

    @Option(name = "--cert", required = true, description = "certificate file")
    @Completion(FileCompleter.class)
    private String certFile;

    @Option(name = "--ca-cert", multiValued = true, description = "CA Certificate file")
    @Completion(FileCompleter.class)
    private Set caCertFiles;

    @Override
    protected Object execute0() throws Exception {
      KeyStore inKs = getInKeyStore();

      char[] pwd = getPassword();
      X509Cert newCert = X509Util.parseCert(new File(certFile));

      assertMatch(inKs, newCert, new String(pwd));

      String keyname = null;
      Enumeration aliases = inKs.aliases();
      while (aliases.hasMoreElements()) {
        String alias = aliases.nextElement();
        if (inKs.isKeyEntry(alias)) {
          keyname = alias;
          break;
        }
      }

      if (keyname == null) {
        throw new XiSecurityException("could not find private key");
      }

      Key key = inKs.getKey(keyname, pwd);
      Set caCerts = new HashSet<>();
      if (isNotEmpty(caCertFiles)) {
        for (String caCertFile : caCertFiles) {
          caCerts.add(X509Util.parseCert(new File(caCertFile)));
        }
      }
      X509Cert[] certChain = X509Util.buildCertPath(newCert, caCerts);
      Certificate[] jceCertChain = new Certificate[certChain.length];
      for (int i = 0; i < certChain.length; i++) {
        jceCertChain[i] = certChain[i].toJceCert();
      }

      KeyStore outKs = KeyUtil.getOutKeyStore("PKCS12");
      outKs.load(null, null);

      aliases = inKs.aliases();
      while (aliases.hasMoreElements()) {
        String alias = aliases.nextElement();
        if (alias.equalsIgnoreCase(keyname)) {
          outKs.setKeyEntry(keyname, key, pwd, jceCertChain);
        } else {
          if (inKs.isKeyEntry(alias)) {
            outKs.setKeyEntry(alias, inKs.getKey(alias, pwd), pwd, inKs.getCertificateChain(alias));
          } else {
            outKs.setCertificateEntry(alias, inKs.getCertificate(alias));
          }
        }
      }

      try (OutputStream out = Files.newOutputStream(Paths.get(expandFilepath(p12File)))) {
        outKs.store(out, pwd);
        println("updated certificate");
        return null;
      }
    } // method execute0

    private void assertMatch(KeyStore ks, X509Cert cert, String password)
        throws Exception {
      String keyAlgName = cert.getPublicKey().getAlgorithm();
      if (StringUtil.orEqualsIgnoreCase(keyAlgName, EdECConstants.X25519, EdECConstants.X448, "XDH")) {
        // cannot be checked via creating dummy signature, just compare the public keys
        char[] pwd = password.toCharArray();
        KeypairWithCert kp = KeypairWithCert.fromKeystore(ks, null, pwd, null);
        byte[] expectedEncoded = kp.getPublicKey().getEncoded();
        byte[] encoded = cert.getPublicKey().getEncoded();
        if (!Arrays.equals(expectedEncoded, encoded)) {
          throw new XiSecurityException("the certificate and private do not match");
        }
      } else {
        ConfPairs pairs = new ConfPairs("keystore", "file:" + expandFilepath(p12File));
        pairs.putPair("parallelism", "1");
        if (password != null) {
          pairs.putPair("password", password);
        }

        SignerConf conf = new SignerConf(pairs.getEncoded(), null);
        securityFactory.createSigner("PKCS12", conf, cert);
      }
    } // method assertMatch

  } // class UpdateCertP12

  @Command(scope = "xi", name = "csr-p12", description = "generate CSR with PKCS#12 keystore")
  @Service
  public static class CsrP12 extends CsrGenAction {

    @Option(name = "--p12", required = true, description = "PKCS#12 keystore file")
    @Completion(FileCompleter.class)
    private String p12File;

    @Option(name = "--password", description = "password of the PKCS#12 keystore file, as plaintext or PBE-encrypted.")
    private String passwordHint;

    private char[] getPassword() throws IOException, PasswordResolverException {
      char[] pwdInChar = readPasswordIfNotSet("Enter the keystore password", passwordHint);
      if (pwdInChar != null) {
        passwordHint = new String(pwdInChar);
      }
      return pwdInChar;
    }

    public KeyStore getKeyStore()
        throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException,
        PasswordResolverException {
      KeyStore ks;
      try (InputStream in = Files.newInputStream(Paths.get(expandFilepath(p12File)))) {
        ks = KeyUtil.getInKeyStore("PKCS12");
        ks.load(in, getPassword());
      }
      return ks;
    }

    @Override
    protected ConcurrentContentSigner getSigner() throws ObjectCreationException, NoSuchAlgorithmException {
      SignatureAlgoControl signatureAlgoControl = getSignatureAlgoControl();
      char[] pwd;
      try {
        pwd = getPassword();
      } catch (IOException | PasswordResolverException ex) {
        throw new ObjectCreationException("could not read password: " + ex.getMessage(), ex);
      }

      ConfPairs conf = new ConfPairs("password", new String(pwd))
          .putPair("parallelism", Integer.toString(1))
          .putPair("keystore", "file:" + p12File);

      SignerConf signerConf = new SignerConf(conf.getEncoded(), null, signatureAlgoControl);
      try {
        signerConf.setPeerCertificates(getPeerCertificates());
      } catch (CertificateException | IOException ex) {
        throw new ObjectCreationException("error getting peer certificates", ex);
      }
      return securityFactory.createSigner("PKCS12", signerConf, (X509Cert[]) null);
    } // method getSigner

  } // class CsrP12

  @Command(scope = "xi", name = "dsa-p12", description = "generate RSA keypair in PKCS#12 keystore")
  @Service
  public static class DsaP12 extends P12KeyGenAction {

    @Option(name = "--subject", aliases = "-s", description = "subject of the self-signed certificate")
    private String subject;

    @Option(name = "--plen", description = "bit length of the prime")
    private Integer plen = 2048;

    @Option(name = "--qlen", description = "bit length of the sub-prime")
    private Integer qlen;

    @Override
    protected Object execute0() throws Exception {
      if (plen % 1024 != 0) {
        throw new IllegalCmdParamException("plen is not multiple of 1024: " + plen);
      }

      if (qlen == null) {
        qlen = (plen <= 1024) ? 160 : ((plen <= 2048) ? 224 : 256);
      }

      saveKey(new P12KeyGenerator().generateDSAKeypair(plen, qlen, getKeyGenParameters(), subject));

      return null;
    } // method execute0

  } // class DsaP12

  @Command(scope = "xi", name = "ec-p12", description = "generate EC keypair in PKCS#12 keystore")
  @Service
  public static class EcP12 extends P12KeyGenAction {

    @Option(name = "--subject", aliases = "-s", description = "subject of the self-signed certificate")
    protected String subject;

    @Option(name = "--curve", description = "EC curve name or OID")
    @Completion(Completers.ECCurveNameCompleter.class)
    private String curveName = "secp256r1";

    @Override
    protected Object execute0() throws Exception {
      P12KeyGenerator keyGen = new P12KeyGenerator();
      KeystoreGenerationParameters keyGenParams = getKeyGenParameters();

      ASN1ObjectIdentifier curveOid = EdECConstants.getCurveOid(curveName);
      KeyStoreWrapper keypair = (curveOid != null)
          ? keyGen.generateEdECKeypair(curveOid, keyGenParams, subject)
          : keyGen.generateECKeypair(AlgorithmUtil.getCurveOidForCurveNameOrOid(curveName),
          keyGenParams, subject);

      saveKey(keypair);

      return null;
    }

  } // class EcP12

  public abstract static class P12KeyGenAction extends SecurityAction {

    @Option(name = "--out", aliases = "-o", required = true, description = "where to save the key")
    @Completion(FileCompleter.class)
    protected String keyOutFile;

    @Option(name = "--password", description = "password of the keystore file, as plaintext or PBE-encrypted.")
    protected String passwordHint;

    protected void saveKey(KeyStoreWrapper keyGenerationResult) throws IOException {
      saveVerbose("saved PKCS#12 keystore to file", keyOutFile,
          Args.notNull(keyGenerationResult, "keyGenerationResult").keystore());
    }

    protected KeystoreGenerationParameters getKeyGenParameters() throws IOException, PasswordResolverException {
      KeystoreGenerationParameters params = new KeystoreGenerationParameters(getPassword());

      SecureRandom random = securityFactory.getRandom4Key();
      if (random != null) {
        params.setRandom(random);
      }

      return params;
    }

    private char[] getPassword() throws IOException, PasswordResolverException {
      char[] pwdInChar = readPasswordIfNotSet("Enter the keystore password", passwordHint);
      if (pwdInChar != null) {
        passwordHint = new String(pwdInChar);
      }
      return pwdInChar;
    }

  } // class P12KeyGenAction

  @Command(scope = "xi", name = "rsa-p12", description = "generate RSA keypair in PKCS#12 keystore")
  @Service
  public static class RsaP12 extends P12KeyGenAction {

    @Option(name = "--subject", aliases = "-s", description = "subject of the self-signed certificate")
    private String subject;

    @Option(name = "--key-size", description = "keysize in bit")
    private Integer keysize = 2048;

    @Option(name = "-e", description = "public exponent")
    private String publicExponent = Actions.TEXT_F4;

    @Override
    protected Object execute0() throws Exception {
      if (keysize % 1024 != 0) {
        throw new IllegalCmdParamException("keysize is not multiple of 1024: " + keysize);
      }

      saveKey(new P12KeyGenerator().generateRSAKeypair(
          keysize, toBigInt(publicExponent), getKeyGenParameters(), subject));
      return null;
    }

  } // class RsaP12

  public abstract static class P12SecurityAction extends SecurityAction {

    @Option(name = "--p12", required = true, description = "PKCS#12 keystore file")
    @Completion(FileCompleter.class)
    protected String p12File;

    @Option(name = "--password", description = "password of the PKCS#12 file, as plaintext or PBE-encrypted.")
    protected String passwordHint;

    protected char[] getPassword() throws IOException, PasswordResolverException {
      char[] pwdInChar = readPasswordIfNotSet("Enter the keystore password", passwordHint);
      if (pwdInChar != null) {
        passwordHint = new String(pwdInChar);
      }
      return pwdInChar;
    }

    protected KeyStore getInKeyStore()
        throws IOException, NoSuchAlgorithmException, CertificateException, KeyStoreException,
        PasswordResolverException {
      try (InputStream in = Files.newInputStream(Paths.get(expandFilepath(p12File)))) {
        KeyStore ks = KeyUtil.getInKeyStore("PKCS12");
        ks.load(in, getPassword());
        return ks;
      }
    }

  } // class P12SecurityAction

  @Command(scope = "xi", name = "sm2-p12", description = "generate SM2 (curve sm2p256v1) keypair in PKCS#12 keystore")
  @Service
  public static class Sm2P12 extends P12KeyGenAction {

    @Option(name = "--subject", aliases = "-s", description = "subject of the self-signed certificate")
    protected String subject;

    @Override
    protected Object execute0() throws Exception {
      saveKey(new P12KeyGenerator().generateECKeypair(
          GMObjectIdentifiers.sm2p256v1, getKeyGenParameters(), subject));
      return null;
    }

  } // class Sm2P12

  @Command(scope = "xi", name = "pkcs12", description = "export PKCS#12 key store, like the 'openssl pkcs12' command")
  @Service
  public static class Pkcs12 extends P12SecurityAction {

    @Option(name = "--key-out", required = true, description = "where to save the key")
    @Completion(FileCompleter.class)
    private String keyOutFile;

    @Option(name = "--cert-out", required = true, description = "where to save the certificate")
    @Completion(FileCompleter.class)
    private String certOutFile;

    @Override
    protected Object execute0() throws Exception {
      char[] password = getPassword();
      try (InputStream keystoreStream = Files.newInputStream(Paths.get(expandFilepath(p12File)))) {
        KeypairWithCert kp = KeypairWithCert.fromKeystore("PKCS12",
                              keystoreStream, password, null, password, (X509Cert) null);
        byte[] encodedKey = PemEncoder.encode(kp.getKey().getEncoded(), PemLabel.PRIVATE_KEY);
        byte[] encodedCert = PemEncoder.encode(kp.getCertificateChain()[0].getEncoded(), PemLabel.CERTIFICATE);
        IoUtil.save(keyOutFile, encodedKey);
        IoUtil.save(certOutFile, encodedCert);
      }
      return null;
    }

  } // class Pkcs12

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy