Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/provider/JavaKeyStore.java
38830 views
/*1* Copyright (c) 1997, 2019, 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.provider;2627import java.io.*;28import java.security.*;29import java.security.cert.Certificate;30import java.security.cert.CertificateFactory;31import java.security.cert.CertificateException;32import java.util.*;3334import sun.misc.IOUtils;35import sun.security.pkcs.EncryptedPrivateKeyInfo;36import sun.security.pkcs12.PKCS12KeyStore;37import sun.security.util.Debug;3839/**40* This class provides the keystore implementation referred to as "JKS".41*42* @author Jan Luehe43* @author David Brownell44*45*46* @see KeyProtector47* @see java.security.KeyStoreSpi48* @see KeyTool49*50* @since 1.251*/5253abstract class JavaKeyStore extends KeyStoreSpi {5455// regular JKS56public static final class JKS extends JavaKeyStore {57String convertAlias(String alias) {58return alias.toLowerCase(Locale.ENGLISH);59}60}6162// special JKS that uses case sensitive aliases63public static final class CaseExactJKS extends JavaKeyStore {64String convertAlias(String alias) {65return alias;66}67}6869// special JKS that supports JKS and PKCS12 file formats70public static final class DualFormatJKS extends KeyStoreDelegator {71public DualFormatJKS() {72super("JKS", JKS.class, "PKCS12", PKCS12KeyStore.class);73}74}7576private static final Debug debug = Debug.getInstance("keystore");77private static final int MAGIC = 0xfeedfeed;78private static final int VERSION_1 = 0x01;79private static final int VERSION_2 = 0x02;8081// Private keys and their supporting certificate chains82private static class KeyEntry {83Date date; // the creation date of this entry84byte[] protectedPrivKey;85Certificate chain[];86};8788// Trusted certificates89private static class TrustedCertEntry {90Date date; // the creation date of this entry91Certificate cert;92};9394/**95* Private keys and certificates are stored in a hashtable.96* Hash entries are keyed by alias names.97*/98private final Hashtable<String, Object> entries;99100JavaKeyStore() {101entries = new Hashtable<String, Object>();102}103104// convert an alias to internal form, overridden in subclasses:105// lower case for regular JKS106// original string for CaseExactJKS107abstract String convertAlias(String alias);108109/**110* Returns the key associated with the given alias, using the given111* password to recover it.112*113* @param alias the alias name114* @param password the password for recovering the key115*116* @return the requested key, or null if the given alias does not exist117* or does not identify a <i>key entry</i>.118*119* @exception NoSuchAlgorithmException if the algorithm for recovering the120* key cannot be found121* @exception UnrecoverableKeyException if the key cannot be recovered122* (e.g., the given password is wrong).123*/124public Key engineGetKey(String alias, char[] password)125throws NoSuchAlgorithmException, UnrecoverableKeyException126{127Object entry = entries.get(convertAlias(alias));128129if (entry == null || !(entry instanceof KeyEntry)) {130return null;131}132if (password == null) {133throw new UnrecoverableKeyException("Password must not be null");134}135136byte[] passwordBytes = convertToBytes(password);137KeyProtector keyProtector = new KeyProtector(passwordBytes);138byte[] encrBytes = ((KeyEntry)entry).protectedPrivKey;139EncryptedPrivateKeyInfo encrInfo;140try {141encrInfo = new EncryptedPrivateKeyInfo(encrBytes);142return keyProtector.recover(encrInfo);143} catch (IOException ioe) {144throw new UnrecoverableKeyException("Private key not stored as "145+ "PKCS #8 "146+ "EncryptedPrivateKeyInfo");147} finally {148Arrays.fill(passwordBytes, (byte) 0x00);149}150}151152/**153* Returns the certificate chain associated with the given alias.154*155* @param alias the alias name156*157* @return the certificate chain (ordered with the user's certificate first158* and the root certificate authority last), or null if the given alias159* does not exist or does not contain a certificate chain (i.e., the given160* alias identifies either a <i>trusted certificate entry</i> or a161* <i>key entry</i> without a certificate chain).162*/163public Certificate[] engineGetCertificateChain(String alias) {164Object entry = entries.get(convertAlias(alias));165166if (entry != null && entry instanceof KeyEntry) {167if (((KeyEntry)entry).chain == null) {168return null;169} else {170return ((KeyEntry)entry).chain.clone();171}172} else {173return null;174}175}176177/**178* Returns the certificate associated with the given alias.179*180* <p>If the given alias name identifies a181* <i>trusted certificate entry</i>, the certificate associated with that182* entry is returned. If the given alias name identifies a183* <i>key entry</i>, the first element of the certificate chain of that184* entry is returned, or null if that entry does not have a certificate185* chain.186*187* @param alias the alias name188*189* @return the certificate, or null if the given alias does not exist or190* does not contain a certificate.191*/192public Certificate engineGetCertificate(String alias) {193Object entry = entries.get(convertAlias(alias));194195if (entry != null) {196if (entry instanceof TrustedCertEntry) {197return ((TrustedCertEntry)entry).cert;198} else {199if (((KeyEntry)entry).chain == null) {200return null;201} else {202return ((KeyEntry)entry).chain[0];203}204}205} else {206return null;207}208}209210/**211* Returns the creation date of the entry identified by the given alias.212*213* @param alias the alias name214*215* @return the creation date of this entry, or null if the given alias does216* not exist217*/218public Date engineGetCreationDate(String alias) {219Object entry = entries.get(convertAlias(alias));220221if (entry != null) {222if (entry instanceof TrustedCertEntry) {223return new Date(((TrustedCertEntry)entry).date.getTime());224} else {225return new Date(((KeyEntry)entry).date.getTime());226}227} else {228return null;229}230}231232/**233* Assigns the given private key to the given alias, protecting234* it with the given password as defined in PKCS8.235*236* <p>The given java.security.PrivateKey <code>key</code> must237* be accompanied by a certificate chain certifying the238* corresponding public key.239*240* <p>If the given alias already exists, the keystore information241* associated with it is overridden by the given key and certificate242* chain.243*244* @param alias the alias name245* @param key the private key to be associated with the alias246* @param password the password to protect the key247* @param chain the certificate chain for the corresponding public248* key (only required if the given key is of type249* <code>java.security.PrivateKey</code>).250*251* @exception KeyStoreException if the given key is not a private key,252* cannot be protected, or this operation fails for some other reason253*/254public void engineSetKeyEntry(String alias, Key key, char[] password,255Certificate[] chain)256throws KeyStoreException257{258KeyProtector keyProtector;259byte[] passwordBytes = null;260261if (!(key instanceof java.security.PrivateKey)) {262throw new KeyStoreException("Cannot store non-PrivateKeys");263}264try {265synchronized(entries) {266KeyEntry entry = new KeyEntry();267entry.date = new Date();268269// Protect the encoding of the key270passwordBytes = convertToBytes(password);271keyProtector = new KeyProtector(passwordBytes);272entry.protectedPrivKey = keyProtector.protect(key);273274// clone the chain275if ((chain != null) &&276(chain.length != 0)) {277entry.chain = chain.clone();278} else {279entry.chain = null;280}281282entries.put(convertAlias(alias), entry);283}284} catch (NoSuchAlgorithmException nsae) {285throw new KeyStoreException("Key protection algorithm not found");286} finally {287if (passwordBytes != null)288Arrays.fill(passwordBytes, (byte) 0x00);289}290}291292/**293* Assigns the given key (that has already been protected) to the given294* alias.295*296* <p>If the protected key is of type297* <code>java.security.PrivateKey</code>, it must be accompanied by a298* certificate chain certifying the corresponding public key. If the299* underlying keystore implementation is of type <code>jks</code>,300* <code>key</code> must be encoded as an301* <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.302*303* <p>If the given alias already exists, the keystore information304* associated with it is overridden by the given key (and possibly305* certificate chain).306*307* @param alias the alias name308* @param key the key (in protected format) to be associated with the alias309* @param chain the certificate chain for the corresponding public310* key (only useful if the protected key is of type311* <code>java.security.PrivateKey</code>).312*313* @exception KeyStoreException if this operation fails.314*/315public void engineSetKeyEntry(String alias, byte[] key,316Certificate[] chain)317throws KeyStoreException318{319synchronized(entries) {320// key must be encoded as EncryptedPrivateKeyInfo as defined in321// PKCS#8322try {323new EncryptedPrivateKeyInfo(key);324} catch (IOException ioe) {325throw new KeyStoreException("key is not encoded as "326+ "EncryptedPrivateKeyInfo");327}328329KeyEntry entry = new KeyEntry();330entry.date = new Date();331332entry.protectedPrivKey = key.clone();333if ((chain != null) &&334(chain.length != 0)) {335entry.chain = chain.clone();336} else {337entry.chain = null;338}339340entries.put(convertAlias(alias), entry);341}342}343344/**345* Assigns the given certificate to the given alias.346*347* <p>If the given alias already exists in this keystore and identifies a348* <i>trusted certificate entry</i>, the certificate associated with it is349* overridden by the given certificate.350*351* @param alias the alias name352* @param cert the certificate353*354* @exception KeyStoreException if the given alias already exists and does355* not identify a <i>trusted certificate entry</i>, or this operation356* fails for some other reason.357*/358public void engineSetCertificateEntry(String alias, Certificate cert)359throws KeyStoreException360{361synchronized(entries) {362363Object entry = entries.get(convertAlias(alias));364if ((entry != null) && (entry instanceof KeyEntry)) {365throw new KeyStoreException366("Cannot overwrite own certificate");367}368369TrustedCertEntry trustedCertEntry = new TrustedCertEntry();370trustedCertEntry.cert = cert;371trustedCertEntry.date = new Date();372entries.put(convertAlias(alias), trustedCertEntry);373}374}375376/**377* Deletes the entry identified by the given alias from this keystore.378*379* @param alias the alias name380*381* @exception KeyStoreException if the entry cannot be removed.382*/383public void engineDeleteEntry(String alias)384throws KeyStoreException385{386synchronized(entries) {387entries.remove(convertAlias(alias));388}389}390391/**392* Lists all the alias names of this keystore.393*394* @return enumeration of the alias names395*/396public Enumeration<String> engineAliases() {397return entries.keys();398}399400/**401* Checks if the given alias exists in this keystore.402*403* @param alias the alias name404*405* @return true if the alias exists, false otherwise406*/407public boolean engineContainsAlias(String alias) {408return entries.containsKey(convertAlias(alias));409}410411/**412* Retrieves the number of entries in this keystore.413*414* @return the number of entries in this keystore415*/416public int engineSize() {417return entries.size();418}419420/**421* Returns true if the entry identified by the given alias is a422* <i>key entry</i>, and false otherwise.423*424* @return true if the entry identified by the given alias is a425* <i>key entry</i>, false otherwise.426*/427public boolean engineIsKeyEntry(String alias) {428Object entry = entries.get(convertAlias(alias));429if ((entry != null) && (entry instanceof KeyEntry)) {430return true;431} else {432return false;433}434}435436/**437* Returns true if the entry identified by the given alias is a438* <i>trusted certificate entry</i>, and false otherwise.439*440* @return true if the entry identified by the given alias is a441* <i>trusted certificate entry</i>, false otherwise.442*/443public boolean engineIsCertificateEntry(String alias) {444Object entry = entries.get(convertAlias(alias));445if ((entry != null) && (entry instanceof TrustedCertEntry)) {446return true;447} else {448return false;449}450}451452/**453* Returns the (alias) name of the first keystore entry whose certificate454* matches the given certificate.455*456* <p>This method attempts to match the given certificate with each457* keystore entry. If the entry being considered458* is a <i>trusted certificate entry</i>, the given certificate is459* compared to that entry's certificate. If the entry being considered is460* a <i>key entry</i>, the given certificate is compared to the first461* element of that entry's certificate chain (if a chain exists).462*463* @param cert the certificate to match with.464*465* @return the (alias) name of the first entry with matching certificate,466* or null if no such entry exists in this keystore.467*/468public String engineGetCertificateAlias(Certificate cert) {469Certificate certElem;470471for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {472String alias = e.nextElement();473Object entry = entries.get(alias);474if (entry instanceof TrustedCertEntry) {475certElem = ((TrustedCertEntry)entry).cert;476} else if (((KeyEntry)entry).chain != null) {477certElem = ((KeyEntry)entry).chain[0];478} else {479continue;480}481if (certElem.equals(cert)) {482return alias;483}484}485return null;486}487488/**489* Stores this keystore to the given output stream, and protects its490* integrity with the given password.491*492* @param stream the output stream to which this keystore is written.493* @param password the password to generate the keystore integrity check494*495* @exception IOException if there was an I/O problem with data496* @exception NoSuchAlgorithmException if the appropriate data integrity497* algorithm could not be found498* @exception CertificateException if any of the certificates included in499* the keystore data could not be stored500*/501public void engineStore(OutputStream stream, char[] password)502throws IOException, NoSuchAlgorithmException, CertificateException503{504synchronized(entries) {505/*506* KEYSTORE FORMAT:507*508* Magic number (big-endian integer),509* Version of this file format (big-endian integer),510*511* Count (big-endian integer),512* followed by "count" instances of either:513*514* {515* tag=1 (big-endian integer),516* alias (UTF string)517* timestamp518* encrypted private-key info according to PKCS #8519* (integer length followed by encoding)520* cert chain (integer count, then certs; for each cert,521* integer length followed by encoding)522* }523*524* or:525*526* {527* tag=2 (big-endian integer)528* alias (UTF string)529* timestamp530* cert (integer length followed by encoding)531* }532*533* ended by a keyed SHA1 hash (bytes only) of534* { password + whitener + preceding body }535*/536537// password is mandatory when storing538if (password == null) {539throw new IllegalArgumentException("password can't be null");540}541542byte[] encoded; // the certificate encoding543544MessageDigest md = getPreKeyedHash(password);545DataOutputStream dos546= new DataOutputStream(new DigestOutputStream(stream, md));547548dos.writeInt(MAGIC);549// always write the latest version550dos.writeInt(VERSION_2);551552dos.writeInt(entries.size());553554for (Enumeration<String> e = entries.keys(); e.hasMoreElements();) {555556String alias = e.nextElement();557Object entry = entries.get(alias);558559if (entry instanceof KeyEntry) {560561// Store this entry as a KeyEntry562dos.writeInt(1);563564// Write the alias565dos.writeUTF(alias);566567// Write the (entry creation) date568dos.writeLong(((KeyEntry)entry).date.getTime());569570// Write the protected private key571dos.writeInt(((KeyEntry)entry).protectedPrivKey.length);572dos.write(((KeyEntry)entry).protectedPrivKey);573574// Write the certificate chain575int chainLen;576if (((KeyEntry)entry).chain == null) {577chainLen = 0;578} else {579chainLen = ((KeyEntry)entry).chain.length;580}581dos.writeInt(chainLen);582for (int i = 0; i < chainLen; i++) {583encoded = ((KeyEntry)entry).chain[i].getEncoded();584dos.writeUTF(((KeyEntry)entry).chain[i].getType());585dos.writeInt(encoded.length);586dos.write(encoded);587}588} else {589590// Store this entry as a certificate591dos.writeInt(2);592593// Write the alias594dos.writeUTF(alias);595596// Write the (entry creation) date597dos.writeLong(((TrustedCertEntry)entry).date.getTime());598599// Write the trusted certificate600encoded = ((TrustedCertEntry)entry).cert.getEncoded();601dos.writeUTF(((TrustedCertEntry)entry).cert.getType());602dos.writeInt(encoded.length);603dos.write(encoded);604}605}606607/*608* Write the keyed hash which is used to detect tampering with609* the keystore (such as deleting or modifying key or610* certificate entries).611*/612byte digest[] = md.digest();613614dos.write(digest);615dos.flush();616}617}618619/**620* Loads the keystore from the given input stream.621*622* <p>If a password is given, it is used to check the integrity of the623* keystore data. Otherwise, the integrity of the keystore is not checked.624*625* @param stream the input stream from which the keystore is loaded626* @param password the (optional) password used to check the integrity of627* the keystore.628*629* @exception IOException if there is an I/O or format problem with the630* keystore data631* @exception NoSuchAlgorithmException if the algorithm used to check632* the integrity of the keystore cannot be found633* @exception CertificateException if any of the certificates in the634* keystore could not be loaded635*/636public void engineLoad(InputStream stream, char[] password)637throws IOException, NoSuchAlgorithmException, CertificateException638{639synchronized(entries) {640DataInputStream dis;641MessageDigest md = null;642CertificateFactory cf = null;643Hashtable<String, CertificateFactory> cfs = null;644ByteArrayInputStream bais = null;645byte[] encoded = null;646int trustedKeyCount = 0, privateKeyCount = 0;647648if (stream == null)649return;650651if (password != null) {652md = getPreKeyedHash(password);653dis = new DataInputStream(new DigestInputStream(stream, md));654} else {655dis = new DataInputStream(stream);656}657658// Body format: see store method659660int xMagic = dis.readInt();661int xVersion = dis.readInt();662663if (xMagic!=MAGIC ||664(xVersion!=VERSION_1 && xVersion!=VERSION_2)) {665throw new IOException("Invalid keystore format");666}667668if (xVersion == VERSION_1) {669cf = CertificateFactory.getInstance("X509");670} else {671// version 2672cfs = new Hashtable<String, CertificateFactory>(3);673}674675entries.clear();676int count = dis.readInt();677678for (int i = 0; i < count; i++) {679int tag;680String alias;681682tag = dis.readInt();683684if (tag == 1) { // private key entry685privateKeyCount++;686KeyEntry entry = new KeyEntry();687688// Read the alias689alias = dis.readUTF();690691// Read the (entry creation) date692entry.date = new Date(dis.readLong());693694// Read the private key695entry.protectedPrivKey =696IOUtils.readExactlyNBytes(dis, dis.readInt());697698// Read the certificate chain699int numOfCerts = dis.readInt();700if (numOfCerts > 0) {701List<Certificate> certs = new ArrayList<>(702numOfCerts > 10 ? 10 : numOfCerts);703for (int j = 0; j < numOfCerts; j++) {704if (xVersion == 2) {705// read the certificate type, and instantiate a706// certificate factory of that type (reuse707// existing factory if possible)708String certType = dis.readUTF();709if (cfs.containsKey(certType)) {710// reuse certificate factory711cf = cfs.get(certType);712} else {713// create new certificate factory714cf = CertificateFactory.getInstance(certType);715// store the certificate factory so we can716// reuse it later717cfs.put(certType, cf);718}719}720// instantiate the certificate721encoded = IOUtils.readExactlyNBytes(dis, dis.readInt());722bais = new ByteArrayInputStream(encoded);723certs.add(cf.generateCertificate(bais));724bais.close();725}726// We can be sure now that numOfCerts of certs are read727entry.chain = certs.toArray(new Certificate[numOfCerts]);728}729730// Add the entry to the list731entries.put(alias, entry);732733} else if (tag == 2) { // trusted certificate entry734trustedKeyCount++;735TrustedCertEntry entry = new TrustedCertEntry();736737// Read the alias738alias = dis.readUTF();739740// Read the (entry creation) date741entry.date = new Date(dis.readLong());742743// Read the trusted certificate744if (xVersion == 2) {745// read the certificate type, and instantiate a746// certificate factory of that type (reuse747// existing factory if possible)748String certType = dis.readUTF();749if (cfs.containsKey(certType)) {750// reuse certificate factory751cf = cfs.get(certType);752} else {753// create new certificate factory754cf = CertificateFactory.getInstance(certType);755// store the certificate factory so we can756// reuse it later757cfs.put(certType, cf);758}759}760encoded = IOUtils.readExactlyNBytes(dis, dis.readInt());761bais = new ByteArrayInputStream(encoded);762entry.cert = cf.generateCertificate(bais);763bais.close();764765// Add the entry to the list766entries.put(alias, entry);767768} else {769throw new IOException("Unrecognized keystore entry: " +770tag);771}772}773774if (debug != null) {775debug.println("JavaKeyStore load: private key count: " +776privateKeyCount + ". trusted key count: " + trustedKeyCount);777}778779/*780* If a password has been provided, we check the keyed digest781* at the end. If this check fails, the store has been tampered782* with783*/784if (password != null) {785byte computed[], actual[];786computed = md.digest();787actual = IOUtils.readExactlyNBytes(dis, computed.length);788if (!MessageDigest.isEqual(computed, actual)) {789Throwable t = new UnrecoverableKeyException790("Password verification failed");791throw (IOException) new IOException792("Keystore was tampered with, or "793+ "password was incorrect").initCause(t);794}795}796}797}798799/**800* To guard against tampering with the keystore, we append a keyed801* hash with a bit of whitener.802*/803private MessageDigest getPreKeyedHash(char[] password)804throws NoSuchAlgorithmException, UnsupportedEncodingException805{806807MessageDigest md = MessageDigest.getInstance("SHA");808byte[] passwdBytes = convertToBytes(password);809md.update(passwdBytes);810Arrays.fill(passwdBytes, (byte) 0x00);811md.update("Mighty Aphrodite".getBytes("UTF8"));812return md;813}814815/**816* Helper method to convert char[] to byte[]817*/818819private byte[] convertToBytes(char[] password) {820int i, j;821byte[] passwdBytes = new byte[password.length * 2];822for (i=0, j=0; i<password.length; i++) {823passwdBytes[j++] = (byte)(password[i] >> 8);824passwdBytes[j++] = (byte)password[i];825}826return passwdBytes;827}828}829830831