|
35 | 35 | import com.microsoft.identity.common.java.crypto.key.AES256KeyLoader; |
36 | 36 | import com.microsoft.identity.common.java.crypto.key.KeyUtil; |
37 | 37 | import com.microsoft.identity.common.java.exception.ClientException; |
| 38 | +import com.microsoft.identity.common.java.flighting.CommonFlight; |
| 39 | +import com.microsoft.identity.common.java.flighting.CommonFlightsManager; |
| 40 | +import com.microsoft.identity.common.java.opentelemetry.AttributeName; |
| 41 | +import com.microsoft.identity.common.java.opentelemetry.OTelUtility; |
| 42 | +import com.microsoft.identity.common.java.opentelemetry.SpanExtension; |
| 43 | +import com.microsoft.identity.common.java.opentelemetry.SpanName; |
38 | 44 | import com.microsoft.identity.common.java.util.CachedData; |
39 | 45 | import com.microsoft.identity.common.java.util.FileUtil; |
40 | 46 | import com.microsoft.identity.common.logging.Logger; |
|
45 | 51 | import java.security.KeyPairGenerator; |
46 | 52 | import java.security.KeyStore; |
47 | 53 | import java.security.spec.AlgorithmParameterSpec; |
| 54 | +import java.security.ProviderException; |
48 | 55 | import java.util.Calendar; |
49 | 56 | import java.util.Date; |
50 | 57 | import java.util.Locale; |
|
55 | 62 |
|
56 | 63 | import edu.umd.cs.findbugs.annotations.Nullable; |
57 | 64 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
| 65 | +import io.opentelemetry.api.trace.Span; |
| 66 | +import io.opentelemetry.api.trace.StatusCode; |
| 67 | +import io.opentelemetry.context.Scope; |
58 | 68 | import lombok.NonNull; |
59 | 69 |
|
60 | 70 | /** |
@@ -249,17 +259,83 @@ private void saveSecretKeyToStorage(@NonNull final SecretKey unencryptedKey) thr |
249 | 259 | * stomping/overwriting one another's keypair. |
250 | 260 | */ |
251 | 261 | KeyPair keyPair = AndroidKeyStoreUtil.readKey(mAlias); |
252 | | - if(keyPair == null){ |
| 262 | + if (keyPair == null) { |
253 | 263 | Logger.info(methodTag, "No existing keypair. Generating a new one."); |
254 | | - keyPair = AndroidKeyStoreUtil.generateKeyPair( |
255 | | - WRAP_KEY_ALGORITHM, |
256 | | - getSpecForKeyStoreKey(mContext, mAlias)); |
| 264 | + final Span span = OTelUtility.createSpan(SpanName.KeyPairGeneration.name()); |
| 265 | + final long keypairGenStartTime = System.currentTimeMillis(); |
| 266 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_NEW_KEY_GEN_SPEC_FOR_WRAP)) { |
| 267 | + try (final Scope scope = SpanExtension.makeCurrentSpan(span)) { |
| 268 | + keyPair = attemptKeyPairGeneration(mAlias, true, keypairGenStartTime); |
| 269 | + Logger.info(methodTag, "Successfully generated keypair with new KeyPairGeneratorSpec with wrap purpose."); |
| 270 | + span.setAttribute(AttributeName.key_pair_gen_successful_method.name(), "new_key_gen_spec_with_wrap"); |
| 271 | + span.setStatus(StatusCode.OK); |
| 272 | + } catch (final ProviderException e) { |
| 273 | + if ("SecureKeyImportUnavailableException".equals(e.getClass().getSimpleName())) { |
| 274 | + Logger.warn(methodTag, "Wrap purpose may not be supported. Retrying without wrap."); |
| 275 | + try { |
| 276 | + keyPair = attemptKeyPairGeneration(mAlias, false, keypairGenStartTime); |
| 277 | + Logger.info(methodTag, "Successfully generated keypair with new KeyPairGeneratorSpec without wrap purpose."); |
| 278 | + span.setAttribute(AttributeName.key_pair_gen_successful_method.name(), "new_key_gen_spec_without_wrap"); |
| 279 | + span.setStatus(StatusCode.OK); |
| 280 | + } catch (final Exception ex) { |
| 281 | + // 2nd fallback to legacy keygen spec |
| 282 | + Logger.error(methodTag, "Second attempt without wrap also failed. Falling back to legacy spec.", ex); |
| 283 | + keyPair = generateKeyPairWithLegacySpec(mAlias, keypairGenStartTime); |
| 284 | + if (e.getMessage() != null) { |
| 285 | + span.setAttribute(AttributeName.keypair_gen_exception.name(), e.getMessage()); |
| 286 | + } |
| 287 | + span.setAttribute(AttributeName.key_pair_gen_successful_method.name(), "legacy_key_gen_spec"); |
| 288 | + span.setStatus(StatusCode.OK); |
| 289 | + } |
| 290 | + } else { |
| 291 | + Logger.info(methodTag, "Some unknown exception occurred. Running legacy keygen spec logic."); |
| 292 | + keyPair = generateKeyPairWithLegacySpec(mAlias, keypairGenStartTime); |
| 293 | + span.setAttribute(AttributeName.key_pair_gen_successful_method.name(), "legacy_key_gen_spec"); |
| 294 | + span.setStatus(StatusCode.OK); |
| 295 | + } |
| 296 | + } catch (final Exception e) { |
| 297 | + Logger.warn(methodTag, "Unexpected error with new KeyPairGeneratorSpec. Falling back to legacy spec. "+ e); |
| 298 | + keyPair = generateKeyPairWithLegacySpec(mAlias, keypairGenStartTime); |
| 299 | + if (e.getMessage() != null) { |
| 300 | + span.setAttribute(AttributeName.keypair_gen_exception.name(), e.getMessage()); |
| 301 | + } |
| 302 | + span.setAttribute(AttributeName.key_pair_gen_successful_method.name(), "legacy_key_gen_spec"); |
| 303 | + span.setStatus(StatusCode.OK); |
| 304 | + } finally { |
| 305 | + span.end(); |
| 306 | + } |
| 307 | + } |
| 308 | + else { |
| 309 | + // If flight for using new keygen spec is not enabled, use the legacy spec. |
| 310 | + Logger.info(methodTag, "Using legacy spec for keypair generation directly."); |
| 311 | + keyPair = generateKeyPairWithLegacySpec(mAlias, keypairGenStartTime); |
| 312 | + } |
257 | 313 | } |
258 | 314 |
|
259 | 315 | final byte[] keyWrapped = AndroidKeyStoreUtil.wrap(unencryptedKey, keyPair, WRAP_ALGORITHM); |
260 | 316 | FileUtil.writeDataToFile(keyWrapped, getKeyFile()); |
261 | 317 | } |
262 | 318 |
|
| 319 | + @RequiresApi(api = Build.VERSION_CODES.P) |
| 320 | + private KeyPair attemptKeyPairGeneration(@NonNull final String alias, boolean useWrapPurpose, long keypairGenStartTime) throws ClientException{ |
| 321 | + KeyPair keyPair = AndroidKeyStoreUtil.generateKeyPair( |
| 322 | + WRAP_KEY_ALGORITHM, getSpecForKeyStoreKey(alias, useWrapPurpose)); |
| 323 | + recordKeyGenerationTime(keypairGenStartTime); |
| 324 | + return keyPair; |
| 325 | + } |
| 326 | + |
| 327 | + private KeyPair generateKeyPairWithLegacySpec(@NonNull final String alias, long keypairGenStartTime) throws ClientException{ |
| 328 | + KeyPair keyPair = AndroidKeyStoreUtil.generateKeyPair( |
| 329 | + WRAP_KEY_ALGORITHM, getLegacySpecForKeyStoreKey(mContext, alias)); |
| 330 | + recordKeyGenerationTime(keypairGenStartTime); |
| 331 | + return keyPair; |
| 332 | + } |
| 333 | + |
| 334 | + private void recordKeyGenerationTime(long keypairGenStartTime) { |
| 335 | + long elapsedTime = System.currentTimeMillis() - keypairGenStartTime; |
| 336 | + SpanExtension.current().setAttribute(AttributeName.elapsed_time_keypair_generation.name(), elapsedTime); |
| 337 | + } |
| 338 | + |
263 | 339 | /** |
264 | 340 | * Wipe all the data associated from this key. |
265 | 341 | */ |
@@ -304,28 +380,21 @@ private static AlgorithmParameterSpec getLegacySpecForKeyStoreKey(@NonNull final |
304 | 380 | * Generate a self-signed cert and derive an AlgorithmParameterSpec from that. |
305 | 381 | * This is for the key to be generated in {@link KeyStore} via {@link KeyPairGenerator} |
306 | 382 | * |
307 | | - * @param context an Android {@link Context} object. |
| 383 | + * @param alias the alias for the key. |
| 384 | + * @param tryPurposeWrap whether to try to use the wrap purpose in the key generation spec. |
308 | 385 | * @return a {@link AlgorithmParameterSpec} for the keystore key (that we'll use to wrap the secret key). |
309 | 386 | */ |
310 | | - private static AlgorithmParameterSpec getSpecForKeyStoreKey(@NonNull final Context context, @NonNull final String alias) { |
311 | | - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { |
312 | | - return getLegacySpecForKeyStoreKey(context, alias); |
313 | | - } else { |
314 | | - final String certInfo = String.format(Locale.ROOT, "CN=%s, OU=%s", |
315 | | - alias, |
316 | | - context.getPackageName()); |
317 | | - final int certValidYears = 100; |
318 | | - int purposes = KeyProperties.PURPOSE_WRAP_KEY | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT; |
319 | | - return new KeyGenParameterSpec.Builder(alias, purposes) |
320 | | - .setCertificateSubject(new X500Principal(certInfo)) |
321 | | - .setCertificateSerialNumber(BigInteger.ONE) |
322 | | - .setCertificateNotBefore(new Date()) |
323 | | - .setCertificateNotAfter(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365 * certValidYears))) |
324 | | - .setKeySize(2048) |
325 | | - .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) |
326 | | - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) |
327 | | - .build(); |
| 387 | + @RequiresApi(api = Build.VERSION_CODES.P) |
| 388 | + private static AlgorithmParameterSpec getSpecForKeyStoreKey(@NonNull final String alias, boolean tryPurposeWrap) { |
| 389 | + int purposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT; |
| 390 | + if (tryPurposeWrap) { |
| 391 | + purposes |= KeyProperties.PURPOSE_WRAP_KEY; |
328 | 392 | } |
| 393 | + return new KeyGenParameterSpec.Builder(alias, purposes) |
| 394 | + .setKeySize(2048) |
| 395 | + .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) |
| 396 | + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) |
| 397 | + .build(); |
329 | 398 | } |
330 | 399 |
|
331 | 400 | /** |
|
0 commit comments