Path: blob/master/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CKeyStore.java
41159 views
/*1* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.mscapi;2627import java.io.ByteArrayInputStream;28import java.io.IOException;29import java.io.InputStream;30import java.io.OutputStream;31import java.security.AccessController;32import java.security.InvalidKeyException;33import java.security.Key;34import java.security.KeyStoreSpi;35import java.security.KeyStoreException;36import java.security.PrivilegedAction;37import java.security.UnrecoverableKeyException;38import java.security.NoSuchAlgorithmException;39import java.security.SecurityPermission;40import java.security.cert.X509Certificate;41import java.security.cert.Certificate;42import java.security.cert.CertificateException;43import java.security.cert.CertificateFactory;44import java.security.interfaces.RSAPrivateCrtKey;45import java.util.*;4647import sun.security.util.Debug;4849/**50* Implementation of key store for Windows using the Microsoft Crypto API.51*52* @since 1.653*/54abstract class CKeyStore extends KeyStoreSpi {5556public static final class MY extends CKeyStore {57public MY() {58super("MY");59}60}6162public static final class ROOT extends CKeyStore {63public ROOT() {64super("ROOT");65}66}6768class KeyEntry {69private CKey privateKey;70private X509Certificate[] certChain;71private String alias;7273KeyEntry(CKey key, X509Certificate[] chain) {74this(null, key, chain);75}7677KeyEntry(String alias, CKey key, X509Certificate[] chain) {78this.privateKey = key;79this.certChain = chain;80/*81* The default alias for both entry types is derived from a82* hash value intrinsic to the first certificate in the chain.83*/84if (alias == null) {85this.alias = Integer.toString(chain[0].hashCode());86} else {87this.alias = alias;88}89}9091/**92* Gets the alias for the keystore entry.93*/94String getAlias() {95return alias;96}9798/**99* Sets the alias for the keystore entry.100*/101void setAlias(String alias) {102// TODO - set friendly name prop in cert store103this.alias = alias;104}105106/**107* Gets the private key for the keystore entry.108*/109CKey getPrivateKey() {110return privateKey;111}112113/**114* Sets the private key for the keystore entry.115*/116void setRSAPrivateKey(Key k)117throws InvalidKeyException, KeyStoreException {118RSAPrivateCrtKey key = (RSAPrivateCrtKey) k;119byte[] modulusBytes = key.getModulus().toByteArray();120121// Adjust key length due to sign bit122int keyBitLength = (modulusBytes[0] == 0)123? (modulusBytes.length - 1) * 8124: modulusBytes.length * 8;125126byte[] keyBlob = generateRSAPrivateKeyBlob(127keyBitLength,128modulusBytes,129key.getPublicExponent().toByteArray(),130key.getPrivateExponent().toByteArray(),131key.getPrimeP().toByteArray(),132key.getPrimeQ().toByteArray(),133key.getPrimeExponentP().toByteArray(),134key.getPrimeExponentQ().toByteArray(),135key.getCrtCoefficient().toByteArray());136137privateKey = storePrivateKey("RSA", Objects.requireNonNull(keyBlob),138"{" + UUID.randomUUID().toString() + "}", keyBitLength);139}140141/**142* Gets the certificate chain for the keystore entry.143*/144X509Certificate[] getCertificateChain() {145return certChain;146}147148/**149* Sets the certificate chain for the keystore entry.150*/151void setCertificateChain(X509Certificate[] chain)152throws CertificateException, KeyStoreException {153for (int i = 0; i < chain.length; i++) {154byte[] encoding = chain[i].getEncoded();155if (i == 0 && privateKey != null) {156storeCertificate(getName(), alias, encoding,157encoding.length, privateKey.getHCryptProvider(),158privateKey.getHCryptKey());159160} else {161storeCertificate(getName(), alias, encoding,162encoding.length, 0L, 0L); // no private key to attach163}164}165certChain = chain;166}167}168169/*170* An X.509 certificate factory.171* Used to create an X.509 certificate from its DER-encoding.172*/173private CertificateFactory certificateFactory = null;174175/*176* Compatibility mode: for applications that assume keystores are177* stream-based this mode tolerates (but ignores) a non-null stream178* or password parameter when passed to the load or store methods.179* The mode is enabled by default.180*/181private static final String KEYSTORE_COMPATIBILITY_MODE_PROP =182"sun.security.mscapi.keyStoreCompatibilityMode";183private final boolean keyStoreCompatibilityMode;184private static final Debug debug = Debug.getInstance("keystore");185186/*187* The keystore entries.188* Keys in the map are unique aliases (thus can differ from189* KeyEntry.getAlias())190*/191private Map<String,KeyEntry> entries = new HashMap<>();192193/*194* The keystore name.195* Case is not significant.196*/197private final String storeName;198199CKeyStore(String storeName) {200// Get the compatibility mode201@SuppressWarnings("removal")202String prop = AccessController.doPrivileged(203(PrivilegedAction<String>) () -> System.getProperty(KEYSTORE_COMPATIBILITY_MODE_PROP));204205if ("false".equalsIgnoreCase(prop)) {206keyStoreCompatibilityMode = false;207} else {208keyStoreCompatibilityMode = true;209}210211this.storeName = storeName;212}213214/**215* Returns the key associated with the given alias.216* <p>217* A compatibility mode is supported for applications that assume218* a password must be supplied. It permits (but ignores) a non-null219* <code>password</code>. The mode is enabled by default.220* Set the221* <code>sun.security.mscapi.keyStoreCompatibilityMode</code>222* system property to <code>false</code> to disable compatibility mode223* and reject a non-null <code>password</code>.224*225* @param alias the alias name226* @param password the password, which should be <code>null</code>227*228* @return the requested key, or null if the given alias does not exist229* or does not identify a <i>key entry</i>.230*231* @exception NoSuchAlgorithmException if the algorithm for recovering the232* key cannot be found,233* or if compatibility mode is disabled and <code>password</code> is234* non-null.235* @exception UnrecoverableKeyException if the key cannot be recovered.236*/237public java.security.Key engineGetKey(String alias, char[] password)238throws NoSuchAlgorithmException, UnrecoverableKeyException {239if (alias == null) {240return null;241}242243if (password != null && !keyStoreCompatibilityMode) {244throw new UnrecoverableKeyException("Password must be null");245}246247if (engineIsKeyEntry(alias) == false)248return null;249250KeyEntry entry = entries.get(alias);251return (entry == null)252? null253: entry.getPrivateKey();254}255256/**257* Returns the certificate chain associated with the given alias.258*259* @param alias the alias name260*261* @return the certificate chain (ordered with the user's certificate first262* and the root certificate authority last), or null if the given alias263* does not exist or does not contain a certificate chain (i.e., the given264* alias identifies either a <i>trusted certificate entry</i> or a265* <i>key entry</i> without a certificate chain).266*/267public Certificate[] engineGetCertificateChain(String alias) {268if (alias == null) {269return null;270}271272KeyEntry entry = entries.get(alias);273X509Certificate[] certChain = (entry == null)274? null275: entry.getCertificateChain();276return (certChain == null)277? null278: certChain.clone();279}280281/**282* Returns the certificate associated with the given alias.283*284* <p>If the given alias name identifies a285* <i>trusted certificate entry</i>, the certificate associated with that286* entry is returned. If the given alias name identifies a287* <i>key entry</i>, the first element of the certificate chain of that288* entry is returned, or null if that entry does not have a certificate289* chain.290*291* @param alias the alias name292*293* @return the certificate, or null if the given alias does not exist or294* does not contain a certificate.295*/296public Certificate engineGetCertificate(String alias) {297if (alias == null) {298return null;299}300301KeyEntry entry = entries.get(alias);302X509Certificate[] certChain = (entry == null)303? null304: entry.getCertificateChain();305return (certChain == null || certChain.length == 0)306? null307: certChain[0];308}309310/**311* Returns the creation date of the entry identified by the given alias.312*313* @param alias the alias name314*315* @return the creation date of this entry, or null if the given alias does316* not exist317*/318public Date engineGetCreationDate(String alias) {319if (alias == null) {320return null;321}322return new Date();323}324325/**326* Stores the given private key and associated certificate chain in the327* keystore.328*329* <p>The given java.security.PrivateKey <code>key</code> must330* be accompanied by a certificate chain certifying the331* corresponding public key.332*333* <p>If the given alias already exists, the keystore information334* associated with it is overridden by the given key and certificate335* chain. Otherwise, a new entry is created.336*337* <p>338* A compatibility mode is supported for applications that assume339* a password must be supplied. It permits (but ignores) a non-null340* <code>password</code>. The mode is enabled by default.341* Set the342* <code>sun.security.mscapi.keyStoreCompatibilityMode</code>343* system property to <code>false</code> to disable compatibility mode344* and reject a non-null <code>password</code>.345*346* @param alias the alias name347* @param key the private key to be associated with the alias348* @param password the password, which should be <code>null</code>349* @param chain the certificate chain for the corresponding public350* key (only required if the given key is of type351* <code>java.security.PrivateKey</code>).352*353* @exception KeyStoreException if the given key is not a private key,354* cannot be protected, or if compatibility mode is disabled and355* <code>password</code> is non-null, or if this operation fails for356* some other reason.357*/358public void engineSetKeyEntry(String alias, java.security.Key key,359char[] password, Certificate[] chain) throws KeyStoreException {360if (alias == null) {361throw new KeyStoreException("alias must not be null");362}363364if (password != null && !keyStoreCompatibilityMode) {365throw new KeyStoreException("Password must be null");366}367368if (key instanceof RSAPrivateCrtKey) {369370KeyEntry entry = entries.get(alias);371372X509Certificate[] xchain;373if (chain != null) {374if (chain instanceof X509Certificate[]) {375xchain = (X509Certificate[]) chain;376} else {377xchain = new X509Certificate[chain.length];378System.arraycopy(chain, 0, xchain, 0, chain.length);379}380} else {381xchain = null;382}383384if (entry == null) {385entry =386//TODO new KeyEntry(alias, key, (X509Certificate[]) chain);387new KeyEntry(alias, null, xchain);388storeWithUniqueAlias(alias, entry);389}390391entry.setAlias(alias);392393try {394entry.setRSAPrivateKey(key);395entry.setCertificateChain(xchain);396397} catch (CertificateException ce) {398throw new KeyStoreException(ce);399400} catch (InvalidKeyException ike) {401throw new KeyStoreException(ike);402}403404} else {405throw new UnsupportedOperationException(406"Cannot assign the key to the given alias.");407}408}409410/**411* Assigns the given key (that has already been protected) to the given412* alias.413*414* <p>If the protected key is of type415* <code>java.security.PrivateKey</code>, it must be accompanied by a416* certificate chain certifying the corresponding public key. If the417* underlying keystore implementation is of type <code>jks</code>,418* <code>key</code> must be encoded as an419* <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.420*421* <p>If the given alias already exists, the keystore information422* associated with it is overridden by the given key (and possibly423* certificate chain).424*425* @param alias the alias name426* @param key the key (in protected format) to be associated with the alias427* @param chain the certificate chain for the corresponding public428* key (only useful if the protected key is of type429* <code>java.security.PrivateKey</code>).430*431* @exception KeyStoreException if this operation fails.432*/433public void engineSetKeyEntry(String alias, byte[] key,434Certificate[] chain)435throws KeyStoreException {436throw new UnsupportedOperationException(437"Cannot assign the encoded key to the given alias.");438}439440/**441* Assigns the given certificate to the given alias.442*443* <p>If the given alias already exists in this keystore and identifies a444* <i>trusted certificate entry</i>, the certificate associated with it is445* overridden by the given certificate.446*447* @param alias the alias name448* @param cert the certificate449*450* @exception KeyStoreException if the given alias already exists and does451* not identify a <i>trusted certificate entry</i>, or this operation452* fails for some other reason.453*/454public void engineSetCertificateEntry(String alias, Certificate cert)455throws KeyStoreException {456if (alias == null) {457throw new KeyStoreException("alias must not be null");458}459460if (cert instanceof X509Certificate) {461462// TODO - build CryptoAPI chain?463X509Certificate[] chain =464new X509Certificate[]{ (X509Certificate) cert };465KeyEntry entry = entries.get(alias);466467if (entry == null) {468entry =469new KeyEntry(alias, null, chain);470storeWithUniqueAlias(alias, entry);471}472473if (entry.getPrivateKey() == null) { // trusted-cert entry474entry.setAlias(alias);475476try {477entry.setCertificateChain(chain);478479} catch (CertificateException ce) {480throw new KeyStoreException(ce);481}482}483484} else {485throw new UnsupportedOperationException(486"Cannot assign the certificate to the given alias.");487}488}489490/**491* Deletes the entry identified by the given alias from this keystore.492*493* @param alias the alias name494*495* @exception KeyStoreException if the entry cannot be removed.496*/497public void engineDeleteEntry(String alias) throws KeyStoreException {498if (alias == null) {499throw new KeyStoreException("alias must not be null");500}501502KeyEntry entry = entries.remove(alias);503if (entry != null) {504// Get end-entity certificate and remove from system cert store505X509Certificate[] certChain = entry.getCertificateChain();506if (certChain != null && certChain.length > 0) {507508try {509510byte[] encoding = certChain[0].getEncoded();511removeCertificate(getName(), entry.getAlias(), encoding,512encoding.length);513514} catch (CertificateException e) {515throw new KeyStoreException("Cannot remove entry: ", e);516}517}518CKey privateKey = entry.getPrivateKey();519if (privateKey != null) {520destroyKeyContainer(521CKey.getContainerName(privateKey.getHCryptProvider()));522}523}524}525526/**527* Lists all the alias names of this keystore.528*529* @return enumeration of the alias names530*/531public Enumeration<String> engineAliases() {532final Iterator<String> iter = entries.keySet().iterator();533534return new Enumeration<String>() {535public boolean hasMoreElements() {536return iter.hasNext();537}538539public String nextElement() {540return iter.next();541}542};543}544545/**546* Checks if the given alias exists in this keystore.547*548* @param alias the alias name549*550* @return true if the alias exists, false otherwise551*/552public boolean engineContainsAlias(String alias) {553return entries.containsKey(alias);554}555556/**557* Retrieves the number of entries in this keystore.558*559* @return the number of entries in this keystore560*/561public int engineSize() {562return entries.size();563}564565/**566* Returns true if the entry identified by the given alias is a567* <i>key entry</i>, and false otherwise.568*569* @return true if the entry identified by the given alias is a570* <i>key entry</i>, false otherwise.571*/572public boolean engineIsKeyEntry(String alias) {573574if (alias == null) {575return false;576}577578KeyEntry entry = entries.get(alias);579return entry != null && entry.getPrivateKey() != null;580}581582/**583* Returns true if the entry identified by the given alias is a584* <i>trusted certificate entry</i>, and false otherwise.585*586* @return true if the entry identified by the given alias is a587* <i>trusted certificate entry</i>, false otherwise.588*/589public boolean engineIsCertificateEntry(String alias) {590591if (alias == null) {592return false;593}594595KeyEntry entry = entries.get(alias);596return entry != null && entry.getPrivateKey() == null;597}598599/**600* Returns the (alias) name of the first keystore entry whose certificate601* matches the given certificate.602*603* <p>This method attempts to match the given certificate with each604* keystore entry. If the entry being considered605* is a <i>trusted certificate entry</i>, the given certificate is606* compared to that entry's certificate. If the entry being considered is607* a <i>key entry</i>, the given certificate is compared to the first608* element of that entry's certificate chain (if a chain exists).609*610* @param cert the certificate to match with.611*612* @return the (alias) name of the first entry with matching certificate,613* or null if no such entry exists in this keystore.614*/615public String engineGetCertificateAlias(Certificate cert) {616617for (Map.Entry<String,KeyEntry> mapEntry : entries.entrySet()) {618KeyEntry entry = mapEntry.getValue();619if (entry.certChain != null &&620entry.certChain.length > 0 &&621entry.certChain[0].equals(cert)) {622return entry.getAlias();623}624}625626return null;627}628629/**630* engineStore is currently a no-op.631* Entries are stored during engineSetEntry.632*633* A compatibility mode is supported for applications that assume634* keystores are stream-based. It permits (but ignores) a non-null635* <code>stream</code> or <code>password</code>.636* The mode is enabled by default.637* Set the638* <code>sun.security.mscapi.keyStoreCompatibilityMode</code>639* system property to <code>false</code> to disable compatibility mode640* and reject a non-null <code>stream</code> or <code>password</code>.641*642* @param stream the output stream, which should be <code>null</code>643* @param password the password, which should be <code>null</code>644*645* @exception IOException if compatibility mode is disabled and either646* parameter is non-null.647*/648public void engineStore(OutputStream stream, char[] password)649throws IOException, NoSuchAlgorithmException, CertificateException {650if (stream != null && !keyStoreCompatibilityMode) {651throw new IOException("Keystore output stream must be null");652}653654if (password != null && !keyStoreCompatibilityMode) {655throw new IOException("Keystore password must be null");656}657}658659/**660* Loads the keystore.661*662* A compatibility mode is supported for applications that assume663* keystores are stream-based. It permits (but ignores) a non-null664* <code>stream</code> or <code>password</code>.665* The mode is enabled by default.666* Set the667* <code>sun.security.mscapi.keyStoreCompatibilityMode</code>668* system property to <code>false</code> to disable compatibility mode669* and reject a non-null <code>stream</code> or <code>password</code>.670*671* @param stream the input stream, which should be <code>null</code>.672* @param password the password, which should be <code>null</code>.673*674* @exception IOException if there is an I/O or format problem with the675* keystore data. Or if compatibility mode is disabled and either676* parameter is non-null.677* @exception NoSuchAlgorithmException if the algorithm used to check678* the integrity of the keystore cannot be found679* @exception CertificateException if any of the certificates in the680* keystore could not be loaded681* @exception SecurityException if the security check for682* <code>SecurityPermission("authProvider.<i>name</i>")</code> does not683* pass, where <i>name</i> is the value returned by684* this provider's <code>getName</code> method.685*/686public void engineLoad(InputStream stream, char[] password)687throws IOException, NoSuchAlgorithmException, CertificateException {688if (stream != null && !keyStoreCompatibilityMode) {689throw new IOException("Keystore input stream must be null");690}691692if (password != null && !keyStoreCompatibilityMode) {693throw new IOException("Keystore password must be null");694}695696/*697* Use the same security check as AuthProvider.login698*/699@SuppressWarnings("removal")700SecurityManager sm = System.getSecurityManager();701if (sm != null) {702sm.checkPermission(new SecurityPermission(703"authProvider.SunMSCAPI"));704}705706// Clear all key entries707entries.clear();708709try {710711// Load keys and/or certificate chains712loadKeysOrCertificateChains(getName());713714} catch (KeyStoreException e) {715throw new IOException(e);716}717718if (debug != null) {719debug.println("MSCAPI keystore load: entry count: " +720entries.size());721}722}723724/**725* Stores the given entry into the map, making sure726* the alias, used as the key is unique.727* If the same alias already exists, it tries to append728* a suffix (1), (2), etc to it until it finds a unique729* value.730*/731private void storeWithUniqueAlias(String alias, KeyEntry entry) {732String uniqAlias = alias;733int uniqNum = 1;734735while (true) {736if (entries.putIfAbsent(uniqAlias, entry) == null) {737break;738}739uniqAlias = alias + " (" + (uniqNum++) + ")";740}741}742743744/**745* Generates a certificate chain from the collection of746* certificates and stores the result into a key entry.747* <p>748* This method is called by native codes in security.cpp.749*/750private void generateCertificateChain(String alias,751Collection<? extends Certificate> certCollection) {752try {753X509Certificate[] certChain =754new X509Certificate[certCollection.size()];755756int i = 0;757for (Iterator<? extends Certificate> iter =758certCollection.iterator(); iter.hasNext(); i++) {759certChain[i] = (X509Certificate) iter.next();760}761762storeWithUniqueAlias(alias,763new KeyEntry(alias, null, certChain));764} catch (Throwable e) {765// Ignore the exception and skip this entry766// If e is thrown, remember to deal with it in767// native code.768}769}770771/**772* Generates key and certificate chain from the private key handle,773* collection of certificates and stores the result into key entries.774* <p>775* This method is called by native codes in security.cpp.776*/777private void generateKeyAndCertificateChain(boolean isRSA, String alias,778long hCryptProv, long hCryptKey, int keyLength,779Collection<? extends Certificate> certCollection) {780try {781X509Certificate[] certChain =782new X509Certificate[certCollection.size()];783784int i = 0;785for (Iterator<? extends Certificate> iter =786certCollection.iterator(); iter.hasNext(); i++) {787certChain[i] = (X509Certificate) iter.next();788}789storeWithUniqueAlias(alias, new KeyEntry(alias,790CPrivateKey.of(isRSA ? "RSA" : "EC", hCryptProv, hCryptKey, keyLength),791certChain));792} catch (Throwable e) {793// Ignore the exception and skip this entry794// If e is thrown, remember to deal with it in795// native code.796}797}798799/**800* Generates certificates from byte data and stores into cert collection.801* <p>802* This method is called by native codes in security.cpp.803*804* @param data Byte data.805* @param certCollection Collection of certificates.806*/807private void generateCertificate(byte[] data,808Collection<Certificate> certCollection) {809try {810ByteArrayInputStream bis = new ByteArrayInputStream(data);811812// Obtain certificate factory813if (certificateFactory == null) {814certificateFactory = CertificateFactory.getInstance("X.509", "SUN");815}816817// Generate certificate818Collection<? extends Certificate> c =819certificateFactory.generateCertificates(bis);820certCollection.addAll(c);821} catch (CertificateException e) {822// Ignore the exception and skip this certificate823// If e is thrown, remember to deal with it in824// native code.825}826catch (Throwable te)827{828// Ignore the exception and skip this certificate829// If e is thrown, remember to deal with it in830// native code.831}832}833834/**835* Returns the name of the keystore.836*/837private String getName() {838return storeName;839}840841/**842* Load keys and/or certificates from keystore into Collection.843*844* @param name Name of keystore.845*/846private native void loadKeysOrCertificateChains(String name)847throws KeyStoreException;848849/**850* Stores a DER-encoded certificate into the certificate store851*852* @param name Name of the keystore.853* @param alias Name of the certificate.854* @param encoding DER-encoded certificate.855*/856private native void storeCertificate(String name, String alias,857byte[] encoding, int encodingLength, long hCryptProvider,858long hCryptKey) throws CertificateException, KeyStoreException;859860/**861* Removes the certificate from the certificate store862*863* @param name Name of the keystore.864* @param alias Name of the certificate.865* @param encoding DER-encoded certificate.866*/867private native void removeCertificate(String name, String alias,868byte[] encoding, int encodingLength)869throws CertificateException, KeyStoreException;870871/**872* Destroys the key container.873*874* @param keyContainerName The name of the key container.875*/876private native void destroyKeyContainer(String keyContainerName)877throws KeyStoreException;878879/**880* Generates a private-key BLOB from a key's components.881*/882private native byte[] generateRSAPrivateKeyBlob(883int keyBitLength,884byte[] modulus,885byte[] publicExponent,886byte[] privateExponent,887byte[] primeP,888byte[] primeQ,889byte[] exponentP,890byte[] exponentQ,891byte[] crtCoefficient) throws InvalidKeyException;892893private native CPrivateKey storePrivateKey(String alg, byte[] keyBlob,894String keyContainerName, int keySize) throws KeyStoreException;895}896897898