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