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}