001package com.box.sdk; 002 003import com.eclipsesource.json.Json; 004import com.eclipsesource.json.JsonObject; 005import java.io.IOException; 006import java.io.StringReader; 007import java.net.MalformedURLException; 008import java.net.URL; 009import java.security.PrivateKey; 010import java.security.Security; 011import java.text.ParseException; 012import java.text.SimpleDateFormat; 013import java.util.Date; 014import java.util.List; 015import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; 016import org.bouncycastle.jce.provider.BouncyCastleProvider; 017import org.bouncycastle.openssl.PEMDecryptorProvider; 018import org.bouncycastle.openssl.PEMEncryptedKeyPair; 019import org.bouncycastle.openssl.PEMKeyPair; 020import org.bouncycastle.openssl.PEMParser; 021import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; 022import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; 023import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; 024import org.bouncycastle.operator.InputDecryptorProvider; 025import org.bouncycastle.operator.OperatorCreationException; 026import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; 027import org.bouncycastle.pkcs.PKCSException; 028import org.jose4j.jws.AlgorithmIdentifiers; 029import org.jose4j.jws.JsonWebSignature; 030import org.jose4j.jwt.JwtClaims; 031import org.jose4j.jwt.NumericDate; 032import org.jose4j.lang.JoseException; 033 034/** 035 * Represents an authenticated Box Developer Edition connection to the Box API. 036 * 037 * <p>This class handles everything for Box Developer Edition that isn't already handled by BoxAPIConnection.</p> 038 */ 039public class BoxDeveloperEditionAPIConnection extends BoxAPIConnection { 040 041 private static final String JWT_AUDIENCE = "https://api.box.com/oauth2/token"; 042 private static final String JWT_GRANT_TYPE = 043 "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&client_id=%s&client_secret=%s&assertion=%s"; 044 045 static { 046 Security.addProvider(new BouncyCastleProvider()); 047 } 048 049 private final String entityID; 050 private final DeveloperEditionEntityType entityType; 051 private final EncryptionAlgorithm encryptionAlgorithm; 052 private final String publicKeyID; 053 private final String privateKey; 054 private final String privateKeyPassword; 055 private BackoffCounter backoffCounter; 056 private final IAccessTokenCache accessTokenCache; 057 058 /** 059 * Disabling an invalid constructor for Box Developer Edition. 060 * 061 * @param accessToken an initial access token to use for authenticating with the API. 062 */ 063 private BoxDeveloperEditionAPIConnection(String accessToken) { 064 super(accessToken); 065 throw new BoxAPIException("This constructor is not available for BoxDeveloperEditionAPIConnection."); 066 } 067 068 /** 069 * Disabling an invalid constructor for Box Developer Edition. 070 * 071 * @param clientID the client ID to use when refreshing the access token. 072 * @param clientSecret the client secret to use when refreshing the access token. 073 * @param accessToken an initial access token to use for authenticating with the API. 074 * @param refreshToken an initial refresh token to use when refreshing the access token. 075 */ 076 private BoxDeveloperEditionAPIConnection(String clientID, String clientSecret, String accessToken, 077 String refreshToken) { 078 super(accessToken); 079 throw new BoxAPIException("This constructor is not available for BoxDeveloperEditionAPIConnection."); 080 } 081 082 /** 083 * Disabling an invalid constructor for Box Developer Edition. 084 * 085 * @param clientID the client ID to use when exchanging the auth code for an access token. 086 * @param clientSecret the client secret to use when exchanging the auth code for an access token. 087 * @param authCode an auth code obtained from the first half of the OAuth process. 088 */ 089 private BoxDeveloperEditionAPIConnection(String clientID, String clientSecret, String authCode) { 090 super(clientID, clientSecret, authCode); 091 throw new BoxAPIException("This constructor is not available for BoxDeveloperEditionAPIConnection."); 092 } 093 094 /** 095 * Disabling an invalid constructor for Box Developer Edition. 096 * 097 * @param clientID the client ID to use when requesting an access token. 098 * @param clientSecret the client secret to use when requesting an access token. 099 */ 100 private BoxDeveloperEditionAPIConnection(String clientID, String clientSecret) { 101 super(clientID, clientSecret); 102 throw new BoxAPIException("This constructor is not available for BoxDeveloperEditionAPIConnection."); 103 } 104 105 /** 106 * Constructs a new BoxDeveloperEditionAPIConnection. 107 * 108 * @param entityId enterprise ID or a user ID. 109 * @param entityType the type of entityId. 110 * @param clientID the client ID to use when exchanging the JWT assertion for an access token. 111 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 112 * @param encryptionPref the encryption preferences for signing the JWT. 113 * @deprecated Use the version of this constructor that accepts an IAccessTokenCache to prevent unneeded 114 * requests to Box for access tokens. 115 */ 116 @Deprecated 117 public BoxDeveloperEditionAPIConnection( 118 String entityId, 119 DeveloperEditionEntityType entityType, 120 String clientID, 121 String clientSecret, 122 JWTEncryptionPreferences encryptionPref 123 ) { 124 125 this(entityId, entityType, clientID, clientSecret, encryptionPref, null); 126 } 127 128 129 /** 130 * Constructs a new BoxDeveloperEditionAPIConnection leveraging an access token cache. 131 * 132 * @param entityId enterprise ID or a user ID. 133 * @param entityType the type of entityId. 134 * @param clientID the client ID to use when exchanging the JWT assertion for an access token. 135 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 136 * @param encryptionPref the encryption preferences for signing the JWT. 137 * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens) 138 */ 139 public BoxDeveloperEditionAPIConnection(String entityId, DeveloperEditionEntityType entityType, 140 String clientID, String clientSecret, 141 JWTEncryptionPreferences encryptionPref, 142 IAccessTokenCache accessTokenCache) { 143 144 super(clientID, clientSecret); 145 146 this.entityID = entityId; 147 this.entityType = entityType; 148 this.publicKeyID = encryptionPref.getPublicKeyID(); 149 this.privateKey = encryptionPref.getPrivateKey(); 150 this.privateKeyPassword = encryptionPref.getPrivateKeyPassword(); 151 this.encryptionAlgorithm = encryptionPref.getEncryptionAlgorithm(); 152 this.accessTokenCache = accessTokenCache; 153 this.backoffCounter = new BackoffCounter(new Time()); 154 } 155 156 /** 157 * Constructs a new BoxDeveloperEditionAPIConnection. 158 * 159 * @param entityId enterprise ID or a user ID. 160 * @param entityType the type of entityId. 161 * @param boxConfig box configuration settings object 162 * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens) 163 */ 164 public BoxDeveloperEditionAPIConnection(String entityId, DeveloperEditionEntityType entityType, 165 BoxConfig boxConfig, IAccessTokenCache accessTokenCache) { 166 167 this(entityId, entityType, boxConfig.getClientId(), boxConfig.getClientSecret(), 168 boxConfig.getJWTEncryptionPreferences(), accessTokenCache); 169 } 170 171 /** 172 * Creates a new Box Developer Edition connection with enterprise token. 173 * 174 * @param enterpriseId the enterprise ID to use for requesting access token. 175 * @param clientId the client ID to use when exchanging the JWT assertion for an access token. 176 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 177 * @param encryptionPref the encryption preferences for signing the JWT. 178 * @return a new instance of BoxAPIConnection. 179 * @deprecated Use the version of this method that accepts an IAccessTokenCache to prevent unneeded 180 * requests to Box for access tokens. 181 */ 182 @Deprecated 183 public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection( 184 String enterpriseId, 185 String clientId, 186 String clientSecret, 187 JWTEncryptionPreferences encryptionPref 188 ) { 189 190 BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection(enterpriseId, 191 DeveloperEditionEntityType.ENTERPRISE, clientId, clientSecret, encryptionPref); 192 193 connection.authenticate(); 194 195 return connection; 196 } 197 198 /** 199 * Creates a new Box Developer Edition connection with enterprise token leveraging an access token cache. 200 * 201 * @param enterpriseId the enterprise ID to use for requesting access token. 202 * @param clientId the client ID to use when exchanging the JWT assertion for an access token. 203 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 204 * @param encryptionPref the encryption preferences for signing the JWT. 205 * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens) 206 * @return a new instance of BoxAPIConnection. 207 */ 208 public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection( 209 String enterpriseId, 210 String clientId, 211 String clientSecret, 212 JWTEncryptionPreferences encryptionPref, 213 IAccessTokenCache accessTokenCache 214 ) { 215 216 BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection(enterpriseId, 217 DeveloperEditionEntityType.ENTERPRISE, clientId, clientSecret, encryptionPref, accessTokenCache); 218 219 connection.tryRestoreUsingAccessTokenCache(); 220 221 return connection; 222 } 223 224 /** 225 * Creates a new Box Developer Edition connection with enterprise token leveraging BoxConfig. 226 * 227 * @param boxConfig box configuration settings object 228 * @return a new instance of BoxAPIConnection. 229 */ 230 public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection(BoxConfig boxConfig) { 231 232 return getAppEnterpriseConnection( 233 boxConfig.getEnterpriseId(), 234 boxConfig.getClientId(), 235 boxConfig.getClientSecret(), 236 boxConfig.getJWTEncryptionPreferences() 237 ); 238 } 239 240 /** 241 * Creates a new Box Developer Edition connection with enterprise token leveraging BoxConfig and access token cache. 242 * 243 * @param boxConfig box configuration settings object 244 * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens) 245 * @return a new instance of BoxAPIConnection. 246 */ 247 public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection(BoxConfig boxConfig, 248 IAccessTokenCache accessTokenCache) { 249 250 return getAppEnterpriseConnection( 251 boxConfig.getEnterpriseId(), 252 boxConfig.getClientId(), 253 boxConfig.getClientSecret(), 254 boxConfig.getJWTEncryptionPreferences(), 255 accessTokenCache 256 ); 257 } 258 259 /** 260 * Creates a new Box Developer Edition connection with App User or Managed User token. 261 * 262 * @param userId the user ID to use for an App User. 263 * @param clientId the client ID to use when exchanging the JWT assertion for an access token. 264 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 265 * @param encryptionPref the encryption preferences for signing the JWT. 266 * @return a new instance of BoxAPIConnection. 267 * @deprecated use {@link BoxDeveloperEditionAPIConnection#getUserConnection(String, String, String, JWTEncryptionPreferences, IAccessTokenCache)} 268 * requests to Box for access tokens. 269 */ 270 @Deprecated 271 public static BoxDeveloperEditionAPIConnection getAppUserConnection( 272 String userId, 273 String clientId, 274 String clientSecret, 275 JWTEncryptionPreferences encryptionPref 276 ) { 277 278 BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection( 279 userId, 280 DeveloperEditionEntityType.USER, 281 clientId, 282 clientSecret, 283 encryptionPref 284 ); 285 286 connection.authenticate(); 287 288 return connection; 289 } 290 291 /** 292 * Creates a new Box Developer Edition connection with App User or Managed User token. 293 * 294 * @param userId the user ID to use for an App User. 295 * @param clientId the client ID to use when exchanging the JWT assertion for an access token. 296 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 297 * @param encryptionPref the encryption preferences for signing the JWT. 298 * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens) 299 * @return a new instance of BoxAPIConnection. 300 * @deprecated use {@link BoxDeveloperEditionAPIConnection#getUserConnection(String, String, String, JWTEncryptionPreferences, IAccessTokenCache)} 301 */ 302 @Deprecated 303 public static BoxDeveloperEditionAPIConnection getAppUserConnection( 304 String userId, 305 String clientId, 306 String clientSecret, 307 JWTEncryptionPreferences encryptionPref, 308 IAccessTokenCache accessTokenCache 309 ) { 310 return getUserConnection(userId, clientId, clientSecret, encryptionPref, accessTokenCache); 311 } 312 313 /** 314 * Creates a new Box Developer Edition connection with App User or Managed User token. 315 * 316 * @param userId the user ID to use for an App User. 317 * @param clientId the client ID to use when exchanging the JWT assertion for an access token. 318 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 319 * @param encryptionPref the encryption preferences for signing the JWT. 320 * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens) 321 * @return a new instance of BoxAPIConnection. 322 */ 323 public static BoxDeveloperEditionAPIConnection getUserConnection( 324 String userId, 325 String clientId, 326 String clientSecret, 327 JWTEncryptionPreferences encryptionPref, 328 IAccessTokenCache accessTokenCache 329 ) { 330 BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection(userId, 331 DeveloperEditionEntityType.USER, clientId, clientSecret, encryptionPref, accessTokenCache); 332 333 connection.tryRestoreUsingAccessTokenCache(); 334 335 return connection; 336 } 337 338 /** 339 * Creates a new Box Developer Edition connection with App User or Managed User token levaraging BoxConfig. 340 * 341 * @param userId the user ID to use for an App User. 342 * @param boxConfig box configuration settings object 343 * @return a new instance of BoxAPIConnection. 344 * @deprecated use {@link BoxDeveloperEditionAPIConnection#getUserConnection(String, BoxConfig, IAccessTokenCache)} 345 */ 346 @Deprecated 347 public static BoxDeveloperEditionAPIConnection getAppUserConnection(String userId, BoxConfig boxConfig) { 348 return getAppUserConnection(userId, boxConfig.getClientId(), boxConfig.getClientSecret(), 349 boxConfig.getJWTEncryptionPreferences()); 350 } 351 352 /** 353 * Creates a new Box Developer Edition connection with App User or Managed User 354 * token leveraging BoxConfig and access token cache. 355 * 356 * @param userId the user ID to use for an App User. 357 * @param boxConfig box configuration settings object 358 * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens) 359 * @return a new instance of BoxAPIConnection. 360 * @deprecated use {@link BoxDeveloperEditionAPIConnection#getUserConnection(String, BoxConfig, IAccessTokenCache)} 361 */ 362 @Deprecated 363 public static BoxDeveloperEditionAPIConnection getAppUserConnection(String userId, BoxConfig boxConfig, 364 IAccessTokenCache accessTokenCache 365 ) { 366 return getUserConnection(userId, boxConfig.getClientId(), boxConfig.getClientSecret(), 367 boxConfig.getJWTEncryptionPreferences(), accessTokenCache); 368 } 369 370 /** 371 * Creates a new Box Developer Edition connection with App User or Managed User token levaraging BoxConfig. 372 * 373 * @param userId the user ID to use for an App User. 374 * @param boxConfig box configuration settings object 375 * @return a new instance of BoxAPIConnection. 376 * @deprecated use {@link BoxDeveloperEditionAPIConnection#getUserConnection(String, BoxConfig, IAccessTokenCache)} 377 */ 378 @Deprecated 379 public static BoxDeveloperEditionAPIConnection getUserConnection(String userId, BoxConfig boxConfig) { 380 return getAppUserConnection(userId, boxConfig.getClientId(), boxConfig.getClientSecret(), 381 boxConfig.getJWTEncryptionPreferences()); 382 } 383 384 /** 385 * Creates a new Box Developer Edition connection with App User or Managed User token leveraging BoxConfig 386 * and access token cache. 387 * 388 * @param userId the user ID to use for an App User. 389 * @param boxConfig box configuration settings object 390 * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens) 391 * @return a new instance of BoxAPIConnection. 392 */ 393 public static BoxDeveloperEditionAPIConnection getUserConnection(String userId, BoxConfig boxConfig, 394 IAccessTokenCache accessTokenCache 395 ) { 396 return getAppUserConnection(userId, boxConfig.getClientId(), boxConfig.getClientSecret(), 397 boxConfig.getJWTEncryptionPreferences(), accessTokenCache); 398 } 399 400 /** 401 * Disabling the non-Box Developer Edition authenticate method. 402 * 403 * @param authCode an auth code obtained from the first half of the OAuth process. 404 */ 405 public void authenticate(String authCode) { 406 throw new BoxAPIException("BoxDeveloperEditionAPIConnection does not allow authenticating with an auth code."); 407 } 408 409 /** 410 * Authenticates the API connection for Box Developer Edition. 411 */ 412 public void authenticate() { 413 URL url; 414 try { 415 url = new URL(this.getTokenURL()); 416 } catch (MalformedURLException e) { 417 assert false : "An invalid token URL indicates a bug in the SDK."; 418 throw new RuntimeException("An invalid token URL indicates a bug in the SDK.", e); 419 } 420 421 this.backoffCounter.reset(this.getMaxRetryAttempts() + 1); 422 NumericDate jwtTime = null; 423 String jwtAssertion; 424 String urlParameters; 425 BoxAPIRequest request; 426 String json = null; 427 final BoxLogger logger = BoxLogger.defaultLogger(); 428 429 while (this.backoffCounter.getAttemptsRemaining() > 0) { 430 // Reconstruct the JWT assertion, which regenerates the jti claim, with the new "current" time 431 jwtAssertion = this.constructJWTAssertion(jwtTime); 432 urlParameters = String.format(JWT_GRANT_TYPE, this.getClientID(), this.getClientSecret(), jwtAssertion); 433 434 request = new BoxAPIRequest(this, url, "POST"); 435 request.shouldAuthenticate(false); 436 request.setBody(urlParameters); 437 438 try { 439 BoxJSONResponse response = (BoxJSONResponse) request.sendWithoutRetry(); 440 json = response.getJSON(); 441 break; 442 } catch (BoxAPIException apiException) { 443 long responseReceivedTime = System.currentTimeMillis(); 444 445 if (!this.backoffCounter.decrement() 446 || (!BoxAPIRequest.isRequestRetryable(apiException) 447 && !BoxAPIRequest.isResponseRetryable(apiException.getResponseCode(), apiException))) { 448 throw apiException; 449 } 450 451 logger.warn(String.format( 452 "Retrying authentication request due to transient error status=%d body=%s", 453 apiException.getResponseCode(), 454 apiException.getResponse() 455 )); 456 457 try { 458 List<String> retryAfterHeader = apiException.getHeaders().get("Retry-After"); 459 if (retryAfterHeader == null) { 460 this.backoffCounter.waitBackoff(); 461 } else { 462 int retryAfterDelay = Integer.parseInt(retryAfterHeader.get(0)) * 1000; 463 this.backoffCounter.waitBackoff(retryAfterDelay); 464 } 465 } catch (InterruptedException interruptedException) { 466 Thread.currentThread().interrupt(); 467 throw apiException; 468 } 469 470 long endWaitTime = System.currentTimeMillis(); 471 long secondsSinceResponseReceived = (endWaitTime - responseReceivedTime) / 1000; 472 473 try { 474 // Use the Date advertised by the Box server in the exception 475 // as the current time to synchronize clocks 476 jwtTime = this.getDateForJWTConstruction(apiException, secondsSinceResponseReceived); 477 } catch (Exception e) { 478 throw apiException; 479 } 480 481 } 482 } 483 484 if (json == null) { 485 throw new RuntimeException("Unable to read authentication response in SDK."); 486 } 487 488 JsonObject jsonObject = Json.parse(json).asObject(); 489 this.setAccessToken(jsonObject.get("access_token").asString()); 490 this.setLastRefresh(System.currentTimeMillis()); 491 this.setExpires(jsonObject.get("expires_in").asLong() * 1000); 492 493 //if token cache is specified, save to cache 494 if (this.accessTokenCache != null) { 495 String key = this.getAccessTokenCacheKey(); 496 JsonObject accessTokenCacheInfo = new JsonObject() 497 .add("accessToken", this.getAccessToken()) 498 .add("lastRefresh", this.getLastRefresh()) 499 .add("expires", this.getExpires()); 500 501 this.accessTokenCache.put(key, accessTokenCacheInfo.toString()); 502 } 503 } 504 505 private NumericDate getDateForJWTConstruction(BoxAPIException apiException, long secondsSinceResponseDateReceived) { 506 NumericDate currentTime; 507 List<String> responseDates = apiException.getHeaders().get("Date"); 508 509 if (responseDates != null) { 510 String responseDate = responseDates.get(0); 511 SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz"); 512 try { 513 Date date = dateFormat.parse(responseDate); 514 currentTime = NumericDate.fromMilliseconds(date.getTime()); 515 currentTime.addSeconds(secondsSinceResponseDateReceived); 516 } catch (ParseException e) { 517 currentTime = NumericDate.now(); 518 } 519 } else { 520 currentTime = NumericDate.now(); 521 } 522 return currentTime; 523 } 524 525 void setBackoffCounter(BackoffCounter counter) { 526 this.backoffCounter = counter; 527 } 528 529 /** 530 * BoxDeveloperEditionAPIConnection can always refresh, but this method is required elsewhere. 531 * 532 * @return true always. 533 */ 534 public boolean canRefresh() { 535 return true; 536 } 537 538 /** 539 * Refresh's this connection's access token using Box Developer Edition. 540 * 541 * @throws IllegalStateException if this connection's access token cannot be refreshed. 542 */ 543 public void refresh() { 544 this.getRefreshLock().writeLock().lock(); 545 546 try { 547 this.authenticate(); 548 } catch (BoxAPIException e) { 549 this.notifyError(e); 550 this.getRefreshLock().writeLock().unlock(); 551 throw e; 552 } 553 554 this.notifyRefresh(); 555 this.getRefreshLock().writeLock().unlock(); 556 } 557 558 private String getAccessTokenCacheKey() { 559 return String.format("/%s/%s/%s/%s", this.getUserAgent(), this.getClientID(), 560 this.entityType.toString(), this.entityID); 561 } 562 563 private void tryRestoreUsingAccessTokenCache() { 564 if (this.accessTokenCache == null) { 565 //no cache specified so force authentication 566 this.authenticate(); 567 } else { 568 String cachedTokenInfo = this.accessTokenCache.get(this.getAccessTokenCacheKey()); 569 if (cachedTokenInfo == null) { 570 //not found; probably first time for this client config so authenticate; info will then be cached 571 this.authenticate(); 572 } else { 573 //pull access token cache info; authentication will occur as needed (if token is expired) 574 JsonObject json = Json.parse(cachedTokenInfo).asObject(); 575 this.setAccessToken(json.get("accessToken").asString()); 576 this.setLastRefresh(json.get("lastRefresh").asLong()); 577 this.setExpires(json.get("expires").asLong()); 578 } 579 } 580 } 581 582 private String constructJWTAssertion() { 583 return this.constructJWTAssertion(null); 584 } 585 586 private String constructJWTAssertion(NumericDate now) { 587 JwtClaims claims = new JwtClaims(); 588 claims.setIssuer(this.getClientID()); 589 claims.setAudience(JWT_AUDIENCE); 590 if (now == null) { 591 claims.setExpirationTimeMinutesInTheFuture(0.5f); 592 } else { 593 now.addSeconds(30L); 594 claims.setExpirationTime(now); 595 } 596 claims.setSubject(this.entityID); 597 claims.setClaim("box_sub_type", this.entityType.toString()); 598 claims.setGeneratedJwtId(64); 599 600 JsonWebSignature jws = new JsonWebSignature(); 601 jws.setPayload(claims.toJson()); 602 jws.setKey(this.decryptPrivateKey()); 603 jws.setAlgorithmHeaderValue(this.getAlgorithmIdentifier()); 604 jws.setHeader("typ", "JWT"); 605 if ((this.publicKeyID != null) && !this.publicKeyID.isEmpty()) { 606 jws.setHeader("kid", this.publicKeyID); 607 } 608 609 String assertion; 610 611 try { 612 assertion = jws.getCompactSerialization(); 613 } catch (JoseException e) { 614 throw new BoxAPIException("Error serializing JSON Web Token assertion.", e); 615 } 616 617 return assertion; 618 } 619 620 private String getAlgorithmIdentifier() { 621 String algorithmId = AlgorithmIdentifiers.RSA_USING_SHA256; 622 switch (this.encryptionAlgorithm) { 623 case RSA_SHA_384: 624 algorithmId = AlgorithmIdentifiers.RSA_USING_SHA384; 625 break; 626 case RSA_SHA_512: 627 algorithmId = AlgorithmIdentifiers.RSA_USING_SHA512; 628 break; 629 case RSA_SHA_256: 630 default: 631 break; 632 } 633 634 return algorithmId; 635 } 636 637 private PrivateKey decryptPrivateKey() { 638 PrivateKey decryptedPrivateKey; 639 try { 640 PEMParser keyReader = new PEMParser(new StringReader(this.privateKey)); 641 Object keyPair = keyReader.readObject(); 642 keyReader.close(); 643 644 if (keyPair instanceof PrivateKeyInfo) { 645 PrivateKeyInfo keyInfo = (PrivateKeyInfo) keyPair; 646 decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo); 647 } else if (keyPair instanceof PEMEncryptedKeyPair) { 648 JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder(); 649 PEMDecryptorProvider decryptionProvider = builder.build(this.privateKeyPassword.toCharArray()); 650 keyPair = ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(decryptionProvider); 651 PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo(); 652 decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo); 653 } else if (keyPair instanceof PKCS8EncryptedPrivateKeyInfo) { 654 InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider("BC") 655 .build(this.privateKeyPassword.toCharArray()); 656 PrivateKeyInfo keyInfo = ((PKCS8EncryptedPrivateKeyInfo) keyPair).decryptPrivateKeyInfo(pkcs8Prov); 657 decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo); 658 } else { 659 PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo(); 660 decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo); 661 } 662 } catch (IOException e) { 663 throw new BoxAPIException("Error parsing private key for Box Developer Edition.", e); 664 } catch (OperatorCreationException e) { 665 throw new BoxAPIException("Error parsing PKCS#8 private key for Box Developer Edition.", e); 666 } catch (PKCSException e) { 667 throw new BoxAPIException("Error parsing PKCS private key for Box Developer Edition.", e); 668 } 669 return decryptedPrivateKey; 670 } 671 672}