Skip to content

Commit 31f3a2a

Browse files
feat: Allow injecting private key decryptor for JWT (box/box-codegen#754) (#355)
1 parent 5bef87d commit 31f3a2a

File tree

7 files changed

+161
-82
lines changed

7 files changed

+161
-82
lines changed

.codegen.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "engineHash": "ac49877", "specHash": "8402463", "version": "0.7.0" }
1+
{ "engineHash": "b8e7dbb", "specHash": "8402463", "version": "0.7.0" }

src/main/java/com/box/sdkgen/box/jwtauth/BoxJWTAuth.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public AccessToken refreshToken(NetworkSession networkSession) {
6363
this.config.getClientId(),
6464
this.subjectId,
6565
getUuid(),
66-
this.config.getJwtKeyId());
66+
this.config.getJwtKeyId(),
67+
this.config.getPrivateKeyDecryptor());
6768
JwtKey jwtKey = new JwtKey(this.config.getPrivateKey(), this.config.getPrivateKeyPassphrase());
6869
String assertion = createJwtAssertion(claims, jwtKey, jwtOptions);
6970
AuthorizationManager authManager =

src/main/java/com/box/sdkgen/box/jwtauth/JWTConfig.java

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
import com.box.sdkgen.box.tokenstorage.InMemoryTokenStorage;
77
import com.box.sdkgen.box.tokenstorage.TokenStorage;
8+
import com.box.sdkgen.internal.utils.DefaultPrivateKeyDecryptor;
89
import com.box.sdkgen.internal.utils.JwtAlgorithm;
10+
import com.box.sdkgen.internal.utils.PrivateKeyDecryptor;
911
import com.box.sdkgen.serialization.json.EnumWrapper;
1012
import com.box.sdkgen.serialization.json.JsonManager;
1113

@@ -29,6 +31,8 @@ public class JWTConfig {
2931

3032
public TokenStorage tokenStorage;
3133

34+
public PrivateKeyDecryptor privateKeyDecryptor;
35+
3236
public JWTConfig(
3337
String clientId,
3438
String clientSecret,
@@ -42,6 +46,7 @@ public JWTConfig(
4246
this.privateKeyPassphrase = privateKeyPassphrase;
4347
this.algorithm = new EnumWrapper<JwtAlgorithm>(JwtAlgorithm.RS256);
4448
this.tokenStorage = new InMemoryTokenStorage();
49+
this.privateKeyDecryptor = new DefaultPrivateKeyDecryptor();
4550
}
4651

4752
protected JWTConfig(Builder builder) {
@@ -54,46 +59,62 @@ protected JWTConfig(Builder builder) {
5459
this.userId = builder.userId;
5560
this.algorithm = builder.algorithm;
5661
this.tokenStorage = builder.tokenStorage;
62+
this.privateKeyDecryptor = builder.privateKeyDecryptor;
5763
}
5864

5965
public static JWTConfig fromConfigJsonString(String configJsonString) {
60-
return fromConfigJsonString(configJsonString, null);
66+
return fromConfigJsonString(configJsonString, null, null);
6167
}
6268

6369
public static JWTConfig fromConfigJsonString(String configJsonString, TokenStorage tokenStorage) {
70+
return fromConfigJsonString(configJsonString, tokenStorage, null);
71+
}
72+
73+
public static JWTConfig fromConfigJsonString(
74+
String configJsonString, PrivateKeyDecryptor privateKeyDecryptor) {
75+
return fromConfigJsonString(configJsonString, null, privateKeyDecryptor);
76+
}
77+
78+
public static JWTConfig fromConfigJsonString(
79+
String configJsonString, TokenStorage tokenStorage, PrivateKeyDecryptor privateKeyDecryptor) {
6480
JwtConfigFile configJson =
6581
JsonManager.deserialize(jsonToSerializedData(configJsonString), JwtConfigFile.class);
82+
TokenStorage tokenStorageToUse =
83+
(tokenStorage == null ? new InMemoryTokenStorage() : tokenStorage);
84+
PrivateKeyDecryptor privateKeyDecryptorToUse =
85+
(privateKeyDecryptor == null ? new DefaultPrivateKeyDecryptor() : privateKeyDecryptor);
6686
JWTConfig newConfig =
67-
(!(tokenStorage == null)
68-
? new JWTConfig.Builder(
69-
configJson.getBoxAppSettings().getClientId(),
70-
configJson.getBoxAppSettings().getClientSecret(),
71-
configJson.getBoxAppSettings().getAppAuth().getPublicKeyId(),
72-
configJson.getBoxAppSettings().getAppAuth().getPrivateKey(),
73-
configJson.getBoxAppSettings().getAppAuth().getPassphrase())
74-
.enterpriseId(configJson.getEnterpriseId())
75-
.userId(configJson.getUserId())
76-
.tokenStorage(tokenStorage)
77-
.build()
78-
: new JWTConfig.Builder(
79-
configJson.getBoxAppSettings().getClientId(),
80-
configJson.getBoxAppSettings().getClientSecret(),
81-
configJson.getBoxAppSettings().getAppAuth().getPublicKeyId(),
82-
configJson.getBoxAppSettings().getAppAuth().getPrivateKey(),
83-
configJson.getBoxAppSettings().getAppAuth().getPassphrase())
84-
.enterpriseId(configJson.getEnterpriseId())
85-
.userId(configJson.getUserId())
86-
.build());
87+
new JWTConfig.Builder(
88+
configJson.getBoxAppSettings().getClientId(),
89+
configJson.getBoxAppSettings().getClientSecret(),
90+
configJson.getBoxAppSettings().getAppAuth().getPublicKeyId(),
91+
configJson.getBoxAppSettings().getAppAuth().getPrivateKey(),
92+
configJson.getBoxAppSettings().getAppAuth().getPassphrase())
93+
.enterpriseId(configJson.getEnterpriseId())
94+
.userId(configJson.getUserId())
95+
.tokenStorage(tokenStorageToUse)
96+
.privateKeyDecryptor(privateKeyDecryptorToUse)
97+
.build();
8798
return newConfig;
8899
}
89100

90101
public static JWTConfig fromConfigFile(String configFilePath) {
91-
return fromConfigFile(configFilePath, null);
102+
return fromConfigFile(configFilePath, null, null);
92103
}
93104

94105
public static JWTConfig fromConfigFile(String configFilePath, TokenStorage tokenStorage) {
106+
return fromConfigFile(configFilePath, tokenStorage, null);
107+
}
108+
109+
public static JWTConfig fromConfigFile(
110+
String configFilePath, PrivateKeyDecryptor privateKeyDecryptor) {
111+
return fromConfigFile(configFilePath, null, privateKeyDecryptor);
112+
}
113+
114+
public static JWTConfig fromConfigFile(
115+
String configFilePath, TokenStorage tokenStorage, PrivateKeyDecryptor privateKeyDecryptor) {
95116
String configJsonString = readTextFromFile(configFilePath);
96-
return JWTConfig.fromConfigJsonString(configJsonString, tokenStorage);
117+
return JWTConfig.fromConfigJsonString(configJsonString, tokenStorage, privateKeyDecryptor);
97118
}
98119

99120
public String getClientId() {
@@ -132,6 +153,10 @@ public TokenStorage getTokenStorage() {
132153
return tokenStorage;
133154
}
134155

156+
public PrivateKeyDecryptor getPrivateKeyDecryptor() {
157+
return privateKeyDecryptor;
158+
}
159+
135160
public static class Builder {
136161

137162
protected final String clientId;
@@ -152,6 +177,8 @@ public static class Builder {
152177

153178
protected TokenStorage tokenStorage;
154179

180+
protected PrivateKeyDecryptor privateKeyDecryptor;
181+
155182
public Builder(
156183
String clientId,
157184
String clientSecret,
@@ -165,6 +192,7 @@ public Builder(
165192
this.privateKeyPassphrase = privateKeyPassphrase;
166193
this.algorithm = new EnumWrapper<JwtAlgorithm>(JwtAlgorithm.RS256);
167194
this.tokenStorage = new InMemoryTokenStorage();
195+
this.privateKeyDecryptor = new DefaultPrivateKeyDecryptor();
168196
}
169197

170198
public Builder enterpriseId(String enterpriseId) {
@@ -192,6 +220,11 @@ public Builder tokenStorage(TokenStorage tokenStorage) {
192220
return this;
193221
}
194222

223+
public Builder privateKeyDecryptor(PrivateKeyDecryptor privateKeyDecryptor) {
224+
this.privateKeyDecryptor = privateKeyDecryptor;
225+
return this;
226+
}
227+
195228
public JWTConfig build() {
196229
return new JWTConfig(this);
197230
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.box.sdkgen.internal.utils;
2+
3+
import com.box.sdkgen.box.errors.BoxSDKError;
4+
import java.io.IOException;
5+
import java.io.StringReader;
6+
import java.security.PrivateKey;
7+
import java.security.Security;
8+
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
9+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
10+
import org.bouncycastle.openssl.PEMDecryptorProvider;
11+
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
12+
import org.bouncycastle.openssl.PEMKeyPair;
13+
import org.bouncycastle.openssl.PEMParser;
14+
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
15+
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
16+
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
17+
import org.bouncycastle.operator.InputDecryptorProvider;
18+
import org.bouncycastle.operator.OperatorCreationException;
19+
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
20+
import org.bouncycastle.pkcs.PKCSException;
21+
22+
public class DefaultPrivateKeyDecryptor implements PrivateKeyDecryptor {
23+
public PrivateKey decryptPrivateKey(String encryptedPrivateKey, String passphrase) {
24+
Security.addProvider(new BouncyCastleProvider());
25+
PrivateKey decryptedPrivateKey;
26+
try {
27+
PEMParser keyReader = new PEMParser(new StringReader(encryptedPrivateKey));
28+
Object keyPair = keyReader.readObject();
29+
keyReader.close();
30+
31+
if (keyPair instanceof PrivateKeyInfo) {
32+
PrivateKeyInfo keyInfo = (PrivateKeyInfo) keyPair;
33+
decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
34+
} else if (keyPair instanceof PEMEncryptedKeyPair) {
35+
JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder();
36+
PEMDecryptorProvider decryptionProvider = builder.build(passphrase.toCharArray());
37+
keyPair = ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(decryptionProvider);
38+
PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
39+
decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
40+
} else if (keyPair instanceof PKCS8EncryptedPrivateKeyInfo) {
41+
InputDecryptorProvider pkcs8Prov =
42+
new JceOpenSSLPKCS8DecryptorProviderBuilder()
43+
.setProvider("BC")
44+
.build(passphrase.toCharArray());
45+
PrivateKeyInfo keyInfo =
46+
((PKCS8EncryptedPrivateKeyInfo) keyPair).decryptPrivateKeyInfo(pkcs8Prov);
47+
decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
48+
} else {
49+
PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
50+
decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
51+
}
52+
} catch (IOException e) {
53+
throw new BoxSDKError("Error parsing private key for Box Developer Edition.", e);
54+
} catch (OperatorCreationException e) {
55+
throw new BoxSDKError("Error parsing PKCS#8 private key for Box Developer Edition.", e);
56+
} catch (PKCSException e) {
57+
throw new BoxSDKError("Error parsing PKCS private key for Box Developer Edition.", e);
58+
}
59+
return decryptedPrivateKey;
60+
}
61+
}

src/main/java/com/box/sdkgen/internal/utils/JwtSignOptions.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class JwtSignOptions {
99
protected String subject;
1010
protected String jwtid;
1111
protected String keyid;
12+
protected PrivateKeyDecryptor privateKeyDecryptor;
1213

1314
public JwtSignOptions(
1415
JwtAlgorithm algorithm,
@@ -17,12 +18,34 @@ public JwtSignOptions(
1718
String subject,
1819
String jwtid,
1920
String keyid) {
21+
this(algorithm, audience, issuer, subject, jwtid, keyid, new DefaultPrivateKeyDecryptor());
22+
}
23+
24+
public JwtSignOptions(
25+
EnumWrapper<JwtAlgorithm> algorithm,
26+
String audience,
27+
String issuer,
28+
String subject,
29+
String jwtid,
30+
String keyid) {
31+
this(algorithm, audience, issuer, subject, jwtid, keyid, new DefaultPrivateKeyDecryptor());
32+
}
33+
34+
public JwtSignOptions(
35+
JwtAlgorithm algorithm,
36+
String audience,
37+
String issuer,
38+
String subject,
39+
String jwtid,
40+
String keyid,
41+
PrivateKeyDecryptor privateKeyDecryptor) {
2042
this.algorithm = algorithm;
2143
this.audience = audience;
2244
this.issuer = issuer;
2345
this.subject = subject;
2446
this.jwtid = jwtid;
2547
this.keyid = keyid;
48+
this.privateKeyDecryptor = privateKeyDecryptor;
2649
}
2750

2851
public JwtSignOptions(
@@ -31,13 +54,15 @@ public JwtSignOptions(
3154
String issuer,
3255
String subject,
3356
String jwtid,
34-
String keyid) {
57+
String keyid,
58+
PrivateKeyDecryptor privateKeyDecryptor) {
3559
this.algorithm = algorithm.getEnumValue();
3660
this.audience = audience;
3761
this.issuer = issuer;
3862
this.subject = subject;
3963
this.jwtid = jwtid;
4064
this.keyid = keyid;
65+
this.privateKeyDecryptor = privateKeyDecryptor;
4166
}
4267

4368
public JwtAlgorithm getAlgorithm() {
@@ -63,4 +88,8 @@ public String getJwtid() {
6388
public String getKeyid() {
6489
return keyid;
6590
}
91+
92+
public PrivateKeyDecryptor getPrivateKeyDecryptor() {
93+
return privateKeyDecryptor;
94+
}
6695
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.box.sdkgen.internal.utils;
2+
3+
import java.security.PrivateKey;
4+
5+
/** Decrypts private key with provided passphrase */
6+
public interface PrivateKeyDecryptor {
7+
/** Decrypts private key using a passphrase. */
8+
PrivateKey decryptPrivateKey(String encryptedPrivateKey, String passphrase);
9+
}

src/main/java/com/box/sdkgen/internal/utils/UtilsManager.java

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,11 @@
1515
import java.io.IOException;
1616
import java.io.InputStream;
1717
import java.io.OutputStream;
18-
import java.io.StringReader;
1918
import java.math.BigInteger;
2019
import java.nio.charset.StandardCharsets;
2120
import java.nio.file.Files;
2221
import java.nio.file.Paths;
2322
import java.security.MessageDigest;
24-
import java.security.PrivateKey;
25-
import java.security.Security;
2623
import java.text.SimpleDateFormat;
2724
import java.util.Arrays;
2825
import java.util.Base64;
@@ -40,19 +37,6 @@
4037
import java.util.stream.Collectors;
4138
import javax.crypto.Mac;
4239
import javax.crypto.spec.SecretKeySpec;
43-
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
44-
import org.bouncycastle.jce.provider.BouncyCastleProvider;
45-
import org.bouncycastle.openssl.PEMDecryptorProvider;
46-
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
47-
import org.bouncycastle.openssl.PEMKeyPair;
48-
import org.bouncycastle.openssl.PEMParser;
49-
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
50-
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
51-
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
52-
import org.bouncycastle.operator.InputDecryptorProvider;
53-
import org.bouncycastle.operator.OperatorCreationException;
54-
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
55-
import org.bouncycastle.pkcs.PKCSException;
5640
import org.jose4j.jws.JsonWebSignature;
5741
import org.jose4j.jwt.JwtClaims;
5842
import org.jose4j.jwt.NumericDate;
@@ -264,45 +248,6 @@ public static long getEpochTimeInSeconds() {
264248
return System.currentTimeMillis() / 1000;
265249
}
266250

267-
public static PrivateKey decryptPrivateKey(String encryptedPrivateKey, String passphrase) {
268-
Security.addProvider(new BouncyCastleProvider());
269-
PrivateKey decryptedPrivateKey;
270-
try {
271-
PEMParser keyReader = new PEMParser(new StringReader(encryptedPrivateKey));
272-
Object keyPair = keyReader.readObject();
273-
keyReader.close();
274-
275-
if (keyPair instanceof PrivateKeyInfo) {
276-
PrivateKeyInfo keyInfo = (PrivateKeyInfo) keyPair;
277-
decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
278-
} else if (keyPair instanceof PEMEncryptedKeyPair) {
279-
JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder();
280-
PEMDecryptorProvider decryptionProvider = builder.build(passphrase.toCharArray());
281-
keyPair = ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(decryptionProvider);
282-
PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
283-
decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
284-
} else if (keyPair instanceof PKCS8EncryptedPrivateKeyInfo) {
285-
InputDecryptorProvider pkcs8Prov =
286-
new JceOpenSSLPKCS8DecryptorProviderBuilder()
287-
.setProvider("BC")
288-
.build(passphrase.toCharArray());
289-
PrivateKeyInfo keyInfo =
290-
((PKCS8EncryptedPrivateKeyInfo) keyPair).decryptPrivateKeyInfo(pkcs8Prov);
291-
decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
292-
} else {
293-
PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo();
294-
decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo);
295-
}
296-
} catch (IOException e) {
297-
throw new BoxSDKError("Error parsing private key for Box Developer Edition.", e);
298-
} catch (OperatorCreationException e) {
299-
throw new BoxSDKError("Error parsing PKCS#8 private key for Box Developer Edition.", e);
300-
} catch (PKCSException e) {
301-
throw new BoxSDKError("Error parsing PKCS private key for Box Developer Edition.", e);
302-
}
303-
return decryptedPrivateKey;
304-
}
305-
306251
public static String createJwtAssertion(
307252
Map<String, Object> claims, JwtKey jwtKey, JwtSignOptions jwtOptions) {
308253
JwtClaims jwtClaims = new JwtClaims();
@@ -316,7 +261,8 @@ public static String createJwtAssertion(
316261

317262
JsonWebSignature jws = new JsonWebSignature();
318263
jws.setPayload(jwtClaims.toJson());
319-
jws.setKey(decryptPrivateKey(jwtKey.getKey(), jwtKey.getPassphrase()));
264+
jws.setKey(
265+
jwtOptions.privateKeyDecryptor.decryptPrivateKey(jwtKey.getKey(), jwtKey.getPassphrase()));
320266
jws.setAlgorithmHeaderValue(jwtOptions.getAlgorithm().getValue());
321267
jws.setHeader("typ", "JWT");
322268
if ((jwtOptions.getKeyid() != null) && !jwtOptions.getKeyid().isEmpty()) {

0 commit comments

Comments
 (0)