/* * JBoss, Home of Professional Open Source. * Copyright 2016 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.wildfly.security.credential.store.impl; import static org.wildfly.security._private.ElytronMessages.log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.UnrecoverableEntryException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidParameterSpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.wildfly.common.Assert; import org.wildfly.security.asn1.ASN1Exception; import org.wildfly.security.asn1.DERDecoder; import org.wildfly.security.asn1.DEREncoder; import org.wildfly.security.credential.AlgorithmCredential; import org.wildfly.security.credential.BearerTokenCredential; import org.wildfly.security.credential.Credential; import org.wildfly.security.credential.KeyPairCredential; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.credential.PublicKeyCredential; import org.wildfly.security.credential.SecretKeyCredential; import org.wildfly.security.credential.X509CertificateChainPrivateCredential; import org.wildfly.security.credential.X509CertificateChainPublicCredential; import org.wildfly.security.credential.source.CredentialSource; import org.wildfly.security.credential.store.CredentialStore; import org.wildfly.security.credential.store.CredentialStoreException; import org.wildfly.security.credential.store.CredentialStoreSpi; import org.wildfly.security.key.KeyUtil; import org.wildfly.security.password.Password; import org.wildfly.security.password.PasswordFactory; import org.wildfly.security.password.interfaces.BCryptPassword; import org.wildfly.security.password.interfaces.BSDUnixDESCryptPassword; import org.wildfly.security.password.interfaces.ClearPassword; import org.wildfly.security.password.interfaces.DigestPassword; import org.wildfly.security.password.interfaces.MaskedPassword; import org.wildfly.security.password.interfaces.OneTimePassword; import org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword; import org.wildfly.security.password.interfaces.ScramDigestPassword; import org.wildfly.security.password.interfaces.SimpleDigestPassword; import org.wildfly.security.password.interfaces.SunUnixMD5CryptPassword; import org.wildfly.security.password.interfaces.UnixDESCryptPassword; import org.wildfly.security.password.interfaces.UnixMD5CryptPassword; import org.wildfly.security.password.interfaces.UnixSHACryptPassword; import org.wildfly.security.password.spec.ClearPasswordSpec; import org.wildfly.security.password.spec.DigestPasswordSpec; import org.wildfly.security.password.spec.HashPasswordSpec; import org.wildfly.security.password.spec.IteratedSaltedHashPasswordSpec; import org.wildfly.security.password.spec.MaskedPasswordSpec; import org.wildfly.security.password.spec.OneTimePasswordSpec; import org.wildfly.security.password.spec.PasswordSpec; import org.wildfly.security.password.spec.SaltedHashPasswordSpec; import org.wildfly.security.util.Alphabet; import org.wildfly.security.util.AtomicFileOutputStream; import org.wildfly.security.util.ByteIterator; import org.wildfly.security.util.ByteStringBuilder; import org.wildfly.security.util.CodePointIterator; import org.wildfly.security.x500.X500; /** * A flexible credential store which is backed by a key store. The key store holds the credentials, encoding identifying * information into the alias to allow multiple credentials to be stored under each alias (something keystores generally * do not support). * <p> * This credential store cannot convert an arbitrary key store into a credential store; it can only understand entries that * it itself has added. Entries not understood by this credential store will be ignored (and a log message will be * generated indicating the presence of unknown credentials). * <p> * The following configuration parameters are supported: * <ul> * <li>{@code location}: specifies the location of the key store (none means, use an in-memory store and do not store changes)</li> * <li>{@code modifiable}: specifies whether the credential store should be modifiable</li> * <li>{@code create}: specifies to automatically create storage file for this credential store (defaults to {@code false})</li> * <li>{@code keyStoreType}: specifies the key store type to use (defaults to {@link KeyStore#getDefaultType()})</li> * <li>{@code keyAlias}: specifies the secret key alias within the key store to use for encrypt/decrypt of data in external storage (defaults to {@code cs_key})</li> * <li>{@code external}: specifies whether to store data to external storage and encrypted by {@code keyAlias} key (defaults to {@code false})</li> * <li>{@code cryptoAlg}: cryptographic algorithm name to be used to encrypt decrypt entries at external storage ({@code external} has to be set to {@code true})</li> * </ul> */ public final class KeyStoreCredentialStore extends CredentialStoreSpi { private static final String DATA_OID = "1.2.840.113549.1.7.1"; /** * The name of this credential store implementation. */ public static final String KEY_STORE_CREDENTIAL_STORE = KeyStoreCredentialStore.class.getSimpleName(); private static final String X_509 = "X.509"; private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final HashMap<String, TopEntry> cache = new HashMap<>(); private volatile boolean modifiable; private KeyStore keyStore; private Path location; private boolean create; private CredentialStore.ProtectionParameter protectionParameter; private Provider[] providers; private String encryptionKeyAlias; private boolean useExternalStorage = false; private ExternalStorage externalStorage; private String cryptographicAlgorithm; public void initialize(final Map<String, String> attributes, final CredentialStore.ProtectionParameter protectionParameter, final Provider[] providers) throws CredentialStoreException { try (Hold hold = lockForWrite()) { if (protectionParameter == null) { throw log.protectionParameterRequired(); } cache.clear(); this.protectionParameter = protectionParameter; modifiable = Boolean.parseBoolean(attributes.getOrDefault("modifiable", "true")); create = Boolean.parseBoolean(attributes.getOrDefault("create", "false")); final String locationName = attributes.get("location"); location = locationName == null ? null : Paths.get(locationName); this.providers = providers; String keyStoreType = attributes.getOrDefault("keyStoreType", KeyStore.getDefaultType()); useExternalStorage = Boolean.parseBoolean(attributes.getOrDefault("external", "false")); encryptionKeyAlias = attributes.getOrDefault("keyAlias", "cs_key"); cryptographicAlgorithm = attributes.get("cryptoAlg"); load(keyStoreType); initialized = true; } } public boolean isModifiable() { return modifiable; } public void store(final String credentialAlias, final Credential credential, final CredentialStore.ProtectionParameter protectionParameter) throws CredentialStoreException { try { // first, attempt to encode the credential into a keystore entry final Class<? extends Credential> credentialClass = credential.getClass(); final String algorithmName = credential instanceof AlgorithmCredential ? ((AlgorithmCredential) credential).getAlgorithm() : null; final AlgorithmParameterSpec parameterSpec = credential.castAndApply(AlgorithmCredential.class, AlgorithmCredential::getParameters); final KeyStore.Entry entry; if (credentialClass == SecretKeyCredential.class) { entry = new KeyStore.SecretKeyEntry(credential.castAndApply(SecretKeyCredential.class, SecretKeyCredential::getSecretKey)); } else if (credentialClass == PublicKeyCredential.class) { final PublicKey publicKey = credential.castAndApply(PublicKeyCredential.class, PublicKeyCredential::getPublicKey); final KeyFactory keyFactory = KeyFactory.getInstance(publicKey.getAlgorithm()); final X509EncodedKeySpec keySpec = keyFactory.getKeySpec(keyFactory.translateKey(publicKey), X509EncodedKeySpec.class); final byte[] encoded = keySpec.getEncoded(); entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(encoded, DATA_OID)); } else if (credentialClass == KeyPairCredential.class) { final KeyPair keyPair = credential.castAndApply(KeyPairCredential.class, KeyPairCredential::getKeyPair); final PublicKey publicKey = keyPair.getPublic(); final PrivateKey privateKey = keyPair.getPrivate(); final KeyFactory keyFactory = KeyFactory.getInstance(publicKey.getAlgorithm()); // ensured by KeyPairCredential assert privateKey.getAlgorithm().equals(publicKey.getAlgorithm()); final X509EncodedKeySpec publicSpec = keyFactory.getKeySpec(keyFactory.translateKey(publicKey), X509EncodedKeySpec.class); final PKCS8EncodedKeySpec privateSpec = keyFactory.getKeySpec(keyFactory.translateKey(privateKey), PKCS8EncodedKeySpec.class); final ByteStringBuilder b = new ByteStringBuilder(); final DEREncoder encoder = new DEREncoder(b); encoder.startSequence(); encoder.writeEncoded(publicSpec.getEncoded()); encoder.writeEncoded(privateSpec.getEncoded()); encoder.endSequence(); entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(b.toArray(), DATA_OID)); } else if (credentialClass == X509CertificateChainPublicCredential.class) { final X509Certificate[] x509Certificates = credential.castAndApply(X509CertificateChainPublicCredential.class, X509CertificateChainPublicCredential::getCertificateChain); final ByteStringBuilder b = new ByteStringBuilder(); final DEREncoder encoder = new DEREncoder(b); encoder.encodeInteger(x509Certificates.length); encoder.startSequence(); for (X509Certificate x509Certificate : x509Certificates) { encoder.writeEncoded(x509Certificate.getEncoded()); } encoder.endSequence(); entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(b.toArray(), DATA_OID)); } else if (credentialClass == X509CertificateChainPrivateCredential.class) { @SuppressWarnings("ConstantConditions") X509CertificateChainPrivateCredential cred = (X509CertificateChainPrivateCredential) credential; entry = new KeyStore.PrivateKeyEntry(cred.getPrivateKey(), cred.getCertificateChain()); } else if (credentialClass == BearerTokenCredential.class) { entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(credential.castAndApply(BearerTokenCredential.class, c -> c.getToken().getBytes(StandardCharsets.UTF_8)), DATA_OID)); } else if (credentialClass == PasswordCredential.class) { final Password password = credential.castAndApply(PasswordCredential.class, PasswordCredential::getPassword); final String algorithm = password.getAlgorithm(); final ByteStringBuilder b = new ByteStringBuilder(); final DEREncoder encoder = new DEREncoder(b); final PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); switch (algorithm) { case BCryptPassword.ALGORITHM_BCRYPT: case BSDUnixDESCryptPassword.ALGORITHM_BSD_CRYPT_DES: case ScramDigestPassword.ALGORITHM_SCRAM_SHA_1: case ScramDigestPassword.ALGORITHM_SCRAM_SHA_256: case ScramDigestPassword.ALGORITHM_SCRAM_SHA_384: case ScramDigestPassword.ALGORITHM_SCRAM_SHA_512: case SunUnixMD5CryptPassword.ALGORITHM_SUN_CRYPT_MD5: case SunUnixMD5CryptPassword.ALGORITHM_SUN_CRYPT_MD5_BARE_SALT: case UnixSHACryptPassword.ALGORITHM_CRYPT_SHA_256: case UnixSHACryptPassword.ALGORITHM_CRYPT_SHA_512: { IteratedSaltedHashPasswordSpec passwordSpec = passwordFactory.getKeySpec(passwordFactory.translate(password), IteratedSaltedHashPasswordSpec.class); encoder.startSequence(); encoder.encodeOctetString(passwordSpec.getHash()); encoder.encodeOctetString(passwordSpec.getSalt()); encoder.encodeInteger(passwordSpec.getIterationCount()); encoder.endSequence(); break; } case ClearPassword.ALGORITHM_CLEAR: { final ClearPasswordSpec passwordSpec = passwordFactory.getKeySpec(passwordFactory.translate(password), ClearPasswordSpec.class); encoder.encodeOctetString(new String(passwordSpec.getEncodedPassword())); break; } case DigestPassword.ALGORITHM_DIGEST_MD5: case DigestPassword.ALGORITHM_DIGEST_SHA: case DigestPassword.ALGORITHM_DIGEST_SHA_256: case DigestPassword.ALGORITHM_DIGEST_SHA_384: case DigestPassword.ALGORITHM_DIGEST_SHA_512: { final DigestPasswordSpec passwordSpec = passwordFactory.getKeySpec(passwordFactory.translate(password), DigestPasswordSpec.class); encoder.startSequence(); encoder.encodeOctetString(passwordSpec.getUsername()); encoder.encodeOctetString(passwordSpec.getRealm()); encoder.encodeOctetString(passwordSpec.getDigest()); encoder.endSequence(); break; } case OneTimePassword.ALGORITHM_OTP_MD5: case OneTimePassword.ALGORITHM_OTP_SHA1: { final OneTimePasswordSpec passwordSpec = passwordFactory.getKeySpec(passwordFactory.translate(password), OneTimePasswordSpec.class); encoder.startSequence(); encoder.encodeOctetString(passwordSpec.getHash()); encoder.encodeOctetString(passwordSpec.getSeed()); encoder.encodeInteger(passwordSpec.getSequenceNumber()); encoder.endSequence(); break; } case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_MD5: case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_1: case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_256: case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_384: case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_MD5: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_1: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_256: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_384: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_512: case UnixDESCryptPassword.ALGORITHM_CRYPT_DES: case UnixMD5CryptPassword.ALGORITHM_CRYPT_MD5: { final SaltedHashPasswordSpec passwordSpec = passwordFactory.getKeySpec(passwordFactory.translate(password), SaltedHashPasswordSpec.class); encoder.startSequence(); encoder.encodeOctetString(passwordSpec.getHash()); encoder.encodeOctetString(passwordSpec.getSalt()); encoder.endSequence(); break; } case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_MD2: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_MD5: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_1: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_256: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_384: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512: { final HashPasswordSpec passwordSpec = passwordFactory.getKeySpec(passwordFactory.translate(password), HashPasswordSpec.class); encoder.startSequence(); encoder.encodeOctetString(passwordSpec.getDigest()); encoder.endSequence(); break; } default: { if (MaskedPassword.isMaskedAlgorithm(algorithmName)) { final MaskedPasswordSpec passwordSpec = passwordFactory.getKeySpec(passwordFactory.translate(password), MaskedPasswordSpec.class); encoder.startSequence(); encoder.encodeOctetString(new String(passwordSpec.getInitialKeyMaterial())); encoder.encodeInteger(passwordSpec.getIterationCount()); encoder.encodeOctetString(passwordSpec.getSalt()); encoder.encodeOctetString(passwordSpec.getMaskedPasswordBytes()); encoder.endSequence(); break; } else { throw log.unsupportedCredentialType(credentialClass); } } } entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(b.toArray(), DATA_OID)); } else { throw log.unsupportedCredentialType(credentialClass); } // now, store it under a unique alias final String ksAlias = calculateNewAlias(credentialAlias, credentialClass, algorithmName, parameterSpec); try (Hold hold = lockForWrite()) { keyStore.setEntry(ksAlias, entry, convertParameter(protectionParameter)); final TopEntry topEntry = cache.computeIfAbsent(toLowercase(credentialAlias), TopEntry::new); final MidEntry midEntry = topEntry.getMap().computeIfAbsent(credentialClass, c -> new MidEntry(topEntry, c)); final BottomEntry bottomEntry; if (algorithmName != null) { bottomEntry = midEntry.getMap().computeIfAbsent(algorithmName, n -> new BottomEntry(midEntry, n)); } else { bottomEntry = midEntry.getOrCreateNoAlgorithm(); } final String oldAlias; if (parameterSpec != null) { oldAlias = bottomEntry.getMap().put(new ParamKey(parameterSpec), ksAlias); } else { oldAlias = bottomEntry.setNoParams(ksAlias); } if (oldAlias != null && ! oldAlias.equals(ksAlias)) { // unlikely but possible keyStore.deleteEntry(oldAlias); } } } catch (KeyStoreException | NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | CertificateException e) { throw log.cannotWriteCredentialToStore(e); } } public <C extends Credential> C retrieve(final String credentialAlias, final Class<C> credentialType, final String credentialAlgorithm, final AlgorithmParameterSpec parameterSpec, final CredentialStore.ProtectionParameter protectionParameter) throws CredentialStoreException { final KeyStore.Entry entry; final MidEntry midEntry; final BottomEntry bottomEntry; final String ksAlias; try (Hold hold = lockForRead()) { final TopEntry topEntry = cache.get(toLowercase(credentialAlias)); if (topEntry == null) { return null; } if (topEntry.getMap().containsKey(credentialType)) { midEntry = topEntry.getMap().get(credentialType); } else { // loose (slow) match final Iterator<MidEntry> iterator = topEntry.getMap().values().iterator(); for (;;) { if (! iterator.hasNext()) { return null; } MidEntry item = iterator.next(); if (credentialType.isAssignableFrom(item.getCredentialType())) { midEntry = item; break; } } } if (credentialAlgorithm != null) { bottomEntry = midEntry.getMap().get(credentialAlgorithm); } else { // match any final Iterator<BottomEntry> iterator = midEntry.getMap().values().iterator(); if (iterator.hasNext()) { bottomEntry = iterator.next(); } else { bottomEntry = midEntry.getNoAlgorithm(); } } if (bottomEntry == null) { return null; } if (parameterSpec != null) { ksAlias = bottomEntry.getMap().get(new ParamKey(parameterSpec)); } else { // match any final Iterator<String> iterator = bottomEntry.getMap().values().iterator(); if (iterator.hasNext()) { ksAlias = iterator.next(); } else { ksAlias = bottomEntry.getNoParams(); } } if (ksAlias == null) { return null; } entry = keyStore.getEntry(ksAlias, convertParameter(protectionParameter)); } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) { throw log.cannotAcquireCredentialFromStore(e); } if (entry == null) { // odd, but we can handle it return null; } final Class<? extends Credential> matchedCredentialType = midEntry.getCredentialType(); if (matchedCredentialType == SecretKeyCredential.class) { if (entry instanceof KeyStore.SecretKeyEntry) { // simple final SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); return credentialType.cast(new SecretKeyCredential(secretKey)); } else { throw log.invalidCredentialStoreEntryType(KeyStore.SecretKeyEntry.class, entry.getClass()); } } else if (matchedCredentialType == PublicKeyCredential.class) { if (entry instanceof KeyStore.SecretKeyEntry) try { // we store as a secret key because we can't store the public key properly... final SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); final byte[] encoded = secretKey.getEncoded(); final String matchedAlgorithm = bottomEntry.getAlgorithm(); assert matchedAlgorithm != null; // because PublicKeyCredential is an AlgorithmCredential final KeyFactory keyFactory = KeyFactory.getInstance(matchedAlgorithm); final PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); return credentialType.cast(new PublicKeyCredential(publicKey)); } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { throw log.cannotAcquireCredentialFromStore(e); } else { throw log.invalidCredentialStoreEntryType(KeyStore.SecretKeyEntry.class, entry.getClass()); } } else if (matchedCredentialType == KeyPairCredential.class) { if (entry instanceof KeyStore.SecretKeyEntry) try { final SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); final byte[] encoded = secretKey.getEncoded(); final String matchedAlgorithm = bottomEntry.getAlgorithm(); assert matchedAlgorithm != null; // because KeyPairCredential is an AlgorithmCredential // extract public and private segments final ByteIterator bi = ByteIterator.ofBytes(encoded); final DERDecoder decoder = new DERDecoder(bi); decoder.startSequence(); final byte[] publicBytes = decoder.drainElement(); final byte[] privateBytes = decoder.drainElement(); decoder.endSequence(); final KeyFactory keyFactory = KeyFactory.getInstance(matchedAlgorithm); final PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicBytes)); final PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateBytes)); final KeyPair keyPair = new KeyPair(publicKey, privateKey); return credentialType.cast(new KeyPairCredential(keyPair)); } catch (InvalidKeySpecException | NoSuchAlgorithmException | ASN1Exception e) { throw log.cannotAcquireCredentialFromStore(e); } else { throw log.invalidCredentialStoreEntryType(KeyStore.SecretKeyEntry.class, entry.getClass()); } } else if (matchedCredentialType == X509CertificateChainPublicCredential.class) { if (entry instanceof KeyStore.SecretKeyEntry) try { // OK so this is pretty ugly, but the TrustedCertificateEntry type only holds a single cert so it's no good final SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); final byte[] encoded = secretKey.getEncoded(); final String matchedAlgorithm = bottomEntry.getAlgorithm(); assert matchedAlgorithm != null; // because it is an AlgorithmCredential final ByteIterator bi = ByteIterator.ofBytes(encoded); final DERDecoder decoder = new DERDecoder(bi); final CertificateFactory certificateFactory = CertificateFactory.getInstance(X_509); final int count = decoder.decodeInteger().intValueExact(); final X509Certificate[] array = new X509Certificate[count]; decoder.startSequence(); int i = 0; while (decoder.hasNextElement()) { final byte[] certBytes = decoder.drainElement(); array[i ++] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certBytes)); } decoder.endSequence(); return credentialType.cast(new X509CertificateChainPublicCredential(array)); } catch (ASN1Exception | CertificateException | ArrayIndexOutOfBoundsException e) { throw log.cannotAcquireCredentialFromStore(e); } else { throw log.invalidCredentialStoreEntryType(KeyStore.SecretKeyEntry.class, entry.getClass()); } } else if (matchedCredentialType == X509CertificateChainPrivateCredential.class) { if (entry instanceof KeyStore.PrivateKeyEntry) { // an entry type that matches our credential type! final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry; final PrivateKey privateKey = privateKeyEntry.getPrivateKey(); final Certificate[] certificateChain = privateKeyEntry.getCertificateChain(); final X509Certificate[] x509Certificates = X500.asX509CertificateArray(certificateChain); return credentialType.cast(new X509CertificateChainPrivateCredential(privateKey, x509Certificates)); } else { throw log.invalidCredentialStoreEntryType(KeyStore.PrivateKeyEntry.class, entry.getClass()); } } else if (matchedCredentialType == BearerTokenCredential.class) { if (entry instanceof KeyStore.SecretKeyEntry) { final SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); final byte[] encoded = secretKey.getEncoded(); return credentialType.cast(new BearerTokenCredential(new String(encoded, StandardCharsets.UTF_8))); } else { throw log.invalidCredentialStoreEntryType(KeyStore.SecretKeyEntry.class, entry.getClass()); } } else if (matchedCredentialType == PasswordCredential.class) { if (entry instanceof KeyStore.SecretKeyEntry) try { final SecretKey secretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); final byte[] encoded = secretKey.getEncoded(); final String matchedAlgorithm = bottomEntry.getAlgorithm(); assert matchedAlgorithm != null; // because it is an AlgorithmCredential final ByteIterator bi = ByteIterator.ofBytes(encoded); final DERDecoder decoder = new DERDecoder(bi); // we use algorithm-based encoding rather than a standard that encompasses all password types. final PasswordSpec passwordSpec; switch (matchedAlgorithm) { case BCryptPassword.ALGORITHM_BCRYPT: case BSDUnixDESCryptPassword.ALGORITHM_BSD_CRYPT_DES: case ScramDigestPassword.ALGORITHM_SCRAM_SHA_1: case ScramDigestPassword.ALGORITHM_SCRAM_SHA_256: case ScramDigestPassword.ALGORITHM_SCRAM_SHA_384: case ScramDigestPassword.ALGORITHM_SCRAM_SHA_512: case SunUnixMD5CryptPassword.ALGORITHM_SUN_CRYPT_MD5: case SunUnixMD5CryptPassword.ALGORITHM_SUN_CRYPT_MD5_BARE_SALT: case UnixSHACryptPassword.ALGORITHM_CRYPT_SHA_256: case UnixSHACryptPassword.ALGORITHM_CRYPT_SHA_512: { decoder.startSequence(); final byte[] hash = decoder.decodeOctetString(); final byte[] salt = decoder.decodeOctetString(); final int iterationCount = decoder.decodeInteger().intValue(); decoder.endSequence(); passwordSpec = new IteratedSaltedHashPasswordSpec(hash, salt, iterationCount); break; } case ClearPassword.ALGORITHM_CLEAR: { passwordSpec = new ClearPasswordSpec(decoder.decodeOctetStringAsString().toCharArray()); break; } case DigestPassword.ALGORITHM_DIGEST_MD5: case DigestPassword.ALGORITHM_DIGEST_SHA: case DigestPassword.ALGORITHM_DIGEST_SHA_256: case DigestPassword.ALGORITHM_DIGEST_SHA_384: case DigestPassword.ALGORITHM_DIGEST_SHA_512: { decoder.startSequence(); final String username = decoder.decodeOctetStringAsString(); final String realm = decoder.decodeOctetStringAsString(); final byte[] digest = decoder.decodeOctetString(); decoder.endSequence(); passwordSpec = new DigestPasswordSpec(username, realm, digest); break; } case OneTimePassword.ALGORITHM_OTP_MD5: case OneTimePassword.ALGORITHM_OTP_SHA1: { decoder.startSequence(); final byte[] hash = decoder.decodeOctetString(); final byte[] seed = decoder.decodeOctetString(); final int sequenceNumber = decoder.decodeInteger().intValue(); decoder.endSequence(); passwordSpec = new OneTimePasswordSpec(hash, seed, sequenceNumber); break; } case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_MD5: case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_1: case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_256: case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_384: case SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_MD5: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_1: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_256: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_384: case SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_512: case UnixDESCryptPassword.ALGORITHM_CRYPT_DES: case UnixMD5CryptPassword.ALGORITHM_CRYPT_MD5: { decoder.startSequence(); final byte[] hash = decoder.decodeOctetString(); final byte[] salt = decoder.decodeOctetString(); decoder.endSequence(); passwordSpec = new SaltedHashPasswordSpec(hash, salt); break; } case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_MD2: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_MD5: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_1: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_256: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_384: case SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512: { decoder.startSequence(); final byte[] hash = decoder.decodeOctetString(); decoder.endSequence(); passwordSpec = new HashPasswordSpec(hash); break; } default: { if (MaskedPassword.isMaskedAlgorithm(matchedAlgorithm)) { decoder.startSequence(); final char[] initialKeyMaterial = decoder.decodeOctetStringAsString().toCharArray(); final int iterationCount = decoder.decodeInteger().intValue(); final byte[] salt = decoder.decodeOctetString(); final byte[] maskedPasswordBytes = decoder.decodeOctetString(); decoder.endSequence(); passwordSpec = new MaskedPasswordSpec(initialKeyMaterial, iterationCount, salt, maskedPasswordBytes); break; } else { throw log.unsupportedCredentialType(credentialType); } } } PasswordFactory passwordFactory = PasswordFactory.getInstance(matchedAlgorithm); final Password password = passwordFactory.generatePassword(passwordSpec); return credentialType.cast(new PasswordCredential(password)); } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { throw log.cannotAcquireCredentialFromStore(e); } else { throw log.invalidCredentialStoreEntryType(KeyStore.SecretKeyEntry.class, entry.getClass()); } } else { throw log.unableToReadCredentialTypeFromStore(matchedCredentialType); } } private KeyStore.ProtectionParameter convertParameter(final CredentialStore.ProtectionParameter protectionParameter) throws CredentialStoreException { // only one conversion is really possible. if (protectionParameter == null) { return convertParameter(this.protectionParameter); } else if (protectionParameter instanceof CredentialStore.CredentialSourceProtectionParameter) { final CredentialSource credentialSource = ((CredentialStore.CredentialSourceProtectionParameter) protectionParameter).getCredentialSource(); try { return credentialSource.applyToCredential(PasswordCredential.class, c -> c.getPassword().castAndApply(ClearPassword.class, p -> new KeyStore.PasswordProtection(p.getPassword()))); } catch (IOException e) { throw log.cannotAcquireCredentialFromStore(e); } } else { throw log.invalidProtectionParameter(protectionParameter); } } public void remove(final String credentialAlias, final Class<? extends Credential> credentialType, final String credentialAlgorithm, final AlgorithmParameterSpec parameterSpec) throws CredentialStoreException { String credentialAliasLowerCase = toLowercase(credentialAlias); try (Hold hold = lockForWrite()) { if (! modifiable) { throw log.nonModifiableCredentialStore("remove"); } // unlike retrieve or store, we want to remove *all* matches final TopEntry topEntry = cache.get(credentialAliasLowerCase); if (topEntry == null) { return; } if (topEntry.getMap().containsKey(credentialType)) { remove(topEntry.getMap().remove(credentialType), credentialAlgorithm, parameterSpec); } else { // loose (slow) match Iterator<MidEntry> iterator = topEntry.getMap().values().iterator(); while (iterator.hasNext()) { final MidEntry item = iterator.next(); if (credentialType.isAssignableFrom(item.getCredentialType())) { remove(item, credentialAlgorithm, parameterSpec); if (item.isEmpty()) iterator.remove(); } } } cache.remove(credentialAliasLowerCase); // done! } catch (KeyStoreException e) { throw log.cannotRemoveCredentialFromStore(e); } } private void remove(final MidEntry midEntry, final String credentialAlgorithm, final AlgorithmParameterSpec parameterSpec) throws KeyStoreException { if (midEntry != null) { if (credentialAlgorithm != null) { remove(midEntry.getMap().get(credentialAlgorithm), parameterSpec); } else { // match any Iterator<BottomEntry> iterator = midEntry.getMap().values().iterator(); while (iterator.hasNext()) { final BottomEntry item = iterator.next(); remove(item, parameterSpec); if (item.isEmpty()) iterator.remove(); } remove(midEntry.removeNoAlgorithm(), parameterSpec); } // done! } } private void remove(final BottomEntry bottomEntry, final AlgorithmParameterSpec parameterSpec) throws KeyStoreException { if (bottomEntry != null) { if (parameterSpec != null) { remove(bottomEntry.getMap().remove(new ParamKey(parameterSpec))); } else { // match any Iterator<String> iterator = bottomEntry.getMap().values().iterator(); while (iterator.hasNext()) { final String item = iterator.next(); remove(item); iterator.remove(); } remove(bottomEntry.removeNoParams()); } } } private void remove(final String ksAlias) throws KeyStoreException { if (ksAlias != null) { keyStore.deleteEntry(ksAlias); } } public void flush() throws CredentialStoreException { try (Hold hold = lockForWrite()) { final Path location = this.location; if (location != null) try { final char[] storePassword = getStorePassword(protectionParameter); try (AtomicFileOutputStream os = new AtomicFileOutputStream(location)) { try { if (useExternalStorage) { externalStorage.store(os); } else { keyStore.store(os, storePassword); } } catch (Throwable t) { try { os.cancel(); } catch (IOException e) { e.addSuppressed(t); throw e; } } } } catch (IOException e) { throw log.cannotFlushCredentialStore(e); } } } /** * Returns credential aliases stored in this store as {@code Set<String>}. * <p> * It is not mandatory to override this method (throws {@link UnsupportedOperationException} by default). * * @return {@code Set<String>} of all keys stored in this store * @throws UnsupportedOperationException when this method is not supported by the underlying credential store * @throws CredentialStoreException if there is any problem with internal store */ @Override public Set<String> getAliases() throws UnsupportedOperationException, CredentialStoreException { return cache.keySet(); } private Hold lockForRead() { readWriteLock.readLock().lock(); return () -> readWriteLock.readLock().unlock(); } private Hold lockForWrite() { readWriteLock.readLock().lock(); return () -> readWriteLock.readLock().unlock(); } private static final Pattern INDEX_PATTERN = Pattern.compile("(.+)/([a-z0-9_]+)/([-a-z0-9_]+)?/([2-7a-z]+)?$"); private void load(String type) throws CredentialStoreException { // lock held final Enumeration<String> enumeration; // load the KeyStore from file if (useExternalStorage) { setupExternalStorage(type); } else { keyStore = getKeyStoreInstance(type); } if (location != null && Files.exists(location)) try (InputStream fileStream = Files.newInputStream(location)) { char[] password = getStorePassword(protectionParameter); if (useExternalStorage) { externalStorage.load(fileStream); } else { keyStore.load(fileStream, password); } enumeration = keyStore.aliases(); } catch (GeneralSecurityException e) { throw log.cannotInitializeCredentialStore( log.internalEncryptionProblem(e, location.toString())); } catch (IOException e) { throw log.cannotInitializeCredentialStore(e); } else if (create) { try { keyStore.load(null, null); enumeration = Collections.emptyEnumeration(); } catch (CertificateException | IOException | NoSuchAlgorithmException e) { throw log.cannotInitializeCredentialStore(e); } } else { throw log.automaticStorageCreationDisabled(location != null ? location.toString() : "null"); } Matcher matcher; while (enumeration.hasMoreElements()) { final String ksAlias = enumeration.nextElement().toLowerCase(Locale.ROOT); try { matcher = INDEX_PATTERN.matcher(ksAlias); if (matcher.matches()) { final String alias = matcher.group(1); // required final String credTypeName = matcher.group(2); // required final String algName = matcher.group(3); // may be null if not given final String parameters = matcher.group(4); // null if not given final Class<? extends Credential> credentialType = CREDENTIAL_TYPES.get(credTypeName); if (credentialType == null) { log.logIgnoredUnrecognizedKeyStoreEntry(ksAlias); } else if (algName != null) { if (parameters != null) { byte[] encodedParameters = CodePointIterator.ofString(parameters).base32Decode(Alphabet.Base32Alphabet.LOWERCASE, false).drain(); final AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(algName); algorithmParameters.init(encodedParameters); final AlgorithmParameterSpec parameterSpec = algorithmParameters.getParameterSpec(AlgorithmParameterSpec.class); final TopEntry topEntry = cache.computeIfAbsent(alias, TopEntry::new); final MidEntry midEntry = topEntry.getMap().computeIfAbsent(credentialType, k -> new MidEntry(topEntry, k)); final BottomEntry bottomEntry = midEntry.getMap().computeIfAbsent(algName, k -> new BottomEntry(midEntry, k)); bottomEntry.getMap().put(new ParamKey(parameterSpec), ksAlias); } else { // algorithm but no parameters final TopEntry topEntry = cache.computeIfAbsent(alias, TopEntry::new); final MidEntry midEntry = topEntry.getMap().computeIfAbsent(credentialType, k -> new MidEntry(topEntry, k)); final BottomEntry bottomEntry = midEntry.getMap().computeIfAbsent(algName, k -> new BottomEntry(midEntry, k)); bottomEntry.setNoParams(ksAlias); } } else { // no algorithm, no parameters final TopEntry topEntry = cache.computeIfAbsent(alias, TopEntry::new); final MidEntry midEntry = topEntry.getMap().computeIfAbsent(credentialType, k -> new MidEntry(topEntry, k)); final BottomEntry bottomEntry = midEntry.getOrCreateNoAlgorithm(); bottomEntry.setNoParams(ksAlias); } } else { log.logIgnoredUnrecognizedKeyStoreEntry(ksAlias); } } catch (NoSuchAlgorithmException | InvalidParameterSpecException | IOException e) { log.logFailedToReadKeyFromKeyStore(e); } } } private KeyStore getKeyStoreInstance(String type) throws CredentialStoreException { if (providers != null) { for (Provider p: providers) { try { return KeyStore.getInstance(type, p); } catch (KeyStoreException e) { // no such keystore type in provider, ignore } } } try { return KeyStore.getInstance(type); } catch (KeyStoreException e) { throw log.cannotInitializeCredentialStore(e); } } /** * Sets {@link #keyStore} to JCEKS type keyStore to be used as external storage. * Sets {@link #externalStorage} used to dump/load stored secret data. */ private void setupExternalStorage(String type) throws CredentialStoreException { KeyStore keyContainingKeyStore = getKeyStoreInstance(type); keyStore = getKeyStoreInstance("JCEKS"); externalStorage = new ExternalStorage(); try { externalStorage.init(cryptographicAlgorithm, encryptionKeyAlias, keyContainingKeyStore, getStorePassword(protectionParameter), keyStore); } catch(IOException e) { throw log.cannotInitializeCredentialStore(e); } } private static final Map<String, Class<? extends Credential>> CREDENTIAL_TYPES; static { Map<String, Class<? extends Credential>> map = new HashMap<>(); for (Class<? extends Credential> type : Arrays.asList( PasswordCredential.class, X509CertificateChainPublicCredential.class, X509CertificateChainPrivateCredential.class, KeyPairCredential.class, PublicKeyCredential.class, SecretKeyCredential.class, BearerTokenCredential.class )) { map.put(type.getSimpleName().toLowerCase(Locale.ROOT), type); } CREDENTIAL_TYPES = map; } private static char[] getStorePassword(final CredentialStore.ProtectionParameter protectionParameter) throws IOException, CredentialStoreException { final char[] password; if (protectionParameter instanceof CredentialStore.CredentialSourceProtectionParameter) { password = ((CredentialStore.CredentialSourceProtectionParameter) protectionParameter).getCredentialSource().applyToCredential(PasswordCredential.class, c -> c.getPassword().castAndApply(ClearPassword.class, ClearPassword::getPassword)); } else if (protectionParameter != null) { throw log.invalidProtectionParameter(protectionParameter); } else { password = null; } return password; } interface Hold extends AutoCloseable { void close(); } private String calculateNewAlias(String alias, Class<? extends Credential> credentialType, String algorithm, AlgorithmParameterSpec parameterSpec) throws CredentialStoreException { final StringBuilder b = new StringBuilder(64 + alias.length()); b.append(alias.toLowerCase(Locale.ROOT)); b.append('/'); b.append(credentialType.getSimpleName().toLowerCase(Locale.ROOT)); b.append('/'); if (algorithm != null) { b.append(algorithm.toLowerCase(Locale.ROOT)); b.append('/'); if (parameterSpec != null) try { final AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(algorithm); algorithmParameters.init(parameterSpec); ByteIterator.ofBytes(algorithmParameters.getEncoded()).base32Encode(Alphabet.Base32Alphabet.LOWERCASE, false).drainTo(b); } catch (NoSuchAlgorithmException | InvalidParameterSpecException | IOException e) { throw log.cannotWriteCredentialToStore(e); } } else { b.append('/'); } return b.toString(); } private static String toLowercase(String str) { return str.toLowerCase(Locale.ROOT); } static final class TopEntry { private final String alias; private final HashMap<Class<? extends Credential>, MidEntry> map = new HashMap<>(0); TopEntry(final String alias) { this.alias = alias; } String getAlias() { return alias; } HashMap<Class<? extends Credential>, MidEntry> getMap() { return map; } } static final class MidEntry { private final TopEntry topEntry; private final Class<? extends Credential> credentialType; private final HashMap<String, BottomEntry> map = new HashMap<>(0); private BottomEntry noAlgorithm; MidEntry(final TopEntry topEntry, final Class<? extends Credential> credentialType) { this.topEntry = topEntry; this.credentialType = credentialType; } Class<? extends Credential> getCredentialType() { return credentialType; } HashMap<String, BottomEntry> getMap() { return map; } BottomEntry getNoAlgorithm() { return noAlgorithm; } void setNoAlgorithm(final BottomEntry noAlgorithm) { this.noAlgorithm = noAlgorithm; } BottomEntry removeNoAlgorithm() { try { return noAlgorithm; } finally { noAlgorithm = null; } } boolean isEmpty() { return noAlgorithm == null && map.isEmpty(); } private BottomEntry getOrCreateNoAlgorithm() { final BottomEntry noAlgorithm = this.noAlgorithm; return noAlgorithm != null ? noAlgorithm : (this.noAlgorithm = new BottomEntry(this, null)); } } static final class BottomEntry { private final MidEntry midEntry; private final String algorithm; private final HashMap<ParamKey, String> map = new HashMap<>(0); private String noParams; BottomEntry(final MidEntry midEntry, final String algorithm) { this.midEntry = midEntry; this.algorithm = algorithm; } String getAlgorithm() { return algorithm; } HashMap<ParamKey, String> getMap() { return map; } String getNoParams() { return noParams; } String setNoParams(final String noParams) { try { return this.noParams; } finally { this.noParams = noParams; } } boolean isEmpty() { return noParams == null && map.isEmpty(); } private String removeNoParams() { try { return noParams; } finally { noParams = null; } } } static final class ParamKey { private final AlgorithmParameterSpec parameterSpec; private final int hashCode; ParamKey(final AlgorithmParameterSpec parameterSpec) { this.parameterSpec = parameterSpec; this.hashCode = KeyUtil.parametersHashCode(parameterSpec); } public int hashCode() { return hashCode; } AlgorithmParameterSpec getParameterSpec() { return parameterSpec; } int getHashCode() { return hashCode; } } private final class ExternalStorage { // version of external storage file, can be used later to enhance functionality and keep backward compatibility private int VERSION = 1; private int SECRET_KEY_ENTRY_TYPE = 100; private static final String DEFAULT_CRYPTOGRAPHIC_ALGORITHM = "AES/CBC/NoPadding"; private Cipher encrypt; private Cipher decrypt; private KeyStore dataKeyStore; private KeyStore storageSecretKeyStore; private SecretKey storageSecretKey; private ExternalStorage() {} void init(String cryptographicAlgorithm, String keyAlias, KeyStore keyStore, char[] keyPassword, KeyStore dataKeyStore) throws CredentialStoreException { if (cryptographicAlgorithm == null) cryptographicAlgorithm = DEFAULT_CRYPTOGRAPHIC_ALGORITHM; storageSecretKeyStore = keyStore; this.dataKeyStore = dataKeyStore; try { fetchStorageSecretKey(keyAlias, keyPassword); Provider provider = keyStore.getProvider(); try { encrypt = Cipher.getInstance(cryptographicAlgorithm, provider); } catch (NoSuchAlgorithmException e) { // fallback to any provider of desired algorithm encrypt = Cipher.getInstance(cryptographicAlgorithm); } try { decrypt = Cipher.getInstance(cryptographicAlgorithm, provider); } catch (NoSuchAlgorithmException e) { // fallback to any provider of desired algorithm decrypt = Cipher.getInstance(cryptographicAlgorithm); } } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnrecoverableEntryException | KeyStoreException | IOException | CertificateException e) { throw new CredentialStoreException(e); } } private void fetchStorageSecretKey(String keyAlias, char[] keyPassword) throws CertificateException, NoSuchAlgorithmException, IOException, CredentialStoreException, UnrecoverableEntryException, KeyStoreException { storageSecretKeyStore.load(null, keyPassword); KeyStore.Entry entry = storageSecretKeyStore.getEntry(keyAlias, new KeyStore.PasswordProtection(keyPassword)); if (entry == null) { throw log.externalStorageKeyDoesNotExist(keyAlias); } if (! (entry instanceof KeyStore.SecretKeyEntry)) { throw log.wrongTypeOfExternalStorageKey(keyAlias); } storageSecretKey = ((KeyStore.SecretKeyEntry) entry).getSecretKey(); } /** * Load {@link #dataKeyStore} with data from the input stream. * * @param inputStream to load data from * @throws IOException if something goes wrong */ void load(InputStream inputStream) throws IOException, GeneralSecurityException { dataKeyStore.load(null, null); ObjectInputStream ois = new ObjectInputStream(inputStream); int fileVersion = ois.readInt(); if (fileVersion == VERSION) { while (ois.available() > 0) { int entryType = ois.readInt(); if (entryType == SECRET_KEY_ENTRY_TYPE) { loadSecretKey(ois); } else { throw log.unrecognizedEntryType(Integer.toString(entryType)); } } } else { throw log.unexpectedFileVersion(Integer.toString(fileVersion)); } ois.close(); } private void loadSecretKey(ObjectInputStream ois) throws IOException, GeneralSecurityException { byte[] encryptedData = readBytes(ois); byte[] iv = readBytes(ois); decrypt.init(Cipher.DECRYPT_MODE, storageSecretKey, new IvParameterSpec(iv)); Assert.checkMaximumParameter("cipher block size", 256, decrypt.getBlockSize()); byte[] unPadded = pkcs7UnPad(decrypt.doFinal(encryptedData)); ObjectInputStream entryOis = new ObjectInputStream(new ByteArrayInputStream(unPadded)); String ksAlias = entryOis.readUTF(); byte[] encodedSecretKey = readBytes(entryOis); KeyStore.Entry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(encodedSecretKey, DATA_OID)); dataKeyStore.setEntry(ksAlias, entry, convertParameter(protectionParameter)); } private byte[] readBytes(ObjectInputStream ois) throws IOException { int len = ois.readInt(); byte[] data = new byte[len]; int actualLen = ois.read(data, 0, len); if (len != actualLen) throw log.readBytesMismatch(actualLen, len); return data; } private int writeBytes(byte[] data, ObjectOutputStream oos) throws IOException { int len = data.length; oos.writeInt(len); oos.write(data, 0, len); return len; } /** * Store data from {@link #dataKeyStore} to output stream. * * @param outputStream to store data to * @throws IOException if something goes wrong */ void store(OutputStream outputStream) throws IOException, GeneralSecurityException { ObjectOutputStream oos = new ObjectOutputStream(outputStream); oos.writeInt(VERSION); Enumeration<String> ksAliases = dataKeyStore.aliases(); while(ksAliases.hasMoreElements()) { String alias = ksAliases.nextElement(); KeyStore.Entry entry = dataKeyStore.getEntry(alias, convertParameter(protectionParameter)); if (entry instanceof KeyStore.SecretKeyEntry) { saveSecretKey(alias, oos, (KeyStore.SecretKeyEntry)entry); } else { throw log.unrecognizedEntryType(entry != null ? entry.getClass().getCanonicalName() : "null"); } } oos.flush(); oos.close(); } private void saveSecretKey(String ksAlias, ObjectOutputStream oos, KeyStore.SecretKeyEntry entry) throws IOException, GeneralSecurityException { ByteArrayOutputStream entryData = new ByteArrayOutputStream(1024); ObjectOutputStream entryOos = new ObjectOutputStream(entryData); entryOos.writeUTF(ksAlias); writeBytes(entry.getSecretKey().getEncoded(), entryOos); entryOos.flush(); encrypt.init(Cipher.ENCRYPT_MODE, storageSecretKey); int blockSize = encrypt.getBlockSize(); if (blockSize == 0) throw log.algorithmNotBlockBased(encrypt.getAlgorithm()); Assert.checkMaximumParameter("cipher block size", 256, blockSize); byte[] padded = pkcs7Pad(entryData.toByteArray(), blockSize); byte[] encrypted = encrypt.doFinal(padded); byte[] iv = encrypt.getIV(); if (iv == null) throw log.algorithmNotIV(encrypt.getAlgorithm()); oos.writeInt(SECRET_KEY_ENTRY_TYPE); writeBytes(encrypted, oos); writeBytes(iv, oos); } private byte[] pkcs7Pad(byte[] buffer, int blockSize) { int len = buffer.length; int toFill = blockSize - (len % blockSize); byte[] padded = Arrays.copyOf(buffer, toFill + len); Arrays.fill(padded, len, padded.length, (byte) toFill); return padded; } private byte[] pkcs7UnPad(byte[] buffer) throws BadPaddingException { byte last = buffer[buffer.length - 1]; int i = buffer.length - 2; while (buffer[i] == last) { i--; } if (i + 1 + last != buffer.length) { throw new BadPaddingException(); } return Arrays.copyOfRange(buffer, 0, i + 1); } } }