/* * * Copyright (c) 2013 - 2020 Lijun Liao * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xipki.security.shell; import java.io.File; 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.Enumeration; import java.util.List; import javax.crypto.SecretKey; 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.bouncycastle.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.P11IdentityId; import org.xipki.security.pkcs11.P11Module; import org.xipki.security.pkcs11.P11ObjectIdentifier; import org.xipki.security.pkcs11.P11Slot; import org.xipki.security.pkcs11.P11Slot.P11NewKeyControl; import org.xipki.security.pkcs11.P11Slot.P11NewObjectControl; import org.xipki.security.pkcs11.P11SlotIdentifier; import org.xipki.security.pkcs11.P11TokenException; import org.xipki.security.pkcs11.P11UnsupportedMechanismException; 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.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.CollectionUtil; import org.xipki.util.ConfPairs; import org.xipki.util.Hex; import org.xipki.util.IoUtil; import org.xipki.util.StringUtil; import iaik.pkcs.pkcs11.wrapper.PKCS11Constants; /** * Actions for PKCS#11 security. * * @author Lijun Liao */ public class P11Actions { @Command(scope = "xi", name = "add-cert-p11", description = "add certificate to PKCS#11 device") @Service public static class AddCertP11 extends P11SecurityAction { @Option(name = "--id", description = "id of the PKCS#11 objects") private String hexId; @Option(name = "--label", description = "label of the PKCS#11 objects.") protected String label; @Option(name = "--cert", required = true, description = "certificate file") @Completion(FileCompleter.class) private String certFile; @Override protected Object execute0() throws Exception { byte[] id = (hexId == null) ? null : Hex.decode(hexId); X509Cert cert = X509Util.parseCert(new File(certFile)); if (label == null) { label = X509Util.getCommonName(cert.getSubject()); } P11NewObjectControl control = new P11NewObjectControl(id, label); P11Slot slot = getSlot(); P11ObjectIdentifier objectId = slot.addCert(cert, control); println("added certificate under " + objectId); return null; } } // class AddCertP11 @Command(scope = "xi", name = "delete-cert-p11", description = "remove certificate from PKCS#11 device") @Service public static class DeleteCertP11 extends P11SecurityAction { @Option(name = "--id", required = true, description = "id of the certificate in the PKCS#11 device") private String id; @Option(name = "--force", aliases = "-f", description = "without prompt") private Boolean force = Boolean.FALSE; @Override protected Object execute0() throws Exception { if (force || confirm("Do you want to remove PKCS#11 certificate object with id " + id, 3)) { P11Slot slot = getSlot(); P11ObjectIdentifier objectId = slot.getObjectId(Hex.decode(id), null); if (objectId == null) { println("unkown certificates"); } else { slot.removeCerts(objectId); println("deleted certificates"); } } return null; } } // class DeleteCertP11 @Command(scope = "xi", name = "export-cert-p11", description = "export certificate from PKCS#11 device") @Service public static class ExportCertP11 extends P11SecurityAction { @Option(name = "--id", description = "id 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 = "--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 { P11Slot slot = getSlot(); P11ObjectIdentifier objIdentifier = getObjectIdentifier(id, label); X509Cert cert = slot.exportCert(objIdentifier); if (cert == null) { throw new CmdFailure("could not export certificate " + objIdentifier); } saveVerbose("saved certificate to file", outFile, encodeCert(cert.getEncoded(), outform)); return null; } } // class ExportCertP11 @Command(scope = "xi", name = "update-cert-p11", description = "update certificate in PKCS#11 device") @Service public static class UpdateCertP11 extends P11SecurityAction { @Option(name = "--id", description = "id 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 = "--cert", required = true, description = "certificate file") @Completion(FileCompleter.class) private String certFile; @Override protected Object execute0() throws Exception { P11Slot slot = getSlot(); P11ObjectIdentifier objIdentifier = getObjectIdentifier(id, label); X509Cert newCert = X509Util.parseCert(new File(certFile)); slot.updateCertificate(objIdentifier, newCert); println("updated certificate"); return null; } } // class UpdateCertP11 @Command(scope = "xi", name = "csr-p11", description = "generate CSR request with PKCS#11 device") @Service public static class CsrP11 extends CsrGenAction { @Option(name = "--slot", required = true, description = "slot index") private Integer slotIndex; @Option(name = "--id", description = "id 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(SignatureAlgoControl signatureAlgoControl) throws Exception { Args.notNull(signatureAlgoControl, "signatureAlgoControl"); byte[] idBytes = null; if (id != null) { idBytes = Hex.decode(id); } SignerConf conf = getPkcs11SignerConf(moduleName, slotIndex, label, idBytes, 1, HashAlgo.getNonNullInstance(hashAlgo), signatureAlgoControl); return securityFactory.createSigner("PKCS11", conf, (X509Cert[]) null); } public static SignerConf getPkcs11SignerConf(String pkcs11ModuleName, Integer slotIndex, String keyLabel, byte[] keyId, int parallelism, HashAlgo hashAlgo, SignatureAlgoControl signatureAlgoControl) { Args.positive(parallelism, "parallelism"); Args.notNull(hashAlgo, "hashAlgo"); if (slotIndex == null) { throw new IllegalArgumentException("slotIndex may not be null"); } 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.length() > 0) { conf.putPair("module", pkcs11ModuleName); } if (slotIndex != null) { conf.putPair("slot", slotIndex.toString()); } 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) { if (plen <= 1024) { qlen = 160; } else if (plen <= 2048) { qlen = 224; } else { qlen = 256; } } P11Slot slot = getSlot(); P11IdentityId identityId = slot.generateDSAKeypair(plen, qlen, getControl()); finalize("DSA", identityId); 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(); P11IdentityId identityId; ASN1ObjectIdentifier curveOid = EdECConstants.getCurveOid(curveName); if (curveOid != null) { if (EdECConstants.isEdwardsCurve(curveOid)) { identityId = slot.generateECEdwardsKeypair(curveOid, control); } else { // Montegomery Curve identityId = slot.generateECMontgomeryKeypair(curveOid, control); } } else { curveOid = AlgorithmUtil.getCurveOidForCurveNameOrOid(curveName); identityId = slot.generateECKeypair(curveOid, control); } finalize("EC", identityId); return null; } } // class EcP11 @Command(scope = "xi", name = "delete-key-p11", description = "delete key and cert in PKCS#11 device") @Service public static class DeleteKeyP11 extends P11SecurityAction { @Option(name = "--id", description = "id 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 { P11Slot slot = getSlot(); P11ObjectIdentifier keyId = getObjectIdentifier(id, label); if (keyId == null) { println("unkown identity"); return null; } if (force || confirm("Do you want to remove the identity " + keyId, 3)) { slot.removeIdentityByKeyId(keyId); println("deleted identity " + keyId); } return null; } } // class DeleteKeyP11 public abstract static class P11KeyGenAction extends P11SecurityAction { @Option(name = "--id", description = "id of the PKCS#11 objects") private String hexId; @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<String> keyusages; protected void finalize(String keyType, P11IdentityId identityId) throws Exception { Args.notNull(identityId, "identityId"); println("generated " + keyType + " key \"" + identityId + "\""); } protected P11NewKeyControl getControl() throws IllegalCmdParamException { byte[] id = (hexId == null) ? null : Hex.decode(hexId); P11NewKeyControl control = new P11NewKeyControl(id, 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 = "--id", description = "id (hex) of the objects in the PKCS#11 device\n" + "at least one of id and label must be specified") 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") 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 (force || confirm("Do you want to remove the PKCS#11 objects (id = " + id + ", label = " + label + ")", 3)) { P11Slot slot = getSlot(); byte[] idBytes = null; if (id != null) { idBytes = Hex.decode(id); } int num = slot.removeObjects(idBytes, label); println("deleted " + num + " objects"); } return null; } } // class DeleteObjectsP11 @Command(scope = "xi", name = "refresh-p11", description = "refresh PKCS#11 module") @Service public static class RefreshP11 extends SecurityAction { @Option(name = "--module", description = "name of the PKCS#11 module.") @Completion(SecurityCompleters.P11ModuleNameCompleter.class) private String moduleName = P11SecurityAction.DEFAULT_P11MODULE_NAME; @Reference P11CryptServiceFactory p11CryptServiceFactory; @Override protected Object execute0() throws Exception { P11CryptService p11Service = p11CryptServiceFactory.getP11CryptService(moduleName); if (p11Service == null) { throw new IllegalCmdParamException("undefined module " + moduleName); } p11Service.refresh(); println("refreshed module " + moduleName); return null; } } // class RefreshP11 @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 = "0x10001"; @Override protected Object execute0() throws Exception { if (keysize % 1024 != 0) { throw new IllegalCmdParamException("keysize is not multiple of 1024: " + keysize); } P11Slot slot = getSlot(); P11IdentityId identityId = slot.generateRSAKeypair(keysize, toBigInt(publicExponent), getControl()); finalize("RSA", identityId); 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", required = true, 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 % 8 != 0) { throw new IllegalCmdParamException("keysize is not multiple of 8: " + keysize); } long p11KeyType; if ("AES".equalsIgnoreCase(keyType)) { p11KeyType = PKCS11Constants.CKK_AES; } else if ("DES3".equalsIgnoreCase(keyType)) { p11KeyType = PKCS11Constants.CKK_DES3; } else if ("GENERIC".equalsIgnoreCase(keyType)) { p11KeyType = PKCS11Constants.CKK_GENERIC_SECRET; } else { throw new IllegalCmdParamException("invalid keyType " + keyType); } P11Slot slot = getSlot(); P11NewKeyControl control = getControl(); P11IdentityId identityId = null; try { identityId = slot.generateSecretKey(p11KeyType, keysize, control); finalize(keyType, identityId); } catch (P11UnsupportedMechanismException 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); P11ObjectIdentifier objId = slot.importSecretKey(p11KeyType, keyValue, control); Arrays.fill(keyValue, (byte) 0); // clear the memory println("generated in memory and imported " + keyType + " key " + objId); } 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") private String password; @Override protected Object execute0() throws Exception { long p11KeyType; if ("AES".equalsIgnoreCase(keyType)) { p11KeyType = PKCS11Constants.CKK_AES; } else if ("DES3".equalsIgnoreCase(keyType)) { p11KeyType = PKCS11Constants.CKK_DES3; } else if ("GENERIC".equalsIgnoreCase(keyType)) { p11KeyType = PKCS11Constants.CKK_GENERIC_SECRET; } else { throw new IllegalCmdParamException("invalid keyType " + keyType); } KeyStore ks = KeyStore.getInstance("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<String> 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 = ((SecretKey) key).getEncoded(); break; } } if (keyValue == null) { throw new IllegalCmdParamException("keystore does not contain secret key"); } P11Slot slot = getSlot(); P11ObjectIdentifier objId = slot.importSecretKey(p11KeyType, keyValue, getControl()); println("imported " + keyType + " key " + objId); return null; } // method execute0 protected char[] getPassword() throws IOException { char[] pwdInChar = readPasswordIfNotSet(password); if (pwdInChar != null) { password = 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", required = true, description = "slot index") protected Integer slotIndex; @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, P11TokenException, IllegalCmdParamException { P11Module module = getP11Module(moduleName); P11SlotIdentifier slotId = module.getSlotIdForIndex(slotIndex); return module.getSlot(slotId); } protected P11Module getP11Module(String moduleName) throws XiSecurityException, P11TokenException, IllegalCmdParamException { P11CryptService p11Service = p11CryptServiceFactory.getP11CryptService(moduleName); if (p11Service == null) { throw new IllegalCmdParamException("undefined module " + moduleName); } return p11Service.getModule(); } public P11ObjectIdentifier getObjectIdentifier(String hexId, String label) throws IllegalCmdParamException, XiSecurityException, P11TokenException { P11Slot slot = getSlot(); P11ObjectIdentifier objIdentifier; if (hexId != null && label == null) { objIdentifier = slot.getObjectId(Hex.decode(hexId), null); } else if (hexId == null && label != null) { objIdentifier = slot.getObjectId(null, label); } else { throw new IllegalCmdParamException( "exactly one of keyId or keyLabel should be specified"); } return objIdentifier; } } // 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(); P11IdentityId identityId = slot.generateSM2Keypair(getControl()); finalize("SM2", identityId); 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; @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<P11SlotIdentifier> slots = module.getSlotIds(); if (slotIndex == null) { output(slots); return null; } P11SlotIdentifier slotId = module.getSlotIdForIndex(slotIndex); P11Slot slot = module.getSlot(slotId); println("Details of slot"); slot.showDetails(System.out, verbose); System.out.println(); System.out.flush(); return null; } private void output(List<P11SlotIdentifier> 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 (P11SlotIdentifier slotId : slots) { println("\tslot[" + slotId.getIndex() + "]: " + slotId.getId()); } } } // class TokenInfoP11 }