1 /*
2  * Copyright 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.conscrypt;
18 
19 import java.security.InvalidKeyException;
20 import java.security.NoSuchAlgorithmException;
21 import java.security.PrivateKey;
22 import java.security.Provider;
23 import java.security.Security;
24 import java.security.Signature;
25 import java.util.ArrayList;
26 import javax.crypto.Cipher;
27 import javax.crypto.NoSuchPaddingException;
28 
29 /**
30  * Provides a place where NativeCrypto can call back up to do Java language
31  * calls to work on delegated key types from native code.
32  */
33 public final class CryptoUpcalls {
34 
CryptoUpcalls()35     private CryptoUpcalls() {
36     }
37 
isOurProvider(Provider p)38     private static boolean isOurProvider(Provider p) {
39         return p.getClass().getPackage().equals(CryptoUpcalls.class.getPackage());
40     }
41 
42     /**
43      * Finds providers that are not us that provide the requested algorithms.
44      */
getExternalProviders(String algorithm)45     private static ArrayList<Provider> getExternalProviders(String algorithm) {
46         ArrayList<Provider> providers = new ArrayList<>(1);
47         for (Provider p : Security.getProviders(algorithm)) {
48             if (!isOurProvider(p)) {
49                 providers.add(p);
50             }
51         }
52         if (providers.isEmpty()) {
53             System.err.println("Could not find external provider for algorithm: " + algorithm);
54         }
55         return providers;
56     }
57 
rawSignDigestWithPrivateKey(PrivateKey javaKey, byte[] message)58     public static byte[] rawSignDigestWithPrivateKey(PrivateKey javaKey, byte[] message) {
59         // Get the raw signature algorithm for this key type.
60         String algorithm;
61         // Hint: Algorithm names come from:
62         // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
63         String keyAlgorithm = javaKey.getAlgorithm();
64         if ("RSA".equals(keyAlgorithm)) {
65             // IMPORTANT: Due to a platform bug, this will throw
66             // NoSuchAlgorithmException
67             // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher.
68             // See https://android-review.googlesource.com/#/c/40352/
69             algorithm = "NONEwithRSA";
70         } else if ("EC".equals(keyAlgorithm)) {
71             algorithm = "NONEwithECDSA";
72         } else {
73             throw new RuntimeException("Unexpected key type: " + javaKey.toString());
74         }
75 
76         Signature signature;
77 
78         // First try to get the most preferred provider as long as it isn't us.
79         try {
80             signature = Signature.getInstance(algorithm);
81             signature.initSign(javaKey);
82 
83             // Ignore it if it points back to us.
84             if (isOurProvider(signature.getProvider())) {
85                 signature = null;
86             }
87         } catch (NoSuchAlgorithmException e) {
88             System.err.println("Unsupported signature algorithm: " + algorithm);
89             return null;
90         } catch (InvalidKeyException e) {
91             System.err.println("Preferred provider doesn't support key:");
92             e.printStackTrace();
93             signature = null;
94         }
95 
96         // If the preferred provider was us, fall back to trying to find the
97         // first not-us provider that initializes correctly.
98         if (signature == null) {
99             ArrayList<Provider> providers = getExternalProviders("Signature." + algorithm);
100             for (Provider p : providers) {
101                 try {
102                     signature = Signature.getInstance(algorithm, p);
103                     signature.initSign(javaKey);
104                     break;
105                 } catch (NoSuchAlgorithmException | InvalidKeyException e) {
106                     signature = null;
107                 }
108             }
109             if (signature == null) {
110                 System.err.println("Could not find provider for algorithm: " + algorithm);
111                 return null;
112             }
113         }
114 
115         // Sign the message.
116         try {
117             signature.update(message);
118             return signature.sign();
119         } catch (Exception e) {
120             System.err.println("Exception while signing message with " + javaKey.getAlgorithm()
121                     + " private key:");
122             e.printStackTrace();
123             return null;
124         }
125     }
126 
rsaDecryptWithPrivateKey(PrivateKey javaKey, int openSSLPadding, byte[] input)127     public static byte[] rsaDecryptWithPrivateKey(PrivateKey javaKey, int openSSLPadding,
128             byte[] input) {
129         String keyAlgorithm = javaKey.getAlgorithm();
130         if (!"RSA".equals(keyAlgorithm)) {
131             System.err.println("Unexpected key type: " + keyAlgorithm);
132             return null;
133         }
134 
135         String jcaPadding;
136         switch (openSSLPadding) {
137             case NativeConstants.RSA_PKCS1_PADDING:
138                 jcaPadding = "PKCS1Padding";
139                 break;
140             case NativeConstants.RSA_NO_PADDING:
141                 jcaPadding = "NoPadding";
142                 break;
143             case NativeConstants.RSA_PKCS1_OAEP_PADDING:
144                 jcaPadding = "OAEPPadding";
145                 break;
146             default:
147                 System.err.println("Unsupported OpenSSL/BoringSSL padding: " + openSSLPadding);
148                 return null;
149         }
150 
151         String transformation = "RSA/ECB/" + jcaPadding;
152         Cipher c = null;
153 
154         // First try to get the most preferred provider as long as it isn't us.
155         try {
156             c = Cipher.getInstance(transformation);
157             c.init(Cipher.DECRYPT_MODE, javaKey);
158 
159             // Ignore it if it points back to us.
160             if (isOurProvider(c.getProvider())) {
161                 c = null;
162             }
163         } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
164             System.err.println("Unsupported cipher algorithm: " + transformation);
165             return null;
166         } catch (InvalidKeyException e) {
167             System.err.println("Preferred provider doesn't support key:");
168             e.printStackTrace();
169             c = null;
170         }
171 
172         // If the preferred provider was us, fall back to trying to find the
173         // first not-us provider that initializes correctly.
174         if (c == null) {
175             ArrayList<Provider> providers = getExternalProviders("Cipher." + transformation);
176             for (Provider p : providers) {
177                 try {
178                     c = Cipher.getInstance(transformation, p);
179                     c.init(Cipher.DECRYPT_MODE, javaKey);
180                     break;
181                 } catch (NoSuchAlgorithmException | InvalidKeyException
182                         | NoSuchPaddingException e) {
183                     c = null;
184                 }
185             }
186             if (c == null) {
187                 System.err.println("Could not find provider for algorithm: " + transformation);
188                 return null;
189             }
190         }
191 
192         try {
193             return c.doFinal(input);
194         } catch (Exception e) {
195             System.err.println("Exception while decrypting message with " + javaKey.getAlgorithm()
196                     + " private key using " + transformation + ":");
197             e.printStackTrace();
198             return null;
199         }
200     }
201 }
202