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