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)
447                    && !BoxAPIRequest.isResponseRetryable(apiException.getResponseCode(), apiException))) {
448                    throw apiException;
449                }
450
451                logger.warn(String.format(
452                    "Retrying authentication request due to transient error status=%d body=%s",
453                    apiException.getResponseCode(),
454                    apiException.getResponse()
455                ));
456
457                try {
458                    List<String> retryAfterHeader = apiException.getHeaders().get("Retry-After");
459                    if (retryAfterHeader == null) {
460                        this.backoffCounter.waitBackoff();
461                    } else {
462                        int retryAfterDelay = Integer.parseInt(retryAfterHeader.get(0)) * 1000;
463                        this.backoffCounter.waitBackoff(retryAfterDelay);
464                    }
465                } catch (InterruptedException interruptedException) {
466                    Thread.currentThread().interrupt();
467                    throw apiException;
468                }
469
470                long endWaitTime = System.currentTimeMillis();
471                long secondsSinceResponseReceived = (endWaitTime - responseReceivedTime) / 1000;
472
473                try {
474                    // Use the Date advertised by the Box server in the exception
475                    // as the current time to synchronize clocks
476                    jwtTime = this.getDateForJWTConstruction(apiException, secondsSinceResponseReceived);
477                } catch (Exception e) {
478                    throw apiException;
479                }
480
481            }
482        }
483
484        if (json == null) {
485            throw new RuntimeException("Unable to read authentication response in SDK.");
486        }
487
488        JsonObject jsonObject = Json.parse(json).asObject();
489        this.setAccessToken(jsonObject.get("access_token").asString());
490        this.setLastRefresh(System.currentTimeMillis());
491        this.setExpires(jsonObject.get("expires_in").asLong() * 1000);
492
493        //if token cache is specified, save to cache
494        if (this.accessTokenCache != null) {
495            String key = this.getAccessTokenCacheKey();
496            JsonObject accessTokenCacheInfo = new JsonObject()
497                .add("accessToken", this.getAccessToken())
498                .add("lastRefresh", this.getLastRefresh())
499                .add("expires", this.getExpires());
500
501            this.accessTokenCache.put(key, accessTokenCacheInfo.toString());
502        }
503    }
504
505    private NumericDate getDateForJWTConstruction(BoxAPIException apiException, long secondsSinceResponseDateReceived) {
506        NumericDate currentTime;
507        List<String> responseDates = apiException.getHeaders().get("Date");
508
509        if (responseDates != null) {
510            String responseDate = responseDates.get(0);
511            SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz");
512            try {
513                Date date = dateFormat.parse(responseDate);
514                currentTime = NumericDate.fromMilliseconds(date.getTime());
515                currentTime.addSeconds(secondsSinceResponseDateReceived);
516            } catch (ParseException e) {
517                currentTime = NumericDate.now();
518            }
519        } else {
520            currentTime = NumericDate.now();
521        }
522        return currentTime;
523    }
524
525    void setBackoffCounter(BackoffCounter counter) {
526        this.backoffCounter = counter;
527    }
528
529    /**
530     * BoxDeveloperEditionAPIConnection can always refresh, but this method is required elsewhere.
531     *
532     * @return true always.
533     */
534    public boolean canRefresh() {
535        return true;
536    }
537
538    /**
539     * Refresh's this connection's access token using Box Developer Edition.
540     *
541     * @throws IllegalStateException if this connection's access token cannot be refreshed.
542     */
543    public void refresh() {
544        this.getRefreshLock().writeLock().lock();
545
546        try {
547            this.authenticate();
548        } catch (BoxAPIException e) {
549            this.notifyError(e);
550            this.getRefreshLock().writeLock().unlock();
551            throw e;
552        }
553
554        this.notifyRefresh();
555        this.getRefreshLock().writeLock().unlock();
556    }
557
558    private String getAccessTokenCacheKey() {
559        return String.format("/%s/%s/%s/%s", this.getUserAgent(), this.getClientID(),
560            this.entityType.toString(), this.entityID);
561    }
562
563    private void tryRestoreUsingAccessTokenCache() {
564        if (this.accessTokenCache == null) {
565            //no cache specified so force authentication
566            this.authenticate();
567        } else {
568            String cachedTokenInfo = this.accessTokenCache.get(this.getAccessTokenCacheKey());
569            if (cachedTokenInfo == null) {
570                //not found; probably first time for this client config so authenticate; info will then be cached
571                this.authenticate();
572            } else {
573                //pull access token cache info; authentication will occur as needed (if token is expired)
574                JsonObject json = Json.parse(cachedTokenInfo).asObject();
575                this.setAccessToken(json.get("accessToken").asString());
576                this.setLastRefresh(json.get("lastRefresh").asLong());
577                this.setExpires(json.get("expires").asLong());
578            }
579        }
580    }
581
582    private String constructJWTAssertion() {
583        return this.constructJWTAssertion(null);
584    }
585
586    private String constructJWTAssertion(NumericDate now) {
587        JwtClaims claims = new JwtClaims();
588        claims.setIssuer(this.getClientID());
589        claims.setAudience(JWT_AUDIENCE);
590        if (now == null) {
591            claims.setExpirationTimeMinutesInTheFuture(0.5f);
592        } else {
593            now.addSeconds(30L);
594            claims.setExpirationTime(now);
595        }
596        claims.setSubject(this.entityID);
597        claims.setClaim("box_sub_type", this.entityType.toString());
598        claims.setGeneratedJwtId(64);
599
600        JsonWebSignature jws = new JsonWebSignature();
601        jws.setPayload(claims.toJson());
602        jws.setKey(this.decryptPrivateKey());
603        jws.setAlgorithmHeaderValue(this.getAlgorithmIdentifier());
604        jws.setHeader("typ", "JWT");
605        if ((this.publicKeyID != null) && !this.publicKeyID.isEmpty()) {
606            jws.setHeader("kid", this.publicKeyID);
607        }
608
609        String assertion;
610
611        try {
612            assertion = jws.getCompactSerialization();
613        } catch (JoseException e) {
614            throw new BoxAPIException("Error serializing JSON Web Token assertion.", e);
615        }
616
617        return assertion;
618    }
619
620    private String getAlgorithmIdentifier() {
621        String algorithmId = AlgorithmIdentifiers.RSA_USING_SHA256;
622        switch (this.encryptionAlgorithm) {
623            case RSA_SHA_384:
624                algorithmId = AlgorithmIdentifiers.RSA_USING_SHA384;
625                break;
626            case RSA_SHA_512:
627                algorithmId = AlgorithmIdentifiers.RSA_USING_SHA512;
628                break;
629            case RSA_SHA_256:
630            default:
631                break;
632        }
633
634        return algorithmId;
635    }
636
637    private PrivateKey decryptPrivateKey() {
638        PrivateKey decryptedPrivateKey;
639        try {
640            PEMParser keyReader = new PEMParser(new StringReader(this.privateKey));
641            Object keyPair = keyReader.readObject();
642            keyReader.close();
643
644            if (keyPair instanceof PrivateKeyInfo) {
645                PrivateKeyInfo keyInfo = (PrivateKeyInfo) keyPair;
646                decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
647            } else if (keyPair instanceof PEMEncryptedKeyPair) {
648                JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder();
649                PEMDecryptorProvider decryptionProvider = builder.build(this.privateKeyPassword.toCharArray());
650                keyPair = ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(decryptionProvider);
651                PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
652                decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
653            } else if (keyPair instanceof PKCS8EncryptedPrivateKeyInfo) {
654                InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider("BC")
655                    .build(this.privateKeyPassword.toCharArray());
656                PrivateKeyInfo keyInfo = ((PKCS8EncryptedPrivateKeyInfo) keyPair).decryptPrivateKeyInfo(pkcs8Prov);
657                decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
658            } else {
659                PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
660                decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
661            }
662        } catch (IOException e) {
663            throw new BoxAPIException("Error parsing private key for Box Developer Edition.", e);
664        } catch (OperatorCreationException e) {
665            throw new BoxAPIException("Error parsing PKCS#8 private key for Box Developer Edition.", e);
666        } catch (PKCSException e) {
667            throw new BoxAPIException("Error parsing PKCS private key for Box Developer Edition.", e);
668        }
669        return decryptedPrivateKey;
670    }
671
672}