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
17package org.conscrypt;
18
19import java.security.InvalidKeyException;
20import java.security.NoSuchAlgorithmException;
21import java.security.PrivateKey;
22import java.security.Provider;
23import java.security.Security;
24import java.security.Signature;
25import java.util.ArrayList;
26import javax.crypto.Cipher;
27import 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. Delegated keys are
32 * usually backed by hardware so we don't have access directly to the private
33 * key material. If it were a key where we can get to the private key, we
34 * would not ever call into this class.
35 *
36 * @hide
37 */
38@Internal
39public final class CryptoUpcalls {
40
41    private CryptoUpcalls() {
42    }
43
44    private static boolean isOurProvider(Provider p) {
45        return p.getClass().getPackage().equals(CryptoUpcalls.class.getPackage());
46    }
47
48    /**
49     * Finds providers that are not us that provide the requested algorithms.
50     */
51    private static ArrayList<Provider> getExternalProviders(String algorithm) {
52        ArrayList<Provider> providers = new ArrayList<>(1);
53        for (Provider p : Security.getProviders(algorithm)) {
54            if (!isOurProvider(p)) {
55                providers.add(p);
56            }
57        }
58        if (providers.isEmpty()) {
59            System.err.println("Could not find external provider for algorithm: " + algorithm);
60        }
61        return providers;
62    }
63
64    public static byte[] rawSignDigestWithPrivateKey(PrivateKey javaKey, byte[] message) {
65        // Get the raw signature algorithm for this key type.
66        String algorithm;
67        // Hint: Algorithm names come from:
68        // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
69        String keyAlgorithm = javaKey.getAlgorithm();
70        if ("RSA".equals(keyAlgorithm)) {
71            // IMPORTANT: Due to a platform bug, this will throw
72            // NoSuchAlgorithmException
73            // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher.
74            // See https://android-review.googlesource.com/#/c/40352/
75            algorithm = "NONEwithRSA";
76        } else if ("EC".equals(keyAlgorithm)) {
77            algorithm = "NONEwithECDSA";
78        } else {
79            throw new RuntimeException("Unexpected key type: " + javaKey.toString());
80        }
81
82        Signature signature;
83
84        // Since this is a delegated key, we cannot handle providing a signature using this key.
85        // Otherwise we wouldn't end up in this classs in the first place. The first step is to
86        // try to get the most preferred provider as long as it isn't us.
87        try {
88            signature = Signature.getInstance(algorithm);
89            signature.initSign(javaKey);
90
91            // Ignore it if it points back to us.
92            if (isOurProvider(signature.getProvider())) {
93                signature = null;
94            }
95        } catch (NoSuchAlgorithmException e) {
96            System.err.println("Unsupported signature algorithm: " + algorithm);
97            return null;
98        } catch (InvalidKeyException e) {
99            System.err.println("Preferred provider doesn't support key:");
100            e.printStackTrace();
101            signature = null;
102        }
103
104        // If the preferred provider was us, fall back to trying to find the
105        // first not-us provider that initializes correctly.
106        if (signature == null) {
107            ArrayList<Provider> providers = getExternalProviders("Signature." + algorithm);
108            for (Provider p : providers) {
109                try {
110                    signature = Signature.getInstance(algorithm, p);
111                    signature.initSign(javaKey);
112                    break;
113                } catch (NoSuchAlgorithmException | InvalidKeyException e) {
114                    signature = null;
115                }
116            }
117            if (signature == null) {
118                System.err.println("Could not find provider for algorithm: " + algorithm);
119                return null;
120            }
121        }
122
123        // Sign the message.
124        try {
125            signature.update(message);
126            return signature.sign();
127        } catch (Exception e) {
128            System.err.println("Exception while signing message with " + javaKey.getAlgorithm()
129                    + " private key:");
130            e.printStackTrace();
131            return null;
132        }
133    }
134
135    public static byte[] rsaDecryptWithPrivateKey(PrivateKey javaKey, int openSSLPadding,
136            byte[] input) {
137        String keyAlgorithm = javaKey.getAlgorithm();
138        if (!"RSA".equals(keyAlgorithm)) {
139            System.err.println("Unexpected key type: " + keyAlgorithm);
140            return null;
141        }
142
143        String jcaPadding;
144        switch (openSSLPadding) {
145            case NativeConstants.RSA_PKCS1_PADDING:
146                jcaPadding = "PKCS1Padding";
147                break;
148            case NativeConstants.RSA_NO_PADDING:
149                jcaPadding = "NoPadding";
150                break;
151            case NativeConstants.RSA_PKCS1_OAEP_PADDING:
152                jcaPadding = "OAEPPadding";
153                break;
154            default:
155                System.err.println("Unsupported OpenSSL/BoringSSL padding: " + openSSLPadding);
156                return null;
157        }
158
159        String transformation = "RSA/ECB/" + jcaPadding;
160        Cipher c = null;
161
162        // Since this is a delegated key, we cannot handle providing a cipher using this key.
163        // Otherwise we wouldn't end up in this classs in the first place. The first step is to
164        // try to get the most preferred provider as long as it isn't us.
165        try {
166            c = Cipher.getInstance(transformation);
167            c.init(Cipher.DECRYPT_MODE, javaKey);
168
169            // Ignore it if it points back to us.
170            if (isOurProvider(c.getProvider())) {
171                c = null;
172            }
173        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
174            System.err.println("Unsupported cipher algorithm: " + transformation);
175            return null;
176        } catch (InvalidKeyException e) {
177            System.err.println("Preferred provider doesn't support key:");
178            e.printStackTrace();
179            c = null;
180        }
181
182        // If the preferred provider was us, fall back to trying to find the
183        // first not-us provider that initializes correctly.
184        if (c == null) {
185            ArrayList<Provider> providers = getExternalProviders("Cipher." + transformation);
186            for (Provider p : providers) {
187                try {
188                    c = Cipher.getInstance(transformation, p);
189                    c.init(Cipher.DECRYPT_MODE, javaKey);
190                    break;
191                } catch (NoSuchAlgorithmException | InvalidKeyException
192                        | NoSuchPaddingException e) {
193                    c = null;
194                }
195            }
196            if (c == null) {
197                System.err.println("Could not find provider for algorithm: " + transformation);
198                return null;
199            }
200        }
201
202        try {
203            return c.doFinal(input);
204        } catch (Exception e) {
205            System.err.println("Exception while decrypting message with " + javaKey.getAlgorithm()
206                    + " private key using " + transformation + ":");
207            e.printStackTrace();
208            return null;
209        }
210    }
211}
212