/* * Copyright (c) 2010-2016 Evolveum * * 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 com.evolveum.midpoint.common.crypto; import java.io.ByteArrayOutputStream; import java.security.Provider; import java.security.Security; import java.util.Collection; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.xml.namespace.QName; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.Itemable; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.Visitable; import com.evolveum.midpoint.prism.Visitor; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.util.exception.TunnelException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.MailServerConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.NotificationConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SmsConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SmsGatewayConfigurationType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; /** * @author semancik * */ public class CryptoUtil { private static final Trace LOGGER = TraceManager.getTrace(CryptoUtil.class); /** * Encrypts all encryptable values in the object. */ public static <T extends ObjectType> void encryptValues(final Protector protector, final PrismObject<T> object) throws EncryptionException{ Visitor visitor = new Visitor() { @Override public void visit(Visitable visitable){ if (!(visitable instanceof PrismPropertyValue)) { return; } PrismPropertyValue<?> pval = (PrismPropertyValue<?>)visitable; try { encryptValue(protector, pval); } catch (EncryptionException e) { throw new TunnelException(e); } } }; try { object.accept(visitor); } catch (TunnelException e) { EncryptionException origEx = (EncryptionException)e.getCause(); throw origEx; } } /** * Encrypts all encryptable values in delta. */ public static <T extends ObjectType> void encryptValues(final Protector protector, final ObjectDelta<T> delta) throws EncryptionException{ Visitor visitor = new Visitor() { @Override public void visit(Visitable visitable){ if (!(visitable instanceof PrismPropertyValue)) { return; } PrismPropertyValue<?> pval = (PrismPropertyValue<?>)visitable; try { encryptValue(protector, pval); } catch (EncryptionException e) { throw new TunnelException(e); } } }; try { delta.accept(visitor); } catch (TunnelException e) { EncryptionException origEx = (EncryptionException)e.getCause(); throw origEx; } } private static <T extends ObjectType> void encryptValue(Protector protector, PrismPropertyValue<?> pval) throws EncryptionException{ Itemable item = pval.getParent(); if (item == null) { return; } ItemDefinition itemDef = item.getDefinition(); if (itemDef == null || itemDef.getTypeName() == null) { return; } if (itemDef.getTypeName().equals(ProtectedStringType.COMPLEX_TYPE)) { QName propName = item.getElementName(); PrismPropertyValue<ProtectedStringType> psPval = (PrismPropertyValue<ProtectedStringType>)pval; ProtectedStringType ps = psPval.getValue(); encryptProtectedStringType(protector, ps, propName.getLocalPart()); if (pval.getParent() == null){ pval.setParent(item); } } else if (itemDef.getTypeName().equals(NotificationConfigurationType.COMPLEX_TYPE)) { // this is really ugly hack needed because currently it is not possible to break NotificationConfigurationType into prism item [pm] NotificationConfigurationType ncfg = ((PrismPropertyValue<NotificationConfigurationType>) pval).getValue(); if (ncfg.getMail() != null) { for (MailServerConfigurationType mscfg : ncfg.getMail().getServer()) { encryptProtectedStringType(protector, mscfg.getPassword(), "mail server password"); } } if (ncfg.getSms() != null) { for (SmsConfigurationType smscfg : ncfg.getSms()) { for (SmsGatewayConfigurationType gwcfg : smscfg.getGateway()) { encryptProtectedStringType(protector, gwcfg.getPassword(), "sms gateway password"); } } } } } private static void encryptProtectedStringType(Protector protector, ProtectedStringType ps, String propName) throws EncryptionException { if (ps != null && ps.getClearValue() != null) { try { protector.encrypt(ps); } catch (EncryptionException e) { throw new EncryptionException("Failed to encrypt value for field " + propName + ": " + e.getMessage(), e); } } } // Checks that everything is encrypted public static <T extends ObjectType> void checkEncrypted(final PrismObject<T> object) { Visitor visitor = new Visitor() { @Override public void visit(Visitable visitable){ if (!(visitable instanceof PrismPropertyValue)) { return; } PrismPropertyValue<?> pval = (PrismPropertyValue<?>)visitable; checkEncrypted(pval); } }; try { object.accept(visitor); } catch (IllegalStateException e) { throw new IllegalStateException(e.getMessage() + " in " + object, e); } } // Checks that everything is encrypted public static <T extends ObjectType> void checkEncrypted(final ObjectDelta<T> delta) { Visitor visitor = new Visitor() { @Override public void visit(Visitable visitable){ if (!(visitable instanceof PrismPropertyValue)) { return; } PrismPropertyValue<?> pval = (PrismPropertyValue<?>)visitable; checkEncrypted(pval); } }; try { delta.accept(visitor); } catch (IllegalStateException e) { throw new IllegalStateException(e.getMessage() + " in delta " + delta, e); } } private static <T extends ObjectType> void checkEncrypted(PrismPropertyValue<?> pval) { Itemable item = pval.getParent(); if (item == null) { return; } ItemDefinition itemDef = item.getDefinition(); if (itemDef == null || itemDef.getTypeName() == null) { return; } if (itemDef.getTypeName().equals(ProtectedStringType.COMPLEX_TYPE)) { QName propName = item.getElementName(); PrismPropertyValue<ProtectedStringType> psPval = (PrismPropertyValue<ProtectedStringType>)pval; ProtectedStringType ps = psPval.getValue(); if (ps.getClearValue() != null) { throw new IllegalStateException("Unencrypted value in field " + propName); } } else if (itemDef.getTypeName().equals(NotificationConfigurationType.COMPLEX_TYPE)) { // this is really ugly hack needed because currently it is not possible to break NotificationConfigurationType into prism item [pm] NotificationConfigurationType ncfg = ((PrismPropertyValue<NotificationConfigurationType>) pval).getValue(); if (ncfg.getMail() != null) { for (MailServerConfigurationType mscfg : ncfg.getMail().getServer()) { if (mscfg.getPassword() != null && mscfg.getPassword().getClearValue() != null) { throw new IllegalStateException("Unencrypted value in mail server config password entry"); } } } if (ncfg.getSms() != null) { for (SmsConfigurationType smscfg : ncfg.getSms()) { for (SmsGatewayConfigurationType gwcfg : smscfg.getGateway()) { if (gwcfg.getPassword() != null && gwcfg.getPassword().getClearValue() != null) { throw new IllegalStateException("Unencrypted value in SMS gateway config password entry"); } } } } } } public static void checkEncrypted(Collection<? extends ItemDelta> modifications) { Visitor visitor = new Visitor() { @Override public void visit(Visitable visitable){ if (!(visitable instanceof PrismPropertyValue)) { return; } PrismPropertyValue<?> pval = (PrismPropertyValue<?>)visitable; checkEncrypted(pval); } }; for (ItemDelta<?,?> delta: modifications) { try { delta.accept(visitor); } catch (IllegalStateException e) { throw new IllegalStateException(e.getMessage() + " in modification " + delta, e); } } } private final static byte [] DEFAULT_IV_BYTES = { (byte) 0x51,(byte) 0x65,(byte) 0x22,(byte) 0x23, (byte) 0x64,(byte) 0x05,(byte) 0x6A,(byte) 0xBE, (byte) 0x51,(byte) 0x65,(byte) 0x22,(byte) 0x23, (byte) 0x64,(byte) 0x05,(byte) 0x6A,(byte) 0xBE, }; public static void securitySelfTest(OperationResult parentTestResult) { OperationResult result = parentTestResult.createSubresult(CryptoUtil.class.getName()+".securitySelfTest"); // Providers for (Provider provider: Security.getProviders()) { String providerName = provider.getName(); OperationResult providerResult = result.createSubresult(CryptoUtil.class.getName()+".securitySelfTest.provider."+providerName); try { providerResult.addContext("info", provider.getInfo()); ByteArrayOutputStream os = new ByteArrayOutputStream(); provider.storeToXML(os, "Crypto provider "+providerName); String propXml = os.toString(); providerResult.addContext("properties", propXml); providerResult.recordSuccess(); } catch (Throwable e) { LOGGER.error("Security self test (provider properties) failed: ", e.getMessage() ,e); providerResult.recordFatalError(e); } } securitySelfTestAlgorithm("AES", "AES/CBC/PKCS5Padding", null, false, result); OperationResult cryptoResult = result.getLastSubresult(); if (cryptoResult.isError()) { // Do a test encryption. It happens sometimes that the key generator // generates a key that is not supported by the cipher. // Fall back to known key size supported by all JCE implementations securitySelfTestAlgorithm("AES", "AES/CBC/PKCS5Padding", 128, true, result); OperationResult cryptoResult2 = result.getLastSubresult(); if (cryptoResult2.isSuccess()) { cryptoResult.setStatus(OperationResultStatus.HANDLED_ERROR); } } result.computeStatus(); } private static void securitySelfTestAlgorithm(String algorithmName, String transformationName, Integer keySize, boolean critical, OperationResult parentResult) { OperationResult subresult = parentResult.createSubresult(CryptoUtil.class.getName()+".securitySelfTest.algorithm."+algorithmName); try { KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithmName); if (keySize != null) { keyGenerator.init(keySize); } subresult.addReturn("keyGeneratorProvider", keyGenerator.getProvider().getName()); subresult.addReturn("keyGeneratorAlgorithm", keyGenerator.getAlgorithm()); subresult.addReturn("keyGeneratorKeySize", keySize); SecretKey key = keyGenerator.generateKey(); subresult.addReturn("keyAlgorithm", key.getAlgorithm()); subresult.addReturn("keyLength", key.getEncoded().length*8); subresult.addReturn("keyFormat", key.getFormat()); subresult.recordSuccess(); IvParameterSpec iv = new IvParameterSpec(DEFAULT_IV_BYTES); String plainString = "Scurvy seadog"; Cipher cipher = Cipher.getInstance(transformationName); subresult.addReturn("cipherAlgorithmName", algorithmName); subresult.addReturn("cipherTansfromationName", transformationName); subresult.addReturn("cipherAlgorithm", cipher.getAlgorithm()); subresult.addReturn("cipherBlockSize", cipher.getBlockSize()); subresult.addReturn("cipherProvider", cipher.getProvider().getName()); subresult.addReturn("cipherMaxAllowedKeyLength", cipher.getMaxAllowedKeyLength(transformationName)); cipher.init(Cipher.ENCRYPT_MODE, key, iv); byte[] encryptedBytes = cipher.doFinal(plainString.getBytes()); cipher = Cipher.getInstance(transformationName); cipher.init(Cipher.DECRYPT_MODE, key, iv); byte[] decryptedBytes = cipher.doFinal(encryptedBytes); String decryptedString = new String(decryptedBytes); if (!plainString.equals(decryptedString)) { subresult.recordFatalError("Encryptor roundtrip failed; encrypted="+plainString+", decrypted="+decryptedString); } else { subresult.recordSuccess(); } LOGGER.debug("Security self test (algorithmName={}, transformationName={}, keySize={}) success", new Object[] {algorithmName, transformationName, keySize}); } catch (Throwable e) { if (critical) { LOGGER.error("Security self test (algorithmName={}, transformationName={}, keySize={}) failed: {}", new Object[] {algorithmName, transformationName, keySize, e.getMessage() ,e}); subresult.recordFatalError(e); } else { LOGGER.warn("Security self test (algorithmName={}, transformationName={}, keySize={}) failed: {} (failure is expected in some cases)", new Object[] {algorithmName, transformationName, keySize, e.getMessage() ,e}); subresult.recordWarning(e); } } } }