blob: 82e4e9f8997b61e6795d18ec746d4a439d00de6a [file] [log] [blame]
/*
* Copyright 2014 The Android Open Source Project
*
* 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.conscrypt;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.Signature;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
/**
* Provides a place where NativeCrypto can call back up to do Java language
* calls to work on delegated key types from native code. Delegated keys are
* usually backed by hardware so we don't have access directly to the private
* key material. If it were a key where we can get to the private key, we
* would not ever call into this class.
*/
final class CryptoUpcalls {
private static final Logger logger = Logger.getLogger(CryptoUpcalls.class.getName());
private CryptoUpcalls() {}
/**
* Finds providers that are not us that provide the requested algorithms.
*/
private static ArrayList<Provider> getExternalProviders(String algorithm) {
ArrayList<Provider> providers = new ArrayList<Provider>(1);
for (Provider p : Security.getProviders(algorithm)) {
if (!Conscrypt.isConscrypt(p)) {
providers.add(p);
}
}
if (providers.isEmpty()) {
logger.warning("Could not find external provider for algorithm: " + algorithm);
}
return providers;
}
static byte[] ecSignDigestWithPrivateKey(PrivateKey javaKey, byte[] message) {
// Hint: Algorithm names come from:
// http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
String keyAlgorithm = javaKey.getAlgorithm();
if (!"EC".equals(keyAlgorithm)) {
throw new RuntimeException("Unexpected key type: " + javaKey.toString());
}
return signDigestWithPrivateKey(javaKey, message, "NONEwithECDSA");
}
private static byte[] signDigestWithPrivateKey(PrivateKey javaKey, byte[] message,
String algorithm) {
Signature signature;
// Since this is a delegated key, we cannot handle providing a signature using this key.
// Otherwise we wouldn't end up in this class in the first place. The first step is to
// try to get the most preferred provider as long as it isn't us.
try {
signature = Signature.getInstance(algorithm);
signature.initSign(javaKey);
// Ignore it if it points back to us.
if (Conscrypt.isConscrypt(signature.getProvider())) {
signature = null;
}
} catch (NoSuchAlgorithmException e) {
logger.warning("Unsupported signature algorithm: " + algorithm);
return null;
} catch (InvalidKeyException e) {
logger.warning("Preferred provider doesn't support key:");
e.printStackTrace();
signature = null;
}
// If the preferred provider was us, fall back to trying to find the
// first not-us provider that initializes correctly.
if (signature == null) {
ArrayList<Provider> providers = getExternalProviders("Signature." + algorithm);
RuntimeException savedRuntimeException = null;
for (Provider p : providers) {
try {
signature = Signature.getInstance(algorithm, p);
signature.initSign(javaKey);
break;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
signature = null;
} catch (RuntimeException e) {
signature = null;
if (savedRuntimeException == null) {
savedRuntimeException = e;
}
}
}
if (signature == null) {
if (savedRuntimeException != null) {
throw savedRuntimeException;
}
logger.warning("Could not find provider for algorithm: " + algorithm);
return null;
}
}
// Sign the message.
try {
signature.update(message);
return signature.sign();
} catch (Exception e) {
logger.log(Level.WARNING,
"Exception while signing message with " + javaKey.getAlgorithm()
+ " private key:",
e);
return null;
}
}
static byte[] rsaSignDigestWithPrivateKey(PrivateKey javaKey, int openSSLPadding,
byte[] message) {
// An RSA cipher + ENCRYPT_MODE produces a standard RSA signature
return rsaOpWithPrivateKey(javaKey, openSSLPadding, Cipher.ENCRYPT_MODE, message);
}
static byte[] rsaDecryptWithPrivateKey(PrivateKey javaKey, int openSSLPadding, byte[] input) {
return rsaOpWithPrivateKey(javaKey, openSSLPadding, Cipher.DECRYPT_MODE, input);
}
private static byte[] rsaOpWithPrivateKey(PrivateKey javaKey, int openSSLPadding,
int cipherMode, byte[] input) {
String keyAlgorithm = javaKey.getAlgorithm();
if (!"RSA".equals(keyAlgorithm)) {
logger.warning("Unexpected key type: " + keyAlgorithm);
return null;
}
String jcaPadding;
switch (openSSLPadding) {
case NativeConstants.RSA_PKCS1_PADDING:
// Since we're using this with a private key, this will produce RSASSA-PKCS1-v1_5
// (signature) padding rather than RSAES-PKCS1-v1_5 (encryption) padding
jcaPadding = "PKCS1Padding";
break;
case NativeConstants.RSA_NO_PADDING:
jcaPadding = "NoPadding";
break;
case NativeConstants.RSA_PKCS1_OAEP_PADDING:
jcaPadding = "OAEPPadding";
break;
default:
logger.warning("Unsupported OpenSSL/BoringSSL padding: " + openSSLPadding);
return null;
}
String transformation = "RSA/ECB/" + jcaPadding;
Cipher c = null;
// Since this is a delegated key, we cannot handle providing a cipher using this key.
// Otherwise we wouldn't end up in this class in the first place. The first step is to
// try to get the most preferred provider as long as it isn't us.
try {
c = Cipher.getInstance(transformation);
c.init(cipherMode, javaKey);
// Ignore it if it points back to us.
if (Conscrypt.isConscrypt(c.getProvider())) {
c = null;
}
} catch (NoSuchAlgorithmException e) {
logger.warning("Unsupported cipher algorithm: " + transformation);
return null;
} catch (NoSuchPaddingException e) {
logger.warning("Unsupported cipher algorithm: " + transformation);
return null;
} catch (InvalidKeyException e) {
logger.log(Level.WARNING, "Preferred provider doesn't support key:", e);
c = null;
}
// If the preferred provider was us, fall back to trying to find the
// first not-us provider that initializes correctly.
if (c == null) {
ArrayList<Provider> providers = getExternalProviders("Cipher." + transformation);
for (Provider p : providers) {
try {
c = Cipher.getInstance(transformation, p);
c.init(cipherMode, javaKey);
break;
} catch (NoSuchAlgorithmException e) {
c = null;
} catch (InvalidKeyException e) {
c = null;
} catch (NoSuchPaddingException e) {
c = null;
}
}
if (c == null) {
logger.warning("Could not find provider for algorithm: " + transformation);
return null;
}
}
try {
return c.doFinal(input);
} catch (Exception e) {
logger.log(Level.WARNING,
"Exception while decrypting message with " + javaKey.getAlgorithm()
+ " private key using " + transformation + ":",
e);
return null;
}
}
}