Update Cipher change markers.
Mostly adds a note documenting that we changed much of the provider
selection code. Also marks some more important/less obviously-changed
chunks of code explicitly.
Reverts a few unnecessary diffs from upstream 8u121-b13, and readds
(commented out) two large blocks of code that were removed.
Test: cts -m CtsLibcoreTestCases -t libcore.javax.crypto.CipherTest
Bug: 38192166
Change-Id: I47769203beb41a8308d7cbd5d1be1d1a12a4c0ac
diff --git a/ojluni/src/main/java/javax/crypto/Cipher.java b/ojluni/src/main/java/javax/crypto/Cipher.java
index e3c266f..dfaf94d 100644
--- a/ojluni/src/main/java/javax/crypto/Cipher.java
+++ b/ojluni/src/main/java/javax/crypto/Cipher.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -227,6 +227,18 @@
public class Cipher {
+ // Android-note: Android reimplements provider selection.
+ //
+ // Android uses different provider/impl selection code than upstream does. Provider
+ // selection permeates much of this class, so this class is forked significantly
+ // from the upstream version. Not every change is marked, and any changes to upstream code
+ // should be evaluated to see if they should be merged.
+ //
+ // The changes are chiefly in construction (constructors, getInstance, and createCipher) and
+ // initialization (init and chooseProvider). Most of the actual implementation is in the
+ // classes and methods at the bottom of this file.
+
+
// Android-removed: this debugging mechanism is not used in Android.
/*
private static final Debug debug =
@@ -280,11 +292,22 @@
private CipherSpi spi;
// The transformation
+ // Android-changed: Made final.
final private String transformation;
+ // Android-added: Added tokenizedTransformation.
// The tokenized version of transformation
final private String[] tokenizedTransformation;
+ // Android-removed: Removed cryptoPerm.
+ /*
+ // 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 exemption mechanism that needs to be enforced
private ExemptionMechanism exmech;
@@ -298,8 +321,28 @@
// The OID for the KeyUsage extension in an X.509 v3 certificate
private static final String KEY_USAGE_EXTENSION_OID = "2.5.29.15";
+ // BEGIN Android-changed: Reimplement provider selection.
+ // See note at top of class.
private final SpiAndProviderUpdater spiAndProviderUpdater;
+ /*
+ // next SPI to try in provider selection
+ // null once provider is selected
+ private CipherSpi firstSpi;
+ // next service to try in provider selection
+ // null once provider is selected
+ private Service firstService;
+
+ // remaining services to try in provider selection
+ // null once provider is selected
+ private Iterator<Service> serviceIterator;
+
+ // list of transform Strings to lookup in the provider
+ private List<Transform> transforms;
+
+ private final Object lock;
+ */
+ // END Android-changed: Reimplement provider selection.
/**
* Creates a Cipher object.
@@ -376,6 +419,141 @@
return parts;
}
+ // BEGIN Android-removed: Reimplement provider selection.
+ // See note at top of class.
+ /*
+ // 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 = 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<Transform> 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<Transform> 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<Transform> transforms) {
+ String alg = s.getAlgorithm().toUpperCase(Locale.ENGLISH);
+ for (Transform tr : transforms) {
+ if (alg.endsWith(tr.suffix)) {
+ return tr;
+ }
+ }
+ return null;
+ }
+ */
+ // END Android-removed: Reimplement provider selection.
+
/**
* Returns a <code>Cipher</code> object that implements the specified
* transformation.
@@ -466,7 +644,7 @@
throw new NoSuchProviderException("No such provider: " +
provider);
}
- return createCipher(transformation, p);
+ return getInstance(transformation, p);
}
/**
@@ -693,6 +871,112 @@
return exmech;
}
+ // BEGIN Android-removed: Eliminate crypto permission checking.
+ // Android doesn't implement SecurityManager permissions.
+ /*
+ //
+ // 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;
+ }
+ */
+ // END Android-removed: Eliminate crypto permission checking.
+
// check if opmode is one of the defined constants
// throw InvalidParameterExeption if not
private static void checkOpmode(int opmode) {
@@ -836,6 +1120,7 @@
initialized = true;
this.opmode = opmode;
+
// Android-removed: this debugging mechanism is not used in Android.
/*
if (!skipDebug && pdebug != null) {
@@ -1263,7 +1548,8 @@
*/
public final void init(int opmode, Certificate certificate,
SecureRandom random)
- throws InvalidKeyException {
+ throws InvalidKeyException
+ {
initialized = false;
checkOpmode(opmode);
@@ -1272,28 +1558,28 @@
if (certificate instanceof java.security.cert.X509Certificate) {
// Check whether the cert has a key usage extension
// marked as a critical extension.
- X509Certificate cert = (X509Certificate) certificate;
+ X509Certificate cert = (X509Certificate)certificate;
Set<String> critSet = cert.getCriticalExtensionOIDs();
if (critSet != null && !critSet.isEmpty()
- && critSet.contains(KEY_USAGE_EXTENSION_OID)) {
+ && critSet.contains(KEY_USAGE_EXTENSION_OID)) {
boolean[] keyUsageInfo = cert.getKeyUsage();
// keyUsageInfo[2] is for keyEncipherment;
// keyUsageInfo[3] is for dataEncipherment.
if ((keyUsageInfo != null) &&
- (((opmode == Cipher.ENCRYPT_MODE) &&
- (keyUsageInfo.length > 3) &&
- (keyUsageInfo[3] == false)) ||
- ((opmode == Cipher.WRAP_MODE) &&
- (keyUsageInfo.length > 2) &&
- (keyUsageInfo[2] == false)))) {
+ (((opmode == Cipher.ENCRYPT_MODE) &&
+ (keyUsageInfo.length > 3) &&
+ (keyUsageInfo[3] == false)) ||
+ ((opmode == Cipher.WRAP_MODE) &&
+ (keyUsageInfo.length > 2) &&
+ (keyUsageInfo[2] == false)))) {
throw new InvalidKeyException("Wrong key usage");
}
}
}
PublicKey publicKey =
- (certificate == null ? null : certificate.getPublicKey());
+ (certificate==null? null:certificate.getPublicKey());
try {
chooseProvider(InitType.KEY, opmode, (Key) publicKey, null, null, random);
@@ -2346,6 +2632,8 @@
spi.engineUpdateAAD(src);
}
+ // BEGIN Android-added: Bulk of the new provider implementation.
+ // See note at top of class.
/**
* Returns the {@code CipherSpi} backing this {@code Cipher} or {@code null} if no
* {@code CipherSpi} is backing this {@code Cipher}.
@@ -2677,4 +2965,5 @@
}
return null;
}
+ // END Android-added: Bulk of the new provider implementation.
}