Path: blob/master/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java
41159 views
/*1* Copyright (c) 1997, 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.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 static java.nio.charset.StandardCharsets.UTF_8;3536import sun.security.pkcs.EncryptedPrivateKeyInfo;37import sun.security.pkcs12.PKCS12KeyStore;38import sun.security.util.Debug;39import sun.security.util.IOUtils;40import sun.security.util.KeyStoreDelegator;4142/**43* This class provides the keystore implementation referred to as "JKS".44*45* @author Jan Luehe46* @author David Brownell47*48*49* @see KeyProtector50* @see java.security.KeyStoreSpi51* @see KeyTool52*53* @since 1.254*/5556public abstract class JavaKeyStore extends KeyStoreSpi {5758// regular JKS59public static final class JKS extends JavaKeyStore {60String convertAlias(String alias) {61return alias.toLowerCase(Locale.ENGLISH);62}63}6465// special JKS that uses case sensitive aliases66public static final class CaseExactJKS extends JavaKeyStore {67String convertAlias(String alias) {68return alias;69}70}7172// special JKS that supports JKS and PKCS12 file formats73public static final class DualFormatJKS extends KeyStoreDelegator {74public DualFormatJKS() {75super("JKS", JKS.class, "PKCS12", PKCS12KeyStore.class);76}7778/**79* Probe the first few bytes of the keystore data stream for a valid80* JKS keystore encoding.81*/82@Override83public boolean engineProbe(InputStream stream) throws IOException {84DataInputStream dataStream;85if (stream instanceof DataInputStream) {86dataStream = (DataInputStream)stream;87} else {88dataStream = new DataInputStream(stream);89}9091return MAGIC == dataStream.readInt();92}93}9495private static final Debug debug = Debug.getInstance("keystore");96private static final int MAGIC = 0xfeedfeed;97private static final int VERSION_1 = 0x01;98private static final int VERSION_2 = 0x02;99100// Private keys and their supporting certificate chains101private static class KeyEntry {102Date date; // the creation date of this entry103byte[] protectedPrivKey;104Certificate[] chain;105};106107// Trusted certificates108private static class TrustedCertEntry {109Date date; // the creation date of this entry110Certificate cert;111};112113/**114* Private keys and certificates are stored in a hashtable.115* Hash entries are keyed by alias names.116*/117private final Hashtable<String, Object> entries;118119JavaKeyStore() {120entries = new Hashtable<String, Object>();121}122123// convert an alias to internal form, overridden in subclasses:124// lower case for regular JKS125// original string for CaseExactJKS126abstract String convertAlias(String alias);127128/**129* Returns the key associated with the given alias, using the given130* password to recover it.131*132* @param alias the alias name133* @param password the password for recovering the key134*135* @return the requested key, or null if the given alias does not exist136* or does not identify a <i>key entry</i>.137*138* @exception NoSuchAlgorithmException if the algorithm for recovering the139* key cannot be found140* @exception UnrecoverableKeyException if the key cannot be recovered141* (e.g., the given password is wrong).142*/143public Key engineGetKey(String alias, char[] password)144throws NoSuchAlgorithmException, UnrecoverableKeyException145{146Object entry = entries.get(convertAlias(alias));147148if (entry == null || !(entry instanceof KeyEntry)) {149return null;150}151if (password == null) {152throw new UnrecoverableKeyException("Password must not be null");153}154155byte[] passwordBytes = convertToBytes(password);156KeyProtector keyProtector = new KeyProtector(passwordBytes);157byte[] encrBytes = ((KeyEntry)entry).protectedPrivKey;158EncryptedPrivateKeyInfo encrInfo;159try {160encrInfo = new EncryptedPrivateKeyInfo(encrBytes);161return keyProtector.recover(encrInfo);162} catch (IOException ioe) {163throw new UnrecoverableKeyException("Private key not stored as "164+ "PKCS #8 "165+ "EncryptedPrivateKeyInfo");166} finally {167Arrays.fill(passwordBytes, (byte) 0x00);168}169}170171/**172* Returns the certificate chain associated with the given alias.173*174* @param alias the alias name175*176* @return the certificate chain (ordered with the user's certificate first177* and the root certificate authority last), or null if the given alias178* does not exist or does not contain a certificate chain (i.e., the given179* alias identifies either a <i>trusted certificate entry</i> or a180* <i>key entry</i> without a certificate chain).181*/182public Certificate[] engineGetCertificateChain(String alias) {183Object entry = entries.get(convertAlias(alias));184185if (entry != null && entry instanceof KeyEntry) {186if (((KeyEntry)entry).chain == null) {187return null;188} else {189return ((KeyEntry)entry).chain.clone();190}191} else {192return null;193}194}195196/**197* Returns the certificate associated with the given alias.198*199* <p>If the given alias name identifies a200* <i>trusted certificate entry</i>, the certificate associated with that201* entry is returned. If the given alias name identifies a202* <i>key entry</i>, the first element of the certificate chain of that203* entry is returned, or null if that entry does not have a certificate204* chain.205*206* @param alias the alias name207*208* @return the certificate, or null if the given alias does not exist or209* does not contain a certificate.210*/211public Certificate engineGetCertificate(String alias) {212Object entry = entries.get(convertAlias(alias));213214if (entry != null) {215if (entry instanceof TrustedCertEntry) {216return ((TrustedCertEntry)entry).cert;217} else {218if (((KeyEntry)entry).chain == null) {219return null;220} else {221return ((KeyEntry)entry).chain[0];222}223}224} else {225return null;226}227}228229/**230* Returns the creation date of the entry identified by the given alias.231*232* @param alias the alias name233*234* @return the creation date of this entry, or null if the given alias does235* not exist236*/237public Date engineGetCreationDate(String alias) {238Object entry = entries.get(convertAlias(alias));239240if (entry != null) {241if (entry instanceof TrustedCertEntry) {242return new Date(((TrustedCertEntry)entry).date.getTime());243} else {244return new Date(((KeyEntry)entry).date.getTime());245}246} else {247return null;248}249}250251/**252* Assigns the given private key to the given alias, protecting253* it with the given password as defined in PKCS8.254*255* <p>The given java.security.PrivateKey <code>key</code> must256* be accompanied by a certificate chain certifying the257* corresponding public key.258*259* <p>If the given alias already exists, the keystore information260* associated with it is overridden by the given key and certificate261* chain.262*263* @param alias the alias name264* @param key the private key to be associated with the alias265* @param password the password to protect the key266* @param chain the certificate chain for the corresponding public267* key (only required if the given key is of type268* <code>java.security.PrivateKey</code>).269*270* @exception KeyStoreException if the given key is not a private key,271* cannot be protected, or this operation fails for some other reason272*/273public void engineSetKeyEntry(String alias, Key key, char[] password,274Certificate[] chain)275throws KeyStoreException276{277KeyProtector keyProtector;278byte[] passwordBytes = null;279280if (!(key instanceof java.security.PrivateKey)) {281throw new KeyStoreException("Cannot store non-PrivateKeys");282}283if (password == null) {284throw new KeyStoreException("password can't be null");285}286try {287synchronized(entries) {288KeyEntry entry = new KeyEntry();289entry.date = new Date();290291// Protect the encoding of the key292passwordBytes = convertToBytes(password);293keyProtector = new KeyProtector(passwordBytes);294entry.protectedPrivKey = keyProtector.protect(key);295296// clone the chain297if ((chain != null) &&298(chain.length != 0)) {299entry.chain = chain.clone();300} else {301entry.chain = null;302}303304entries.put(convertAlias(alias), entry);305}306} catch (NoSuchAlgorithmException nsae) {307throw new KeyStoreException("Key protection algorithm not found");308} finally {309if (passwordBytes != null)310Arrays.fill(passwordBytes, (byte) 0x00);311}312}313314/**315* Assigns the given key (that has already been protected) to the given316* alias.317*318* <p>If the protected key is of type319* <code>java.security.PrivateKey</code>, it must be accompanied by a320* certificate chain certifying the corresponding public key. If the321* underlying keystore implementation is of type <code>jks</code>,322* <code>key</code> must be encoded as an323* <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.324*325* <p>If the given alias already exists, the keystore information326* associated with it is overridden by the given key (and possibly327* certificate chain).328*329* @param alias the alias name330* @param key the key (in protected format) to be associated with the alias331* @param chain the certificate chain for the corresponding public332* key (only useful if the protected key is of type333* <code>java.security.PrivateKey</code>).334*335* @exception KeyStoreException if this operation fails.336*/337public void engineSetKeyEntry(String alias, byte[] key,338Certificate[] chain)339throws KeyStoreException340{341synchronized(entries) {342// key must be encoded as EncryptedPrivateKeyInfo as defined in343// PKCS#8344try {345new EncryptedPrivateKeyInfo(key);346} catch (IOException ioe) {347throw new KeyStoreException("key is not encoded as "348+ "EncryptedPrivateKeyInfo");349}350351KeyEntry entry = new KeyEntry();352entry.date = new Date();353354entry.protectedPrivKey = key.clone();355if ((chain != null) &&356(chain.length != 0)) {357entry.chain = chain.clone();358} else {359entry.chain = null;360}361362entries.put(convertAlias(alias), entry);363}364}365366/**367* Assigns the given certificate to the given alias.368*369* <p>If the given alias already exists in this keystore and identifies a370* <i>trusted certificate entry</i>, the certificate associated with it is371* overridden by the given certificate.372*373* @param alias the alias name374* @param cert the certificate375*376* @exception KeyStoreException if the given alias already exists and does377* not identify a <i>trusted certificate entry</i>, or this operation378* fails for some other reason.379*/380public void engineSetCertificateEntry(String alias, Certificate cert)381throws KeyStoreException382{383synchronized(entries) {384385Object entry = entries.get(convertAlias(alias));386if ((entry != null) && (entry instanceof KeyEntry)) {387throw new KeyStoreException388("Cannot overwrite own certificate");389}390391TrustedCertEntry trustedCertEntry = new TrustedCertEntry();392trustedCertEntry.cert = cert;393trustedCertEntry.date = new Date();394entries.put(convertAlias(alias), trustedCertEntry);395}396}397398/**399* Deletes the entry identified by the given alias from this keystore.400*401* @param alias the alias name402*403* @exception KeyStoreException if the entry cannot be removed.404*/405public void engineDeleteEntry(String alias)406throws KeyStoreException407{408synchronized(entries) {409entries.remove(convertAlias(alias));410}411}412413/**414* Lists all the alias names of this keystore.415*416* @return enumeration of the alias names417*/418public Enumeration<String> engineAliases() {419return entries.keys();420}421422/**423* Checks if the given alias exists in this keystore.424*425* @param alias the alias name426*427* @return true if the alias exists, false otherwise428*/429public boolean engineContainsAlias(String alias) {430return entries.containsKey(convertAlias(alias));431}432433/**434* Retrieves the number of entries in this keystore.435*436* @return the number of entries in this keystore437*/438public int engineSize() {439return entries.size();440}441442/**443* Returns true if the entry identified by the given alias is a444* <i>key entry</i>, and false otherwise.445*446* @return true if the entry identified by the given alias is a447* <i>key entry</i>, false otherwise.448*/449public boolean engineIsKeyEntry(String alias) {450Object entry = entries.get(convertAlias(alias));451if ((entry != null) && (entry instanceof KeyEntry)) {452return true;453} else {454return false;455}456}457458/**459* Returns true if the entry identified by the given alias is a460* <i>trusted certificate entry</i>, and false otherwise.461*462* @return true if the entry identified by the given alias is a463* <i>trusted certificate entry</i>, false otherwise.464*/465public boolean engineIsCertificateEntry(String alias) {466Object entry = entries.get(convertAlias(alias));467if ((entry != null) && (entry instanceof TrustedCertEntry)) {468return true;469} else {470return false;471}472}473474/**475* Returns the (alias) name of the first keystore entry whose certificate476* matches the given certificate.477*478* <p>This method attempts to match the given certificate with each479* keystore entry. If the entry being considered480* is a <i>trusted certificate entry</i>, the given certificate is481* compared to that entry's certificate. If the entry being considered is482* a <i>key entry</i>, the given certificate is compared to the first483* element of that entry's certificate chain (if a chain exists).484*485* @param cert the certificate to match with.486*487* @return the (alias) name of the first entry with matching certificate,488* or null if no such entry exists in this keystore.489*/490public String engineGetCertificateAlias(Certificate cert) {491Certificate certElem;492493for (Enumeration<String> e = entries.keys(); e.hasMoreElements(); ) {494String alias = e.nextElement();495Object entry = entries.get(alias);496if (entry instanceof TrustedCertEntry) {497certElem = ((TrustedCertEntry)entry).cert;498} else if (((KeyEntry)entry).chain != null) {499certElem = ((KeyEntry)entry).chain[0];500} else {501continue;502}503if (certElem.equals(cert)) {504return alias;505}506}507return null;508}509510/**511* Stores this keystore to the given output stream, and protects its512* integrity with the given password.513*514* @param stream the output stream to which this keystore is written.515* @param password the password to generate the keystore integrity check516*517* @exception IOException if there was an I/O problem with data518* @exception NoSuchAlgorithmException if the appropriate data integrity519* algorithm could not be found520* @exception CertificateException if any of the certificates included in521* the keystore data could not be stored522*/523public void engineStore(OutputStream stream, char[] password)524throws IOException, NoSuchAlgorithmException, CertificateException525{526synchronized(entries) {527/*528* KEYSTORE FORMAT:529*530* Magic number (big-endian integer),531* Version of this file format (big-endian integer),532*533* Count (big-endian integer),534* followed by "count" instances of either:535*536* {537* tag=1 (big-endian integer),538* alias (UTF string)539* timestamp540* encrypted private-key info according to PKCS #8541* (integer length followed by encoding)542* cert chain (integer count, then certs; for each cert,543* integer length followed by encoding)544* }545*546* or:547*548* {549* tag=2 (big-endian integer)550* alias (UTF string)551* timestamp552* cert (integer length followed by encoding)553* }554*555* ended by a keyed SHA1 hash (bytes only) of556* { password + extra data + preceding body }557*/558559// password is mandatory when storing560if (password == null) {561throw new IllegalArgumentException("password can't be null");562}563564byte[] encoded; // the certificate encoding565566MessageDigest md = getPreKeyedHash(password);567DataOutputStream dos568= new DataOutputStream(new DigestOutputStream(stream, md));569570dos.writeInt(MAGIC);571// always write the latest version572dos.writeInt(VERSION_2);573574dos.writeInt(entries.size());575576for (Enumeration<String> e = entries.keys(); e.hasMoreElements();) {577578String alias = e.nextElement();579Object entry = entries.get(alias);580581if (entry instanceof KeyEntry) {582583// Store this entry as a KeyEntry584dos.writeInt(1);585586// Write the alias587dos.writeUTF(alias);588589// Write the (entry creation) date590dos.writeLong(((KeyEntry)entry).date.getTime());591592// Write the protected private key593dos.writeInt(((KeyEntry)entry).protectedPrivKey.length);594dos.write(((KeyEntry)entry).protectedPrivKey);595596// Write the certificate chain597int chainLen;598if (((KeyEntry)entry).chain == null) {599chainLen = 0;600} else {601chainLen = ((KeyEntry)entry).chain.length;602}603dos.writeInt(chainLen);604for (int i = 0; i < chainLen; i++) {605encoded = ((KeyEntry)entry).chain[i].getEncoded();606dos.writeUTF(((KeyEntry)entry).chain[i].getType());607dos.writeInt(encoded.length);608dos.write(encoded);609}610} else {611612// Store this entry as a certificate613dos.writeInt(2);614615// Write the alias616dos.writeUTF(alias);617618// Write the (entry creation) date619dos.writeLong(((TrustedCertEntry)entry).date.getTime());620621// Write the trusted certificate622encoded = ((TrustedCertEntry)entry).cert.getEncoded();623dos.writeUTF(((TrustedCertEntry)entry).cert.getType());624dos.writeInt(encoded.length);625dos.write(encoded);626}627}628629/*630* Write the keyed hash which is used to detect tampering with631* the keystore (such as deleting or modifying key or632* certificate entries).633*/634byte[] digest = md.digest();635636dos.write(digest);637dos.flush();638}639}640641/**642* Loads the keystore from the given input stream.643*644* <p>If a password is given, it is used to check the integrity of the645* keystore data. Otherwise, the integrity of the keystore is not checked.646*647* @param stream the input stream from which the keystore is loaded648* @param password the (optional) password used to check the integrity of649* the keystore.650*651* @exception IOException if there is an I/O or format problem with the652* keystore data653* @exception NoSuchAlgorithmException if the algorithm used to check654* the integrity of the keystore cannot be found655* @exception CertificateException if any of the certificates in the656* keystore could not be loaded657*/658public void engineLoad(InputStream stream, char[] password)659throws IOException, NoSuchAlgorithmException, CertificateException660{661synchronized(entries) {662DataInputStream dis;663MessageDigest md = null;664CertificateFactory cf = null;665Hashtable<String, CertificateFactory> cfs = null;666ByteArrayInputStream bais = null;667byte[] encoded = null;668int trustedKeyCount = 0, privateKeyCount = 0;669670if (stream == null)671return;672673if (password != null) {674md = getPreKeyedHash(password);675dis = new DataInputStream(new DigestInputStream(stream, md));676} else {677dis = new DataInputStream(stream);678}679680// Body format: see store method681682int xMagic = dis.readInt();683int xVersion = dis.readInt();684685if (xMagic!=MAGIC ||686(xVersion!=VERSION_1 && xVersion!=VERSION_2)) {687throw new IOException("Invalid keystore format");688}689690if (xVersion == VERSION_1) {691cf = CertificateFactory.getInstance("X509");692} else {693// version 2694cfs = new Hashtable<String, CertificateFactory>(3);695}696697entries.clear();698int count = dis.readInt();699700for (int i = 0; i < count; i++) {701int tag;702String alias;703704tag = dis.readInt();705706if (tag == 1) { // private key entry707privateKeyCount++;708KeyEntry entry = new KeyEntry();709710// Read the alias711alias = dis.readUTF();712713// Read the (entry creation) date714entry.date = new Date(dis.readLong());715716// Read the private key717entry.protectedPrivKey =718IOUtils.readExactlyNBytes(dis, dis.readInt());719720// Read the certificate chain721int numOfCerts = dis.readInt();722if (numOfCerts > 0) {723List<Certificate> certs = new ArrayList<>(724numOfCerts > 10 ? 10 : numOfCerts);725for (int j = 0; j < numOfCerts; j++) {726if (xVersion == 2) {727// read the certificate type, and instantiate a728// certificate factory of that type (reuse729// existing factory if possible)730String certType = dis.readUTF();731if (cfs.containsKey(certType)) {732// reuse certificate factory733cf = cfs.get(certType);734} else {735// create new certificate factory736cf = CertificateFactory.getInstance(certType);737// store the certificate factory so we can738// reuse it later739cfs.put(certType, cf);740}741}742// instantiate the certificate743encoded = IOUtils.readExactlyNBytes(dis, dis.readInt());744bais = new ByteArrayInputStream(encoded);745certs.add(cf.generateCertificate(bais));746bais.close();747}748// We can be sure now that numOfCerts of certs are read749entry.chain = certs.toArray(new Certificate[numOfCerts]);750}751752// Add the entry to the list753entries.put(alias, entry);754755} else if (tag == 2) { // trusted certificate entry756trustedKeyCount++;757TrustedCertEntry entry = new TrustedCertEntry();758759// Read the alias760alias = dis.readUTF();761762// Read the (entry creation) date763entry.date = new Date(dis.readLong());764765// Read the trusted certificate766if (xVersion == 2) {767// read the certificate type, and instantiate a768// certificate factory of that type (reuse769// existing factory if possible)770String certType = dis.readUTF();771if (cfs.containsKey(certType)) {772// reuse certificate factory773cf = cfs.get(certType);774} else {775// create new certificate factory776cf = CertificateFactory.getInstance(certType);777// store the certificate factory so we can778// reuse it later779cfs.put(certType, cf);780}781}782encoded = IOUtils.readExactlyNBytes(dis, dis.readInt());783bais = new ByteArrayInputStream(encoded);784entry.cert = cf.generateCertificate(bais);785bais.close();786787// Add the entry to the list788entries.put(alias, entry);789790} else {791throw new IOException("Unrecognized keystore entry: " +792tag);793}794}795796if (debug != null) {797debug.println("JavaKeyStore load: private key count: " +798privateKeyCount + ". trusted key count: " + trustedKeyCount);799}800801/*802* If a password has been provided, we check the keyed digest803* at the end. If this check fails, the store has been tampered804* with805*/806if (password != null) {807byte[] computed = md.digest();808byte[] actual = IOUtils.readExactlyNBytes(dis, computed.length);809if (!MessageDigest.isEqual(computed, actual)) {810Throwable t = new UnrecoverableKeyException811("Password verification failed");812throw (IOException) new IOException813("Keystore was tampered with, or "814+ "password was incorrect").initCause(t);815}816}817}818}819820/**821* To guard against tampering with the keystore, we append a keyed822* hash with a bit of extra data.823*/824private MessageDigest getPreKeyedHash(char[] password)825throws NoSuchAlgorithmException826{827828MessageDigest md = MessageDigest.getInstance("SHA");829byte[] passwdBytes = convertToBytes(password);830md.update(passwdBytes);831Arrays.fill(passwdBytes, (byte) 0x00);832md.update("Mighty Aphrodite".getBytes(UTF_8));833return md;834}835836/**837* Helper method to convert char[] to byte[]838*/839840private byte[] convertToBytes(char[] password) {841int i, j;842byte[] passwdBytes = new byte[password.length * 2];843for (i=0, j=0; i<password.length; i++) {844passwdBytes[j++] = (byte)(password[i] >> 8);845passwdBytes[j++] = (byte)password[i];846}847return passwdBytes;848}849}850851852