/* * * Copyright (c) 2013 - 2017 Lijun Liao * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the XiPKI software without * disclosing the source code of your own applications. * * For more information, please contact Lijun Liao at this * address: lijun.liao@gmail.com */ package org.xipki.commons.security.pkcs11; import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.spec.DSAParameterSpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.util.encoders.Hex; import org.eclipse.jdt.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.commons.common.util.LogUtil; import org.xipki.commons.common.util.ParamUtil; import org.xipki.commons.security.HashAlgoType; import org.xipki.commons.security.X509Cert; import org.xipki.commons.security.exception.P11DuplicateEntityException; import org.xipki.commons.security.exception.P11PermissionException; import org.xipki.commons.security.exception.P11TokenException; import org.xipki.commons.security.exception.P11UnknownEntityException; import org.xipki.commons.security.exception.P11UnsupportedMechanismException; import org.xipki.commons.security.exception.XiSecurityException; import org.xipki.commons.security.util.AlgorithmUtil; import org.xipki.commons.security.util.DSAParameterCache; import org.xipki.commons.security.util.X509Util; /** * @author Lijun Liao * @since 2.0.0 */ public abstract class AbstractP11Slot implements P11Slot { private static final Logger LOG = LoggerFactory.getLogger(AbstractP11Slot.class); protected final String moduleName; protected final P11SlotIdentifier slotId; private final boolean readOnly; private final SecureRandom random = new SecureRandom(); private final ConcurrentHashMap<P11ObjectIdentifier, P11Identity> identities = new ConcurrentHashMap<>(); private final ConcurrentHashMap<P11ObjectIdentifier, X509Cert> certificates = new ConcurrentHashMap<>(); private final Set<Long> mechanisms = new HashSet<>(); private final P11MechanismFilter mechanismFilter; protected AbstractP11Slot(final String moduleName, final P11SlotIdentifier slotId, final boolean readOnly, final P11MechanismFilter mechanismFilter) throws P11TokenException { this.mechanismFilter = ParamUtil.requireNonNull("mechanismFilter", mechanismFilter); this.moduleName = ParamUtil.requireNonBlank("moduleName", moduleName); this.slotId = ParamUtil.requireNonNull("slotId", slotId); this.readOnly = readOnly; } protected static String hex(@NonNull final byte[] bytes) { return Hex.toHexString(bytes).toUpperCase(); } protected static String getDescription(final byte[] keyId, final char[] keyLabel) { StringBuilder sb = new StringBuilder(); sb.append("id ").append((keyId == null) ? "null" : Hex.toHexString(keyId)); sb.append(" and label ").append((keyLabel == null) ? "null" : new String(keyLabel)); return sb.toString(); } protected static String getDescription(final byte[] keyId, final String keyLabel) { StringBuilder sb = new StringBuilder(); sb.append("id ").append((keyId == null) ? "null" : Hex.toHexString(keyId)); sb.append(" and label ").append(keyLabel); return sb.toString(); } protected abstract void doUpdateCertificate(final P11ObjectIdentifier objectId, final X509Certificate newCert) throws XiSecurityException, P11TokenException; protected abstract void doRemoveIdentity(P11ObjectIdentifier objectId) throws P11TokenException; protected abstract void doAddCert(@NonNull final P11ObjectIdentifier objectId, @NonNull final X509Certificate cert) throws P11TokenException, XiSecurityException; // CHECKSTYLE:OFF protected abstract P11Identity doGenerateDSAKeypair(final BigInteger p, final BigInteger q, final BigInteger g, @NonNull final String label) throws P11TokenException; // CHECKSTYLE:ON // CHECKSTYLE:SKIP protected abstract P11Identity doGenerateECKeypair(@NonNull ASN1ObjectIdentifier curveId, @NonNull String label) throws P11TokenException; // CHECKSTYLE:SKIP protected abstract P11Identity doGenerateRSAKeypair(int keysize, @NonNull BigInteger publicExponent, @NonNull String label) throws P11TokenException; protected abstract P11SlotRefreshResult doRefresh() throws P11TokenException; protected abstract void doRemoveCerts(final P11ObjectIdentifier objectId) throws P11TokenException; protected X509Cert getCertForId(@NonNull final byte[] id) { for (P11ObjectIdentifier objId : certificates.keySet()) { if (objId.matchesId(id)) { return certificates.get(objId); } } return null; } private void updateCaCertsOfIdentities() { for (P11Identity identity : identities.values()) { updateCaCertsOfIdentity(identity); } } private void updateCaCertsOfIdentity(@NonNull final P11Identity identity) { X509Certificate[] certchain = identity.getCertificateChain(); if (certchain == null || certchain.length == 0) { return; } X509Certificate[] newCertchain = buildCertPath(certchain[0]); if (!Arrays.equals(certchain, newCertchain)) { try { identity.setCertificates(newCertchain); } catch (P11TokenException ex) { LOG.warn("could not set certificates for identity {}", identity.getIdentityId()); } } } private X509Certificate[] buildCertPath(@NonNull final X509Certificate cert) { List<X509Certificate> certs = new LinkedList<>(); X509Certificate cur = cert; while (cur != null) { certs.add(cur); cur = getIssuerForCert(cur); } return certs.toArray(new X509Certificate[0]); } private X509Certificate getIssuerForCert(@NonNull final X509Certificate cert) { try { if (X509Util.isSelfSigned(cert)) { return null; } for (X509Cert cert2 : certificates.values()) { if (cert2.getCert() == cert) { continue; } if (X509Util.issues(cert2.getCert(), cert)) { return cert2.getCert(); } } } catch (CertificateEncodingException ex) { LOG.warn("invalid encoding of certificate {}", ex.getMessage()); } return null; } @Override public void refresh() throws P11TokenException { P11SlotRefreshResult res = doRefresh(); // CHECKSTYLE:SKIP mechanisms.clear(); certificates.clear(); identities.clear(); for (Long mech : res.getMechanisms()) { if (mechanismFilter.isMechanismPermitted(slotId, mech)) { mechanisms.add(mech); } } certificates.putAll(res.getCertificates()); identities.putAll(res.getIdentities()); updateCaCertsOfIdentities(); if (LOG.isInfoEnabled()) { StringBuilder sb = new StringBuilder(); sb.append("initialized module ").append(moduleName).append(", slot ").append(slotId); sb.append("\nsupported mechanisms:\n"); List<Long> sortedMechs = new ArrayList<>(mechanisms); Collections.sort(sortedMechs); for (Long mech : sortedMechs) { sb.append("\t").append(P11Constants.getMechanismDesc(mech)).append("\n"); } List<P11ObjectIdentifier> ids = getSortedObjectIds(certificates.keySet()); sb.append(ids.size()).append(" certificates:\n"); for (P11ObjectIdentifier objectId : ids) { X509Cert entity = certificates.get(objectId); sb.append("\t").append(objectId); sb.append(", subject='").append(entity.getSubject()).append("'\n"); } ids = getSortedObjectIds(identities.keySet()); sb.append(ids.size()).append(" identities:\n"); for (P11ObjectIdentifier objectId : ids) { P11Identity identity = identities.get(objectId); sb.append("\t").append(objectId); sb.append(", algo=").append(identity.getPublicKey().getAlgorithm()); if (identity.getCertificate() != null) { String subject = X509Util.getRfc4519Name( identity.getCertificate().getSubjectX500Principal()); sb.append(", subject='").append(subject).append("'"); } sb.append("\n"); } LOG.info(sb.toString()); } } protected void addIdentity(final P11Identity identity) throws P11DuplicateEntityException { if (!slotId.equals(identity.getIdentityId().getSlotId())) { throw new IllegalArgumentException("invalid identity"); } P11ObjectIdentifier objectId = identity.getIdentityId().getObjectId(); if (hasIdentity(objectId)) { throw new P11DuplicateEntityException(slotId, objectId); } identities.put(objectId, identity); updateCaCertsOfIdentity(identity); } @Override public boolean hasIdentity(final P11ObjectIdentifier objectId) { return identities.containsKey(objectId); } @Override public Set<Long> getMechanisms() { return Collections.unmodifiableSet(mechanisms); } @Override public boolean supportsMechanism(final long mechanism) { return mechanisms.contains(mechanism); } @Override public void assertMechanismSupported(final long mechanism) throws P11UnsupportedMechanismException { if (!mechanisms.contains(mechanism)) { throw new P11UnsupportedMechanismException(mechanism, slotId); } } @Override public Set<P11ObjectIdentifier> getIdentityIdentifiers() { return Collections.unmodifiableSet(identities.keySet()); } @Override public Set<P11ObjectIdentifier> getCertIdentifiers() { return Collections.unmodifiableSet(certificates.keySet()); } @Override public String getModuleName() { return moduleName; } @Override public P11SlotIdentifier getSlotId() { return slotId; } @Override public boolean isReadOnly() { return readOnly; } @Override public P11Identity getIdentity(final P11ObjectIdentifier objectId) throws P11UnknownEntityException { P11Identity ident = identities.get(objectId); if (ident == null) { throw new P11UnknownEntityException(slotId, objectId); } return ident; } @Override public P11ObjectIdentifier getObjectIdForId(final byte[] id) { for (P11ObjectIdentifier objectId : identities.keySet()) { if (objectId.matchesId(id)) { return objectId; } } for (P11ObjectIdentifier objectId : certificates.keySet()) { if (objectId.matchesId(id)) { return objectId; } } return null; } @Override public P11ObjectIdentifier getObjectIdForLabel(final String label) { for (P11ObjectIdentifier objectId : identities.keySet()) { if (objectId.getLabel().equals(label)) { return objectId; } } for (P11ObjectIdentifier objectId : certificates.keySet()) { if (objectId.getLabel().equals(label)) { return objectId; } } return null; } @Override public X509Certificate exportCert(final P11ObjectIdentifier objectId) throws XiSecurityException, P11TokenException { ParamUtil.requireNonNull("objectId", objectId); try { return getIdentity(objectId).getCertificate(); } catch (P11UnknownEntityException ex) { // CHECKSTYLE:SKIP } X509Cert cert = certificates.get(objectId); if (cert == null) { throw new P11UnknownEntityException(slotId, objectId); } return cert.getCert(); } @Override public void removeCerts(final P11ObjectIdentifier objectId) throws P11TokenException { ParamUtil.requireNonNull("objectId", objectId); assertWritable("removeCerts"); if (identities.containsKey(objectId)) { certificates.remove(objectId); identities.get(objectId).setCertificates(null); } else if (certificates.containsKey(objectId)) { certificates.remove(objectId); } else { throw new P11UnknownEntityException(slotId, objectId); } updateCaCertsOfIdentities(); doRemoveCerts(objectId); } @Override public void removeIdentity(final P11ObjectIdentifier objectId) throws P11TokenException { ParamUtil.requireNonNull("objectId", objectId); assertWritable("removeIdentity"); if (identities.containsKey(objectId)) { certificates.remove(objectId); identities.get(objectId).setCertificates(null); identities.remove(objectId); updateCaCertsOfIdentities(); } doRemoveIdentity(objectId); } @Override public P11ObjectIdentifier addCert(final X509Certificate cert) throws P11TokenException, XiSecurityException { ParamUtil.requireNonNull("cert", cert); assertWritable("addCert"); byte[] encodedCert; try { encodedCert = cert.getEncoded(); } catch (CertificateEncodingException ex) { throw new XiSecurityException("could not encode certificate: " + ex.getMessage(), ex); } for (P11ObjectIdentifier objectId : certificates.keySet()) { X509Cert tmpCert = certificates.get(objectId); if (Arrays.equals(encodedCert, tmpCert.getEncodedCert())) { return objectId; } } byte[] id = generateId(); String cn = X509Util.getCommonName(cert.getSubjectX500Principal()); String label = generateLabel(cn); P11ObjectIdentifier objectId = new P11ObjectIdentifier(id, label); addCert(objectId, cert); return objectId; } @Override public void addCert(final P11ObjectIdentifier objectId, final X509Certificate cert) throws P11TokenException, XiSecurityException { doAddCert(objectId, cert); certificates.put(objectId, new X509Cert(cert)); updateCaCertsOfIdentities(); LOG.info("added certificate {}", objectId); } protected byte[] generateId() throws P11TokenException { byte[] id = new byte[8]; while (true) { random.nextBytes(id); boolean duplicated = false; for (P11ObjectIdentifier objectId : identities.keySet()) { if (objectId.matchesId(id)) { duplicated = true; break; } } if (!duplicated) { for (P11ObjectIdentifier objectId : certificates.keySet()) { if (objectId.matchesId(id)) { duplicated = true; break; } } } if (!duplicated) { return id; } } } protected String generateLabel(final String label) throws P11TokenException { String tmpLabel = label; int idx = 0; while (true) { boolean duplicated = false; for (P11ObjectIdentifier objectId : identities.keySet()) { if (objectId.getLabel().equals(label)) { duplicated = true; break; } } if (!duplicated) { for (P11ObjectIdentifier objectId : certificates.keySet()) { if (objectId.getLabel().equals(label)) { duplicated = true; break; } } } if (!duplicated) { return tmpLabel; } idx++; tmpLabel = label + "-" + idx; } } @Override public P11ObjectIdentifier generateRSAKeypair(final int keysize, final BigInteger publicExponent, final String label) throws P11TokenException { ParamUtil.requireNonBlank("label", label); ParamUtil.requireMin("keysize", keysize, 1024); if (keysize % 1024 != 0) { throw new IllegalArgumentException("key size is not multiple of 1024: " + keysize); } assertWritable("generateRSAKeypair"); assertMechanismSupported(P11Constants.CKM_RSA_PKCS_KEY_PAIR_GEN); BigInteger tmpPublicExponent = publicExponent; if (tmpPublicExponent == null) { tmpPublicExponent = BigInteger.valueOf(65537); } P11Identity identity = doGenerateRSAKeypair(keysize, tmpPublicExponent, label); addIdentity(identity); P11ObjectIdentifier objId = identity.getIdentityId().getObjectId(); LOG.info("generated RSA keypair {}", objId); return objId; } @Override public P11ObjectIdentifier generateDSAKeypair(final int plength, final int qlength, final String label) throws P11TokenException { ParamUtil.requireMin("plength", plength, 1024); if (plength % 1024 != 0) { throw new IllegalArgumentException("key size is not multiple of 1024: " + plength); } assertWritable("generateDSAKeypair"); assertMechanismSupported(P11Constants.CKM_DSA_KEY_PAIR_GEN); DSAParameterSpec dsaParams = DSAParameterCache.getDSAParameterSpec(plength, qlength, random); P11Identity identity = doGenerateDSAKeypair(dsaParams.getP(), dsaParams.getQ(), dsaParams.getG(), label); addIdentity(identity); P11ObjectIdentifier objId = identity.getIdentityId().getObjectId(); LOG.info("generated DSA keypair {}", objId); return objId; } @Override // CHECKSTYLE:OFF public P11ObjectIdentifier generateDSAKeypair(final BigInteger p, final BigInteger q, final BigInteger g, final String label) throws P11TokenException { // CHECKSTYLE:ON ParamUtil.requireNonBlank("label", label); ParamUtil.requireNonNull("p", p); ParamUtil.requireNonNull("q", q); ParamUtil.requireNonNull("g", g); assertWritable("generateDSAKeypair"); assertMechanismSupported(P11Constants.CKM_DSA_KEY_PAIR_GEN); P11Identity identity = doGenerateDSAKeypair(p, q, g, label); addIdentity(identity); P11ObjectIdentifier objId = identity.getIdentityId().getObjectId(); LOG.info("generated DSA keypair {}", objId); return objId; } @Override public P11ObjectIdentifier generateECKeypair(final String curveNameOrOid, final String label) throws XiSecurityException, P11TokenException { ParamUtil.requireNonBlank("curveNameOrOid", curveNameOrOid); ParamUtil.requireNonBlank("label", label); assertWritable("generateECKeypair"); assertMechanismSupported(P11Constants.CKM_EC_KEY_PAIR_GEN); ASN1ObjectIdentifier curveId = AlgorithmUtil.getCurveOidForCurveNameOrOid(curveNameOrOid); if (curveId == null) { throw new IllegalArgumentException("unknown curve " + curveNameOrOid); } P11Identity identity = doGenerateECKeypair(curveId, label); addIdentity(identity); P11ObjectIdentifier objId = identity.getIdentityId().getObjectId(); LOG.info("generated EC keypair {}", objId); return objId; } @Override public void updateCertificate(final P11ObjectIdentifier objectId, final X509Certificate newCert) throws XiSecurityException, P11TokenException { ParamUtil.requireNonNull("objectId", objectId); ParamUtil.requireNonNull("newCert", newCert); assertWritable("updateCertificate"); P11Identity identity = identities.get(objectId); if (identity == null) { throw new P11UnknownEntityException("could not find private key " + objectId); } java.security.PublicKey pk = identity.getPublicKey(); java.security.PublicKey newPk = newCert.getPublicKey(); if (!pk.equals(newPk)) { throw new XiSecurityException("the given certificate is not for the key " + objectId); } doUpdateCertificate(objectId, newCert); identity.setCertificates(new X509Certificate[]{newCert}); updateCaCertsOfIdentities(); LOG.info("updated certificate {}", objectId); } @Override public void showDetails(final OutputStream stream, final boolean verbose) throws IOException, XiSecurityException, P11TokenException { ParamUtil.requireNonNull("stream", stream); List<P11ObjectIdentifier> sortedObjectIds = getSortedObjectIds(identities.keySet()); int size = sortedObjectIds.size(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { P11ObjectIdentifier objectId = sortedObjectIds.get(i); sb.append("\t").append(i + 1).append(". ").append(objectId.getLabel()); sb.append(" (").append("id: ").append(objectId.getIdHex()).append(")\n"); String algo = identities.get(objectId).getPublicKey().getAlgorithm(); sb.append("\t\tAlgorithm: ").append(algo).append("\n"); X509Certificate[] certs = identities.get(objectId).getCertificateChain(); if (certs == null || certs.length == 0) { sb.append("\t\tCertificate: NONE\n"); } else { for (int j = 0; j < certs.length; j++) { formatString(j, verbose, sb, certs[j]); } } } sortedObjectIds.clear(); for (P11ObjectIdentifier objectId : certificates.keySet()) { if (!identities.containsKey(objectId)) { sortedObjectIds.add(objectId); } } Collections.sort(sortedObjectIds); if (!sortedObjectIds.isEmpty()) { Collections.sort(sortedObjectIds); size = sortedObjectIds.size(); for (int i = 0; i < size; i++) { P11ObjectIdentifier objectId = sortedObjectIds.get(i); sb.append("\tCert-").append(i + 1).append(". ").append(objectId.getLabel()); sb.append(" (").append("id: ").append(objectId.getLabel()).append(")\n"); formatString(null, verbose, sb, certificates.get(objectId).getCert()); } } if (sb.length() > 0) { stream.write(sb.toString().getBytes()); } } protected void assertWritable(final String operationName) throws P11PermissionException { if (readOnly) { throw new P11PermissionException("Operation " + operationName + " is not permitted"); } } private static void formatString(final Integer index, final boolean verbose, final StringBuilder sb, final X509Certificate cert) { String subject = X509Util.getRfc4519Name(cert.getSubjectX500Principal()); sb.append("\t\tCertificate"); if (index != null) { sb.append("[").append(index).append("]"); } sb.append(": "); if (!verbose) { sb.append(subject).append("\n"); return; } else { sb.append("\n"); } sb.append("\t\t\tSubject: ").append(subject).append("\n"); String issuer = X509Util.getRfc4519Name(cert.getIssuerX500Principal()); sb.append("\t\t\tIssuer: ").append(issuer).append("\n"); sb.append("\t\t\tSerial: ").append(LogUtil.formatCsn(cert.getSerialNumber())).append("\n"); sb.append("\t\t\tStart time: ").append(cert.getNotBefore()).append("\n"); sb.append("\t\t\tEnd time: ").append(cert.getNotAfter()).append("\n"); sb.append("\t\t\tSHA1 Sum: "); try { sb.append(HashAlgoType.SHA1.hexHash(cert.getEncoded())); } catch (CertificateEncodingException ex) { sb.append("ERROR"); } sb.append("\n"); } private List<P11ObjectIdentifier> getSortedObjectIds(Set<P11ObjectIdentifier> sets) { List<P11ObjectIdentifier> ids = new ArrayList<>(sets); Collections.sort(ids); return ids; } }