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