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