All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tencent.kona.sun.security.provider.JavaKeyStore Maven / Gradle / Ivy

Go to download

A Java security provider for supporting ShangMi algorithms in public key infrastructure

There is a newer version: 1.0.15
Show newest version
/*
 * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.tencent.kona.sun.security.provider;

import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateException;
import java.util.*;

import com.tencent.kona.crypto.CryptoInsts;
import com.tencent.kona.pkix.PKIXInsts;
import com.tencent.kona.sun.security.pkcs12.PKCS12KeyStore;
import com.tencent.kona.sun.security.util.Debug;
import com.tencent.kona.sun.security.util.IOUtils;
import com.tencent.kona.sun.security.pkcs.EncryptedPrivateKeyInfo;

/**
 * This class provides the keystore implementation referred to as "JKS".
 *
 * @author Jan Luehe
 * @author David Brownell
 *
 *
 * @see KeyProtector
 * @see java.security.KeyStoreSpi
 *
 * @since 1.2
 */

public abstract class JavaKeyStore extends KeyStoreSpi {

    // regular JKS
    public static final class JKS extends JavaKeyStore {
        String convertAlias(String alias) {
            return alias.toLowerCase(Locale.ENGLISH);
        }
    }

    // special JKS that uses case-sensitive aliases
    public static final class CaseExactJKS extends JavaKeyStore {
        String convertAlias(String alias) {
            return alias;
        }
    }

    // special JKS that supports JKS and PKCS12 file formats
    public static final class DualFormatJKS extends KeyStoreDelegator {
        public DualFormatJKS() {
            super("JKS", JKS.class, "PKCS12", PKCS12KeyStore.class);
        }
    }

    private static final Debug debug = Debug.getInstance("keystore");
    private static final int MAGIC = 0xfeedfeed;
    private static final int VERSION_1 = 0x01;
    private static final int VERSION_2 = 0x02;

    // Private keys and their supporting certificate chains
    private static class KeyEntry {
        Date date; // the creation date of this entry
        byte[] protectedPrivKey;
        Certificate chain[];
    }

    // Trusted certificates
    private static class TrustedCertEntry {
        Date date; // the creation date of this entry
        Certificate cert;
    }

    /**
     * Private keys and certificates are stored in a hashtable.
     * Hash entries are keyed by alias names.
     */
    private final Hashtable entries;

    JavaKeyStore() {
        entries = new Hashtable<>();
    }

    // convert an alias to internal form, overridden in subclasses:
    // lower case for regular JKS
    // original string for CaseExactJKS
    abstract String convertAlias(String alias);

    /**
     * Returns the key associated with the given alias, using the given
     * password to recover it.
     *
     * @param alias the alias name
     * @param password the password for recovering the key
     *
     * @return the requested key, or null if the given alias does not exist
     * or does not identify a key entry.
     *
     * @exception NoSuchAlgorithmException if the algorithm for recovering the
     * key cannot be found
     * @exception UnrecoverableKeyException if the key cannot be recovered
     * (e.g., the given password is wrong).
     */
    public Key engineGetKey(String alias, char[] password)
        throws NoSuchAlgorithmException, UnrecoverableKeyException
    {
        Object entry = entries.get(convertAlias(alias));

        if (!(entry instanceof KeyEntry)) {
            return null;
        }
        if (password == null) {
            throw new UnrecoverableKeyException("Password must not be null");
        }

        byte[] passwordBytes = convertToBytes(password);
        KeyProtector keyProtector = new KeyProtector(passwordBytes);
        byte[] encrBytes = ((KeyEntry)entry).protectedPrivKey;
        EncryptedPrivateKeyInfo encrInfo;
        try {
            encrInfo = new EncryptedPrivateKeyInfo(encrBytes);
            return keyProtector.recover(encrInfo);
        } catch (IOException ioe) {
            throw new UnrecoverableKeyException("Private key not stored as "
                                                + "PKCS #8 "
                                                + "EncryptedPrivateKeyInfo");
        } finally {
            Arrays.fill(passwordBytes, (byte) 0x00);
        }
    }

    /**
     * Returns the certificate chain associated with the given alias.
     *
     * @param alias the alias name
     *
     * @return the certificate chain (ordered with the user's certificate first
     * and the root certificate authority last), or null if the given alias
     * does not exist or does not contain a certificate chain (i.e., the given
     * alias identifies either a trusted certificate entry or a
     * key entry without a certificate chain).
     */
    public Certificate[] engineGetCertificateChain(String alias) {
        Object entry = entries.get(convertAlias(alias));

        if (entry instanceof KeyEntry) {
            if (((KeyEntry)entry).chain == null) {
                return null;
            } else {
                return ((KeyEntry)entry).chain.clone();
            }
        } else {
            return null;
        }
    }

    /**
     * Returns the certificate associated with the given alias.
     *
     * 

If the given alias name identifies a * trusted certificate entry, the certificate associated with that * entry is returned. If the given alias name identifies a * key entry, the first element of the certificate chain of that * entry is returned, or null if that entry does not have a certificate * chain. * * @param alias the alias name * * @return the certificate, or null if the given alias does not exist or * does not contain a certificate. */ public Certificate engineGetCertificate(String alias) { Object entry = entries.get(convertAlias(alias)); if (entry != null) { if (entry instanceof TrustedCertEntry) { return ((TrustedCertEntry)entry).cert; } else { if (((KeyEntry)entry).chain == null) { return null; } else { return ((KeyEntry)entry).chain[0]; } } } else { return null; } } /** * Returns the creation date of the entry identified by the given alias. * * @param alias the alias name * * @return the creation date of this entry, or null if the given alias does * not exist */ public Date engineGetCreationDate(String alias) { Object entry = entries.get(convertAlias(alias)); if (entry != null) { if (entry instanceof TrustedCertEntry) { return new Date(((TrustedCertEntry)entry).date.getTime()); } else { return new Date(((KeyEntry)entry).date.getTime()); } } else { return null; } } /** * Assigns the given private key to the given alias, protecting * it with the given password as defined in PKCS8. * *

The given java.security.PrivateKey key must * be accompanied by a certificate chain certifying the * corresponding public key. * *

If the given alias already exists, the keystore information * associated with it is overridden by the given key and certificate * chain. * * @param alias the alias name * @param key the private key to be associated with the alias * @param password the password to protect the key * @param chain the certificate chain for the corresponding public * key (only required if the given key is of type * java.security.PrivateKey). * * @exception KeyStoreException if the given key is not a private key, * cannot be protected, or this operation fails for some other reason */ public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException { KeyProtector keyProtector; byte[] passwordBytes = null; if (!(key instanceof java.security.PrivateKey)) { throw new KeyStoreException("Cannot store non-PrivateKeys"); } try { synchronized(entries) { KeyEntry entry = new KeyEntry(); entry.date = new Date(); // Protect the encoding of the key passwordBytes = convertToBytes(password); keyProtector = new KeyProtector(passwordBytes); entry.protectedPrivKey = keyProtector.protect(key); // clone the chain if ((chain != null) && (chain.length != 0)) { entry.chain = chain.clone(); } else { entry.chain = null; } entries.put(convertAlias(alias), entry); } } catch (NoSuchAlgorithmException nsae) { throw new KeyStoreException("Key protection algorithm not found"); } finally { if (passwordBytes != null) Arrays.fill(passwordBytes, (byte) 0x00); } } /** * Assigns the given key (that has already been protected) to the given * alias. * *

If the protected key is of type * java.security.PrivateKey, it must be accompanied by a * certificate chain certifying the corresponding public key. If the * underlying keystore implementation is of type jks, * key must be encoded as an * EncryptedPrivateKeyInfo as defined in the PKCS #8 standard. * *

If the given alias already exists, the keystore information * associated with it is overridden by the given key (and possibly * certificate chain). * * @param alias the alias name * @param key the key (in protected format) to be associated with the alias * @param chain the certificate chain for the corresponding public * key (only useful if the protected key is of type * java.security.PrivateKey). * * @exception KeyStoreException if this operation fails. */ public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException { synchronized(entries) { // key must be encoded as EncryptedPrivateKeyInfo as defined in // PKCS#8 try { new EncryptedPrivateKeyInfo(key); } catch (IOException ioe) { throw new KeyStoreException("key is not encoded as " + "EncryptedPrivateKeyInfo"); } KeyEntry entry = new KeyEntry(); entry.date = new Date(); entry.protectedPrivKey = key.clone(); if ((chain != null) && (chain.length != 0)) { entry.chain = chain.clone(); } else { entry.chain = null; } entries.put(convertAlias(alias), entry); } } /** * Assigns the given certificate to the given alias. * *

If the given alias already exists in this keystore and identifies a * trusted certificate entry, the certificate associated with it is * overridden by the given certificate. * * @param alias the alias name * @param cert the certificate * * @exception KeyStoreException if the given alias already exists and does * not identify a trusted certificate entry, or this operation * fails for some other reason. */ public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { synchronized(entries) { Object entry = entries.get(convertAlias(alias)); if ((entry instanceof KeyEntry)) { throw new KeyStoreException ("Cannot overwrite own certificate"); } TrustedCertEntry trustedCertEntry = new TrustedCertEntry(); trustedCertEntry.cert = cert; trustedCertEntry.date = new Date(); entries.put(convertAlias(alias), trustedCertEntry); } } /** * Deletes the entry identified by the given alias from this keystore. * * @param alias the alias name * * @exception KeyStoreException if the entry cannot be removed. */ public void engineDeleteEntry(String alias) throws KeyStoreException { synchronized(entries) { entries.remove(convertAlias(alias)); } } /** * Lists all the alias names of this keystore. * * @return enumeration of the alias names */ public Enumeration engineAliases() { return entries.keys(); } /** * Checks if the given alias exists in this keystore. * * @param alias the alias name * * @return true if the alias exists, false otherwise */ public boolean engineContainsAlias(String alias) { return entries.containsKey(convertAlias(alias)); } /** * Retrieves the number of entries in this keystore. * * @return the number of entries in this keystore */ public int engineSize() { return entries.size(); } /** * Returns true if the entry identified by the given alias is a * key entry, and false otherwise. * * @return true if the entry identified by the given alias is a * key entry, false otherwise. */ public boolean engineIsKeyEntry(String alias) { Object entry = entries.get(convertAlias(alias)); return entry instanceof KeyEntry; } /** * Returns true if the entry identified by the given alias is a * trusted certificate entry, and false otherwise. * * @return true if the entry identified by the given alias is a * trusted certificate entry, false otherwise. */ public boolean engineIsCertificateEntry(String alias) { Object entry = entries.get(convertAlias(alias)); return entry instanceof TrustedCertEntry; } /** * Returns the (alias) name of the first keystore entry whose certificate * matches the given certificate. * *

This method attempts to match the given certificate with each * keystore entry. If the entry being considered * is a trusted certificate entry, the given certificate is * compared to that entry's certificate. If the entry being considered is * a key entry, the given certificate is compared to the first * element of that entry's certificate chain (if a chain exists). * * @param cert the certificate to match with. * * @return the (alias) name of the first entry with matching certificate, * or null if no such entry exists in this keystore. */ public String engineGetCertificateAlias(Certificate cert) { Certificate certElem; for (Map.Entry e : entries.entrySet()) { String alias = e.getKey(); Object entry = e.getValue(); if (entry instanceof TrustedCertEntry) { certElem = ((TrustedCertEntry)entry).cert; } else if (((KeyEntry)entry).chain != null) { certElem = ((KeyEntry)entry).chain[0]; } else { continue; } if (certElem.equals(cert)) { return alias; } } return null; } /** * Stores this keystore to the given output stream, and protects its * integrity with the given password. * * @param stream the output stream to which this keystore is written. * @param password the password to generate the keystore integrity check * * @exception IOException if there was an I/O problem with data * @exception NoSuchAlgorithmException if the appropriate data integrity * algorithm could not be found * @exception CertificateException if any of the certificates included in * the keystore data could not be stored */ public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { synchronized(entries) { /* * KEYSTORE FORMAT: * * Magic number (big-endian integer), * Version of this file format (big-endian integer), * * Count (big-endian integer), * followed by "count" instances of either: * * { * tag=1 (big-endian integer), * alias (UTF string) * timestamp * encrypted private-key info according to PKCS #8 * (integer length followed by encoding) * cert chain (integer count, then certs; for each cert, * integer length followed by encoding) * } * * or: * * { * tag=2 (big-endian integer) * alias (UTF string) * timestamp * cert (integer length followed by encoding) * } * * ended by a keyed SHA1 hash (bytes only) of * { password + whitener + preceding body } */ // password is mandatory when storing if (password == null) { throw new IllegalArgumentException("password can't be null"); } byte[] encoded; // the certificate encoding MessageDigest md = getPreKeyedHash(password); DataOutputStream dos = new DataOutputStream(new DigestOutputStream(stream, md)); dos.writeInt(MAGIC); // always write the latest version dos.writeInt(VERSION_2); dos.writeInt(entries.size()); for (Map.Entry e : entries.entrySet()) { String alias = e.getKey(); Object entry = e.getValue(); if (entry instanceof KeyEntry) { // Store this entry as a KeyEntry dos.writeInt(1); // Write the alias dos.writeUTF(alias); // Write the (entry creation) date dos.writeLong(((KeyEntry)entry).date.getTime()); // Write the protected private key dos.writeInt(((KeyEntry)entry).protectedPrivKey.length); dos.write(((KeyEntry)entry).protectedPrivKey); // Write the certificate chain int chainLen; if (((KeyEntry)entry).chain == null) { chainLen = 0; } else { chainLen = ((KeyEntry)entry).chain.length; } dos.writeInt(chainLen); for (int i = 0; i < chainLen; i++) { encoded = ((KeyEntry)entry).chain[i].getEncoded(); dos.writeUTF(((KeyEntry)entry).chain[i].getType()); dos.writeInt(encoded.length); dos.write(encoded); } } else { // Store this entry as a certificate dos.writeInt(2); // Write the alias dos.writeUTF(alias); // Write the (entry creation) date dos.writeLong(((TrustedCertEntry)entry).date.getTime()); // Write the trusted certificate encoded = ((TrustedCertEntry)entry).cert.getEncoded(); dos.writeUTF(((TrustedCertEntry)entry).cert.getType()); dos.writeInt(encoded.length); dos.write(encoded); } } /* * Write the keyed hash which is used to detect tampering with * the keystore (such as deleting or modifying key or * certificate entries). */ byte digest[] = md.digest(); dos.write(digest); dos.flush(); } } /** * Loads the keystore from the given input stream. * *

If a password is given, it is used to check the integrity of the * keystore data. Otherwise, the integrity of the keystore is not checked. * * @param stream the input stream from which the keystore is loaded * @param password the (optional) password used to check the integrity of * the keystore. * * @exception IOException if there is an I/O or format problem with the * keystore data * @exception NoSuchAlgorithmException if the algorithm used to check * the integrity of the keystore cannot be found * @exception CertificateException if any of the certificates in the * keystore could not be loaded */ public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { synchronized(entries) { DataInputStream dis; MessageDigest md = null; CertificateFactory cf = null; Hashtable cfs = null; ByteArrayInputStream bais; byte[] encoded; int trustedKeyCount = 0, privateKeyCount = 0; if (stream == null) return; if (password != null) { md = getPreKeyedHash(password); dis = new DataInputStream(new DigestInputStream(stream, md)); } else { dis = new DataInputStream(stream); } // Body format: see store method int xMagic = dis.readInt(); int xVersion = dis.readInt(); if (xMagic!=MAGIC || (xVersion!=VERSION_1 && xVersion!=VERSION_2)) { throw new IOException("Invalid keystore format"); } if (xVersion == VERSION_1) { cf = PKIXInsts.getCertificateFactory("X509"); } else { // version 2 cfs = new Hashtable<>(3); } entries.clear(); int count = dis.readInt(); for (int i = 0; i < count; i++) { int tag; String alias; tag = dis.readInt(); if (tag == 1) { // private key entry privateKeyCount++; KeyEntry entry = new KeyEntry(); // Read the alias alias = dis.readUTF(); // Read the (entry creation) date entry.date = new Date(dis.readLong()); // Read the private key entry.protectedPrivKey = IOUtils.readExactlyNBytes(dis, dis.readInt()); // Read the certificate chain int numOfCerts = dis.readInt(); if (numOfCerts > 0) { List certs = new ArrayList<>( Math.min(numOfCerts, 10)); for (int j = 0; j < numOfCerts; j++) { if (xVersion == 2) { // read the certificate type, and instantiate a // certificate factory of that type (reuse // existing factory if possible) String certType = dis.readUTF(); if (cfs.containsKey(certType)) { // reuse certificate factory cf = cfs.get(certType); } else { // create new certificate factory cf = PKIXInsts.getCertificateFactory(certType); // store the certificate factory so we can // reuse it later cfs.put(certType, cf); } } // instantiate the certificate encoded = IOUtils.readExactlyNBytes(dis, dis.readInt()); bais = new ByteArrayInputStream(encoded); certs.add(cf.generateCertificate(bais)); bais.close(); } // We can be sure now that numOfCerts of certs are read entry.chain = certs.toArray(new Certificate[numOfCerts]); } // Add the entry to the list entries.put(alias, entry); } else if (tag == 2) { // trusted certificate entry trustedKeyCount++; TrustedCertEntry entry = new TrustedCertEntry(); // Read the alias alias = dis.readUTF(); // Read the (entry creation) date entry.date = new Date(dis.readLong()); // Read the trusted certificate if (xVersion == 2) { // read the certificate type, and instantiate a // certificate factory of that type (reuse // existing factory if possible) String certType = dis.readUTF(); if (cfs.containsKey(certType)) { // reuse certificate factory cf = cfs.get(certType); } else { // create new certificate factory cf = PKIXInsts.getCertificateFactory(certType); // store the certificate factory so we can // reuse it later cfs.put(certType, cf); } } encoded = IOUtils.readExactlyNBytes(dis, dis.readInt()); bais = new ByteArrayInputStream(encoded); entry.cert = cf.generateCertificate(bais); bais.close(); // Add the entry to the list entries.put(alias, entry); } else { throw new IOException("Unrecognized keystore entry: " + tag); } } if (debug != null) { debug.println("JavaKeyStore load: private key count: " + privateKeyCount + ". trusted key count: " + trustedKeyCount); } /* * If a password has been provided, we check the keyed digest * at the end. If this check fails, the store has been tampered * with */ if (password != null) { byte computed[], actual[]; computed = md.digest(); actual = IOUtils.readExactlyNBytes(dis, computed.length); if (!MessageDigest.isEqual(computed, actual)) { Throwable t = new UnrecoverableKeyException ("Password verification failed"); throw (IOException) new IOException ("Keystore was tampered with, or " + "password was incorrect").initCause(t); } } } } /** * To guard against tampering with the keystore, we append a keyed * hash with a bit of whitener. */ private MessageDigest getPreKeyedHash(char[] password) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md = CryptoInsts.getMessageDigest("SHA"); byte[] passwdBytes = convertToBytes(password); md.update(passwdBytes); Arrays.fill(passwdBytes, (byte) 0x00); md.update("Mighty Aphrodite".getBytes("UTF8")); return md; } /** * Helper method to convert char[] to byte[] */ private byte[] convertToBytes(char[] password) { int i, j; byte[] passwdBytes = new byte[password.length * 2]; for (i=0, j=0; i> 8); passwdBytes[j++] = (byte)password[i]; } return passwdBytes; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy