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 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    public static BoxDeveloperEditionAPIConnection getUserConnection(String userId, BoxConfig boxConfig) {
379        return getAppUserConnection(userId, boxConfig.getClientId(), boxConfig.getClientSecret(),
380            boxConfig.getJWTEncryptionPreferences());
381    }
382
383    /**
384     * Creates a new Box Developer Edition connection with App User or Managed User token leveraging BoxConfig
385     * and access token cache.
386     *
387     * @param userId           the user ID to use for an App User.
388     * @param boxConfig        box configuration settings object
389     * @param accessTokenCache the cache for storing access token information (to minimize fetching new tokens)
390     * @return a new instance of BoxAPIConnection.
391     */
392    public static BoxDeveloperEditionAPIConnection getUserConnection(String userId, BoxConfig boxConfig,
393                                                                     IAccessTokenCache accessTokenCache
394    ) {
395        return getAppUserConnection(userId, boxConfig.getClientId(), boxConfig.getClientSecret(),
396            boxConfig.getJWTEncryptionPreferences(), accessTokenCache);
397    }
398
399    /**
400     * Disabling the non-Box Developer Edition authenticate method.
401     *
402     * @param authCode an auth code obtained from the first half of the OAuth process.
403     */
404    public void authenticate(String authCode) {
405        throw new BoxAPIException("BoxDeveloperEditionAPIConnection does not allow authenticating with an auth code.");
406    }
407
408    /**
409     * Authenticates the API connection for Box Developer Edition.
410     */
411    public void authenticate() {
412        URL url;
413        try {
414            url = new URL(this.getTokenURL());
415        } catch (MalformedURLException e) {
416            assert false : "An invalid token URL indicates a bug in the SDK.";
417            throw new RuntimeException("An invalid token URL indicates a bug in the SDK.", e);
418        }
419
420        this.backoffCounter.reset(this.getMaxRetryAttempts() + 1);
421        NumericDate jwtTime = null;
422        String jwtAssertion;
423        String urlParameters;
424        BoxAPIRequest request;
425        String json = null;
426        final BoxLogger logger = BoxLogger.defaultLogger();
427
428        while (this.backoffCounter.getAttemptsRemaining() > 0) {
429            // Reconstruct the JWT assertion, which regenerates the jti claim, with the new "current" time
430            jwtAssertion = this.constructJWTAssertion(jwtTime);
431            urlParameters = String.format(JWT_GRANT_TYPE, this.getClientID(), this.getClientSecret(), jwtAssertion);
432
433            request = new BoxAPIRequest(this, url, "POST");
434            request.shouldAuthenticate(false);
435            request.setBody(urlParameters);
436
437            try {
438                BoxJSONResponse response = (BoxJSONResponse) request.sendWithoutRetry();
439                json = response.getJSON();
440                break;
441            } catch (BoxAPIException apiException) {
442                long responseReceivedTime = System.currentTimeMillis();
443
444                if (!this.backoffCounter.decrement()
445                    || (!BoxAPIRequest.isRequestRetryable(apiException)
446                    && !BoxAPIRequest.isResponseRetryable(apiException.getResponseCode(), 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 NumericDate getDateForJWTConstruction(BoxAPIException apiException, long secondsSinceResponseDateReceived) {
505        NumericDate currentTime;
506        List<String> responseDates = apiException.getHeaders().get("Date");
507
508        if (responseDates != null) {
509            String responseDate = responseDates.get(0);
510            SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz");
511            try {
512                Date date = dateFormat.parse(responseDate);
513                currentTime = NumericDate.fromMilliseconds(date.getTime());
514                currentTime.addSeconds(secondsSinceResponseDateReceived);
515            } catch (ParseException e) {
516                currentTime = NumericDate.now();
517            }
518        } else {
519            currentTime = NumericDate.now();
520        }
521        return currentTime;
522    }
523
524    void setBackoffCounter(BackoffCounter counter) {
525        this.backoffCounter = counter;
526    }
527
528    /**
529     * BoxDeveloperEditionAPIConnection can always refresh, but this method is required elsewhere.
530     *
531     * @return true always.
532     */
533    public boolean canRefresh() {
534        return true;
535    }
536
537    /**
538     * Refresh's this connection's access token using Box Developer Edition.
539     *
540     * @throws IllegalStateException if this connection's access token cannot be refreshed.
541     */
542    public void refresh() {
543        this.getRefreshLock().writeLock().lock();
544
545        try {
546            this.authenticate();
547        } catch (BoxAPIException e) {
548            this.notifyError(e);
549            this.getRefreshLock().writeLock().unlock();
550            throw e;
551        }
552
553        this.notifyRefresh();
554        this.getRefreshLock().writeLock().unlock();
555    }
556
557    private String getAccessTokenCacheKey() {
558        return String.format("/%s/%s/%s/%s", this.getUserAgent(), this.getClientID(),
559            this.entityType.toString(), this.entityID);
560    }
561
562    private void tryRestoreUsingAccessTokenCache() {
563        if (this.accessTokenCache == null) {
564            //no cache specified so force authentication
565            this.authenticate();
566        } else {
567            String cachedTokenInfo = this.accessTokenCache.get(this.getAccessTokenCacheKey());
568            if (cachedTokenInfo == null) {
569                //not found; probably first time for this client config so authenticate; info will then be cached
570                this.authenticate();
571            } else {
572                //pull access token cache info; authentication will occur as needed (if token is expired)
573                JsonObject json = Json.parse(cachedTokenInfo).asObject();
574                this.setAccessToken(json.get("accessToken").asString());
575                this.setLastRefresh(json.get("lastRefresh").asLong());
576                this.setExpires(json.get("expires").asLong());
577            }
578        }
579    }
580
581    private String constructJWTAssertion() {
582        return this.constructJWTAssertion(null);
583    }
584
585    private String constructJWTAssertion(NumericDate now) {
586        JwtClaims claims = new JwtClaims();
587        claims.setIssuer(this.getClientID());
588        claims.setAudience(JWT_AUDIENCE);
589        if (now == null) {
590            claims.setExpirationTimeMinutesInTheFuture(0.5f);
591        } else {
592            now.addSeconds(30L);
593            claims.setExpirationTime(now);
594        }
595        claims.setSubject(this.entityID);
596        claims.setClaim("box_sub_type", this.entityType.toString());
597        claims.setGeneratedJwtId(64);
598
599        JsonWebSignature jws = new JsonWebSignature();
600        jws.setPayload(claims.toJson());
601        jws.setKey(this.decryptPrivateKey());
602        jws.setAlgorithmHeaderValue(this.getAlgorithmIdentifier());
603        jws.setHeader("typ", "JWT");
604        if ((this.publicKeyID != null) && !this.publicKeyID.isEmpty()) {
605            jws.setHeader("kid", this.publicKeyID);
606        }
607
608        String assertion;
609
610        try {
611            assertion = jws.getCompactSerialization();
612        } catch (JoseException e) {
613            throw new BoxAPIException("Error serializing JSON Web Token assertion.", e);
614        }
615
616        return assertion;
617    }
618
619    private String getAlgorithmIdentifier() {
620        String algorithmId = AlgorithmIdentifiers.RSA_USING_SHA256;
621        switch (this.encryptionAlgorithm) {
622            case RSA_SHA_384:
623                algorithmId = AlgorithmIdentifiers.RSA_USING_SHA384;
624                break;
625            case RSA_SHA_512:
626                algorithmId = AlgorithmIdentifiers.RSA_USING_SHA512;
627                break;
628            case RSA_SHA_256:
629            default:
630                break;
631        }
632
633        return algorithmId;
634    }
635
636    private PrivateKey decryptPrivateKey() {
637        PrivateKey decryptedPrivateKey;
638        try {
639            PEMParser keyReader = new PEMParser(new StringReader(this.privateKey));
640            Object keyPair = keyReader.readObject();
641            keyReader.close();
642
643            if (keyPair instanceof PrivateKeyInfo) {
644                PrivateKeyInfo keyInfo = (PrivateKeyInfo) keyPair;
645                decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
646            } else if (keyPair instanceof PEMEncryptedKeyPair) {
647                JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder();
648                PEMDecryptorProvider decryptionProvider = builder.build(this.privateKeyPassword.toCharArray());
649                keyPair = ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(decryptionProvider);
650                PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
651                decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
652            } else if (keyPair instanceof PKCS8EncryptedPrivateKeyInfo) {
653                InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider("BC")
654                    .build(this.privateKeyPassword.toCharArray());
655                PrivateKeyInfo keyInfo = ((PKCS8EncryptedPrivateKeyInfo) keyPair).decryptPrivateKeyInfo(pkcs8Prov);
656                decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
657            } else {
658                PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
659                decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
660            }
661        } catch (IOException e) {
662            throw new BoxAPIException("Error parsing private key for Box Developer Edition.", e);
663        } catch (OperatorCreationException e) {
664            throw new BoxAPIException("Error parsing PKCS#8 private key for Box Developer Edition.", e);
665        } catch (PKCSException e) {
666            throw new BoxAPIException("Error parsing PKCS private key for Box Developer Edition.", e);
667        }
668        return decryptedPrivateKey;
669    }
670
671}