Merge "Port android CipherSpi selection algorithm to openJdk"
diff --git a/ojluni/src/main/java/javax/crypto/Cipher.java b/ojluni/src/main/java/javax/crypto/Cipher.java
index 05b346f..2ce2279 100755
--- a/ojluni/src/main/java/javax/crypto/Cipher.java
+++ b/ojluni/src/main/java/javax/crypto/Cipher.java
@@ -199,13 +199,10 @@
     private CipherSpi spi;
 
     // The transformation
-    private String transformation;
+    final private String transformation;
 
-    // Crypto permission representing the maximum allowable cryptographic
-    // strength that this Cipher object can be used for. (The cryptographic
-    // strength is a function of the keysize and algorithm parameters encoded
-    // in the crypto permission.)
-    private CryptoPermission cryptoPerm;
+    // The tokenized version of transformation
+    final private String[] tokenizedTransformation;
 
     // The exemption mechanism that needs to be enforced
     private ExemptionMechanism exmech;
@@ -220,10 +217,8 @@
     // The OID for the KeyUsage extension in an X.509 v3 certificate
     private static final String KEY_USAGE_EXTENSION_OID = "2.5.29.15";
 
-    // list of transform Strings to lookup in the provider
-    private List transforms;
+    private final SpiAndProviderUpdater spiAndProviderUpdater;
 
-    private final Object lock;
 
     /**
      * Creates a Cipher object.
@@ -235,41 +230,38 @@
     protected Cipher(CipherSpi cipherSpi,
                      Provider provider,
                      String transformation) {
-        // See bug 4341369 & 4334690 for more info.
-        // If the caller is trusted, then okey.
-        // Otherwise throw a NullPointerException.
-        if (!JceSecurityManager.INSTANCE.isCallerTrusted()) {
-            throw new NullPointerException();
+        if (cipherSpi == null) {
+            throw new NullPointerException("cipherSpi == null");
         }
+        if (!(cipherSpi instanceof NullCipherSpi) && provider == null) {
+            throw new NullPointerException("provider == null");
+        }
+
         this.spi = cipherSpi;
         this.provider = provider;
         this.transformation = transformation;
-        this.cryptoPerm = CryptoAllPermission.INSTANCE;
-        this.lock = null;
+        this.tokenizedTransformation = null;
+
+        this.spiAndProviderUpdater =
+            new SpiAndProviderUpdater(provider, cipherSpi);
     }
 
-    /**
-     * Creates a Cipher object. Called internally and by NullCipher.
-     *
-     * @param cipherSpi the delegate
-     * @param transformation the transformation
-     */
-    Cipher(CipherSpi cipherSpi, String transformation) {
+    private Cipher(CipherSpi cipherSpi,
+                   Provider provider,
+                   String transformation,
+                   String[] tokenizedTransformation) {
         this.spi = cipherSpi;
+        this.provider = provider;
         this.transformation = transformation;
-        this.cryptoPerm = CryptoAllPermission.INSTANCE;
-        this.lock = null;
-    }
+        this.tokenizedTransformation = tokenizedTransformation;
 
-    private Cipher(String transformation, List transforms) {
-        this.transforms = transforms;
-        this.transformation = transformation;
-        this.lock = new Object();
+        this.spiAndProviderUpdater =
+            new SpiAndProviderUpdater(provider, cipherSpi);
     }
 
     private static String[] tokenizeTransformation(String transformation)
             throws NoSuchAlgorithmException {
-        if (transformation == null) {
+        if (transformation == null || transformation.isEmpty()) {
             throw new NoSuchAlgorithmException("No transformation given");
         }
         /*
@@ -303,136 +295,6 @@
         return parts;
     }
 
-    // Provider attribute name for supported chaining mode
-    private final static String ATTR_MODE = "SupportedModes";
-    // Provider attribute name for supported padding names
-    private final static String ATTR_PAD  = "SupportedPaddings";
-
-    // constants indicating whether the provider supports
-    // a given mode or padding
-    private final static int S_NO    = 0;       // does not support
-    private final static int S_MAYBE = 1;       // unable to determine
-    private final static int S_YES   = 2;       // does support
-
-    /**
-     * Nested class to deal with modes and paddings.
-     */
-    private static class Transform {
-        // transform string to lookup in the provider
-        final String transform;
-        // the mode/padding suffix in upper case. for example, if the algorithm
-        // to lookup is "DES/CBC/PKCS5Padding" suffix is "/CBC/PKCS5PADDING"
-        // if loopup is "DES", suffix is the empty string
-        // needed because aliases prevent straight transform.equals()
-        final String suffix;
-        // value to pass to setMode() or null if no such call required
-        final String mode;
-        // value to pass to setPadding() or null if no such call required
-        final String pad;
-        Transform(String alg, String suffix, String mode, String pad) {
-            this.transform = alg + suffix;
-            this.suffix = suffix.toUpperCase(Locale.ENGLISH);
-            this.mode = mode;
-            this.pad = pad;
-        }
-        // set mode and padding for the given SPI
-        void setModePadding(CipherSpi spi) throws NoSuchAlgorithmException,
-                NoSuchPaddingException {
-            if (mode != null) {
-                spi.engineSetMode(mode);
-            }
-            if (pad != null) {
-                spi.engineSetPadding(pad);
-            }
-        }
-        // check whether the given services supports the mode and
-        // padding described by this Transform
-        int supportsModePadding(Service s) {
-            int smode = supportsMode(s);
-            if (smode == S_NO) {
-                return smode;
-            }
-            int spad = supportsPadding(s);
-            // our constants are defined so that Math.min() is a tri-valued AND
-            return Math.min(smode, spad);
-        }
-
-        // separate methods for mode and padding
-        // called directly by Cipher only to throw the correct exception
-        int supportsMode(Service s) {
-            return supports(s, ATTR_MODE, mode);
-        }
-        int supportsPadding(Service s) {
-            return supports(s, ATTR_PAD, pad);
-        }
-
-        private static int supports(Service s, String attrName, String value) {
-            if (value == null) {
-                return S_YES;
-            }
-            String regexp = s.getAttribute(attrName);
-            if (regexp == null) {
-                return S_MAYBE;
-            }
-            return matches(regexp, value) ? S_YES : S_NO;
-        }
-
-        // ConcurrentMap<String,Pattern> for previously compiled patterns
-        private final static ConcurrentMap<String, Pattern> patternCache =
-            new ConcurrentHashMap<String, Pattern>();
-
-        private static boolean matches(String regexp, String str) {
-            Pattern pattern = (Pattern)patternCache.get(regexp);
-            if (pattern == null) {
-                pattern = Pattern.compile(regexp);
-                patternCache.putIfAbsent(regexp, pattern);
-            }
-            return pattern.matcher(str.toUpperCase(Locale.ENGLISH)).matches();
-        }
-
-    }
-
-    private static List getTransforms(String transformation)
-            throws NoSuchAlgorithmException {
-        String[] parts = tokenizeTransformation(transformation);
-
-        String alg = parts[0];
-        String mode = parts[1];
-        String pad = parts[2];
-        if ((mode != null) && (mode.length() == 0)) {
-            mode = null;
-        }
-        if ((pad != null) && (pad.length() == 0)) {
-            pad = null;
-        }
-
-        if ((mode == null) && (pad == null)) {
-            // DES
-            Transform tr = new Transform(alg, "", null, null);
-            return Collections.singletonList(tr);
-        } else { // if ((mode != null) && (pad != null)) {
-            // DES/CBC/PKCS5Padding
-            List list = new ArrayList(4);
-            list.add(new Transform(alg, "/" + mode + "/" + pad, null, null));
-            list.add(new Transform(alg, "/" + mode, null, pad));
-            list.add(new Transform(alg, "//" + pad, mode, null));
-            list.add(new Transform(alg, "", mode, pad));
-            return list;
-        }
-    }
-
-    // get the transform matching the specified service
-    private static Transform getTransform(Service s, List transforms) {
-        String alg = s.getAlgorithm().toUpperCase(Locale.ENGLISH);
-        for (Iterator t = transforms.iterator(); t.hasNext(); ) {
-            Transform tr = (Transform)t.next();
-            if (alg.endsWith(tr.suffix)) {
-                return tr;
-            }
-        }
-        return null;
-    }
-
     /**
      * Returns a <code>Cipher</code> object that implements the specified
      * transformation.
@@ -468,60 +330,7 @@
     public static final Cipher getInstance(String transformation)
             throws NoSuchAlgorithmException, NoSuchPaddingException
     {
-        List transforms = getTransforms(transformation);
-        List services = getServices(transforms);
-        // make sure there is at least one service from a signed provider
-        // and that it can use the specified mode and padding
-        Iterator t = services.iterator();
-        Exception failure = null;
-        while (t.hasNext()) {
-            Service s = (Service)t.next();
-            if (JceSecurity.canUseProvider(s.getProvider()) == false) {
-                continue;
-            }
-            Transform tr = getTransform(s, transforms);
-            if (tr == null) {
-                // should never happen
-                continue;
-            }
-            int canuse = tr.supportsModePadding(s);
-            if (canuse == S_NO) {
-                // does not support mode or padding we need, ignore
-                continue;
-            }
-            if (canuse == S_YES) {
-                // Android changed: Check if the service is valid even if
-                // canuse == S_YES.
-                //
-                // TODO: Why is this necessary ?
-                try {
-                    // Check if service is valid.
-                    CipherSpi spi = (CipherSpi)s.newInstance(null);
-                    return new Cipher(transformation, transforms);
-                } catch (Exception e) {
-                    failure = e;
-                }
-            } else { // S_MAYBE, try out if it works
-                try {
-                    CipherSpi spi = (CipherSpi)s.newInstance(null);
-                    tr.setModePadding(spi);
-                    return new Cipher(transformation, transforms);
-                } catch (Exception e) {
-                    failure = e;
-                }
-            }
-        }
-        throw new NoSuchAlgorithmException
-            ("Cannot find any provider supporting " + transformation, failure);
-    }
-
-    static final List<Service> getServices(List<Transform> transforms) {
-        List<ServiceId> cipherServices =
-            new ArrayList<ServiceId>(transforms.size());
-        for (Transform transform : transforms) {
-            cipherServices.add(new ServiceId("Cipher", transform.transform));
-        }
-        return GetInstance.getServices(cipherServices);
+        return createCipher(transformation, null);
     }
 
     /**
@@ -576,7 +385,7 @@
             throw new NoSuchProviderException("No such provider: " +
                                               provider);
         }
-        return getInstance(transformation, p);
+        return createCipher(transformation, p);
     }
 
     /**
@@ -619,138 +428,44 @@
         if (provider == null) {
             throw new IllegalArgumentException("Missing provider");
         }
-        Exception failure = null;
-        List transforms = getTransforms(transformation);
-        boolean providerChecked = false;
-        String paddingError = null;
-        for (Iterator t = transforms.iterator(); t.hasNext();) {
-            Transform tr = (Transform)t.next();
-            Service s = provider.getService("Cipher", tr.transform);
-            if (s == null) {
-                continue;
-            }
-            if (providerChecked == false) {
-                // for compatibility, first do the lookup and then verify
-                // the provider. this makes the difference between a NSAE
-                // and a SecurityException if the
-                // provider does not support the algorithm.
-                Exception ve = JceSecurity.getVerificationResult(provider);
-                if (ve != null) {
-                    String msg = "JCE cannot authenticate the provider "
-                        + provider.getName();
-                    throw new SecurityException(msg, ve);
-                }
-                providerChecked = true;
-            }
-            if (tr.supportsMode(s) == S_NO) {
-                continue;
-            }
-            if (tr.supportsPadding(s) == S_NO) {
-                paddingError = tr.pad;
-                continue;
-            }
-            try {
-                CipherSpi spi = (CipherSpi)s.newInstance(null);
-                tr.setModePadding(spi);
-                Cipher cipher = new Cipher(spi, transformation);
-                cipher.provider = s.getProvider();
-                cipher.initCryptoPermission();
-                return cipher;
-            } catch (Exception e) {
-                failure = e;
-            }
-        }
-
-        // throw NoSuchPaddingException if the problem is with padding
-        if (failure instanceof NoSuchPaddingException) {
-            throw (NoSuchPaddingException)failure;
-        }
-        if (paddingError != null) {
-            throw new NoSuchPaddingException
-                ("Padding not supported: " + paddingError);
-        }
-        throw new NoSuchAlgorithmException
-                ("No such algorithm: " + transformation, failure);
+        return createCipher(transformation, provider);
     }
 
-    // If the requested crypto service is export-controlled,
-    // determine the maximum allowable keysize.
-    private void initCryptoPermission() throws NoSuchAlgorithmException {
-        /* ----- BEGIN android -----
-        if (JceSecurity.isRestricted() == false) {
-        */
-        if (true) {
-        // ----- END android -----
-            cryptoPerm = CryptoAllPermission.INSTANCE;
-            exmech = null;
-            return;
-        }
-        cryptoPerm = getConfiguredPermission(transformation);
-        // Instantiate the exemption mechanism (if required)
-        String exmechName = cryptoPerm.getExemptionMechanism();
-        if (exmechName != null) {
-            exmech = ExemptionMechanism.getInstance(exmechName);
-        }
-    }
+    static final Cipher createCipher(String transformation, Provider provider)
+        throws NoSuchAlgorithmException, NoSuchPaddingException {
+        String[] tokenizedTransformation = tokenizeTransformation(transformation);
 
-    // max number of debug warnings to print from chooseFirstProvider()
-    private static int warnCount = 10;
+        CipherSpiAndProvider cipherSpiAndProvider = null;
+        try {
+            cipherSpiAndProvider =
+                tryCombinations(null /*params*/, provider, tokenizedTransformation);
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            // Shouldn't happen.
+            throw new IllegalStateException("Key/Algorithm excepton despite not passing one", e);
+        }
+
+        if (cipherSpiAndProvider == null) {
+            if (provider == null) {
+                throw new NoSuchAlgorithmException("No provider found for " + transformation);
+            } else {
+                throw new NoSuchAlgorithmException("Provider " + provider.getName()
+                        + " does not provide " + transformation);
+            }
+        }
+
+        // exceptions and stuff
+        return new Cipher(null, provider, transformation, tokenizedTransformation);
+    }
 
     /**
      * Choose the Spi from the first provider available. Used if
      * delayed provider selection is not possible because init()
      * is not the first method called.
      */
-    void chooseFirstProvider() {
-        if (spi != null) {
-            return;
-        }
-        synchronized (lock) {
-            if (spi != null) {
-                return;
-            }
-            if (debug != null) {
-                int w = --warnCount;
-                if (w >= 0) {
-                    debug.println("Cipher.init() not first method "
-                        + "called, disabling delayed provider selection");
-                    if (w == 0) {
-                        debug.println("Further warnings of this type will "
-                            + "be suppressed");
-                    }
-                    new Exception("Call trace").printStackTrace();
-                }
-            }
-            Exception lastException = null;
-            final List<Service> services = getServices(transforms);
-            for (Service s : services) {
-                if (JceSecurity.canUseProvider(s.getProvider()) == false) {
-                    continue;
-                }
-                Transform tr = getTransform(s, transforms);
-                if (tr == null) {
-                    // should never happen
-                    continue;
-                }
-                if (tr.supportsModePadding(s) == S_NO) {
-                    continue;
-                }
-                try {
-                    CipherSpi thisSpi;
-                    Object obj = s.newInstance(null);
-                    if (obj instanceof CipherSpi == false) {
-                        continue;
-                    }
-                    thisSpi = (CipherSpi)obj;
-                    tr.setModePadding(thisSpi);
-                    initCryptoPermission();
-                    spi = thisSpi;
-                    provider = s.getProvider();
-                    return;
-                } catch (Exception e) {
-                    lastException = e;
-                }
-            }
+    void updateProviderIfNeeded() {
+        try {
+            spiAndProviderUpdater.updateAndGetSpiAndProvider(null, spi, provider);
+        } catch (Exception lastException) {
             ProviderException e = new ProviderException
                     ("Could not construct CipherSpi instance");
             if (lastException != null) {
@@ -760,83 +475,16 @@
         }
     }
 
-    private final static int I_KEY       = 1;
-    private final static int I_PARAMSPEC = 2;
-    private final static int I_PARAMS    = 3;
-    private final static int I_CERT      = 4;
-
-    private void implInit(CipherSpi thisSpi, int type, int opmode, Key key,
-            AlgorithmParameterSpec paramSpec, AlgorithmParameters params,
-            SecureRandom random) throws InvalidKeyException,
-            InvalidAlgorithmParameterException {
-        switch (type) {
-        case I_KEY:
-            checkCryptoPerm(thisSpi, key);
-            thisSpi.engineInit(opmode, key, random);
-            break;
-        case I_PARAMSPEC:
-            checkCryptoPerm(thisSpi, key, paramSpec);
-            thisSpi.engineInit(opmode, key, paramSpec, random);
-            break;
-        case I_PARAMS:
-            checkCryptoPerm(thisSpi, key, params);
-            thisSpi.engineInit(opmode, key, params, random);
-            break;
-        case I_CERT:
-            checkCryptoPerm(thisSpi, key);
-            thisSpi.engineInit(opmode, key, random);
-            break;
-        default:
-            throw new AssertionError("Internal Cipher error: " + type);
-        }
-    }
-
-    private void chooseProvider(int initType, int opmode, Key key,
+    private void chooseProvider(InitType initType, int opmode, Key key,
             AlgorithmParameterSpec paramSpec,
             AlgorithmParameters params, SecureRandom random)
             throws InvalidKeyException, InvalidAlgorithmParameterException {
-        synchronized (lock) {
-            if (spi != null && (lock == null || key == null)) {
-                implInit(spi, initType, opmode, key, paramSpec, params, random);
-                return;
-            }
-            Exception lastException = null;
-            final List<Service> services = getServices(transforms);
-            for (Service s : services) {
-                // if provider says it does not support this key, ignore it
-                if (s.supportsParameter(key) == false) {
-                    continue;
-                }
-                if (JceSecurity.canUseProvider(s.getProvider()) == false) {
-                    continue;
-                }
-                Transform tr = getTransform(s, transforms);
-                if (tr == null) {
-                    // should never happen
-                    continue;
-                }
-                if (tr.supportsModePadding(s) == S_NO) {
-                    continue;
-                }
-                try {
-                    CipherSpi thisSpi = (CipherSpi)s.newInstance(null);
-                    tr.setModePadding(thisSpi);
-                    initCryptoPermission();
-                    implInit(thisSpi, initType, opmode, key, paramSpec,
-                                                        params, random);
-                    provider = s.getProvider();
-                    this.spi = thisSpi;
-                    return;
-                } catch (Exception e) {
-                    // NoSuchAlgorithmException from newInstance()
-                    // InvalidKeyException from init()
-                    // RuntimeException (ProviderException) from init()
-                    // SecurityException from crypto permission check
-                    if (lastException == null) {
-                        lastException = e;
-                    }
-                }
-            }
+
+        try {
+            final InitParams initParams = new InitParams(initType, opmode, key, random,
+                                                         paramSpec, params);
+            spiAndProviderUpdater.updateAndGetSpiAndProvider(initParams, spi, provider);
+        } catch (Exception lastException) {
             // no working provider found, fail
             if (lastException instanceof InvalidKeyException) {
                 throw (InvalidKeyException)lastException;
@@ -860,7 +508,7 @@
      * @return the provider of this <code>Cipher</code> object
      */
     public final Provider getProvider() {
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return this.provider;
     }
 
@@ -884,7 +532,7 @@
      * not a block cipher
      */
     public final int getBlockSize() {
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineGetBlockSize();
     }
 
@@ -917,7 +565,7 @@
             throw new IllegalArgumentException("Input size must be equal " +
                                                "to or greater than zero");
         }
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineGetOutputSize(inputLen);
     }
 
@@ -933,7 +581,7 @@
      * been set.
      */
     public final byte[] getIV() {
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineGetIV();
     }
 
@@ -949,7 +597,7 @@
      * does not use any parameters.
      */
     public final AlgorithmParameters getParameters() {
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineGetParameters();
     }
 
@@ -960,111 +608,10 @@
      * null if this cipher does not use any exemption mechanism.
      */
     public final ExemptionMechanism getExemptionMechanism() {
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return exmech;
     }
 
-    //
-    // Crypto permission check code below
-    //
-    private void checkCryptoPerm(CipherSpi checkSpi, Key key)
-            throws InvalidKeyException {
-        if (cryptoPerm == CryptoAllPermission.INSTANCE) {
-            return;
-        }
-        // Check if key size and default parameters are within legal limits
-        AlgorithmParameterSpec params;
-        try {
-            params = getAlgorithmParameterSpec(checkSpi.engineGetParameters());
-        } catch (InvalidParameterSpecException ipse) {
-            throw new InvalidKeyException
-                ("Unsupported default algorithm parameters");
-        }
-        if (!passCryptoPermCheck(checkSpi, key, params)) {
-            throw new InvalidKeyException(
-                "Illegal key size or default parameters");
-        }
-    }
-
-    private void checkCryptoPerm(CipherSpi checkSpi, Key key,
-            AlgorithmParameterSpec params) throws InvalidKeyException,
-            InvalidAlgorithmParameterException {
-        if (cryptoPerm == CryptoAllPermission.INSTANCE) {
-            return;
-        }
-        // Determine keysize and check if it is within legal limits
-        if (!passCryptoPermCheck(checkSpi, key, null)) {
-            throw new InvalidKeyException("Illegal key size");
-        }
-        if ((params != null) && (!passCryptoPermCheck(checkSpi, key, params))) {
-            throw new InvalidAlgorithmParameterException("Illegal parameters");
-        }
-    }
-
-    private void checkCryptoPerm(CipherSpi checkSpi, Key key,
-            AlgorithmParameters params)
-            throws InvalidKeyException, InvalidAlgorithmParameterException {
-        if (cryptoPerm == CryptoAllPermission.INSTANCE) {
-            return;
-        }
-        // Convert the specified parameters into specs and then delegate.
-        AlgorithmParameterSpec pSpec;
-        try {
-            pSpec = getAlgorithmParameterSpec(params);
-        } catch (InvalidParameterSpecException ipse) {
-            throw new InvalidAlgorithmParameterException
-                ("Failed to retrieve algorithm parameter specification");
-        }
-        checkCryptoPerm(checkSpi, key, pSpec);
-    }
-
-    private boolean passCryptoPermCheck(CipherSpi checkSpi, Key key,
-                                        AlgorithmParameterSpec params)
-            throws InvalidKeyException {
-        String em = cryptoPerm.getExemptionMechanism();
-        int keySize = checkSpi.engineGetKeySize(key);
-        // Use the "algorithm" component of the cipher
-        // transformation so that the perm check would
-        // work when the key has the "aliased" algo.
-        String algComponent;
-        int index = transformation.indexOf('/');
-        if (index != -1) {
-            algComponent = transformation.substring(0, index);
-        } else {
-            algComponent = transformation;
-        }
-        CryptoPermission checkPerm =
-            new CryptoPermission(algComponent, keySize, params, em);
-
-        if (!cryptoPerm.implies(checkPerm)) {
-            if (debug != null) {
-                debug.println("Crypto Permission check failed");
-                debug.println("granted: " + cryptoPerm);
-                debug.println("requesting: " + checkPerm);
-            }
-            return false;
-        }
-        if (exmech == null) {
-            return true;
-        }
-        try {
-            if (!exmech.isCryptoAllowed(key)) {
-                if (debug != null) {
-                    debug.println(exmech.getName() + " isn't enforced");
-                }
-                return false;
-            }
-        } catch (ExemptionMechanismException eme) {
-            if (debug != null) {
-                debug.println("Cannot determine whether "+
-                              exmech.getName() + " has been enforced");
-                eme.printStackTrace();
-            }
-            return false;
-        }
-        return true;
-    }
-
     // check if opmode is one of the defined constants
     // throw InvalidParameterExeption if not
     private static void checkOpmode(int opmode) {
@@ -1178,12 +725,11 @@
         initialized = false;
         checkOpmode(opmode);
 
-        if (spi != null && (lock == null || key == null)) {
-            checkCryptoPerm(spi, key);
+        if (spi != null && (key == null)) {
             spi.engineInit(opmode, key, random);
         } else {
             try {
-                chooseProvider(I_KEY, opmode, key, null, null, random);
+                chooseProvider(InitType.KEY, opmode, key, null, null, random);
             } catch (InvalidAlgorithmParameterException e) {
                 // should never occur
                 throw new InvalidKeyException(e);
@@ -1315,10 +861,9 @@
         checkOpmode(opmode);
 
         if (spi != null) {
-            checkCryptoPerm(spi, key, params);
             spi.engineInit(opmode, key, params, random);
         } else {
-            chooseProvider(I_PARAMSPEC, opmode, key, params, null, random);
+            chooseProvider(InitType.ALGORITHM_PARAM_SPEC, opmode, key, params, null, random);
         }
 
         initialized = true;
@@ -1446,10 +991,9 @@
         checkOpmode(opmode);
 
         if (spi != null) {
-            checkCryptoPerm(spi, key, params);
             spi.engineInit(opmode, key, params, random);
         } else {
-            chooseProvider(I_PARAMS, opmode, key, null, params, random);
+            chooseProvider(InitType.ALGORITHM_PARAMS, opmode, key, null, params, random);
         }
 
         initialized = true;
@@ -1619,11 +1163,10 @@
             (certificate==null? null:certificate.getPublicKey());
 
         if (spi != null) {
-            checkCryptoPerm(spi, publicKey);
             spi.engineInit(opmode, publicKey, random);
         } else {
             try {
-                chooseProvider(I_CERT, opmode, publicKey, null, null, random);
+                chooseProvider(InitType.KEY, opmode, (Key)publicKey, null, null, random);
             } catch (InvalidAlgorithmParameterException e) {
                 // should never occur
                 throw new InvalidKeyException(e);
@@ -1680,7 +1223,7 @@
             throw new IllegalArgumentException("Null input buffer");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         if (input.length == 0) {
             return null;
         }
@@ -1720,7 +1263,7 @@
             throw new IllegalArgumentException("Bad arguments");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         if (inputLen == 0) {
             return null;
         }
@@ -1774,7 +1317,7 @@
             throw new IllegalArgumentException("Bad arguments");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         if (inputLen == 0) {
             return 0;
         }
@@ -1833,7 +1376,7 @@
             throw new IllegalArgumentException("Bad arguments");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         if (inputLen == 0) {
             return 0;
         }
@@ -1894,7 +1437,7 @@
             throw new ReadOnlyBufferException();
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineUpdate(input, output);
     }
 
@@ -1939,7 +1482,7 @@
             throws IllegalBlockSizeException, BadPaddingException {
         checkCipherState();
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineDoFinal(null, 0, 0);
     }
 
@@ -2003,7 +1546,7 @@
             throw new IllegalArgumentException("Bad arguments");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineDoFinal(null, 0, 0, output, outputOffset);
     }
 
@@ -2056,7 +1599,7 @@
             throw new IllegalArgumentException("Null input buffer");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineDoFinal(input, 0, input.length);
     }
 
@@ -2114,7 +1657,7 @@
             throw new IllegalArgumentException("Bad arguments");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineDoFinal(input, inputOffset, inputLen);
     }
 
@@ -2188,7 +1731,7 @@
             throw new IllegalArgumentException("Bad arguments");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineDoFinal(input, inputOffset, inputLen,
                                        output, 0);
     }
@@ -2268,7 +1811,7 @@
             throw new IllegalArgumentException("Bad arguments");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineDoFinal(input, inputOffset, inputLen,
                                        output, outputOffset);
     }
@@ -2351,7 +1894,7 @@
             throw new ReadOnlyBufferException();
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineDoFinal(input, output);
     }
 
@@ -2386,7 +1929,7 @@
             }
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineWrap(key);
     }
 
@@ -2435,7 +1978,7 @@
             throw new InvalidParameterException("Invalid key type");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         return spi.engineUnwrap(wrappedKey,
                                       wrappedKeyAlgorithm,
                                       wrappedKeyType);
@@ -2468,14 +2011,6 @@
         return null;
     }
 
-    private static CryptoPermission getConfiguredPermission(
-            String transformation) throws NullPointerException,
-            NoSuchAlgorithmException {
-        if (transformation == null) throw new NullPointerException();
-        String[] parts = tokenizeTransformation(transformation);
-        return JceSecurityManager.INSTANCE.getCryptoPermission(parts[0]);
-    }
-
     /**
      * Returns the maximum key length for the specified transformation
      * according to the installed JCE jurisdiction policy files. If
@@ -2615,7 +2150,7 @@
             throw new IllegalArgumentException("Bad arguments");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         if (len == 0) {
             return;
         }
@@ -2660,7 +2195,7 @@
             throw new IllegalArgumentException("src ByteBuffer is null");
         }
 
-        chooseFirstProvider();
+        updateProviderIfNeeded();
         if (src.remaining() == 0) {
             return;
         }
@@ -2676,4 +2211,326 @@
     public CipherSpi getCurrentSpi() {
         return spi;
     }
+
+    /** The attribute used for supported paddings. */
+    private static final String ATTRIBUTE_PADDINGS = "SupportedPaddings";
+
+    /** The attribute used for supported modes. */
+    private static final String ATTRIBUTE_MODES = "SupportedModes";
+
+    /**
+     * If the attribute listed exists, check that it matches the regular
+     * expression.
+     */
+    static boolean matchAttribute(Provider.Service service, String attr, String value) {
+        if (value == null) {
+            return true;
+        }
+        final String pattern = service.getAttribute(attr);
+        if (pattern == null) {
+            return true;
+        }
+        final String valueUc = value.toUpperCase(Locale.US);
+        return valueUc.matches(pattern.toUpperCase(Locale.US));
+    }
+
+    /** Items that need to be set on the Cipher instance. */
+    enum NeedToSet {
+        NONE, MODE, PADDING, BOTH,
+    }
+
+    /**
+     * Expresses the various types of transforms that may be used during
+     * initialization.
+     */
+    static class Transform {
+        private final String name;
+        private final NeedToSet needToSet;
+
+        public Transform(String name, NeedToSet needToSet) {
+            this.name = name;
+            this.needToSet = needToSet;
+        }
+    }
+
+    /**
+     * Keeps track of the possible arguments to {@code Cipher#init(...)}.
+     */
+    static class InitParams {
+        final InitType initType;
+        final int opmode;
+        final Key key;
+        final SecureRandom random;
+        final AlgorithmParameterSpec spec;
+        final AlgorithmParameters params;
+
+        InitParams(InitType initType, int opmode, Key key, SecureRandom random,
+                AlgorithmParameterSpec spec, AlgorithmParameters params) {
+            this.initType = initType;
+            this.opmode = opmode;
+            this.key = key;
+            this.random = random;
+            this.spec = spec;
+            this.params = params;
+        }
+    }
+
+    /**
+     * Used to keep track of which underlying {@code CipherSpi#engineInit(...)}
+     * variant to call when testing suitability.
+     */
+    static enum InitType {
+        KEY, ALGORITHM_PARAMS, ALGORITHM_PARAM_SPEC,
+    }
+
+    class SpiAndProviderUpdater {
+        /**
+         * Lock held while the SPI is initializing.
+         */
+        private final Object initSpiLock = new Object();
+
+        /**
+         * The provider specified when instance created.
+         */
+        private final Provider specifiedProvider;
+
+        /**
+         * The SPI implementation.
+         */
+        private final CipherSpi specifiedSpi;
+
+        SpiAndProviderUpdater(Provider specifiedProvider, CipherSpi specifiedSpi) {
+            this.specifiedProvider = specifiedProvider;
+            this.specifiedSpi = specifiedSpi;
+        }
+
+        void setCipherSpiImplAndProvider(CipherSpi cipherSpi, Provider provider) {
+            Cipher.this.spi = cipherSpi;
+            Cipher.this.provider = provider;
+        }
+
+        /**
+         * Makes sure a CipherSpi that matches this type is selected. If
+         * {@code key != null} then it assumes that a suitable provider exists for
+         * this instance (used by {@link Cipher#init}. If the {@code initParams} is passed
+         * in, then the {@code CipherSpi} returned will be initialized.
+         *
+         * @throws InvalidKeyException if the specified key cannot be used to
+         *                             initialize this cipher.
+         */
+        CipherSpiAndProvider updateAndGetSpiAndProvider(
+                InitParams initParams,
+                CipherSpi spiImpl,
+                Provider provider)
+                throws InvalidKeyException, InvalidAlgorithmParameterException {
+            if (specifiedSpi != null) {
+                return new CipherSpiAndProvider(specifiedSpi, provider);
+            }
+            synchronized (initSpiLock) {
+                // This is not only a matter of performance. Many methods like update, doFinal, etc.
+                // call {@code #getSpi()} (ie, {@code #getSpi(null /* params */)}) and without this
+                // shortcut they would override an spi that was chosen using the key.
+                if (spiImpl != null && initParams == null) {
+                    return new CipherSpiAndProvider(spiImpl, provider);
+                }
+                final CipherSpiAndProvider sap = tryCombinations(
+                        initParams, specifiedProvider, tokenizedTransformation);
+                if (sap == null) {
+                    throw new ProviderException("No provider found for "
+                            + Arrays.toString(tokenizedTransformation));
+                }
+                setCipherSpiImplAndProvider(sap.cipherSpi, sap.provider);
+                return new CipherSpiAndProvider(sap.cipherSpi, sap.provider);
+            }
+        }
+
+        /**
+         * Convenience call when the Key is not available.
+         */
+        CipherSpiAndProvider updateAndGetSpiAndProvider(CipherSpi spiImpl, Provider provider) {
+            try {
+                return updateAndGetSpiAndProvider(null, spiImpl, provider);
+            } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+                throw new ProviderException("Exception thrown when params == null", e);
+           }
+        }
+
+        CipherSpi getCurrentSpi(CipherSpi spiImpl) {
+            if (specifiedSpi != null) {
+                return specifiedSpi;
+            }
+
+            synchronized (initSpiLock) {
+                return spiImpl;
+            }
+        }
+    }
+
+    /**
+     * Tries to find the correct {@code Cipher} transform to use. Returns a
+     * {@link org.apache.harmony.security.fortress.Engine.SpiAndProvider}, throws the first exception that was
+     * encountered during attempted initialization, or {@code null} if there are
+     * no providers that support the {@code initParams}.
+     * <p>
+     * {@code tokenizedTransformation} must be in the format returned by
+     * {@link Cipher#checkTransformation(String)}. The combinations of mode strings
+     * tried are as follows:
+     * <ul>
+     * <li><code>[cipher]/[mode]/[padding]</code>
+     * <li><code>[cipher]/[mode]</code>
+     * <li><code>[cipher]//[padding]</code>
+     * <li><code>[cipher]</code>
+     * </ul>
+     * {@code services} is a list of cipher services. Needs to be non-null only if
+     * {@code provider != null}
+     */
+    static CipherSpiAndProvider tryCombinations(InitParams initParams, Provider provider,
+            String[] tokenizedTransformation)
+            throws InvalidKeyException,
+            InvalidAlgorithmParameterException {
+        // Enumerate all the transforms we need to try
+        ArrayList<Transform> transforms = new ArrayList<Transform>();
+        if (tokenizedTransformation[1] != null && tokenizedTransformation[2] != null) {
+            transforms.add(new Transform(tokenizedTransformation[0] + "/" + tokenizedTransformation[1] + "/"
+                    + tokenizedTransformation[2], NeedToSet.NONE));
+        }
+        if (tokenizedTransformation[1] != null) {
+            transforms.add(new Transform(tokenizedTransformation[0] + "/" + tokenizedTransformation[1],
+                    NeedToSet.PADDING));
+        }
+        if (tokenizedTransformation[2] != null) {
+            transforms.add(new Transform(tokenizedTransformation[0] + "//" + tokenizedTransformation[2],
+                    NeedToSet.MODE));
+        }
+        transforms.add(new Transform(tokenizedTransformation[0], NeedToSet.BOTH));
+
+        // Try each of the transforms and keep track of the first exception
+        // encountered.
+        Exception cause = null;
+
+        if (provider != null) {
+            for (Transform transform : transforms) {
+                Provider.Service service = provider.getService("Cipher", transform.name);
+                if (service == null) {
+                    continue;
+                }
+                return tryTransformWithProvider(initParams, tokenizedTransformation, transform.needToSet,
+                                service);
+            }
+        } else {
+            for (Provider prov : Security.getProviders()) {
+                for (Transform transform : transforms) {
+                    Provider.Service service = prov.getService("Cipher", transform.name);
+                    if (service == null) {
+                        continue;
+                    }
+
+                    if (initParams == null || initParams.key == null
+                            || service.supportsParameter(initParams.key)) {
+                        try {
+                            CipherSpiAndProvider sap = tryTransformWithProvider(initParams,
+                                    tokenizedTransformation, transform.needToSet, service);
+                            if (sap != null) {
+                                return sap;
+                            }
+                        } catch (Exception e) {
+                            if (cause == null) {
+                                cause = e;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (cause instanceof InvalidKeyException) {
+            throw (InvalidKeyException) cause;
+        } else if (cause instanceof InvalidAlgorithmParameterException) {
+            throw (InvalidAlgorithmParameterException) cause;
+        } else if (cause instanceof RuntimeException) {
+            throw (RuntimeException) cause;
+        } else if (cause != null) {
+            throw new InvalidKeyException("No provider can be initialized with given key", cause);
+        } else if (initParams == null || initParams.key == null) {
+            return null;
+        } else {
+            // Since the key is not null, a suitable provider exists,
+            // and it is an InvalidKeyException.
+            throw new InvalidKeyException(
+                    "No provider offers " + Arrays.toString(tokenizedTransformation) + " for "
+                    + initParams.key.getAlgorithm() + " key of class "
+                    + initParams.key.getClass().getName() + " and export format "
+                    + initParams.key.getFormat());
+        }
+    }
+
+    static class CipherSpiAndProvider {
+        CipherSpi cipherSpi;
+        Provider provider;
+
+        CipherSpiAndProvider(CipherSpi cipherSpi, Provider provider) {
+            this.cipherSpi = cipherSpi;
+            this.provider = provider;
+        }
+    }
+
+    /**
+     * Tries to initialize the {@code Cipher} from a given {@code service}. If
+     * initialization is successful, the initialized {@code spi} is returned. If
+     * the {@code service} cannot be initialized with the specified
+     * {@code initParams}, then it's expected to throw
+     * {@code InvalidKeyException} or {@code InvalidAlgorithmParameterException}
+     * as a hint to the caller that it should continue searching for a
+     * {@code Service} that will work.
+     */
+    static CipherSpiAndProvider tryTransformWithProvider(InitParams initParams,
+            String[] tokenizedTransformation, NeedToSet type, Provider.Service service)
+                throws InvalidKeyException, InvalidAlgorithmParameterException  {
+        try {
+            /*
+             * Check to see if the Cipher even supports the attributes before
+             * trying to instantiate it.
+             */
+            if (!matchAttribute(service, ATTRIBUTE_MODES, tokenizedTransformation[1])
+                    || !matchAttribute(service, ATTRIBUTE_PADDINGS, tokenizedTransformation[2])) {
+                return null;
+            }
+
+            CipherSpiAndProvider sap = new CipherSpiAndProvider(
+                (CipherSpi) service.newInstance(null), service.getProvider());
+            if (sap.cipherSpi == null || sap.provider == null) {
+                return null;
+            }
+            CipherSpi spi = sap.cipherSpi;
+            if (((type == NeedToSet.MODE) || (type == NeedToSet.BOTH))
+                    && (tokenizedTransformation[1] != null)) {
+                spi.engineSetMode(tokenizedTransformation[1]);
+            }
+            if (((type == NeedToSet.PADDING) || (type == NeedToSet.BOTH))
+                    && (tokenizedTransformation[2] != null)) {
+                spi.engineSetPadding(tokenizedTransformation[2]);
+            }
+
+            if (initParams != null) {
+                switch (initParams.initType) {
+                    case ALGORITHM_PARAMS:
+                        spi.engineInit(initParams.opmode, initParams.key, initParams.params,
+                                initParams.random);
+                        break;
+                    case ALGORITHM_PARAM_SPEC:
+                        spi.engineInit(initParams.opmode, initParams.key, initParams.spec,
+                                initParams.random);
+                        break;
+                    case KEY:
+                        spi.engineInit(initParams.opmode, initParams.key, initParams.random);
+                        break;
+                    default:
+                        throw new AssertionError("This should never be reached");
+                }
+            }
+            return new CipherSpiAndProvider(spi, sap.provider);
+        } catch (NoSuchAlgorithmException ignored) {
+        } catch (NoSuchPaddingException ignored) {
+        }
+        return null;
+    }
 }
diff --git a/ojluni/src/main/java/javax/crypto/NullCipher.java b/ojluni/src/main/java/javax/crypto/NullCipher.java
index 39afb2a..73a43fe 100755
--- a/ojluni/src/main/java/javax/crypto/NullCipher.java
+++ b/ojluni/src/main/java/javax/crypto/NullCipher.java
@@ -39,6 +39,6 @@
 public class NullCipher extends Cipher {
 
     public NullCipher() {
-        super(new NullCipherSpi(), null);
+        super(new NullCipherSpi(), null, null);
     }
 }