44import static org .junit .Assert .assertFalse ;
55import static org .junit .Assert .assertNull ;
66import static org .junit .Assert .assertSame ;
7+ import static org .junit .Assert .assertThrows ;
78import static org .junit .Assert .assertTrue ;
89
10+ import io .approov .util .sig .ComponentProvider ;
11+ import io .approov .util .sig .SignatureParameters ;
12+
913import java .nio .charset .StandardCharsets ;
1014import java .util .Base64 ;
1115
@@ -47,6 +51,20 @@ protected byte[] decodeBase64(String base64) {
4751 }
4852 }
4953
54+ private static final class UnsupportedAlgorithmFactory
55+ extends ApproovDefaultMessageSigning .SignatureParametersFactory {
56+ @ Override
57+ protected SignatureParameters buildSignatureParameters (
58+ ApproovDefaultMessageSigning .OkHttpComponentProvider provider ,
59+ ApproovRequestMutations changes ) {
60+ return new SignatureParameters ()
61+ .addComponentIdentifier (ComponentProvider .DC_METHOD )
62+ .addComponentIdentifier (ComponentProvider .DC_TARGET_URI )
63+ .addComponentIdentifier (changes .getTokenHeaderKey ())
64+ .setAlg ("unsupported-alg" );
65+ }
66+ }
67+
5068 private static String derEncodedInstallSignature () {
5169 byte [] der = new byte [] { 0x30 , 0x06 , 0x02 , 0x01 , 0x01 , 0x02 , 0x01 , 0x02 };
5270 return Base64 .getEncoder ().encodeToString (der );
@@ -67,13 +85,42 @@ private static Request signedRequestFixture() {
6785 .build ();
6886 }
6987
88+ private static Request unsignedRequestFixture () {
89+ return signedRequestFixture ().newBuilder ()
90+ .removeHeader ("Content-Digest" )
91+ .removeHeader ("Signature" )
92+ .removeHeader ("Signature-Input" )
93+ .removeHeader ("Signature-Base-Digest" )
94+ .build ();
95+ }
96+
97+ private static Request requestWithoutBodyFixture () {
98+ return new Request .Builder ()
99+ .url ("https://api.example.com/reply" )
100+ .get ()
101+ .header ("Approov-Token" , "Bearer jwt-token" )
102+ .header ("Approov-TraceID" , "trace-123" )
103+ .header ("Authorization" , "Bearer auth-token" )
104+ .build ();
105+ }
106+
70107 private static ApproovRequestMutations defaultChanges () {
71108 ApproovRequestMutations changes = new ApproovRequestMutations ();
72109 changes .setTokenHeaderKey ("Approov-Token" );
73110 changes .setTraceIDHeaderKey ("Approov-TraceID" );
74111 return changes ;
75112 }
76113
114+ private static void assertUnsignedWithoutSignatureHeaders (Request original , Request processed ) {
115+ assertSame (original , processed );
116+ assertEquals ("Bearer jwt-token" , processed .header ("Approov-Token" ));
117+ assertEquals ("trace-123" , processed .header ("Approov-TraceID" ));
118+ assertNull (processed .header ("Content-Digest" ));
119+ assertNull (processed .header ("Signature" ));
120+ assertNull (processed .header ("Signature-Input" ));
121+ assertNull (processed .header ("Signature-Base-Digest" ));
122+ }
123+
77124 @ Test
78125 public void defaultSigningAddsDigestAndSignatureHeaders () throws Exception {
79126 RecordingSigner signer = new RecordingSigner ();
@@ -137,20 +184,11 @@ public void signingSkipsGracefullyWhenInstallSigningIsUnavailable() throws Excep
137184 RecordingSigner signer = new RecordingSigner ();
138185 signer .setDefaultFactory (ApproovDefaultMessageSigning .generateDefaultSignatureParametersFactory ());
139186 signer .installError = new ApproovException ("no device signature available" );
140- Request request = signedRequestFixture ().newBuilder ()
141- .removeHeader ("Content-Digest" )
142- .removeHeader ("Signature" )
143- .removeHeader ("Signature-Input" )
144- .removeHeader ("Signature-Base-Digest" )
145- .build ();
187+ Request request = unsignedRequestFixture ();
146188
147189 Request signed = signer .processedRequest (request , defaultChanges ());
148190
149- assertSame (request , signed );
150- assertNull (signed .header ("Content-Digest" ));
151- assertNull (signed .header ("Signature" ));
152- assertNull (signed .header ("Signature-Input" ));
153- assertNull (signed .header ("Signature-Base-Digest" ));
191+ assertUnsignedWithoutSignatureHeaders (request , signed );
154192 }
155193
156194 @ Test
@@ -167,4 +205,103 @@ public void accountSigningUsesTheAccountSignatureFlow() throws Exception {
167205 assertNull (signer .lastInstallMessage );
168206 assertTrue (signer .lastAccountMessage .contains ("\" approov-token\" " ));
169207 }
208+
209+ @ Test
210+ public void accountSigningSkipsGracefullyWhenSignatureBase64IsInvalid () throws Exception {
211+ RecordingSigner signer = new RecordingSigner ();
212+ signer .setDefaultFactory (ApproovDefaultMessageSigning .generateDefaultSignatureParametersFactory ()
213+ .setUseAccountMessageSigning ());
214+ signer .accountSignatureBase64 = "not-base64" ;
215+ Request request = unsignedRequestFixture ();
216+
217+ Request signed = signer .processedRequest (request , defaultChanges ());
218+
219+ assertTrue (signer .lastAccountMessage .contains ("\" approov-token\" " ));
220+ assertUnsignedWithoutSignatureHeaders (request , signed );
221+ }
222+
223+ @ Test
224+ public void installSigningSkipsGracefullyWhenSignatureBase64IsInvalid () throws Exception {
225+ RecordingSigner signer = new RecordingSigner ();
226+ signer .setDefaultFactory (ApproovDefaultMessageSigning .generateDefaultSignatureParametersFactory ());
227+ signer .installSignatureBase64 = "not-base64" ;
228+ Request request = unsignedRequestFixture ();
229+
230+ Request signed = signer .processedRequest (request , defaultChanges ());
231+
232+ assertTrue (signer .lastInstallMessage .contains ("\" approov-token\" " ));
233+ assertUnsignedWithoutSignatureHeaders (request , signed );
234+ }
235+
236+ @ Test
237+ public void installSigningSkipsGracefullyWhenDerSignatureIsMalformed () throws Exception {
238+ RecordingSigner signer = new RecordingSigner ();
239+ signer .setDefaultFactory (ApproovDefaultMessageSigning .generateDefaultSignatureParametersFactory ());
240+ signer .installSignatureBase64 = Base64 .getEncoder ().encodeToString (
241+ new byte [] { 0x31 , 0x00 });
242+ Request request = unsignedRequestFixture ();
243+
244+ Request signed = signer .processedRequest (request , defaultChanges ());
245+
246+ assertTrue (signer .lastInstallMessage .contains ("\" approov-token\" " ));
247+ assertUnsignedWithoutSignatureHeaders (request , signed );
248+ }
249+
250+ @ Test
251+ public void installSigningSkipsGracefullyWhenDerSignatureIsTruncated () throws Exception {
252+ RecordingSigner signer = new RecordingSigner ();
253+ signer .setDefaultFactory (ApproovDefaultMessageSigning .generateDefaultSignatureParametersFactory ());
254+ signer .installSignatureBase64 = Base64 .getEncoder ().encodeToString (
255+ new byte [] { 0x30 , 0x06 , 0x02 });
256+ Request request = unsignedRequestFixture ();
257+
258+ Request signed = signer .processedRequest (request , defaultChanges ());
259+
260+ assertTrue (signer .lastInstallMessage .contains ("\" approov-token\" " ));
261+ assertUnsignedWithoutSignatureHeaders (request , signed );
262+ }
263+
264+ @ Test
265+ public void signingSkipsGracefullyWhenSignatureBaseCannotBeBuilt () throws Exception {
266+ RecordingSigner signer = new RecordingSigner ();
267+ SignatureParameters missingRequiredHeader = new SignatureParameters ()
268+ .addComponentIdentifier ("X-Missing-Required-Header" );
269+ signer .setDefaultFactory (ApproovDefaultMessageSigning .generateDefaultSignatureParametersFactory (missingRequiredHeader )
270+ .setUseAccountMessageSigning ()
271+ .setAddApproovTokenHeader (false )
272+ .setAddApproovTraceIDHeader (false )
273+ .setBodyDigestConfig (null , false ));
274+ signer .accountSignatureBase64 = Base64 .getEncoder ().encodeToString (
275+ "account-signature" .getBytes (StandardCharsets .UTF_8 ));
276+ Request request = unsignedRequestFixture ();
277+
278+ Request signed = signer .processedRequest (request , defaultChanges ());
279+
280+ assertNull (signer .lastAccountMessage );
281+ assertUnsignedWithoutSignatureHeaders (request , signed );
282+ }
283+
284+ @ Test
285+ public void requiredBodyDigestFailureIsFailClosed () {
286+ RecordingSigner signer = new RecordingSigner ();
287+ signer .setDefaultFactory (ApproovDefaultMessageSigning .generateDefaultSignatureParametersFactory ()
288+ .setBodyDigestConfig (ApproovDefaultMessageSigning .DIGEST_SHA256 , true ));
289+
290+ assertThrows (ApproovDefaultMessageSigning .RequiredBodyDigestException .class ,
291+ () -> signer .processedRequest (requestWithoutBodyFixture (), defaultChanges ()));
292+ assertNull (signer .lastInstallMessage );
293+ }
294+
295+ @ Test
296+ public void unsupportedSigningAlgorithmIsFailClosed () {
297+ RecordingSigner signer = new RecordingSigner ();
298+ signer .setDefaultFactory (new UnsupportedAlgorithmFactory ());
299+
300+ IllegalStateException error = assertThrows (IllegalStateException .class ,
301+ () -> signer .processedRequest (unsignedRequestFixture (), defaultChanges ()));
302+
303+ assertTrue (error .getMessage ().contains ("Unsupported algorithm identifier" ));
304+ assertNull (signer .lastInstallMessage );
305+ assertNull (signer .lastAccountMessage );
306+ }
170307}
0 commit comments