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