1/* 2 * Copyright (C) 2012 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.io.InputStream; 20import java.security.InvalidKeyException; 21import java.security.NoSuchAlgorithmException; 22import java.security.PrivateKey; 23import java.security.PublicKey; 24import java.security.interfaces.ECPrivateKey; 25import java.security.interfaces.RSAPrivateKey; 26import java.security.spec.ECParameterSpec; 27import java.security.spec.InvalidKeySpecException; 28import java.security.spec.PKCS8EncodedKeySpec; 29import java.security.spec.X509EncodedKeySpec; 30 31/** 32 * Represents a BoringSSL {@code EVP_PKEY}. 33 * 34 * @hide 35 */ 36@Internal 37public class OpenSSLKey { 38 private final NativeRef.EVP_PKEY ctx; 39 40 private final boolean wrapped; 41 42 public OpenSSLKey(long ctx) { 43 this(ctx, false); 44 } 45 46 public OpenSSLKey(long ctx, boolean wrapped) { 47 this.ctx = new NativeRef.EVP_PKEY(ctx); 48 this.wrapped = wrapped; 49 } 50 51 /** 52 * Returns the EVP_PKEY context for use in JNI calls. 53 */ 54 public NativeRef.EVP_PKEY getNativeRef() { 55 return ctx; 56 } 57 58 public boolean isWrapped() { 59 return wrapped; 60 } 61 62 public static OpenSSLKey fromPrivateKey(PrivateKey key) throws InvalidKeyException { 63 if (key instanceof OpenSSLKeyHolder) { 64 return ((OpenSSLKeyHolder) key).getOpenSSLKey(); 65 } 66 67 final String keyFormat = key.getFormat(); 68 if (keyFormat == null) { 69 return wrapPrivateKey(key); 70 } else if (!"PKCS#8".equals(key.getFormat())) { 71 throw new InvalidKeyException("Unknown key format " + keyFormat); 72 } 73 74 final byte[] encoded = key.getEncoded(); 75 if (encoded == null) { 76 throw new InvalidKeyException("Key encoding is null"); 77 } 78 79 return new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(key.getEncoded())); 80 } 81 82 /** 83 * Parse a private key in PEM encoding from the provided input stream. 84 * 85 * @throws InvalidKeyException if parsing fails 86 */ 87 public static OpenSSLKey fromPrivateKeyPemInputStream(InputStream is) 88 throws InvalidKeyException { 89 OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 90 try { 91 long keyCtx = NativeCrypto.PEM_read_bio_PrivateKey(bis.getBioContext()); 92 if (keyCtx == 0L) { 93 return null; 94 } 95 96 return new OpenSSLKey(keyCtx); 97 } catch (Exception e) { 98 throw new InvalidKeyException(e); 99 } finally { 100 bis.release(); 101 } 102 } 103 104 /** 105 * Gets an {@code OpenSSLKey} instance backed by the provided private key. The resulting key is 106 * usable only by this provider's TLS/SSL stack. 107 * 108 * @param privateKey private key. 109 * @param publicKey corresponding public key or {@code null} if not available. Some opaque 110 * private keys cannot be used by the TLS/SSL stack without the public key. 111 */ 112 public static OpenSSLKey fromPrivateKeyForTLSStackOnly( 113 PrivateKey privateKey, PublicKey publicKey) throws InvalidKeyException { 114 OpenSSLKey result = getOpenSSLKey(privateKey); 115 if (result != null) { 116 return result; 117 } 118 119 result = fromKeyMaterial(privateKey); 120 if (result != null) { 121 return result; 122 } 123 124 return wrapJCAPrivateKeyForTLSStackOnly(privateKey, publicKey); 125 } 126 127 /** 128 * Gets an {@code OpenSSLKey} instance backed by the provided EC private key. The resulting key 129 * is usable only by this provider's TLS/SSL stack. 130 * 131 * @param key private key. 132 * @param ecParams EC parameters {@code null} if not available. Some opaque private keys cannot 133 * be used by the TLS/SSL stack without the parameters because the private key itself 134 * might not expose the parameters. 135 */ 136 public static OpenSSLKey fromECPrivateKeyForTLSStackOnly( 137 PrivateKey key, ECParameterSpec ecParams) throws InvalidKeyException { 138 OpenSSLKey result = getOpenSSLKey(key); 139 if (result != null) { 140 return result; 141 } 142 143 result = fromKeyMaterial(key); 144 if (result != null) { 145 return result; 146 } 147 148 return OpenSSLECPrivateKey.wrapJCAPrivateKeyForTLSStackOnly(key, ecParams); 149 } 150 151 /** 152 * Gets the {@code OpenSSLKey} instance of the provided key. 153 * 154 * @return instance or {@code null} if the {@code key} is not backed by OpenSSL's 155 * {@code EVP_PKEY}. 156 */ 157 private static OpenSSLKey getOpenSSLKey(PrivateKey key) { 158 if (key instanceof OpenSSLKeyHolder) { 159 return ((OpenSSLKeyHolder) key).getOpenSSLKey(); 160 } 161 162 if ("RSA".equals(key.getAlgorithm())) { 163 return Platform.wrapRsaKey(key); 164 } 165 166 return null; 167 } 168 169 /** 170 * Gets an {@code OpenSSLKey} instance initialized with the key material of the provided key. 171 * 172 * @return instance or {@code null} if the {@code key} does not export its key material in a 173 * suitable format. 174 */ 175 private static OpenSSLKey fromKeyMaterial(PrivateKey key) { 176 if (!"PKCS#8".equals(key.getFormat())) { 177 return null; 178 } 179 byte[] encoded = key.getEncoded(); 180 if (encoded == null) { 181 return null; 182 } 183 return new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded)); 184 } 185 186 /** 187 * Wraps the provided private key for use in the TLS/SSL stack only. Sign/decrypt operations 188 * using the key will be delegated to the {@code Signature}/{@code Cipher} implementation of the 189 * provider which accepts the key. 190 */ 191 private static OpenSSLKey wrapJCAPrivateKeyForTLSStackOnly(PrivateKey privateKey, 192 PublicKey publicKey) throws InvalidKeyException { 193 String keyAlgorithm = privateKey.getAlgorithm(); 194 if ("RSA".equals(keyAlgorithm)) { 195 return OpenSSLRSAPrivateKey.wrapJCAPrivateKeyForTLSStackOnly(privateKey, publicKey); 196 } else if ("EC".equals(keyAlgorithm)) { 197 return OpenSSLECPrivateKey.wrapJCAPrivateKeyForTLSStackOnly(privateKey, publicKey); 198 } else { 199 throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); 200 } 201 } 202 203 private static OpenSSLKey wrapPrivateKey(PrivateKey key) throws InvalidKeyException { 204 if (key instanceof RSAPrivateKey) { 205 return OpenSSLRSAPrivateKey.wrapPlatformKey((RSAPrivateKey) key); 206 } else if (key instanceof ECPrivateKey) { 207 return OpenSSLECPrivateKey.wrapPlatformKey((ECPrivateKey) key); 208 } else { 209 throw new InvalidKeyException("Unknown key type: " + key.toString()); 210 } 211 } 212 213 public static OpenSSLKey fromPublicKey(PublicKey key) throws InvalidKeyException { 214 if (key instanceof OpenSSLKeyHolder) { 215 return ((OpenSSLKeyHolder) key).getOpenSSLKey(); 216 } 217 218 if (!"X.509".equals(key.getFormat())) { 219 throw new InvalidKeyException("Unknown key format " + key.getFormat()); 220 } 221 222 final byte[] encoded = key.getEncoded(); 223 if (encoded == null) { 224 throw new InvalidKeyException("Key encoding is null"); 225 } 226 227 try { 228 return new OpenSSLKey(NativeCrypto.d2i_PUBKEY(key.getEncoded())); 229 } catch (Exception e) { 230 throw new InvalidKeyException(e); 231 } 232 } 233 234 /** 235 * Parse a public key in PEM encoding from the provided input stream. 236 * 237 * @throws InvalidKeyException if parsing fails 238 */ 239 public static OpenSSLKey fromPublicKeyPemInputStream(InputStream is) 240 throws InvalidKeyException { 241 OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 242 try { 243 long keyCtx = NativeCrypto.PEM_read_bio_PUBKEY(bis.getBioContext()); 244 if (keyCtx == 0L) { 245 return null; 246 } 247 248 return new OpenSSLKey(keyCtx); 249 } catch (Exception e) { 250 throw new InvalidKeyException(e); 251 } finally { 252 bis.release(); 253 } 254 } 255 256 public PublicKey getPublicKey() throws NoSuchAlgorithmException { 257 switch (NativeCrypto.EVP_PKEY_type(ctx)) { 258 case NativeConstants.EVP_PKEY_RSA: 259 return new OpenSSLRSAPublicKey(this); 260 case NativeConstants.EVP_PKEY_EC: 261 return new OpenSSLECPublicKey(this); 262 default: 263 throw new NoSuchAlgorithmException("unknown PKEY type"); 264 } 265 } 266 267 static PublicKey getPublicKey(X509EncodedKeySpec keySpec, int type) 268 throws InvalidKeySpecException { 269 X509EncodedKeySpec x509KeySpec = keySpec; 270 271 final OpenSSLKey key; 272 try { 273 key = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(x509KeySpec.getEncoded())); 274 } catch (Exception e) { 275 throw new InvalidKeySpecException(e); 276 } 277 278 if (NativeCrypto.EVP_PKEY_type(key.getNativeRef()) != type) { 279 throw new InvalidKeySpecException("Unexpected key type"); 280 } 281 282 try { 283 return key.getPublicKey(); 284 } catch (NoSuchAlgorithmException e) { 285 throw new InvalidKeySpecException(e); 286 } 287 } 288 289 public PrivateKey getPrivateKey() throws NoSuchAlgorithmException { 290 switch (NativeCrypto.EVP_PKEY_type(ctx)) { 291 case NativeConstants.EVP_PKEY_RSA: 292 return new OpenSSLRSAPrivateKey(this); 293 case NativeConstants.EVP_PKEY_EC: 294 return new OpenSSLECPrivateKey(this); 295 default: 296 throw new NoSuchAlgorithmException("unknown PKEY type"); 297 } 298 } 299 300 static PrivateKey getPrivateKey(PKCS8EncodedKeySpec keySpec, int type) 301 throws InvalidKeySpecException { 302 PKCS8EncodedKeySpec pkcs8KeySpec = keySpec; 303 304 final OpenSSLKey key; 305 try { 306 key = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(pkcs8KeySpec.getEncoded())); 307 } catch (Exception e) { 308 throw new InvalidKeySpecException(e); 309 } 310 311 if (NativeCrypto.EVP_PKEY_type(key.getNativeRef()) != type) { 312 throw new InvalidKeySpecException("Unexpected key type"); 313 } 314 315 try { 316 return key.getPrivateKey(); 317 } catch (NoSuchAlgorithmException e) { 318 throw new InvalidKeySpecException(e); 319 } 320 } 321 322 @Override 323 public boolean equals(Object o) { 324 if (o == this) { 325 return true; 326 } 327 328 if (!(o instanceof OpenSSLKey)) { 329 return false; 330 } 331 332 OpenSSLKey other = (OpenSSLKey) o; 333 if (ctx.equals(other.getNativeRef())) { 334 return true; 335 } 336 337 return NativeCrypto.EVP_PKEY_cmp(ctx, other.getNativeRef()) == 1; 338 } 339 340 @Override 341 public int hashCode() { 342 return ctx.hashCode(); 343 } 344} 345