org.xipki.security.shell.P11Actions 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.Reference;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.karaf.shell.support.completers.FileCompleter;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.password.PasswordResolverException;
import org.xipki.pkcs11.wrapper.PKCS11KeyId;
import org.xipki.pkcs11.wrapper.TokenException;
import org.xipki.security.ConcurrentContentSigner;
import org.xipki.security.EdECConstants;
import org.xipki.security.HashAlgo;
import org.xipki.security.SignatureAlgoControl;
import org.xipki.security.SignerConf;
import org.xipki.security.X509Cert;
import org.xipki.security.XiSecurityException;
import org.xipki.security.pkcs11.P11CryptService;
import org.xipki.security.pkcs11.P11CryptServiceFactory;
import org.xipki.security.pkcs11.P11Module;
import org.xipki.security.pkcs11.P11Slot;
import org.xipki.security.pkcs11.P11Slot.P11NewKeyControl;
import org.xipki.security.pkcs11.P11SlotId;
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.shell.Completers;
import org.xipki.shell.IllegalCmdParamException;
import org.xipki.util.Args;
import org.xipki.util.CollectionUtil;
import org.xipki.util.ConfPairs;
import org.xipki.util.Hex;
import org.xipki.util.IoUtil;
import org.xipki.util.StringUtil;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Key;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import static org.xipki.pkcs11.wrapper.PKCS11Constants.CKK_AES;
import static org.xipki.pkcs11.wrapper.PKCS11Constants.CKK_DES3;
import static org.xipki.pkcs11.wrapper.PKCS11Constants.CKK_GENERIC_SECRET;
/**
* Actions for PKCS#11 security.
*
* @author Lijun Liao (xipki)
*/
public class P11Actions {
@Command(scope = "xi", name = "csr-p11", description = "generate CSR request with PKCS#11 device")
@Service
public static class CsrP11 extends CsrGenAction {
@Option(name = "--slot", description = "slot index")
private String slotIndex = "0"; // use String instead int so that the default value 0 will be shown in the help.
@Option(name = "--id", description = "id (hex) of the private key in the PKCS#11 device\n"
+ "either keyId or keyLabel must be specified")
private String id;
@Option(name = "--label", description = "label of the private key in the PKCS#11 device\n"
+ "either keyId or keyLabel must be specified")
private String label;
@Option(name = "--module", description = "name of the PKCS#11 module")
@Completion(SecurityCompleters.P11ModuleNameCompleter.class)
private String moduleName = "default";
@Override
protected ConcurrentContentSigner getSigner() throws Exception {
SignatureAlgoControl signatureAlgoControl = getSignatureAlgoControl();
byte[] idBytes = null;
if (id != null) {
idBytes = Hex.decode(id);
}
SignerConf conf = getPkcs11SignerConf(moduleName, Integer.parseInt(slotIndex), label,
idBytes, 1, null, signatureAlgoControl);
return securityFactory.createSigner("PKCS11", conf, (X509Cert[]) null);
}
public static SignerConf getPkcs11SignerConf(
String pkcs11ModuleName, int slotIndex, String keyLabel, byte[] keyId, int parallelism,
HashAlgo hashAlgo, SignatureAlgoControl signatureAlgoControl) {
Args.positive(parallelism, "parallelism");
if (keyId == null && keyLabel == null) {
throw new IllegalArgumentException("at least one of keyId and keyLabel may not be null");
}
ConfPairs conf = new ConfPairs();
conf.putPair("parallelism", Integer.toString(parallelism));
if (pkcs11ModuleName != null && !pkcs11ModuleName.isEmpty()) {
conf.putPair("module", pkcs11ModuleName);
}
conf.putPair("slot", Integer.toString(slotIndex));
if (keyId != null) {
conf.putPair("key-id", Hex.encode(keyId));
}
if (keyLabel != null) {
conf.putPair("key-label", keyLabel);
}
return new SignerConf(conf.getEncoded(), hashAlgo, signatureAlgoControl);
} // method getPkcs11SignerConf
} // class CsrP11
@Command(scope = "xi", name = "dsa-p11", description = "generate DSA keypair in PKCS#11 device")
@Service
public static class Dsa11 extends P11KeyGenAction {
@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);
}
P11Slot slot = getSlot();
finalize("DSA", slot.generateDSAKeypair(plen, qlen, getControl()));
return null;
}
} // method Dsa11
@Command(scope = "xi", name = "ec-p11", description = "generate EC keypair in PKCS#11 device")
@Service
public static class EcP11 extends P11KeyGenAction {
@Option(name = "--curve", description = "EC curve name")
@Completion(Completers.ECCurveNameCompleter.class)
private String curveName = "secp256r1";
@Override
protected Object execute0() throws Exception {
P11Slot slot = getSlot();
P11NewKeyControl control = getControl();
ASN1ObjectIdentifier curveOid = EdECConstants.getCurveOid(curveName);
if (curveOid == null) {
curveOid = AlgorithmUtil.getCurveOidForCurveNameOrOid(curveName);
}
if (curveOid == null) {
throw new Exception("unknown curve " + curveName);
}
finalize("EC", slot.generateECKeypair(curveOid, control));
return null;
}
} // class EcP11
@Command(scope = "xi", name = "delete-key-p11", description = "delete key in PKCS#11 device")
@Service
public static class DeleteKeyP11 extends P11SecurityAction {
@Option(name = "--id", description = "id (hex) of the private key in the PKCS#11 device\n"
+ "either keyId or keyLabel must be specified")
protected String id;
@Option(name = "--label", description = "label of the private key in the PKCS#11 device\n"
+ "either keyId or keyLabel must be specified")
protected String label;
@Option(name = "--force", aliases = "-f", description = "remove identifies without prompt")
private Boolean force = Boolean.FALSE;
@Override
protected Object execute0() throws Exception {
PKCS11KeyId identity = getIdentity(id, label);
if (identity == null) {
println("unknown identity");
return null;
}
if (force || confirm("Do you want to remove the identity " + identity, 3)) {
Long publicKeyHandle = identity.getPublicKeyHandle();
long[] failedHandles;
if (publicKeyHandle == null) {
failedHandles = getSlot().destroyObjectsByHandle(identity.getHandle());
} else {
failedHandles = getSlot().destroyObjectsByHandle(identity.getHandle(), publicKeyHandle);
}
if (failedHandles == null || failedHandles.length == 0) {
println("deleted identity " + identity);
} else {
println("error deleting identity " + identity);
}
}
return null;
}
} // class DeleteKeyP11
@Command(scope = "xi", name = "object-exists-p11", description = "return whether objects exist in PKCS#11 device")
@Service
public static class ObjecctExistsP11 extends P11SecurityAction {
@Option(name = "--id", description = "id (hex) of the object in the PKCS#11 device\n"
+ "either keyId or keyLabel must be specified")
protected String id;
@Option(name = "--label", description = "label of the object key in the PKCS#11 device\n"
+ "either id or label must be specified")
protected String label;
@Override
protected Object execute0() throws Exception {
byte[] idBytes = id == null ? null : Hex.decode(id);
return getSlot().objectExistsByIdLabel(idBytes, label);
}
} // class KeyExistsP11
public abstract static class P11KeyGenAction extends P11SecurityAction {
@Option(name = "--id", description = "id (hex) of the PKCS#11 objects")
private String id;
@Option(name = "--label", required = true, description = "label of the PKCS#11 objects")
protected String label;
@Option(name = "--extractable", aliases = {"-x"},
description = "whether the key is extractable, valid values are yes|no|true|false")
private String extractable;
@Option(name = "--sensitive", description = "whether the key is sensitive, valid values are yes|no|true|false")
private String sensitive;
@Option(name = "--key-usage", multiValued = true, description = "key usage of the private key")
@Completion(SecurityCompleters.P11KeyUsageCompleter.class)
private List keyusages;
protected void finalize(String keyType, PKCS11KeyId keyId) {
Args.notNull(keyId, "keyId");
println("generated " + keyType + " key " + keyId + " on slot " + slotIndex);
}
protected P11NewKeyControl getControl() {
byte[] id0 = (id == null) ? null : Hex.decode(id);
P11NewKeyControl control = new P11NewKeyControl(id0, label);
if (StringUtil.isNotBlank(extractable)) {
control.setExtractable(isEnabled(extractable, false, "extractable"));
}
if (StringUtil.isNotBlank(sensitive)) {
control.setSensitive(isEnabled(sensitive, false, "sensitive"));
}
if (CollectionUtil.isNotEmpty(keyusages)) {
control.setUsages(SecurityCompleters.P11KeyUsageCompleter.parseUsages(keyusages));
}
return control;
}
} // class P11KeyGenAction
@Command(scope = "xi", name = "delete-objects-p11", description = "delete objects in PKCS#11 device")
@Service
public static class DeleteObjectsP11 extends P11SecurityAction {
@Option(name = "--handle", aliases = "-h", multiValued = true,
description = "Object handle, if specified, id and label must not be set")
private long[] handles;
@Option(name = "--id", description = "id (hex) of the objects in the PKCS#11 device\n"
+ "at least one of id and label must be specified (if handle is not set).")
private String id;
@Option(name = "--label", description = "label of the objects in the PKCS#11 device\n"
+ "at least one of id and label must be specified (if handle is not set).")
private String label;
@Option(name = "--force", aliases = "-f", description = "remove identifies without prompt")
private Boolean force = Boolean.FALSE;
@Override
protected Object execute0() throws Exception {
if (handles != null && handles.length > 0) {
if (id != null || label != null) {
throw new IllegalCmdParamException("If handle is set, id an label must not be set.");
}
if (force || confirm("Do you want to remove the PKCS#11 objects " + Arrays.toString(handles), 3)) {
P11Slot slot = getSlot();
long[] failedHandles = slot.destroyObjectsByHandle(handles);
if (failedHandles.length == 0) {
println("deleted all " + handles.length + " objects");
} else {
println("deleted " + (handles.length - failedHandles.length) + " objects except " +
failedHandles.length + " objects: " + Arrays.toString(failedHandles));
}
}
} else {
if (id == null && label == null) {
throw new IllegalCmdParamException("If handle is not set, at least one of id and label must be set.");
}
if (force || confirm("Do you want to remove the PKCS#11 objects (id = " + id
+ ", label = " + label + ")", 3)) {
P11Slot slot = getSlot();
byte[] idBytes = null;
int num;
if (id != null) {
idBytes = Hex.decode(id);
if (label == null) {
num = slot.destroyObjectsById(idBytes);
} else {
num = slot.destroyObjectsByIdLabel(idBytes, label);
}
} else {
num = slot.destroyObjectsByLabel(label);
}
println("deleted " + num + " objects");
}
}
return null;
}
} // class DeleteObjectsP11
@Command(scope = "xi", name = "delete-all-objects-p11", description = "delete all objects in PKCS#11 device")
@Service
public static class DeleteAllObjectsP11 extends P11SecurityAction {
@Override
protected Object execute0() throws Exception {
String prompt = "!!!DANGEROUS OPERATION!!!, do you want to remove ALL PKCS#11 objects";
// this is not a bug to require 3 confirmations.
if (confirm(prompt, 1)) {
if (confirm(prompt, 1)) {
if (confirm(prompt, 1)) {
P11Slot slot = getSlot();
int num = slot.destroyAllObjects();
System.out.println("Destroyed " + num + " objects!");
}
}
}
return null;
}
} // class DeleteAllObjectsP11
@Command(scope = "xi", name = "rsa-p11", description = "generate RSA keypair in PKCS#11 device")
@Service
public static class RsaP11 extends P11KeyGenAction {
@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);
}
P11Slot slot = getSlot();
finalize("RSA", slot.generateRSAKeypair(keysize, toBigInt(publicExponent), getControl()));
return null;
}
} // class RsaP11
@Command(scope = "xi", name = "secretkey-p11", description = "generate secret key in PKCS#11 device")
@Service
public static class SecretkeyP11 extends P11KeyGenAction {
private static final Logger LOG = LoggerFactory.getLogger(SecretkeyP11.class);
@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", description = "keysize in bit")
private Integer keysize;
@Option(name = "--extern-if-gen-unsupported",
description = "If set, if the generation mechanism is not supported by the PKCS#11 "
+ "device, create in memory and then import it to the device")
private Boolean createExternIfGenUnsupported = Boolean.FALSE;
@Override
protected Object execute0() throws Exception {
if (keysize != null && keysize % 8 != 0) {
throw new IllegalCmdParamException("keysize is not multiple of 8: " + keysize);
}
long p11KeyType;
if ("AES".equalsIgnoreCase(keyType)) {
p11KeyType = CKK_AES;
} else if ("DES3".equalsIgnoreCase(keyType)) {
p11KeyType = CKK_DES3;
keysize = 192;
} else if ("GENERIC".equalsIgnoreCase(keyType)) {
p11KeyType = CKK_GENERIC_SECRET;
} else {
throw new IllegalCmdParamException("invalid keyType " + keyType);
}
if (keysize == null) {
throw new IllegalCmdParamException("key-size is not specified");
}
P11Slot slot = getSlot();
P11NewKeyControl control = getControl();
try {
finalize(keyType, slot.generateSecretKey(p11KeyType, keysize, control));
} catch (TokenException ex) {
if (!createExternIfGenUnsupported) {
throw ex;
}
String msgPrefix = "could not generate secret key ";
if (control.getId() != null) {
msgPrefix += "id=" + Hex.encode(control.getId());
if (control.getLabel() != null) {
msgPrefix += " and ";
}
}
if (control.getLabel() != null) {
msgPrefix += "label=" + control.getLabel();
}
if (LOG.isInfoEnabled()) {
LOG.info(msgPrefix + ex.getMessage());
}
if (LOG.isDebugEnabled()) {
LOG.debug(msgPrefix, ex);
}
byte[] keyValue = new byte[keysize / 8];
securityFactory.getRandom4Key().nextBytes(keyValue);
PKCS11KeyId objId = slot.importSecretKey(p11KeyType, keyValue, control);
Arrays.fill(keyValue, (byte) 0); // clear the memory
String msg = "generated in memory and imported " + keyType + " key " + objId;
if (LOG.isInfoEnabled()) {
LOG.info(msg);
}
println(msg);
}
return null;
} // method execute0
} // class SecretkeyP11
@Command(scope = "xi", name = "import-secretkey-p11",
description = "import secret key with given value in PKCS#11 device")
@Service
public static class ImportSecretkeyP11 extends P11KeyGenAction {
@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 = "--keystore", required = true, description = "JCEKS keystore from which the key is imported")
@Completion(FileCompleter.class)
private String keyOutFile;
@Option(name = "--password", description = "password of the keystore file, as plaintext or PBE-encrypted.")
private String passwordHint;
@Override
protected Object execute0() throws Exception {
long p11KeyType;
if ("AES".equalsIgnoreCase(keyType)) {
p11KeyType = CKK_AES;
} else if ("DES3".equalsIgnoreCase(keyType)) {
p11KeyType = CKK_DES3;
} else if ("GENERIC".equalsIgnoreCase(keyType)) {
p11KeyType = CKK_GENERIC_SECRET;
} else {
throw new IllegalCmdParamException("invalid keyType " + keyType);
}
KeyStore ks = KeyUtil.getInKeyStore("JCEKS");
InputStream ksStream = Files.newInputStream(Paths.get(IoUtil.expandFilepath(keyOutFile)));
char[] pwd = getPassword();
try {
ks.load(ksStream, pwd);
} finally {
ksStream.close();
}
byte[] keyValue = null;
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
if (!ks.isKeyEntry(alias)) {
continue;
}
Key key = ks.getKey(alias, pwd);
if (key instanceof SecretKey) {
keyValue = key.getEncoded();
break;
}
}
if (keyValue == null) {
throw new IllegalCmdParamException("keystore does not contain secret key");
}
P11Slot slot = getSlot();
PKCS11KeyId objId = slot.importSecretKey(p11KeyType, keyValue, getControl());
println("imported " + keyType + " key " + objId);
return null;
} // method execute0
protected char[] getPassword() throws IOException, PasswordResolverException {
char[] pwdInChar = readPasswordIfNotSet("Enter the keystore password", passwordHint);
if (pwdInChar != null) {
passwordHint = new String(pwdInChar);
}
return pwdInChar;
}
} // class ImportSecretkeyP11
public abstract static class P11SecurityAction extends SecurityAction {
protected static final String DEFAULT_P11MODULE_NAME = P11CryptServiceFactory.DEFAULT_P11MODULE_NAME;
@Option(name = "--slot", description = "slot index")
protected String slotIndex = "0"; // use String instead int so that the default value 0 will be shown in the help.
@Option(name = "--module", description = "name of the PKCS#11 module")
@Completion(SecurityCompleters.P11ModuleNameCompleter.class)
protected String moduleName = DEFAULT_P11MODULE_NAME;
@Reference (optional = true)
protected P11CryptServiceFactory p11CryptServiceFactory;
protected P11Slot getSlot() throws XiSecurityException, TokenException, IllegalCmdParamException {
P11Module module = getP11Module(moduleName);
P11SlotId slotId = module.getSlotIdForIndex(Integer.parseInt(slotIndex));
return module.getSlot(slotId);
}
protected P11Module getP11Module(String moduleName)
throws XiSecurityException, TokenException, IllegalCmdParamException {
P11CryptService p11Service = p11CryptServiceFactory.getP11CryptService(moduleName);
if (p11Service == null) {
throw new IllegalCmdParamException("undefined module " + moduleName);
}
return p11Service.getModule();
}
public PKCS11KeyId getIdentity(String hexId, String label)
throws IllegalCmdParamException, XiSecurityException, TokenException {
P11Slot slot = getSlot();
byte[] id = hexId == null ? null : Hex.decode(hexId);
return slot.getKeyId(id, label);
}
} // class P11SecurityAction
@Command(scope = "xi", name = "sm2-p11", description = "generate SM2 (curve sm2p256v1) keypair in PKCS#11 device")
@Service
public static class Sm2P11 extends P11KeyGenAction {
@Override
protected Object execute0() throws Exception {
P11Slot slot = getSlot();
finalize("SM2", slot.generateSM2Keypair(getControl()));
return null;
}
} // class Sm2P11
@Command(scope = "xi", name = "token-info-p11", description = "list objects in PKCS#11 device")
@Service
public static class TokenInfoP11 extends SecurityAction {
@Option(name = "--verbose", aliases = "-v", description = "show object information verbosely")
private Boolean verbose = Boolean.FALSE;
@Option(name = "--module", description = "name of the PKCS#11 module.")
@Completion(SecurityCompleters.P11ModuleNameCompleter.class)
private String moduleName = P11SecurityAction.DEFAULT_P11MODULE_NAME;
@Option(name = "--slot", description = "slot index")
private Integer slotIndex;
@Option(name = "--object", description = "object handle")
private Long objectHandle;
@Reference (optional = true)
protected P11CryptServiceFactory p11CryptServiceFactory;
@Override
protected Object execute0() throws Exception {
P11CryptService p11Service = p11CryptServiceFactory.getP11CryptService(moduleName);
if (p11Service == null) {
throw new IllegalCmdParamException("undefined module " + moduleName);
}
P11Module module = p11Service.getModule();
println("module: " + moduleName);
println(module.getDescription());
List slots = module.getSlotIds();
if (slotIndex == null) {
output(slots);
return null;
}
P11SlotId slotId = module.getSlotIdForIndex(slotIndex);
P11Slot slot = module.getSlot(slotId);
println("Details of slot " + slotId + ":");
slot.showDetails(System.out, objectHandle, verbose);
System.out.flush();
System.out.println();
return null;
}
private void output(List slots) {
// list all slots
final int n = slots.size();
if (n == 0 || n == 1) {
String numText = (n == 0) ? "no" : "1";
println(numText + " slot is configured");
} else {
println(n + " slots are configured");
}
for (P11SlotId slotId : slots) {
println("\tslot[" + slotId.getIndex() + "]: " + slotId.getId());
}
}
} // class TokenInfoP11
}