• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperwallet / java-sdk / #654

01 May 2025 10:10PM CUT coverage: 97.037%. Remained the same
#654

push

grmeyer-hw-dev
Update changelog

5567 of 5737 relevant lines covered (97.04%)

67.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.68
/src/main/java/com/hyperwallet/clientsdk/util/HyperwalletEncryption.java
1
package com.hyperwallet.clientsdk.util;
2

3
import com.hyperwallet.clientsdk.HyperwalletException;
4
import com.nimbusds.jose.Algorithm;
5
import com.nimbusds.jose.EncryptionMethod;
6
import com.nimbusds.jose.JOSEException;
7
import com.nimbusds.jose.JWEAlgorithm;
8
import com.nimbusds.jose.JWEDecrypter;
9
import com.nimbusds.jose.JWEEncrypter;
10
import com.nimbusds.jose.JWEHeader;
11
import com.nimbusds.jose.JWEObject;
12
import com.nimbusds.jose.JWSAlgorithm;
13
import com.nimbusds.jose.JWSHeader;
14
import com.nimbusds.jose.JWSObject;
15
import com.nimbusds.jose.JWSSigner;
16
import com.nimbusds.jose.JWSVerifier;
17
import com.nimbusds.jose.Payload;
18
import com.nimbusds.jose.crypto.ECDHDecrypter;
19
import com.nimbusds.jose.crypto.ECDHEncrypter;
20
import com.nimbusds.jose.crypto.ECDSASigner;
21
import com.nimbusds.jose.crypto.ECDSAVerifier;
22
import com.nimbusds.jose.crypto.RSADecrypter;
23
import com.nimbusds.jose.crypto.RSAEncrypter;
24
import com.nimbusds.jose.crypto.RSASSASigner;
25
import com.nimbusds.jose.crypto.RSASSAVerifier;
26
import com.nimbusds.jose.jwk.ECKey;
27
import com.nimbusds.jose.jwk.JWK;
28
import com.nimbusds.jose.jwk.JWKSet;
29
import com.nimbusds.jose.jwk.KeyType;
30
import com.nimbusds.jose.jwk.RSAKey;
31

32

33
import java.io.File;
34
import java.io.IOException;
35
import java.net.*;
36
import java.nio.file.Files;
37
import java.nio.file.Paths;
38
import java.text.ParseException;
39
import java.util.Arrays;
40
import java.util.Collections;
41
import java.util.Date;
42
import java.util.HashSet;
43
import java.util.List;
44

45
public class HyperwalletEncryption {
46

47
    private static final String EXPIRATION = "exp";
48
    private static final Integer MILLISECONDS_IN_ONE_MINUTE = 60000;
1✔
49
    private static final Long MILLISECONDS_IN_SECOND = 1000L;
1✔
50
    private static final Integer EXPIRATION_MINUTES = 5;
1✔
51
    private static final JWEAlgorithm ENCRYPTION_ALGORITHM = JWEAlgorithm.RSA_OAEP_256;
1✔
52
    private static final JWSAlgorithm SIGN_ALGORITHM = JWSAlgorithm.RS256;
1✔
53
    private static final EncryptionMethod ENCRYPTION_METHOD = EncryptionMethod.A256CBC_HS512;
1✔
54
    private static final String INVALID_KEY_TYPE_STRING = "'kty' not supported = %s";
55

56
    private static final List<JWEAlgorithm> SUPPORTED_JWE_ALGORITHMS = Arrays.asList(JWEAlgorithm.RSA_OAEP_256,
1✔
57
            JWEAlgorithm.ECDH_ES,
58
            JWEAlgorithm.ECDH_ES_A128KW,
59
            JWEAlgorithm.ECDH_ES_A192KW,
60
            JWEAlgorithm.ECDH_ES_A256KW);
61
    private static final List<JWSAlgorithm> SUPPORTED_JWS_ALGORITHMS = Arrays.asList(JWSAlgorithm.RS256,
1✔
62
            JWSAlgorithm.RS384,
63
            JWSAlgorithm.RS512,
64
            JWSAlgorithm.PS256,
65
            JWSAlgorithm.PS384,
66
            JWSAlgorithm.PS512,
67
            JWSAlgorithm.ES256,
68
            JWSAlgorithm.ES384,
69
            JWSAlgorithm.ES512);
70
    private static final List<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS = Arrays.asList(EncryptionMethod.A128CBC_HS256,
1✔
71
            EncryptionMethod.A192CBC_HS384,
72
            EncryptionMethod.A256CBC_HS512,
73
            EncryptionMethod.A128GCM,
74
            EncryptionMethod.A256GCM);
75

76
    private final JWEAlgorithm encryptionAlgorithm;
77
    private final JWSAlgorithm signAlgorithm;
78
    private final EncryptionMethod encryptionMethod;
79
    private final String clientPrivateKeySetLocation;
80
    private final String hyperwalletKeySetLocation;
81
    private final Integer jwsExpirationMinutes;
82

83
    private Proxy proxy;
84
    private String proxyUsername;
85
    private String proxyPassword;
86

87
    public HyperwalletEncryption(JWEAlgorithm encryptionAlgorithm, JWSAlgorithm signAlgorithm, EncryptionMethod encryptionMethod,
88
            String clientPrivateKeySetLocation, String hyperwalletKeySetLocation, Integer jwsExpirationMinutes) {
129✔
89
        this.encryptionAlgorithm = encryptionAlgorithm == null ? ENCRYPTION_ALGORITHM : encryptionAlgorithm;
129✔
90
        this.signAlgorithm = signAlgorithm == null ? SIGN_ALGORITHM : signAlgorithm;
129✔
91
        this.encryptionMethod = encryptionMethod == null ? ENCRYPTION_METHOD : encryptionMethod;
129✔
92
        this.clientPrivateKeySetLocation = clientPrivateKeySetLocation;
129✔
93
        this.hyperwalletKeySetLocation = hyperwalletKeySetLocation;
129✔
94
        this.jwsExpirationMinutes = jwsExpirationMinutes == null ? EXPIRATION_MINUTES : jwsExpirationMinutes;
129✔
95

96
        if (!SUPPORTED_JWS_ALGORITHMS.contains(this.signAlgorithm)) {
129✔
97
            throw new IllegalArgumentException("Unsupported signing algorithm " + this.signAlgorithm);
1✔
98
        }
99
        if (!SUPPORTED_JWE_ALGORITHMS.contains(this.encryptionAlgorithm)) {
128✔
100
            throw new IllegalArgumentException("Unsupported encryption algorithm " + this.encryptionAlgorithm);
1✔
101
        }
102
        if (!SUPPORTED_ENCRYPTION_METHODS.contains(this.encryptionMethod)) {
127✔
103
            throw new IllegalArgumentException("Unsupported encryption method " + this.encryptionMethod);
2✔
104
        }
105
    }
125✔
106

107
    public String encrypt(String body) throws JOSEException, IOException, ParseException {
108

109
        JWK clientPrivateKey = getKeyByAlgorithm(loadClientPrivateKeySet(), signAlgorithm);
22✔
110
        JWK hyperwalletPublicKey = getKeyByAlgorithm(loadHyperwalletKeySet(), encryptionAlgorithm);
21✔
111
        JWSSigner jwsSigner = getJWSSigner(clientPrivateKey);
20✔
112
        JWEEncrypter jweEncrypter = getJWEEncrypter(hyperwalletPublicKey);
20✔
113

114
        JWSObject jwsObject = new JWSObject(
19✔
115
                new JWSHeader.Builder(signAlgorithm)
116
                        .keyID(clientPrivateKey.getKeyID())
19✔
117
                        .criticalParams(new HashSet<>(Collections.singletonList(EXPIRATION)))
19✔
118
                        .customParam(EXPIRATION, getJWSExpirationMillis()).build(),
19✔
119
                new Payload(body));
120

121
        jwsObject.sign(jwsSigner);
19✔
122

123
        JWEObject jweObject = new JWEObject(
19✔
124
                new JWEHeader.Builder(encryptionAlgorithm, encryptionMethod)
125
                        .keyID(hyperwalletPublicKey.getKeyID()).build(),
19✔
126
                new Payload(jwsObject));
127

128
        jweObject.encrypt(jweEncrypter);
19✔
129

130
        return jweObject.serialize();
19✔
131
    }
132

133
    public String decrypt(String body) throws ParseException, IOException, JOSEException {
134

135
        JWK privateKeyToDecrypt = getKeyByAlgorithm(loadClientPrivateKeySet(), encryptionAlgorithm);
12✔
136
        JWK publicKeyToSign = getKeyByAlgorithm(loadHyperwalletKeySet(), signAlgorithm);
12✔
137
        JWEDecrypter jweDecrypter = getJWEDecrypter(privateKeyToDecrypt);
12✔
138
        JWSVerifier jwsVerifier = getJWSVerifier(publicKeyToSign);
12✔
139

140
        JWEObject jweObject = JWEObject.parse(body);
12✔
141
        jweObject.decrypt(jweDecrypter);
12✔
142
        JWSObject jwsObject = jweObject.getPayload().toJWSObject();
11✔
143
        verifySignatureExpirationDate(jwsObject.getHeader().getCustomParam(EXPIRATION));
11✔
144
        boolean verifyStatus = jwsObject.verify(jwsVerifier);
11✔
145
        if (!verifyStatus) {
11✔
146
            throw new HyperwalletException("JWS signature is incorrect");
×
147
        }
148
        return jwsObject.getPayload().toString();
11✔
149
    }
150

151
    /**
152
     * Allows clients to implement a custom loading of their private JWK set.
153
     */
154
    protected JWKSet loadClientPrivateKeySet() throws IOException, ParseException {
155
        return loadKeySet(clientPrivateKeySetLocation);
34✔
156
    }
157

158
    /**
159
     * Allows clients to implement a custom loading of Hyperwallet public JWK set.
160
     */
161
    protected JWKSet loadHyperwalletKeySet() throws IOException, ParseException {
162
        return loadKeySet(hyperwalletKeySetLocation);
33✔
163
    }
164

165
    public void verifySignatureExpirationDate(Object signatureExpirationDate) {
166
        if (signatureExpirationDate == null) {
14✔
167
            throw new HyperwalletException("exp JWS header param was null");
1✔
168
        }
169
        if (!(signatureExpirationDate instanceof Long)) {
13✔
170
            throw new HyperwalletException("exp JWS header must be of type Long");
1✔
171
        }
172
        long expirationTimeSeconds = (long) signatureExpirationDate;
12✔
173
        if (new Date().getTime() / MILLISECONDS_IN_SECOND > expirationTimeSeconds) {
12✔
174
            throw new HyperwalletException("Response message signature(JWS) has expired");
1✔
175
        }
176
    }
11✔
177

178
    public JWEAlgorithm getEncryptionAlgorithm() {
179
        return encryptionAlgorithm;
3✔
180
    }
181

182
    public JWSAlgorithm getSignAlgorithm() {
183
        return signAlgorithm;
3✔
184
    }
185

186
    public EncryptionMethod getEncryptionMethod() {
187
        return encryptionMethod;
3✔
188
    }
189

190
    public String getClientPrivateKeySetLocation() {
191
        return clientPrivateKeySetLocation;
3✔
192
    }
193

194
    public String getHyperwalletKeySetLocation() {
195
        return hyperwalletKeySetLocation;
3✔
196
    }
197

198
    public Integer getJwsExpirationMinutes() {
199
        return jwsExpirationMinutes;
3✔
200
    }
201

202
    private JWKSet loadKeySet(String keySetLocation) throws IOException, ParseException {
203
        URL url;
204
        try {
205
            url = new URL(keySetLocation);
67✔
206
        } catch (MalformedURLException e) {
65✔
207
            checkKeySetLocationIsFile(keySetLocation);
65✔
208
            return JWKSet.load(new File(keySetLocation));
64✔
209
        }
2✔
210

211
        if (usesProxy()) {
2✔
212
            if (proxyUsername != null && proxyPassword != null) {
×
213
                Authenticator authenticator = new Request.DefaultPasswordAuthenticator(
×
214
                        proxyUsername, proxyPassword);
215
                Authenticator.setDefault(authenticator);
×
216
            }
217
            return JWKSet.load(url, 0, 0, 0, getProxy());
×
218
        }
219
        return JWKSet.load(url);
2✔
220
    }
221

222
    private long getJWSExpirationMillis() {
223
        return new Date(new Date().getTime() + MILLISECONDS_IN_ONE_MINUTE * jwsExpirationMinutes).getTime() / MILLISECONDS_IN_SECOND;
19✔
224
    }
225

226
    private <T extends Algorithm> JWK getKeyByAlgorithm(JWKSet keySet, T algorithm) {
227
        for (JWK key : keySet.getKeys()) {
66✔
228
            if (key.getAlgorithm().equals(algorithm)) {
108✔
229
                return key;
65✔
230
            }
231
        }
43✔
232
        throw new IllegalStateException("Algorithm = " + algorithm + " is not found in client or Hyperwallet key set");
1✔
233
    }
234

235
    private void checkKeySetLocationIsFile(String keySetLocation) {
236
        if (Files.notExists(Paths.get(keySetLocation))) {
65✔
237
            throw new IllegalArgumentException("Wrong client JWK set location");
1✔
238
        }
239
    }
64✔
240

241
    private JWSSigner getJWSSigner(JWK jwk) {
242
        try {
243
            KeyType kty = jwk.getKeyType();
20✔
244
            if (kty.equals(KeyType.RSA)) {
20✔
245
                return new RSASSASigner((RSAKey) jwk);
18✔
246
            } else if (kty.equals(KeyType.EC)) {
2✔
247
                return new ECDSASigner((ECKey) jwk);
2✔
248
            } else {
249
                throw new IllegalArgumentException(String.format(INVALID_KEY_TYPE_STRING, kty));
×
250
            }
251
        } catch (JOSEException e) {
×
252
            throw new HyperwalletException("Unable to create signer");
×
253
        }
254
    }
255

256
    private JWEEncrypter getJWEEncrypter(JWK jwk) {
257
        try {
258
            KeyType kty = jwk.getKeyType();
20✔
259
            if (kty.equals(KeyType.RSA)) {
20✔
260
                return new RSAEncrypter((RSAKey) jwk);
18✔
261
            } else if (kty.equals(KeyType.EC)) {
2✔
262
                return new ECDHEncrypter((ECKey) jwk);
1✔
263
            } else {
264
                throw new IllegalArgumentException(String.format(INVALID_KEY_TYPE_STRING, kty));
1✔
265
            }
266
        } catch (JOSEException e) {
×
267
            throw new HyperwalletException("Unable to create encrypter");
×
268
        }
269
    }
270

271
    private JWSVerifier getJWSVerifier(JWK jwk) {
272
        try {
273
            KeyType kty = jwk.getKeyType();
12✔
274
            if (kty.equals(KeyType.RSA)) {
12✔
275
                return new RSASSAVerifier(((RSAKey) jwk).toRSAPublicKey(), new HashSet<>(Collections.singletonList(EXPIRATION)));
11✔
276
            } else if (kty.equals(KeyType.EC)) {
1✔
277
                return new ECDSAVerifier(((ECKey) jwk).toECPublicKey(), new HashSet<>(Collections.singletonList(EXPIRATION)));
1✔
278
            } else {
279
                throw new IllegalArgumentException(String.format(INVALID_KEY_TYPE_STRING, kty));
×
280
            }
281
        } catch (JOSEException e) {
×
282
            throw new HyperwalletException("Unable to create verifier");
×
283
        }
284
    }
285

286
    private JWEDecrypter getJWEDecrypter(JWK jwk) {
287
        try {
288
            KeyType kty = jwk.getKeyType();
12✔
289
            if (kty.equals(KeyType.RSA)) {
12✔
290
                return new RSADecrypter((RSAKey) jwk);
11✔
291
            } else if (kty.equals(KeyType.EC)) {
1✔
292
                return new ECDHDecrypter((ECKey) jwk);
1✔
293
            } else {
294
                throw new IllegalArgumentException(String.format(INVALID_KEY_TYPE_STRING, kty));
×
295
            }
296
        } catch (JOSEException e) {
×
297
            throw new HyperwalletException("Unable to create decrypter");
×
298
        }
299
    }
300

301
    public Boolean usesProxy() {
302
        return proxy != null;
2✔
303
    }
304

305
    public void setProxy(String url, Integer port) {
306
        this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(url, port));
9✔
307
    }
9✔
308

309
    public void setProxy(Proxy proxy) {
310
        this.proxy = proxy;
1✔
311
    }
1✔
312

313
    public Proxy getProxy() {
314
        return proxy;
3✔
315
    }
316

317
    public String getProxyUsername() {
318
        return proxyUsername;
3✔
319
    }
320

321
    public void setProxyUsername(String proxyUsername) {
322
        this.proxyUsername = proxyUsername;
10✔
323
    }
10✔
324

325
    public String getProxyPassword() {
326
        return proxyPassword;
3✔
327
    }
328

329
    public void setProxyPassword(String proxyPassword) {
330
        this.proxyPassword = proxyPassword;
10✔
331
    }
10✔
332

333
    public static class HyperwalletEncryptionBuilder {
120✔
334

335
        private JWEAlgorithm encryptionAlgorithm;
336
        private JWSAlgorithm signAlgorithm;
337
        private EncryptionMethod encryptionMethod;
338
        private String clientPrivateKeySetLocation;
339
        private String hyperwalletKeySetLocation;
340
        private Integer jwsExpirationMinutes;
341

342
        private Proxy proxy;
343
        private String proxyUsername;
344
        private String proxyPassword;
345

346
        public HyperwalletEncryptionBuilder encryptionAlgorithm(JWEAlgorithm encryptionAlgorithm) {
347
            this.encryptionAlgorithm = encryptionAlgorithm;
82✔
348
            return this;
82✔
349
        }
350

351
        public HyperwalletEncryptionBuilder signAlgorithm(JWSAlgorithm signAlgorithm) {
352
            this.signAlgorithm = signAlgorithm;
81✔
353
            return this;
81✔
354
        }
355

356
        public HyperwalletEncryptionBuilder encryptionMethod(EncryptionMethod encryptionMethod) {
357
            this.encryptionMethod = encryptionMethod;
80✔
358
            return this;
80✔
359
        }
360

361
        public HyperwalletEncryptionBuilder clientPrivateKeySetLocation(String clientPrivateKeySetLocation) {
362
            this.clientPrivateKeySetLocation = clientPrivateKeySetLocation;
113✔
363
            return this;
113✔
364
        }
365

366
        public HyperwalletEncryptionBuilder hyperwalletKeySetLocation(String hyperwalletKeySetLocation) {
367
            this.hyperwalletKeySetLocation = hyperwalletKeySetLocation;
113✔
368
            return this;
113✔
369
        }
370

371
        public HyperwalletEncryptionBuilder jwsExpirationMinutes(Integer jwsExpirationMinutes) {
372
            this.jwsExpirationMinutes = jwsExpirationMinutes;
2✔
373
            return this;
2✔
374
        }
375

376
        public HyperwalletEncryptionBuilder proxy(Proxy proxy) {
377
            this.proxy = proxy;
2✔
378
            return this;
2✔
379
        }
380

381
        public HyperwalletEncryptionBuilder proxyUsername(String proxyUsername) {
382
            this.proxyUsername = proxyUsername;
2✔
383
            return this;
2✔
384
        }
385

386
        public HyperwalletEncryptionBuilder proxyPassword(String proxyPassword) {
387
            this.proxyPassword = proxyPassword;
2✔
388
            return this;
2✔
389
        }
390

391
        public HyperwalletEncryption build() {
392
            HyperwalletEncryption hyperwalletEncryption = new HyperwalletEncryption(encryptionAlgorithm, signAlgorithm, encryptionMethod,
129✔
393
                    clientPrivateKeySetLocation, hyperwalletKeySetLocation, jwsExpirationMinutes);
394
            if (proxy != null) {
125✔
395
                hyperwalletEncryption.setProxy(proxy);
1✔
396
            }
397
            if (proxyUsername != null) {
125✔
398
                hyperwalletEncryption.setProxyUsername(proxyUsername);
1✔
399
            }
400
            if (proxyPassword != null) {
125✔
401
                hyperwalletEncryption.setProxyPassword(proxyPassword);
1✔
402
            }
403
            return hyperwalletEncryption;
125✔
404
        }
405
    }
406
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc