diff --git a/pom.xml b/pom.xml index 8d4b8a1b..8005c4dd 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ yoti-sdk-parent yoti-sdk-api + yoti-sdk-auth yoti-sdk-sandbox yoti-sdk-spring-boot-auto-config yoti-sdk-spring-security diff --git a/yoti-sdk-auth/pom.xml b/yoti-sdk-auth/pom.xml new file mode 100644 index 00000000..f50af392 --- /dev/null +++ b/yoti-sdk-auth/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + + com.yoti + yoti-sdk-parent + 4.0.0-SNAPSHOT + ../yoti-sdk-parent + + + yoti-sdk-auth + + + 0.13.0 + + + + + org.bouncycastle + bcpkix-jdk18on + + + com.yoti + yoti-sdk-api + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + com.fasterxml.jackson.core + jackson-databind + + + commons-logging + commons-logging + 1.1.1 + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + + + + + org.hamcrest + hamcrest-library + test + + + junit + junit + test + + + org.mockito + mockito-core + test + + + + + + + + org.owasp + dependency-check-maven + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + + maven-enforcer-plugin + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + maven-source-plugin + + + maven-javadoc-plugin + + + org.jacoco + jacoco-maven-plugin + + + + + + + + maven-project-info-reports-plugin + + + + + diff --git a/yoti-sdk-auth/src/main/java/com/yoti/auth/AuthenticationTokenGenerator.java b/yoti-sdk-auth/src/main/java/com/yoti/auth/AuthenticationTokenGenerator.java new file mode 100644 index 00000000..8a0cf036 --- /dev/null +++ b/yoti-sdk-auth/src/main/java/com/yoti/auth/AuthenticationTokenGenerator.java @@ -0,0 +1,193 @@ +package com.yoti.auth; + +import static com.yoti.validation.Validation.notNull; +import static com.yoti.validation.Validation.notNullOrEmpty; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyPair; +import java.time.OffsetDateTime; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; + +import com.yoti.api.client.InitialisationException; +import com.yoti.api.client.KeyPairSource; +import com.yoti.api.client.spi.remote.KeyStreamVisitor; +import com.yoti.api.client.spi.remote.call.ResourceException; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Jwts; + +/** + * The {@link AuthenticationTokenGenerator} is used for generation authorization tokens + * that can be used for accessing Yoti services. An authorization token must have + * a unique identifier, and an expiry timestamp. One or more scopes can be provided + * to allow the authorization token access to different parts of Yoti systems. + *

+ * The {@link AuthenticationTokenGenerator.Builder} can be accessed via {@code AuthorizationTokenGenerator.builder()} + * method, and then configured via the fluent API. + */ +public class AuthenticationTokenGenerator { + + private final String sdkId; + private final KeyPair keyPair; + private final Supplier jwtIdSupplier; + private final FormRequestClient formRequestClient; + + private final URL authApiUrl; + private final ObjectMapper objectMapper; + + AuthenticationTokenGenerator( + String sdkId, + KeyPair keyPair, + Supplier jwtIdSupplier, + FormRequestClient formRequestClient, + ObjectMapper objectMapper) { + this.sdkId = sdkId; + this.keyPair = keyPair; + this.jwtIdSupplier = jwtIdSupplier; + this.formRequestClient = formRequestClient; + this.objectMapper = objectMapper; + + try { + authApiUrl = new URL(System.getProperty(Properties.PROPERTY_YOTI_AUTH_URL, Properties.DEFAULT_YOTI_AUTH_URL)); + } catch (MalformedURLException e) { + throw new IllegalStateException("Invalid Yoti auth url", e); + } + } + + /** + * Creates a new instance of {@link AuthenticationTokenGenerator.Builder} + * + * @return the builder + */ + public static AuthenticationTokenGenerator.Builder builder() { + return new AuthenticationTokenGenerator.Builder(); + } + + /** + * Creates a new authentication token, using the supplied scopes and comment. + * + * @param scopes a list of scopes to be used by the authentication token + * @return a {@link CreateAuthenticationTokenResponse} containing information about the created token. + * @throws ResourceException if something was incorrect with the request to the Yoti authentication service + * @throws IOException + */ + public CreateAuthenticationTokenResponse generate(List scopes) throws ResourceException, IOException { + notNullOrEmpty(scopes, "scopes"); + + String jwts = createSignedJwt(sdkId, keyPair, jwtIdSupplier, authApiUrl); + + Map formParams = new HashMap<>(); + formParams.put("grant_type", "client_credentials"); + formParams.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); + formParams.put("scope", String.join(" ", scopes)); + formParams.put("client_assertion", jwts); + + byte[] responseBody = formRequestClient.performRequest(authApiUrl, "POST", formParams); + + return objectMapper.readValue(responseBody, CreateAuthenticationTokenResponse.class); + } + + private String createSignedJwt(String sdkId, KeyPair keyPair, Supplier jwtIdSupplier, URL authApiUrl) { + String sdkIdProperty = String.format("sdk:%s", sdkId); + OffsetDateTime now = OffsetDateTime.now(); + return Jwts.builder() + .issuer(sdkIdProperty) + .subject(sdkIdProperty) + .id(jwtIdSupplier.get()) + .audience() + .add(authApiUrl.toString()) + .and() + .expiration(new Date(now.plusMinutes(5).toInstant().toEpochMilli())) + .issuedAt(new Date(now.toInstant().toEpochMilli())) + .header() + .add("alg", "PS384") + .add("typ", "JWT") + .and() + .signWith(keyPair.getPrivate(), Jwts.SIG.PS384) + .compact(); + } + + public static final class Builder { + + private String sdkId; + private KeyPairSource keyPairSource; + private Supplier jwtIdSupplier = () -> UUID.randomUUID().toString(); + + private Builder() { + } + + /** + * Sets the SDK ID that the authorization token will be generated against. + * + * @param sdkId the SDK ID + * @return the builder for method chaining. + */ + public Builder withSdkId(String sdkId) { + this.sdkId = sdkId; + return this; + } + + /** + * Sets the {@link KeyPairSource} that will be used to load the {@link KeyPair} + * + * @param keyPairSource the key pair source that will be used to load the {@link KeyPair} + * @return the builder for method chaining. + */ + public Builder withKeyPairSource(KeyPairSource keyPairSource) { + this.keyPairSource = keyPairSource; + return this; + } + + /** + * Sets the supplier that will be used to generate a unique ID for the + * authorization token. By default, this will be a UUID v4. + * + * @param jwtIdSupplier the supplier used for generating authorization token ID + * @return the builder for method chaining. + */ + public Builder withJwtIdSupplier(Supplier jwtIdSupplier) { + this.jwtIdSupplier = jwtIdSupplier; + return this; + } + + /** + * Builds an {@link AuthenticationTokenGenerator} using the values supplied to the {@link Builder}. + * + * @return the configured {@link AuthenticationTokenGenerator} + */ + public AuthenticationTokenGenerator build() { + notNullOrEmpty(sdkId, "sdkId"); + notNull(keyPairSource, "keyPairSource"); + notNull(jwtIdSupplier, "jwtIdSupplier"); + + ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + return new AuthenticationTokenGenerator( + sdkId, + loadKeyPair(keyPairSource), + jwtIdSupplier, + new FormRequestClient(), + objectMapper + ); + } + + private KeyPair loadKeyPair(KeyPairSource kpSource) throws InitialisationException { + try { + return kpSource.getFromStream(new KeyStreamVisitor()); + } catch (IOException e) { + throw new InitialisationException("Cannot load key pair", e); + } + } + + } + +} diff --git a/yoti-sdk-auth/src/main/java/com/yoti/auth/CreateAuthenticationTokenResponse.java b/yoti-sdk-auth/src/main/java/com/yoti/auth/CreateAuthenticationTokenResponse.java new file mode 100644 index 00000000..9d437c72 --- /dev/null +++ b/yoti-sdk-auth/src/main/java/com/yoti/auth/CreateAuthenticationTokenResponse.java @@ -0,0 +1,47 @@ +package com.yoti.auth; + +public final class CreateAuthenticationTokenResponse { + + private String accessToken; + private String tokenType; + private Integer expiresIn; + private String scope; + + /** + * Returns the Yoti Authentication token used to perform requests to other Yoti services. + * + * @return the newly created access token + */ + public String getAccessToken() { + return accessToken; + } + + /** + * Returns the type of the newly generated authentication token. + * + * @return the token type + */ + public String getTokenType() { + return tokenType; + } + + /** + * Returns the amount of time (in seconds) in which the newly generated Authentication Token + * will expire in. + * + * @return the time (in seconds) of when the token will expire + */ + public Integer getExpiresIn() { + return expiresIn; + } + + /** + * A whitespace delimited string of scopes that the Authentication token has. + * + * @return the scopes of the token as a whitespace delimited string + */ + public String getScope() { + return scope; + } + +} diff --git a/yoti-sdk-auth/src/main/java/com/yoti/auth/FormRequestClient.java b/yoti-sdk-auth/src/main/java/com/yoti/auth/FormRequestClient.java new file mode 100644 index 00000000..fbb46372 --- /dev/null +++ b/yoti-sdk-auth/src/main/java/com/yoti/auth/FormRequestClient.java @@ -0,0 +1,84 @@ +package com.yoti.auth; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.stream.Collectors; + +import com.yoti.api.client.spi.remote.call.ResourceException; +import com.yoti.api.client.spi.remote.util.QuietCloseable; + +/** + * Internal use only. + *

+ * The {@link FormRequestClient} is used for performing an application/x-www-form-urlencoded + * HTTP request using base Java libraries only. + */ +final class FormRequestClient { + + byte[] performRequest(URL url, String method, Map formParams) throws IOException, ResourceException { + byte[] postData = getData(formParams); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoOutput(true); + connection.setRequestMethod(method); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + connection.setRequestProperty("Content-Length", Integer.toString(postData.length)); + connection.setRequestProperty("charset", StandardCharsets.UTF_8.toString()); + connection.setUseCaches(false); + connection.setInstanceFollowRedirects(false); + + try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { + wr.write(postData); + } + + return parseResponse(connection); + } + + private byte[] getData(Map params) { + return params.entrySet().stream() + .map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue())) + .collect(Collectors.joining("&")) + .getBytes(StandardCharsets.UTF_8); + } + + private static String encode(String v) { + try { + return URLEncoder.encode(v, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private byte[] parseResponse(HttpURLConnection httpUrlConnection) throws ResourceException, IOException { + int responseCode = httpUrlConnection.getResponseCode(); + if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) { + byte[] responseBody = readBody(httpUrlConnection); + throw new ResourceException(responseCode, httpUrlConnection.getResponseMessage(), new String(responseBody)); + } + return readBody(httpUrlConnection); + } + + private byte[] readBody(HttpURLConnection httpURLConnection) throws IOException { + try (QuietCloseable inputStream = new QuietCloseable<>(httpURLConnection.getInputStream())) { + return readChunked(inputStream.get()); + } + } + + private byte[] readChunked(InputStream inputStream) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] byteChunk = new byte[4096]; + int n; + while ((n = inputStream.read(byteChunk)) > 0) { + byteArrayOutputStream.write(byteChunk, 0, n); + } + return byteArrayOutputStream.toByteArray(); + } + +} diff --git a/yoti-sdk-auth/src/main/java/com/yoti/auth/Properties.java b/yoti-sdk-auth/src/main/java/com/yoti/auth/Properties.java new file mode 100644 index 00000000..d72e3092 --- /dev/null +++ b/yoti-sdk-auth/src/main/java/com/yoti/auth/Properties.java @@ -0,0 +1,17 @@ +package com.yoti.auth; + +/** + * Internal use only. + *

+ * Contains property values used by the `yoti-sdk-auth` module for + * creating Yoti Authentication tokens. + */ +final class Properties { + + private static final String YOTI_AUTH_HOST = "https://auth.api.yoti.com"; + private static final String YOTI_AUTH_PATH_PREFIX = "/v1/oauth/token"; + + public static final String PROPERTY_YOTI_AUTH_URL = "yoti.auth.url"; + public static final String DEFAULT_YOTI_AUTH_URL = YOTI_AUTH_HOST + YOTI_AUTH_PATH_PREFIX; + +} diff --git a/yoti-sdk-auth/src/test/java/com/yoti/auth/AuthenticationTokenGeneratorTest.java b/yoti-sdk-auth/src/test/java/com/yoti/auth/AuthenticationTokenGeneratorTest.java new file mode 100644 index 00000000..cec6eb0a --- /dev/null +++ b/yoti-sdk-auth/src/test/java/com/yoti/auth/AuthenticationTokenGeneratorTest.java @@ -0,0 +1,124 @@ +package com.yoti.auth; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; + +import com.yoti.api.client.InitialisationException; +import com.yoti.api.client.KeyPairSource; +import com.yoti.auth.util.CryptoUtil; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class AuthenticationTokenGeneratorTest { + + private static final String SOME_AUTH_API_URL = "https://someAuthApiUrl"; + private static final String SOME_SDK_ID = "someSdkId"; + private static final Supplier SOME_JWT_ID_SUPPLIER = () -> UUID.randomUUID().toString(); + private static final byte[] SOME_RESPONSE_BODY = new byte[] { 1, 2, 3 }; + + @Mock FormRequestClient formRequestClientMock; + @Mock ObjectMapper objectMapperMock; + + @Mock KeyPairSource keyPairSourceMock; + @Mock CreateAuthenticationTokenResponse createAuthenticationTokenResponseMock; + + @Captor ArgumentCaptor urlArgumentCaptor; + @Captor ArgumentCaptor> formParamsCaptor; + + private KeyPair validKeyPair; + + @Before + public void setUp() throws IOException { + System.setProperty(Properties.PROPERTY_YOTI_AUTH_URL, SOME_AUTH_API_URL); + validKeyPair = CryptoUtil.generateKeyPairFrom(CryptoUtil.KEY_PAIR_PEM); + } + + @Test + public void builder_shouldThrowIfSdkIdIsNotProvided() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + AuthenticationTokenGenerator.builder().build(); + }); + + assertThat(exception.getMessage(), is("'sdkId' must not be empty or null")); + } + + @Test + public void builder_shouldThrowIfKeyPairSourceIsNotProvided() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + AuthenticationTokenGenerator.builder() + .withSdkId(SOME_SDK_ID) + .build(); + }); + + assertThat(exception.getMessage(), is("'keyPairSource' must not be null")); + } + + @Test + public void builder_shouldThrowIfKeyPairSourceIsNull() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + AuthenticationTokenGenerator.builder() + .withSdkId(SOME_SDK_ID) + .withKeyPairSource(null) + .build(); + }); + + assertThat(exception.getMessage(), is("'keyPairSource' must not be null")); + } + + @Test + public void builder_shouldThrowIfJwtIdSupplierIsNull() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + AuthenticationTokenGenerator.builder() + .withSdkId(SOME_SDK_ID) + .withKeyPairSource(keyPairSourceMock) + .withJwtIdSupplier(null) + .build(); + }); + + assertThat(exception.getMessage(), is("'jwtIdSupplier' must not be null")); + } + + @Test + public void shouldJoinScopesCorrectly() throws Exception { + when(formRequestClientMock.performRequest(urlArgumentCaptor.capture(), eq("POST"), formParamsCaptor.capture())).thenReturn(SOME_RESPONSE_BODY); + when(objectMapperMock.readValue(SOME_RESPONSE_BODY, CreateAuthenticationTokenResponse.class)).thenReturn(createAuthenticationTokenResponseMock); + + AuthenticationTokenGenerator tokenGenerator = new AuthenticationTokenGenerator(SOME_SDK_ID, validKeyPair, SOME_JWT_ID_SUPPLIER, formRequestClientMock, objectMapperMock); + + List scopes = Arrays.asList("scope1", "scope2", "scope3:read"); + + CreateAuthenticationTokenResponse result = tokenGenerator.generate(scopes); + + assertThat(result, is(createAuthenticationTokenResponseMock)); + + Map formParams = formParamsCaptor.getValue(); + assertThat(formParams.get("grant_type"), is("client_credentials")); + assertThat(formParams.get("client_assertion_type"), is("urn:ietf:params:oauth:client-assertion-type:jwt-bearer")); + assertThat(formParams.get("scope"), is("scope1 scope2 scope3:read")); + + assertThat(urlArgumentCaptor.getValue().toString(), is(SOME_AUTH_API_URL)); + } + +} diff --git a/yoti-sdk-auth/src/test/java/com/yoti/auth/util/CryptoUtil.java b/yoti-sdk-auth/src/test/java/com/yoti/auth/util/CryptoUtil.java new file mode 100644 index 00000000..c8520df5 --- /dev/null +++ b/yoti-sdk-auth/src/test/java/com/yoti/auth/util/CryptoUtil.java @@ -0,0 +1,64 @@ +package com.yoti.auth.util; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.KeyPair; +import java.security.Security; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; + +public class CryptoUtil { + + public static final String KEY_PAIR_PEM = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIEogIBAAKCAQEAvTGVVGqq2jv9K1BwT8+7GAURqoZOT8gfk2vIT3YKf3ZEB2OG\n" + + "qp9qiAaZZpL4RJKOA9x3uJxC3XVIRa+8TpvWmpUnFJJZMotE/W6jjPvOLiu4V27m\n" + + "H0i1CNy5Icjx7g3Wo2Pj2AUEOqV+bB1we2VSbNhPpJHmN2azmOMRuc7SMyXukWy5\n" + + "EeS2ZlwD2cfm3jJdTmWysV6+D4t02y3DYckjRe3RA/5pOYYg7+qgL/fjtb8eUOx3\n" + + "08fwzlS0dXwuYegX0aJuUd4KOu/drDzg2KDHDKlUmuPgHTKwlKX/mgjaXbldB9ER\n" + + "pTyq68OtJ37TElNgTl/rashfbEaC3LbQtn3lFQIDAQABAoIBADS3XSmhcyvN7VQl\n" + + "XLYQZsxhlTOTqrx2Qb4dGTpy5KfxdzEr3TkrpE50sEexifXpdCLFSqKo/8SfSl0I\n" + + "g4rPx3NZPgNwZ+Q6hCWtr2q4OxIIYpwSLZLn+nGWtwsf57FyL61lRvZJJ42D0X8k\n" + + "kNQBPn9PoplzgddMCZz/IFBKva08WDlib6h9eBMUXxw94/kEpp1WBhWreq4trADg\n" + + "l8l4BHTheT259PmgDq8L83irGjxYbuMrlNrOigXfesgbyX8UBVVkTEqHUWNnNRAR\n" + + "XONm+75GJT8r51ijdpHfEnpy54bNTXT7KHVMaHmPSQIazylvQJQew0WVZ0a9VwvF\n" + + "QZPOngECgYEA9AngQ+GCJW9vC4UCtIpuWZajCw+KNXlw4rfpXJuajnm3O+znfO+q\n" + + "FQn8IA5u1Sw1DimvdLACSavCax0p6vvJtAxjTgPtID6OnBJ3cD0rjqAcqEbZ29Xu\n" + + "YeaAg0dnSRfb5wkpbk0EtK2RkFocwdM/ql1s8WkrFkqs681zRqGLs5UCgYEAxneH\n" + + "nuHkIq8O69h+CI1RmNZpLhQBspseHXbhwTzQIvqzdiIwlPTR9mfZsGrnCokb/0CC\n" + + "BvYih+GLZN62iEaf+aCUA+662BZrNAMwEY/k4Ris+sKFNT0tBNoPDOgqdnygH9gm\n" + + "coKdIDO9sPL0144U/Hq1YsdwuK2SDQcLY3ydC4ECgYA71QALJIsIKp4LMP1MznPn\n" + + "uysWVyUHn1KyA21Pq0blj6oBI0BOPWRx7BTIt0EtOr13T3kZHt4wuc/c+zV/y2PU\n" + + "pQTj58qHkU7drRljh1vaiB7+kwBvCbB8iEsR5LvKC/N6XaCuzmtM8REzVySd0PFX\n" + + "D7jaJ3LM8FodJi4RLyJVUQKBgEy80teACDHQ9jgC0ViFK9Oos6p5Wd6xU4eY+9k3\n" + + "plKgFNvMhHRT5QsdRHKOIx9TvFuJmb0PVnKrprYt1u4CQMDIcfLDT8NVh8XopaFk\n" + + "vd67J8cdh1v6d3m0xrT639BIh7FIZjVIg3B8ERBmIH1oFn05BQFYlCEUG7Cl1KV2\n" + + "/VIBAoGAYin6eHFtBXpPRMddTt4lLOFtpBb9gGp9DxKGAsH0pdfQYgZHz0xKtxrq\n" + + "6l+d2M9sRhtisxw8YevPFWEVtcUWxz+qESM+wzUMrmWSuwwxXyVgjR4rC8untfPM\n" + + "laDBX8dDBwBQ3i0FhvuGR/LEjHWr9hj00faKROHOLFim6wbRIkg=\n" + + "-----END RSA PRIVATE KEY-----"; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + public static KeyPair generateKeyPairFrom(String keyPairString) throws IOException { + KeyPair keyPair = null; + PEMParser reader = new PEMParser( + new BufferedReader(new InputStreamReader(new ByteArrayInputStream(keyPairString.getBytes())))); + + for (Object o; (o = reader.readObject()) != null; ) { + if (o instanceof PEMKeyPair) { + keyPair = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) o); + break; + } + } + reader.close(); + return keyPair; + } + +} diff --git a/yoti-sdk-auth/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/yoti-sdk-auth/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000..1f0955d4 --- /dev/null +++ b/yoti-sdk-auth/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline